xref: /third_party/alsa-lib/src/control/namehint.c (revision d5ac70f0)
1/**
2 * \file control/namehint.c
3 * \ingroup Configuration
4 * \brief Give device name hints
5 * \author Jaroslav Kysela <perex@perex.cz>
6 * \date 2006
7 */
8/*
9 *  Give device name hints  - main file
10 *  Copyright (c) 2006 by Jaroslav Kysela <perex@perex.cz>
11 *
12 *
13 *   This library is free software; you can redistribute it and/or modify
14 *   it under the terms of the GNU Lesser General Public License as
15 *   published by the Free Software Foundation; either version 2.1 of
16 *   the License, or (at your option) any later version.
17 *
18 *   This program is distributed in the hope that it will be useful,
19 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
20 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 *   GNU Lesser General Public License for more details.
22 *
23 *   You should have received a copy of the GNU Lesser General Public
24 *   License along with this library; if not, write to the Free Software
25 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
26 *
27 */
28
29#include "local.h"
30
31#ifndef DOC_HIDDEN
32#define DEV_SKIP	9999 /* some non-existing device number */
33struct hint_list {
34	char **list;
35	unsigned int count;
36	unsigned int allocated;
37	const char *siface;
38	snd_ctl_elem_iface_t iface;
39	snd_ctl_t *ctl;
40	snd_ctl_card_info_t *info;
41	int card;
42	int device;
43	long device_input;
44	long device_output;
45	int stream;
46	int show_all;
47	char *cardname;
48};
49#endif
50
51static int hint_list_add(struct hint_list *list,
52			 const char *name,
53			 const char *description)
54{
55	char *x;
56
57	if (list->count + 1 >= list->allocated) {
58		char **n = realloc(list->list, (list->allocated + 10) * sizeof(char *));
59		if (n == NULL)
60			return -ENOMEM;
61		memset(n + list->allocated, 0, 10 * sizeof(*n));
62		list->allocated += 10;
63		list->list = n;
64	}
65	if (name == NULL) {
66		x = NULL;
67	} else {
68		x = malloc(4 + strlen(name) + (description != NULL ? (4 + strlen(description) + 1) : 0) + 1);
69		if (x == NULL)
70			return -ENOMEM;
71		memcpy(x, "NAME", 4);
72		strcpy(x + 4, name);
73		if (description != NULL) {
74			strcat(x, "|DESC");
75			strcat(x, description);
76		}
77	}
78	list->list[list->count++] = x;
79	return 0;
80}
81
82/**
83 * Add a namehint from string given in a user configuration file
84 */
85static int hint_list_add_custom(struct hint_list *list,
86				const char *entry)
87{
88	int err;
89	const char *sep;
90	char *name;
91
92	assert(entry);
93
94	sep = strchr(entry, '|');
95	if (sep == NULL)
96		return hint_list_add(list, entry, NULL);
97
98	name = strndup(entry, sep - entry);
99	if (name == NULL)
100		return -ENOMEM;
101
102	err = hint_list_add(list, name, sep + 1);
103	free(name);
104	return err;
105}
106
107static void zero_handler(const char *file ATTRIBUTE_UNUSED,
108			 int line ATTRIBUTE_UNUSED,
109			 const char *function ATTRIBUTE_UNUSED,
110			 int err ATTRIBUTE_UNUSED,
111			 const char *fmt ATTRIBUTE_UNUSED,
112			 va_list arg ATTRIBUTE_UNUSED)
113{
114}
115
116static int get_dev_name1(struct hint_list *list, char **res, int device,
117			 int stream)
118{
119	*res = NULL;
120	if (device < 0 || device == DEV_SKIP)
121		return 0;
122	switch (list->iface) {
123#ifdef BUILD_HWDEP
124	case SND_CTL_ELEM_IFACE_HWDEP:
125		{
126			snd_hwdep_info_t info = {0};
127			snd_hwdep_info_set_device(&info, device);
128			if (snd_ctl_hwdep_info(list->ctl, &info) < 0)
129				return 0;
130			*res = strdup(snd_hwdep_info_get_name(&info));
131			return 0;
132		}
133#endif
134#ifdef BUILD_PCM
135	case SND_CTL_ELEM_IFACE_PCM:
136		{
137			snd_pcm_info_t info = {0};
138			snd_pcm_info_set_device(&info, device);
139			snd_pcm_info_set_stream(&info, stream ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
140			if (snd_ctl_pcm_info(list->ctl, &info) < 0)
141				return 0;
142			switch (snd_pcm_info_get_class(&info)) {
143			case SND_PCM_CLASS_MODEM:
144			case SND_PCM_CLASS_DIGITIZER:
145				return -ENODEV;
146			default:
147				break;
148			}
149			*res = strdup(snd_pcm_info_get_name(&info));
150			return 0;
151		}
152#endif
153#ifdef BUILD_RAWMIDI
154	case SND_CTL_ELEM_IFACE_RAWMIDI:
155		{
156			snd_rawmidi_info_t info = {0};
157			snd_rawmidi_info_set_device(&info, device);
158			snd_rawmidi_info_set_stream(&info, stream ? SND_RAWMIDI_STREAM_INPUT : SND_RAWMIDI_STREAM_OUTPUT);
159			if (snd_ctl_rawmidi_info(list->ctl, &info) < 0)
160				return 0;
161			*res = strdup(snd_rawmidi_info_get_name(&info));
162			return 0;
163		}
164#endif
165	default:
166		return 0;
167	}
168}
169
170static char *get_dev_name(struct hint_list *list)
171{
172	char *str1, *str2, *res;
173	int device;
174
175	device = list->device_input >= 0 ? list->device_input : list->device;
176	if (get_dev_name1(list, &str1, device, 1) < 0)
177		return NULL;
178	device = list->device_output >= 0 ? list->device_output : list->device;
179	if (get_dev_name1(list, &str2, device, 0) < 0) {
180		if (str1)
181			free(str1);
182		return NULL;
183	}
184	if (str1 != NULL || str2 != NULL) {
185		if (str1 != NULL && str2 != NULL) {
186			if (strcmp(str1, str2) == 0) {
187				res = malloc(strlen(list->cardname) + strlen(str2) + 3);
188				if (res != NULL) {
189					strcpy(res, list->cardname);
190					strcat(res, ", ");
191					strcat(res, str2);
192				}
193			} else {
194				res = malloc(strlen(list->cardname) + strlen(str2) + strlen(str1) + 6);
195				if (res != NULL) {
196					strcpy(res, list->cardname);
197					strcat(res, ", ");
198					strcat(res, str2);
199					strcat(res, " / ");
200					strcat(res, str1);
201				}
202			}
203			free(str2);
204			free(str1);
205			return res;
206		} else {
207			if (str1 != NULL) {
208				str2 = "Input";
209			} else {
210				str1 = str2;
211				str2 = "Output";
212			}
213			res = malloc(strlen(list->cardname) + strlen(str1) + 19);
214			if (res == NULL) {
215				free(str1);
216				return NULL;
217			}
218			strcpy(res, list->cardname);
219			strcat(res, ", ");
220			strcat(res, str1);
221			strcat(res, "|IOID");
222			strcat(res, str2);
223			free(str1);
224			return res;
225		}
226	}
227	/* if the specified device doesn't exist, skip this entry */
228	if (list->device >= 0 || list->device_input >= 0 || list->device_output >= 0)
229		return NULL;
230	return strdup(list->cardname);
231}
232
233#ifndef DOC_HIDDEN
234#define BUF_SIZE 128
235#endif
236
237static int try_config(snd_config_t *config,
238		      struct hint_list *list,
239		      const char *base,
240		      const char *name)
241{
242	snd_local_error_handler_t eh;
243	snd_config_t *res = NULL, *cfg, *cfg1, *n;
244	snd_config_iterator_t i, next;
245	char *buf, *buf1 = NULL, *buf2;
246	const char *str;
247	int err = 0, level;
248	long dev = list->device;
249	int cleanup_res = 0;
250
251	list->device_input = -1;
252	list->device_output = -1;
253	buf = malloc(BUF_SIZE);
254	if (buf == NULL)
255		return -ENOMEM;
256	sprintf(buf, "%s.%s", base, name);
257	/* look for redirection */
258	if (snd_config_search(config, buf, &cfg) >= 0 &&
259	    snd_config_get_string(cfg, &str) >= 0 &&
260	    ((strncmp(base, str, strlen(base)) == 0 &&
261	     str[strlen(base)] == '.') || strchr(str, '.') == NULL))
262	     	goto __skip_add;
263	if (list->card >= 0 && list->device >= 0)
264		sprintf(buf, "%s:CARD=%s,DEV=%i", name, snd_ctl_card_info_get_id(list->info), list->device);
265	else if (list->card >= 0)
266		sprintf(buf, "%s:CARD=%s", name, snd_ctl_card_info_get_id(list->info));
267	else
268		strcpy(buf, name);
269	eh = snd_lib_error_set_local(&zero_handler);
270	err = snd_config_search_definition(config, base, buf, &res);
271	snd_lib_error_set_local(eh);
272	if (err < 0)
273		goto __skip_add;
274	cleanup_res = 1;
275	err = -EINVAL;
276	if (snd_config_get_type(res) != SND_CONFIG_TYPE_COMPOUND)
277		goto __cleanup;
278	if (snd_config_search(res, "type", NULL) < 0)
279		goto __cleanup;
280
281#if 0	/* for debug purposes */
282		{
283			snd_output_t *out;
284			fprintf(stderr, "********* PCM '%s':\n", buf);
285			snd_output_stdio_attach(&out, stderr, 0);
286			snd_config_save(res, out);
287			snd_output_close(out);
288			fprintf(stderr, "\n");
289		}
290#endif
291
292	cfg1 = res;
293	level = 0;
294      __hint:
295      	level++;
296	if (snd_config_search(cfg1, "type", &cfg) >= 0 &&
297	    snd_config_get_string(cfg, &str) >= 0 &&
298	    strcmp(str, "hw") == 0) {
299		if (snd_config_search(cfg1, "device", &cfg) >= 0) {
300			if (snd_config_get_integer(cfg, &dev) < 0) {
301				SNDERR("(%s) device must be an integer", buf);
302				err = -EINVAL;
303				goto __cleanup;
304			}
305		}
306	}
307
308	if (snd_config_search(cfg1, "hint", &cfg) >= 0) {
309		if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
310			SNDERR("hint (%s) must be a compound", buf);
311			err = -EINVAL;
312			goto __cleanup;
313		}
314		if (list->card < 0 &&
315		    snd_config_search(cfg, "omit_noargs", &n) >= 0 &&
316		    snd_config_get_bool(n) > 0)
317			goto __skip_add;
318		if (level == 1 &&
319		    snd_config_search(cfg, "show", &n) >= 0 &&
320		    snd_config_get_bool(n) <= 0)
321			goto __skip_add;
322		if (buf1 == NULL &&
323		    snd_config_search(cfg, "description", &n) >= 0 &&
324		    snd_config_get_string(n, &str) >= 0) {
325			buf1 = strdup(str);
326			if (buf1 == NULL) {
327				err = -ENOMEM;
328				goto __cleanup;
329			}
330		}
331		if (snd_config_search(cfg, "device", &n) >= 0) {
332			if (snd_config_get_integer(n, &dev) < 0) {
333				SNDERR("(%s) device must be an integer", buf);
334				err = -EINVAL;
335				goto __cleanup;
336			}
337			list->device_input = dev;
338			list->device_output = dev;
339		}
340		if (snd_config_search(cfg, "device_input", &n) >= 0) {
341			if (snd_config_get_integer(n, &list->device_input) < 0) {
342				SNDERR("(%s) device_input must be an integer", buf);
343				err = -EINVAL;
344				goto __cleanup;
345			}
346			/* skip the counterpart if only a single direction is defined */
347			if (list->device_output < 0)
348				list->device_output = DEV_SKIP;
349		}
350		if (snd_config_search(cfg, "device_output", &n) >= 0) {
351			if (snd_config_get_integer(n, &list->device_output) < 0) {
352				SNDERR("(%s) device_output must be an integer", buf);
353				err = -EINVAL;
354				goto __cleanup;
355			}
356			/* skip the counterpart if only a single direction is defined */
357			if (list->device_input < 0)
358				list->device_input = DEV_SKIP;
359		}
360	} else if (level == 1 && !list->show_all)
361		goto __skip_add;
362	if (snd_config_search(cfg1, "slave", &cfg) >= 0 &&
363	    snd_config_search(cfg, base, &cfg1) >= 0)
364	    	goto __hint;
365	snd_config_delete(res);
366	res = NULL;
367	cleanup_res = 0;
368	if (strchr(buf, ':') != NULL)
369		goto __ok;
370	/* find, if all parameters have a default, */
371	/* otherwise filter this definition */
372	eh = snd_lib_error_set_local(&zero_handler);
373	err = snd_config_search_alias_hooks(config, base, buf, &res);
374	snd_lib_error_set_local(eh);
375	if (err < 0)
376		goto __cleanup;
377	if (snd_config_search(res, "@args", &cfg) >= 0) {
378		snd_config_for_each(i, next, cfg) {
379			/* skip the argument list */
380			if (snd_config_get_id(snd_config_iterator_entry(i), &str) < 0)
381				continue;
382			while (*str && *str >= '0' && *str <= '9') str++;
383			if (*str == '\0')
384				continue;
385			/* the argument definition must have the default */
386			if (snd_config_search(snd_config_iterator_entry(i),
387					      "default", NULL) < 0) {
388				err = -EINVAL;
389				goto __cleanup;
390			}
391		}
392	}
393      __ok:
394	err = 0;
395      __cleanup:
396      	if (err >= 0) {
397      		list->device = dev;
398 		str = list->card >= 0 ? get_dev_name(list) : NULL;
399      		if (str != NULL) {
400      			level = (buf1 == NULL ? 0 : strlen(buf1)) + 1 + strlen(str);
401      			buf2 = realloc((char *)str, level + 1);
402      			if (buf2 != NULL) {
403      				if (buf1 != NULL) {
404      					str = strchr(buf2, '|');
405      					if (str != NULL)
406						memmove(buf2 + (level - strlen(str)), str, strlen(str));
407					else
408						str = buf2 + strlen(buf2);
409      					*(char *)str++ = '\n';
410	      				memcpy((char *)str, buf1, strlen(buf1));
411	      				buf2[level] = '\0';
412					free(buf1);
413				}
414				buf1 = buf2;
415			} else {
416				free((char *)str);
417			}
418      		} else if (list->device >= 0)
419      			goto __skip_add;
420	      	err = hint_list_add(list, buf, buf1);
421	}
422      __skip_add:
423	if (res && cleanup_res)
424	      	snd_config_delete(res);
425	if (buf1)
426		free(buf1);
427      	free(buf);
428	return err;
429}
430
431#ifndef DOC_HIDDEN
432#define IFACE(v, fcn) [SND_CTL_ELEM_IFACE_##v] = (next_devices_t)fcn
433
434typedef int (*next_devices_t)(snd_ctl_t *, int *);
435
436static const next_devices_t next_devices[] = {
437	IFACE(CARD, NULL),
438	IFACE(HWDEP, snd_ctl_hwdep_next_device),
439	IFACE(MIXER, NULL),
440	IFACE(PCM, snd_ctl_pcm_next_device),
441	IFACE(RAWMIDI, snd_ctl_rawmidi_next_device),
442	IFACE(TIMER, NULL),
443	IFACE(SEQUENCER, NULL)
444};
445#endif
446
447static int add_card(snd_config_t *config, snd_config_t *rw_config, struct hint_list *list, int card)
448{
449	int err, ok;
450	snd_config_t *conf, *n;
451	snd_config_iterator_t i, next;
452	const char *str;
453	char ctl_name[16];
454	snd_ctl_card_info_t info = {0};
455	int device, max_device = 0;
456
457	list->info = &info;
458	err = snd_config_search(config, list->siface, &conf);
459	if (err < 0)
460		return err;
461	sprintf(ctl_name, "hw:%i", card);
462	err = snd_ctl_open(&list->ctl, ctl_name, 0);
463	if (err < 0)
464		return err;
465	err = snd_ctl_card_info(list->ctl, &info);
466	if (err < 0)
467		goto __error;
468	snd_config_for_each(i, next, conf) {
469		n = snd_config_iterator_entry(i);
470		if (snd_config_get_id(n, &str) < 0)
471			continue;
472
473		if (next_devices[list->iface] != NULL) {
474			list->card = card;
475			device = max_device = -1;
476			err = next_devices[list->iface](list->ctl, &device);
477			if (device < 0)
478				err = -EINVAL;
479			else
480				max_device = device;
481			while (err >= 0 && device >= 0) {
482				err = next_devices[list->iface](list->ctl, &device);
483				if (err >= 0 && device > max_device)
484					max_device = device;
485			}
486			ok = 0;
487			for (device = 0; err >= 0 && device <= max_device; device++) {
488				list->device = device;
489				err = try_config(rw_config, list, list->siface, str);
490				if (err < 0)
491					break;
492				ok++;
493			}
494			if (ok)
495				continue;
496		} else {
497			err = -EINVAL;
498		}
499		if (err == -EXDEV)
500			continue;
501		if (err < 0) {
502			list->card = card;
503			list->device = -1;
504			err = try_config(rw_config, list, list->siface, str);
505		}
506		if (err == -ENOMEM)
507			goto __error;
508	}
509	err = 0;
510      __error:
511      	snd_ctl_close(list->ctl);
512	return err;
513}
514
515static int get_card_name(struct hint_list *list, int card)
516{
517	char scard[16], *s;
518	int err;
519
520	free(list->cardname);
521	list->cardname = NULL;
522	err = snd_card_get_name(card, &list->cardname);
523	if (err <= 0)
524		return 0;
525	sprintf(scard, " #%i", card);
526	s = realloc(list->cardname, strlen(list->cardname) + strlen(scard) + 1);
527	if (s == NULL)
528		return -ENOMEM;
529	list->cardname = s;
530	return 0;
531}
532
533static int add_software_devices(snd_config_t *config, snd_config_t *rw_config,
534				struct hint_list *list)
535{
536	int err;
537	snd_config_t *conf, *n;
538	snd_config_iterator_t i, next;
539	const char *str;
540
541	err = snd_config_search(config, list->siface, &conf);
542	if (err < 0)
543		return err;
544	snd_config_for_each(i, next, conf) {
545		n = snd_config_iterator_entry(i);
546		if (snd_config_get_id(n, &str) < 0)
547			continue;
548		list->card = -1;
549		list->device = -1;
550		err = try_config(rw_config, list, list->siface, str);
551		if (err == -ENOMEM)
552			return -ENOMEM;
553	}
554	return 0;
555}
556
557/**
558 * \brief Get a set of device name hints
559 * \param card Card number or -1 (means all cards)
560 * \param iface Interface identification (like "pcm", "rawmidi", "timer", "seq")
561 * \param hints Result - array of device name hints
562 * \result zero if success, otherwise a negative error code
563 *
564 * hints will receive a NULL-terminated array of device name hints,
565 * which can be passed to #snd_device_name_get_hint to extract usable
566 * values. When no longer needed, hints should be passed to
567 * #snd_device_name_free_hint to release resources.
568 *
569 * User-defined hints are gathered from namehint.IFACE tree like:
570 *
571 * <code>
572 * namehint.pcm [<br>
573 *   myfile "file:FILE=/tmp/soundwave.raw|Save sound output to /tmp/soundwave.raw"<br>
574 *   myplug "plug:front|Do all conversions for front speakers"<br>
575 * ]
576 * </code>
577 *
578 * Note: The device description is separated with '|' char.
579 *
580 * Special variables: defaults.namehint.showall specifies if all device
581 * definitions are accepted (boolean type).
582 */
583int snd_device_name_hint(int card, const char *iface, void ***hints)
584{
585	struct hint_list list;
586	char ehints[24];
587	const char *str;
588	snd_config_t *conf, *local_config = NULL, *local_config_rw = NULL;
589	snd_config_update_t *local_config_update = NULL;
590	snd_config_iterator_t i, next;
591	int err;
592
593	if (hints == NULL)
594		return -EINVAL;
595	err = snd_config_update_r(&local_config, &local_config_update, NULL);
596	if (err < 0)
597		return err;
598	err = snd_config_copy(&local_config_rw, local_config);
599	if (err < 0)
600		return err;
601	list.list = NULL;
602	list.count = list.allocated = 0;
603	list.siface = iface;
604	list.show_all = 0;
605	list.cardname = NULL;
606	if (strcmp(iface, "pcm") == 0)
607		list.iface = SND_CTL_ELEM_IFACE_PCM;
608	else if (strcmp(iface, "rawmidi") == 0)
609		list.iface = SND_CTL_ELEM_IFACE_RAWMIDI;
610	else if (strcmp(iface, "timer") == 0)
611		list.iface = SND_CTL_ELEM_IFACE_TIMER;
612	else if (strcmp(iface, "seq") == 0)
613		list.iface = SND_CTL_ELEM_IFACE_SEQUENCER;
614	else if (strcmp(iface, "hwdep") == 0)
615		list.iface = SND_CTL_ELEM_IFACE_HWDEP;
616	else if (strcmp(iface, "ctl") == 0)
617		list.iface = SND_CTL_ELEM_IFACE_MIXER;
618	else {
619		err = -EINVAL;
620		goto __error;
621	}
622
623	if (snd_config_search(local_config, "defaults.namehint.showall", &conf) >= 0)
624		list.show_all = snd_config_get_bool(conf) > 0;
625	if (card >= 0) {
626		err = get_card_name(&list, card);
627		if (err >= 0)
628			err = add_card(local_config, local_config_rw, &list, card);
629	} else {
630		add_software_devices(local_config, local_config_rw, &list);
631		err = snd_card_next(&card);
632		if (err < 0)
633			goto __error;
634		while (card >= 0) {
635			err = get_card_name(&list, card);
636			if (err < 0)
637				goto __error;
638			err = add_card(local_config, local_config_rw, &list, card);
639			if (err < 0)
640				goto __error;
641			err = snd_card_next(&card);
642			if (err < 0)
643				goto __error;
644		}
645	}
646	sprintf(ehints, "namehint.%s", list.siface);
647	err = snd_config_search(local_config, ehints, &conf);
648	if (err >= 0) {
649		snd_config_for_each(i, next, conf) {
650			if (snd_config_get_string(snd_config_iterator_entry(i),
651						  &str) < 0)
652				continue;
653			err = hint_list_add_custom(&list, str);
654			if (err < 0)
655				goto __error;
656		}
657	}
658	err = 0;
659      __error:
660	/* add an empty entry if nothing has been added yet; the caller
661	 * expects non-NULL return
662	 */
663	if (!err && !list.list)
664		err = hint_list_add(&list, NULL, NULL);
665	if (err < 0)
666      		snd_device_name_free_hint((void **)list.list);
667	else
668      		*hints = (void **)list.list;
669	free(list.cardname);
670	if (local_config_rw)
671		snd_config_delete(local_config_rw);
672	if (local_config)
673		snd_config_delete(local_config);
674	if (local_config_update)
675		snd_config_update_free(local_config_update);
676	return err;
677}
678
679/**
680 * \brief Free a list of device name hints.
681 * \param hints List to free
682 * \result zero if success, otherwise a negative error code
683 */
684int snd_device_name_free_hint(void **hints)
685{
686	char **h;
687
688	if (hints == NULL)
689		return 0;
690	h = (char **)hints;
691	while (*h) {
692		free(*h);
693		h++;
694	}
695	free(hints);
696	return 0;
697}
698
699/**
700 * \brief Extract a value from a hint
701 * \param hint A pointer to hint
702 * \param id Hint value to extract ("NAME", "DESC", or "IOID", see below)
703 * \result an allocated ASCII string if success, otherwise NULL
704 *
705 * List of valid IDs:
706 * NAME - name of device
707 * DESC - description of device
708 * IOID - input / output identification ("Input" or "Output"), NULL means both
709 *
710 * The return value should be freed when no longer needed.
711 */
712char *snd_device_name_get_hint(const void *hint, const char *id)
713{
714	const char *hint1 = (const char *)hint, *delim;
715	char *res;
716	unsigned size;
717
718	if (strlen(id) != 4)
719		return NULL;
720	while (*hint1 != '\0') {
721		delim = strchr(hint1, '|');
722		if (memcmp(id, hint1, 4) != 0) {
723			if (delim == NULL)
724				return NULL;
725			hint1 = delim + 1;
726			continue;
727		}
728		if (delim == NULL)
729			return strdup(hint1 + 4);
730		size = delim - hint1 - 4;
731		res = malloc(size + 1);
732		if (res != NULL) {
733			memcpy(res, hint1 + 4, size);
734			res[size] = '\0';
735		}
736		return res;
737	}
738	return NULL;
739}
740