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