1 /*- 2 * Copyright (c) 1980, 1989, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 * 33 * @(#) Copyright (c) 1980, 1989, 1993, 1994 The Regents of the University of California. All rights reserved. 34 * @(#)mount.c 8.25 (Berkeley) 5/8/95 35 * $FreeBSD: src/sbin/mount/mount.c,v 1.39.2.3 2001/08/01 08:26:23 obrien Exp $ 36 * $DragonFly: src/sbin/mount/mount.c,v 1.10 2005/04/03 17:13:08 joerg Exp $ 37 */ 38 39 #include <sys/param.h> 40 #include <sys/mount.h> 41 #include <sys/mountctl.h> 42 #include <sys/stat.h> 43 #include <sys/wait.h> 44 45 #include <err.h> 46 #include <errno.h> 47 #include <fstab.h> 48 #include <pwd.h> 49 #include <signal.h> 50 #include <stdio.h> 51 #include <stdlib.h> 52 #include <string.h> 53 #include <unistd.h> 54 55 #include "extern.h" 56 #include "mntopts.h" 57 #include "pathnames.h" 58 59 /* `meta' options */ 60 #define MOUNT_META_OPTION_FSTAB "fstab" 61 #define MOUNT_META_OPTION_CURRENT "current" 62 63 int debug, fstab_style, verbose; 64 65 static char *catopt(char *, const char *); 66 static struct statfs 67 *getmntpt(const char *); 68 static int hasopt(const char *, const char *); 69 static int ismounted(struct fstab *, struct statfs *, int); 70 static int isremountable(const char *); 71 static void mangle(char *, int *, const char **); 72 static char *update_options(char *, char *, int); 73 static int mountfs(const char *, const char *, const char *, 74 int, const char *, const char *); 75 static void remopt(char *, const char *); 76 static void printdefvals(const struct statfs *); 77 static void prmount(struct statfs *); 78 static void putfsent(const struct statfs *); 79 static void usage(void); 80 static char *flags2opts(int); 81 static char *xstrdup(const char *str); 82 83 /* 84 * List of VFS types that can be remounted without becoming mounted on top 85 * of each other. 86 * XXX Is this list correct? 87 */ 88 static const char * 89 remountable_fs_names[] = { 90 "ufs", "ffs", "ext2fs", 91 0 92 }; 93 94 int 95 main(int argc, char **argv) 96 { 97 const char *mntfromname, **vfslist, *vfstype; 98 struct fstab *fs; 99 struct statfs *mntbuf; 100 FILE *mountdfp; 101 pid_t pid; 102 int all, ch, i, init_flags, mntsize, rval, have_fstab; 103 char *options; 104 105 all = init_flags = 0; 106 options = NULL; 107 vfslist = NULL; 108 vfstype = "ufs"; 109 while ((ch = getopt(argc, argv, "adF:fo:prwt:uv")) != -1) 110 switch (ch) { 111 case 'a': 112 all = 1; 113 break; 114 case 'd': 115 debug = 1; 116 break; 117 case 'F': 118 setfstab(optarg); 119 break; 120 case 'f': 121 init_flags |= MNT_FORCE; 122 break; 123 case 'o': 124 if (*optarg) 125 options = catopt(options, optarg); 126 break; 127 case 'p': 128 fstab_style = 1; 129 verbose = 1; 130 break; 131 case 'r': 132 options = catopt(options, "ro"); 133 break; 134 case 't': 135 if (vfslist != NULL) 136 errx(1, "only one -t option may be specified"); 137 vfslist = makevfslist(optarg); 138 vfstype = optarg; 139 break; 140 case 'u': 141 init_flags |= MNT_UPDATE; 142 break; 143 case 'v': 144 verbose = 1; 145 break; 146 case 'w': 147 options = catopt(options, "noro"); 148 break; 149 case '?': 150 default: 151 usage(); 152 /* NOTREACHED */ 153 } 154 argc -= optind; 155 argv += optind; 156 157 #define BADTYPE(type) \ 158 (strcmp(type, FSTAB_RO) && \ 159 strcmp(type, FSTAB_RW) && strcmp(type, FSTAB_RQ)) 160 161 rval = 0; 162 switch (argc) { 163 case 0: 164 if ((mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0) 165 err(1, "getmntinfo"); 166 if (all) { 167 while ((fs = getfsent()) != NULL) { 168 if (BADTYPE(fs->fs_type)) 169 continue; 170 if (checkvfsname(fs->fs_vfstype, vfslist)) 171 continue; 172 if (hasopt(fs->fs_mntops, "noauto")) 173 continue; 174 if (!(init_flags & MNT_UPDATE) && 175 ismounted(fs, mntbuf, mntsize)) 176 continue; 177 options = update_options(options, 178 fs->fs_mntops, mntbuf->f_flags); 179 if (mountfs(fs->fs_vfstype, fs->fs_spec, 180 fs->fs_file, init_flags, options, 181 fs->fs_mntops)) 182 rval = 1; 183 } 184 } else if (fstab_style) { 185 for (i = 0; i < mntsize; i++) { 186 if (checkvfsname(mntbuf[i].f_fstypename, vfslist)) 187 continue; 188 putfsent(&mntbuf[i]); 189 } 190 } else { 191 for (i = 0; i < mntsize; i++) { 192 if (checkvfsname(mntbuf[i].f_fstypename, 193 vfslist)) 194 continue; 195 prmount(&mntbuf[i]); 196 } 197 } 198 exit(rval); 199 case 1: 200 if (vfslist != NULL) 201 usage(); 202 203 rmslashes(*argv, *argv); 204 205 if (init_flags & MNT_UPDATE) { 206 mntfromname = NULL; 207 have_fstab = 0; 208 if ((mntbuf = getmntpt(*argv)) == NULL) 209 errx(1, "not currently mounted %s", *argv); 210 /* 211 * Only get the mntflags from fstab if both mntpoint 212 * and mntspec are identical. Also handle the special 213 * case where just '/' is mounted and 'spec' is not 214 * identical with the one from fstab ('/dev' is missing 215 * in the spec-string at boot-time). 216 */ 217 if ((fs = getfsfile(mntbuf->f_mntonname)) != NULL) { 218 if (strcmp(fs->fs_spec, 219 mntbuf->f_mntfromname) == 0 && 220 strcmp(fs->fs_file, 221 mntbuf->f_mntonname) == 0) { 222 have_fstab = 1; 223 mntfromname = mntbuf->f_mntfromname; 224 } else if (argv[0][0] == '/' && 225 argv[0][1] == '\0') { 226 fs = getfsfile("/"); 227 have_fstab = 1; 228 mntfromname = fs->fs_spec; 229 } 230 } 231 if (have_fstab) { 232 options = update_options(options, fs->fs_mntops, 233 mntbuf->f_flags); 234 } else { 235 mntfromname = mntbuf->f_mntfromname; 236 options = update_options(options, NULL, 237 mntbuf->f_flags); 238 } 239 rval = mountfs(mntbuf->f_fstypename, mntfromname, 240 mntbuf->f_mntonname, init_flags, options, 0); 241 break; 242 } 243 if ((fs = getfsfile(*argv)) == NULL && 244 (fs = getfsspec(*argv)) == NULL) 245 errx(1, "%s: unknown special file or file system", 246 *argv); 247 if (BADTYPE(fs->fs_type)) 248 errx(1, "%s has unknown file system type", 249 *argv); 250 rval = mountfs(fs->fs_vfstype, fs->fs_spec, fs->fs_file, 251 init_flags, options, fs->fs_mntops); 252 break; 253 case 2: 254 /* 255 * If -t flag has not been specified, the path cannot be 256 * found, spec contains either a ':' or a '@', and the 257 * spec is not a file with those characters, then assume 258 * that an NFS filesystem is being specified ala Sun. 259 */ 260 if (vfslist == NULL && strpbrk(argv[0], ":@") != NULL && 261 access(argv[0], 0) == -1) 262 vfstype = "nfs"; 263 rval = mountfs(vfstype, getdevpath(argv[0], 0), argv[1], 264 init_flags, options, NULL); 265 break; 266 default: 267 usage(); 268 /* NOTREACHED */ 269 } 270 271 /* 272 * If the mount was successfully, and done by root, tell mountd the 273 * good news. Pid checks are probably unnecessary, but don't hurt. 274 */ 275 if (rval == 0 && getuid() == 0 && 276 (mountdfp = fopen(_PATH_MOUNTDPID, "r")) != NULL) { 277 if (fscanf(mountdfp, "%d", &pid) == 1 && 278 pid > 0 && kill(pid, SIGHUP) == -1 && errno != ESRCH) 279 err(1, "signal mountd"); 280 fclose(mountdfp); 281 } 282 283 exit(rval); 284 } 285 286 static int 287 ismounted(struct fstab *fs, struct statfs *mntbuf, int mntsize) 288 { 289 int i; 290 291 if (fs->fs_file[0] == '/' && fs->fs_file[1] == '\0') 292 /* the root file system can always be remounted */ 293 return (0); 294 295 for (i = mntsize - 1; i >= 0; --i) 296 if (strcmp(fs->fs_file, mntbuf[i].f_mntonname) == 0 && 297 (!isremountable(fs->fs_vfstype) || 298 strcmp(fs->fs_spec, mntbuf[i].f_mntfromname) == 0)) 299 return (1); 300 return (0); 301 } 302 303 static int 304 isremountable(const char *vfsname) 305 { 306 const char **cp; 307 308 for (cp = remountable_fs_names; *cp; cp++) 309 if (strcmp(*cp, vfsname) == 0) 310 return (1); 311 return (0); 312 } 313 314 static int 315 hasopt(const char *mntopts, const char *option) 316 { 317 int negative, found; 318 char *opt, *optbuf; 319 320 if (option[0] == 'n' && option[1] == 'o') { 321 negative = 1; 322 option += 2; 323 } else 324 negative = 0; 325 optbuf = xstrdup(mntopts); 326 found = 0; 327 for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) { 328 if (opt[0] == 'n' && opt[1] == 'o') { 329 if (!strcasecmp(opt + 2, option)) 330 found = negative; 331 } else if (!strcasecmp(opt, option)) 332 found = !negative; 333 } 334 free(optbuf); 335 return (found); 336 } 337 338 static int 339 mountfs(const char *vfstype, const char *spec, const char *name, int flags, 340 const char *options, const char *mntopts) 341 { 342 /* List of directories containing mount_xxx subcommands. */ 343 static const char *edirs[] = { 344 _PATH_SBIN, 345 _PATH_USRSBIN, 346 NULL 347 }; 348 const char *argv[100], **edir; 349 struct statfs sf; 350 pid_t pid; 351 int argc, i, status; 352 char *optbuf, execname[MAXPATHLEN + 1], mntpath[MAXPATHLEN]; 353 354 #if __GNUC__ 355 (void)&optbuf; 356 (void)&name; 357 #endif 358 359 /* resolve the mountpoint with realpath(3) */ 360 checkpath(name, mntpath); 361 name = mntpath; 362 363 if (mntopts == NULL) 364 mntopts = ""; 365 if (options == NULL) { 366 if (*mntopts == '\0') { 367 options = "rw"; 368 } else { 369 options = mntopts; 370 mntopts = ""; 371 } 372 } 373 optbuf = catopt(xstrdup(mntopts), options); 374 375 if (strcmp(name, "/") == 0) 376 flags |= MNT_UPDATE; 377 if (flags & MNT_FORCE) 378 optbuf = catopt(optbuf, "force"); 379 if (flags & MNT_RDONLY) 380 optbuf = catopt(optbuf, "ro"); 381 /* 382 * XXX 383 * The mount_mfs (newfs) command uses -o to select the 384 * optimization mode. We don't pass the default "-o rw" 385 * for that reason. 386 */ 387 if (flags & MNT_UPDATE) 388 optbuf = catopt(optbuf, "update"); 389 390 argc = 0; 391 argv[argc++] = vfstype; 392 mangle(optbuf, &argc, argv); 393 argv[argc++] = spec; 394 argv[argc++] = name; 395 argv[argc] = NULL; 396 397 if (debug) { 398 printf("exec: mount_%s", vfstype); 399 for (i = 1; i < argc; i++) 400 printf(" %s", argv[i]); 401 printf("\n"); 402 return (0); 403 } 404 405 switch (pid = fork()) { 406 case -1: /* Error. */ 407 warn("fork"); 408 free(optbuf); 409 return (1); 410 case 0: /* Child. */ 411 if (strcmp(vfstype, "ufs") == 0) 412 exit(mount_ufs(argc, argv)); 413 414 /* Go find an executable. */ 415 for (edir = edirs; *edir; edir++) { 416 snprintf(execname, 417 sizeof(execname), "%s/mount_%s", *edir, vfstype); 418 execv(execname, __DECONST(char * const *, argv)); 419 } 420 if (errno == ENOENT) { 421 int len = 0; 422 char *cp; 423 for (edir = edirs; *edir; edir++) 424 len += strlen(*edir) + 2; /* ", " */ 425 if ((cp = malloc(len)) == NULL) 426 errx(1, "malloc failed"); 427 cp[0] = '\0'; 428 for (edir = edirs; *edir; edir++) { 429 strcat(cp, *edir); 430 if (edir[1] != NULL) 431 strcat(cp, ", "); 432 } 433 warn("exec mount_%s not found in %s", vfstype, cp); 434 } 435 exit(1); 436 /* NOTREACHED */ 437 default: /* Parent. */ 438 free(optbuf); 439 440 if (waitpid(pid, &status, 0) < 0) { 441 warn("waitpid"); 442 return (1); 443 } 444 445 if (WIFEXITED(status)) { 446 if (WEXITSTATUS(status) != 0) 447 return (WEXITSTATUS(status)); 448 } else if (WIFSIGNALED(status)) { 449 warnx("%s: %s", name, sys_siglist[WTERMSIG(status)]); 450 return (1); 451 } 452 453 if (verbose) { 454 if (statfs(name, &sf) < 0) { 455 warn("statfs %s", name); 456 return (1); 457 } 458 if (fstab_style) 459 putfsent(&sf); 460 else 461 prmount(&sf); 462 } 463 break; 464 } 465 466 return (0); 467 } 468 469 static void 470 prmount(struct statfs *sfp) 471 { 472 struct passwd *pw; 473 char *buf; 474 int error; 475 int len; 476 477 error = 0; 478 len = 256; 479 480 if ((buf = malloc(len)) == NULL) 481 errx(1, "malloc failed"); 482 483 printf("%s on %s (%s", sfp->f_mntfromname, sfp->f_mntonname, 484 sfp->f_fstypename); 485 486 /* Get a string buffer with all the used flags names */ 487 error = mountctl(sfp->f_mntonname, MOUNTCTL_MOUNTFLAGS, 488 -1, NULL, 0, buf, len); 489 490 if (sfp->f_owner) { 491 printf(", mounted by "); 492 if ((pw = getpwuid(sfp->f_owner)) != NULL) 493 printf("%s", pw->pw_name); 494 else 495 printf("%d", sfp->f_owner); 496 } 497 498 if (strlen(buf)) 499 printf(", %s", buf); 500 501 if (verbose) { 502 if (sfp->f_syncwrites != 0 || sfp->f_asyncwrites != 0) 503 printf(", writes: sync %ld async %ld", 504 sfp->f_syncwrites, sfp->f_asyncwrites); 505 if (sfp->f_syncreads != 0 || sfp->f_asyncreads != 0) 506 printf(", reads: sync %ld async %ld", 507 sfp->f_syncreads, sfp->f_asyncreads); 508 } 509 printf(")\n"); 510 } 511 512 static struct statfs * 513 getmntpt(const char *name) 514 { 515 struct statfs *mntbuf; 516 int i, mntsize; 517 518 mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); 519 for (i = mntsize - 1; i >= 0; i--) { 520 if (strcmp(mntbuf[i].f_mntfromname, name) == 0 || 521 strcmp(mntbuf[i].f_mntonname, name) == 0) 522 return (&mntbuf[i]); 523 } 524 return (NULL); 525 } 526 527 static char * 528 catopt(char *s0, const char *s1) 529 { 530 size_t i; 531 char *cp; 532 533 if (s1 == NULL || *s1 == '\0') 534 return s0; 535 536 if (s0 && *s0) { 537 i = strlen(s0) + strlen(s1) + 1 + 1; 538 if ((cp = malloc(i)) == NULL) 539 errx(1, "malloc failed"); 540 snprintf(cp, i, "%s,%s", s0, s1); 541 } else 542 cp = xstrdup(s1); 543 544 if (s0) 545 free(s0); 546 return (cp); 547 } 548 549 static void 550 mangle(char *options, int *argcp, const char **argv) 551 { 552 char *p, *s; 553 int argc; 554 555 argc = *argcp; 556 for (s = options; (p = strsep(&s, ",")) != NULL;) 557 if (*p != '\0') { 558 if (*p == '-') { 559 argv[argc++] = p; 560 p = strchr(p, '='); 561 if (p) { 562 *p = '\0'; 563 argv[argc++] = p+1; 564 } 565 } else if (strcmp(p, "rw") != 0) { 566 argv[argc++] = "-o"; 567 argv[argc++] = p; 568 } 569 } 570 571 *argcp = argc; 572 } 573 574 575 static char * 576 update_options(char *opts, char *fstab, int curflags) 577 { 578 char *o, *p; 579 char *cur; 580 char *expopt, *newopt, *tmpopt; 581 582 if (opts == NULL) 583 return xstrdup(""); 584 585 /* remove meta options from list */ 586 remopt(fstab, MOUNT_META_OPTION_FSTAB); 587 remopt(fstab, MOUNT_META_OPTION_CURRENT); 588 cur = flags2opts(curflags); 589 590 /* 591 * Expand all meta-options passed to us first. 592 */ 593 expopt = NULL; 594 for (p = opts; (o = strsep(&p, ",")) != NULL;) { 595 if (strcmp(MOUNT_META_OPTION_FSTAB, o) == 0) 596 expopt = catopt(expopt, fstab); 597 else if (strcmp(MOUNT_META_OPTION_CURRENT, o) == 0) 598 expopt = catopt(expopt, cur); 599 else 600 expopt = catopt(expopt, o); 601 } 602 free(cur); 603 free(opts); 604 605 /* 606 * Remove previous contradictory arguments. Given option "foo" we 607 * remove all the "nofoo" options. Given "nofoo" we remove "nonofoo" 608 * and "foo" - so we can deal with possible options like "notice". 609 */ 610 newopt = NULL; 611 for (p = expopt; (o = strsep(&p, ",")) != NULL;) { 612 if ((tmpopt = malloc( strlen(o) + 2 + 1 )) == NULL) 613 errx(1, "malloc failed"); 614 615 strcpy(tmpopt, "no"); 616 strcat(tmpopt, o); 617 remopt(newopt, tmpopt); 618 free(tmpopt); 619 620 if (strncmp("no", o, 2) == 0) 621 remopt(newopt, o+2); 622 623 newopt = catopt(newopt, o); 624 } 625 free(expopt); 626 627 return newopt; 628 } 629 630 static void 631 remopt(char *string, const char *opt) 632 { 633 char *o, *p, *r; 634 635 if (string == NULL || *string == '\0' || opt == NULL || *opt == '\0') 636 return; 637 638 r = string; 639 640 for (p = string; (o = strsep(&p, ",")) != NULL;) { 641 if (strcmp(opt, o) != 0) { 642 if (*r == ',' && *o != '\0') 643 r++; 644 while ((*r++ = *o++) != '\0') 645 ; 646 *--r = ','; 647 } 648 } 649 *r = '\0'; 650 } 651 652 static void 653 usage(void) 654 { 655 656 fprintf(stderr, "%s\n%s\n%s\n", 657 "usage: mount [-adfpruvw] [-F fstab] [-o options] [-t type]", 658 " mount [-dfpruvw] {special | node}", 659 " mount [-dfpruvw] [-o options] [-t type] special node"); 660 exit(1); 661 } 662 663 /* 664 * Prints the default values for dump frequency and pass number of fsck. 665 * This happens when mount(8) is called with -p and there is no fstab file 666 * or there is but the values aren't specified. 667 */ 668 static void 669 printdefvals(const struct statfs *ent) 670 { 671 if (strcmp(ent->f_fstypename, "ufs") == 0) { 672 if (strcmp(ent->f_mntonname, "/") == 0) 673 printf("\t1 1\n"); 674 else 675 printf("\t2 2\n"); 676 } else { 677 printf("\t0 0\n"); 678 } 679 } 680 681 static void 682 putfsent(const struct statfs *ent) 683 { 684 struct stat sb; 685 struct fstab *fst; 686 char *opts; 687 688 opts = flags2opts(ent->f_flags); 689 printf("%s\t%s\t%s %s", ent->f_mntfromname, ent->f_mntonname, 690 ent->f_fstypename, opts); 691 free(opts); 692 693 /* 694 * If fstab file is missing don't call getfsspec() or getfsfile() 695 * at all, because the same warning will be printed twice for every 696 * mounted filesystem. 697 */ 698 if (stat(_PATH_FSTAB, &sb) != -1) { 699 if ((fst = getfsspec(ent->f_mntfromname))) 700 printf("\t%u %u\n", fst->fs_freq, fst->fs_passno); 701 else if ((fst = getfsfile(ent->f_mntonname))) 702 printf("\t%u %u\n", fst->fs_freq, fst->fs_passno); 703 else 704 printdefvals(ent); 705 } else { 706 printdefvals(ent); 707 } 708 } 709 710 711 static char * 712 flags2opts(int flags) 713 { 714 char *res; 715 716 res = NULL; 717 718 res = catopt(res, (flags & MNT_RDONLY) ? "ro" : "rw"); 719 720 if (flags & MNT_SYNCHRONOUS) res = catopt(res, "sync"); 721 if (flags & MNT_NOEXEC) res = catopt(res, "noexec"); 722 if (flags & MNT_NOSUID) res = catopt(res, "nosuid"); 723 if (flags & MNT_NODEV) res = catopt(res, "nodev"); 724 if (flags & MNT_UNION) res = catopt(res, "union"); 725 if (flags & MNT_ASYNC) res = catopt(res, "async"); 726 if (flags & MNT_NOATIME) res = catopt(res, "noatime"); 727 if (flags & MNT_NOCLUSTERR) res = catopt(res, "noclusterr"); 728 if (flags & MNT_NOCLUSTERW) res = catopt(res, "noclusterw"); 729 if (flags & MNT_NOSYMFOLLOW) res = catopt(res, "nosymfollow"); 730 if (flags & MNT_SUIDDIR) res = catopt(res, "suiddir"); 731 if (flags & MNT_IGNORE) res = catopt(res, "ignore"); 732 733 return res; 734 } 735 736 static char* 737 xstrdup(const char *str) 738 { 739 char* ret = strdup(str); 740 if(ret == NULL) { 741 errx(1, "strdup failed (could not allocate memory)"); 742 } 743 return ret; 744 } 745