1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1980, 1990, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * (c) UNIX System Laboratories, Inc. 7 * All or some portions of this file are derived from material licensed 8 * to the University of California by American Telephone and Telegraph 9 * Co. or Unix System Laboratories, Inc. and are reproduced herein with 10 * the permission of UNIX System Laboratories, Inc. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #include <sys/param.h> 38 #include <sys/stat.h> 39 #include <sys/mount.h> 40 #include <sys/sysctl.h> 41 #include <getopt.h> 42 #include <libutil.h> 43 #include <locale.h> 44 #include <stdint.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <sysexits.h> 49 #include <unistd.h> 50 #include <libxo/xo.h> 51 52 #define UNITS_SI 1 53 #define UNITS_2 2 54 55 /* Maximum widths of various fields. */ 56 struct maxwidths { 57 int mntfrom; 58 int fstype; 59 int total; 60 int used; 61 int avail; 62 int iused; 63 int ifree; 64 }; 65 66 static void addstat(struct statfs *, struct statfs *); 67 static char *getmntpt(const char *); 68 static const char **makevfslist(char *fslist, int *skip); 69 static int checkvfsname(const char *vfsname, const char **vfslist, int skip); 70 static int checkvfsselected(char *); 71 static int int64width(int64_t); 72 static char *makenetvfslist(void); 73 static void prthuman(const struct statfs *, int64_t); 74 static void prthumanval(const char *, int64_t); 75 static intmax_t fsbtoblk(int64_t, uint64_t, u_long); 76 static void prtstat(struct statfs *, struct maxwidths *); 77 static size_t regetmntinfo(struct statfs **, long); 78 static void update_maxwidths(struct maxwidths *, const struct statfs *); 79 static void usage(void); 80 81 static __inline int 82 imax(int a, int b) 83 { 84 return (a > b ? a : b); 85 } 86 87 static int aflag = 0, cflag, hflag, iflag, kflag, lflag = 0, nflag, Tflag; 88 static int thousands; 89 static int skipvfs_l, skipvfs_t; 90 static const char **vfslist_l, **vfslist_t; 91 92 static const struct option long_options[] = 93 { 94 { "si", no_argument, NULL, 'H' }, 95 { NULL, no_argument, NULL, 0 }, 96 }; 97 98 int 99 main(int argc, char *argv[]) 100 { 101 struct stat stbuf; 102 struct statfs statfsbuf, totalbuf; 103 struct maxwidths maxwidths; 104 struct statfs *mntbuf; 105 char *mntpt; 106 int i, mntsize; 107 int ch, rv; 108 109 (void)setlocale(LC_ALL, ""); 110 memset(&maxwidths, 0, sizeof(maxwidths)); 111 memset(&totalbuf, 0, sizeof(totalbuf)); 112 totalbuf.f_bsize = DEV_BSIZE; 113 strlcpy(totalbuf.f_mntfromname, "total", MNAMELEN); 114 115 argc = xo_parse_args(argc, argv); 116 if (argc < 0) 117 exit(1); 118 119 while ((ch = getopt_long(argc, argv, "+abcgHhiklmnPt:T,", long_options, 120 NULL)) != -1) 121 switch (ch) { 122 case 'a': 123 aflag = 1; 124 break; 125 case 'b': 126 /* FALLTHROUGH */ 127 case 'P': 128 /* 129 * POSIX specifically discusses the behavior of 130 * both -k and -P. It states that the blocksize should 131 * be set to 1024. 132 */ 133 if (kflag) { 134 setenv("BLOCKSIZE", "1024", 1); 135 break; 136 } 137 setenv("BLOCKSIZE", "512", 1); 138 hflag = 0; 139 break; 140 case 'c': 141 cflag = 1; 142 break; 143 case 'g': 144 setenv("BLOCKSIZE", "1g", 1); 145 hflag = 0; 146 break; 147 case 'H': 148 hflag = UNITS_SI; 149 break; 150 case 'h': 151 hflag = UNITS_2; 152 break; 153 case 'i': 154 iflag = 1; 155 break; 156 case 'k': 157 kflag++; 158 setenv("BLOCKSIZE", "1k", 1); 159 hflag = 0; 160 break; 161 case 'l': 162 /* Ignore duplicate -l */ 163 if (lflag) 164 break; 165 vfslist_l = makevfslist(makenetvfslist(), &skipvfs_l); 166 lflag = 1; 167 break; 168 case 'm': 169 setenv("BLOCKSIZE", "1m", 1); 170 hflag = 0; 171 break; 172 case 'n': 173 nflag = 1; 174 break; 175 case 't': 176 if (vfslist_t != NULL) 177 xo_errx(1, "only one -t option may be specified"); 178 vfslist_t = makevfslist(optarg, &skipvfs_t); 179 break; 180 case 'T': 181 Tflag = 1; 182 break; 183 case ',': 184 thousands = 1; 185 break; 186 case '?': 187 default: 188 usage(); 189 } 190 argc -= optind; 191 argv += optind; 192 193 rv = EXIT_SUCCESS; 194 if (!*argv) { 195 /* everything (modulo -t) */ 196 mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); 197 mntsize = regetmntinfo(&mntbuf, mntsize); 198 } else { 199 /* just the filesystems specified on the command line */ 200 mntbuf = malloc(argc * sizeof(*mntbuf)); 201 if (mntbuf == NULL) 202 xo_err(1, "malloc()"); 203 mntsize = 0; 204 /* continued in for loop below */ 205 } 206 207 xo_open_container("storage-system-information"); 208 xo_open_list("filesystem"); 209 210 /* iterate through specified filesystems */ 211 for (; *argv; argv++) { 212 if (stat(*argv, &stbuf) < 0) { 213 if ((mntpt = getmntpt(*argv)) == NULL) { 214 xo_warn("%s", *argv); 215 rv = EXIT_FAILURE; 216 continue; 217 } 218 } else if (S_ISCHR(stbuf.st_mode)) { 219 mntpt = getmntpt(*argv); 220 if (mntpt == NULL) { 221 xo_warnx("%s: not mounted", *argv); 222 rv = EXIT_FAILURE; 223 continue; 224 } 225 } else { 226 mntpt = *argv; 227 } 228 229 /* 230 * Statfs does not take a `wait' flag, so we cannot 231 * implement nflag here. 232 */ 233 if (statfs(mntpt, &statfsbuf) < 0) { 234 xo_warn("%s", mntpt); 235 rv = EXIT_FAILURE; 236 continue; 237 } 238 239 /* 240 * Check to make sure the arguments we've been given are 241 * satisfied. Return an error if we have been asked to 242 * list a mount point that does not match the other args 243 * we've been given (-l, -t, etc.). 244 */ 245 if (checkvfsselected(statfsbuf.f_fstypename) != 0) { 246 rv = EXIT_FAILURE; 247 continue; 248 } 249 250 /* the user asked for it, so ignore the ignore flag */ 251 statfsbuf.f_flags &= ~MNT_IGNORE; 252 253 /* add to list */ 254 mntbuf[mntsize++] = statfsbuf; 255 } 256 257 memset(&maxwidths, 0, sizeof(maxwidths)); 258 for (i = 0; i < mntsize; i++) { 259 if (aflag || (mntbuf[i].f_flags & MNT_IGNORE) == 0) { 260 update_maxwidths(&maxwidths, &mntbuf[i]); 261 if (cflag) 262 addstat(&totalbuf, &mntbuf[i]); 263 } 264 } 265 for (i = 0; i < mntsize; i++) 266 if (aflag || (mntbuf[i].f_flags & MNT_IGNORE) == 0) 267 prtstat(&mntbuf[i], &maxwidths); 268 269 xo_close_list("filesystem"); 270 271 if (cflag) 272 prtstat(&totalbuf, &maxwidths); 273 274 xo_close_container("storage-system-information"); 275 if (xo_finish() < 0) 276 rv = EXIT_FAILURE; 277 exit(rv); 278 } 279 280 static char * 281 getmntpt(const char *name) 282 { 283 size_t mntsize, i; 284 struct statfs *mntbuf; 285 286 mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); 287 for (i = 0; i < mntsize; i++) { 288 if (!strcmp(mntbuf[i].f_mntfromname, name)) 289 return (mntbuf[i].f_mntonname); 290 } 291 return (NULL); 292 } 293 294 static const char ** 295 makevfslist(char *fslist, int *skip) 296 { 297 const char **av; 298 int i; 299 char *nextcp; 300 301 if (fslist == NULL) 302 return (NULL); 303 *skip = 0; 304 if (fslist[0] == 'n' && fslist[1] == 'o') { 305 fslist += 2; 306 *skip = 1; 307 } 308 for (i = 0, nextcp = fslist; *nextcp; nextcp++) 309 if (*nextcp == ',') 310 i++; 311 if ((av = malloc((size_t)(i + 2) * sizeof(char *))) == NULL) { 312 xo_warnx("malloc failed"); 313 return (NULL); 314 } 315 nextcp = fslist; 316 i = 0; 317 av[i++] = nextcp; 318 while ((nextcp = strchr(nextcp, ',')) != NULL) { 319 *nextcp++ = '\0'; 320 av[i++] = nextcp; 321 } 322 av[i++] = NULL; 323 return (av); 324 } 325 326 static int 327 checkvfsname(const char *vfsname, const char **vfslist, int skip) 328 { 329 330 if (vfslist == NULL) 331 return (0); 332 while (*vfslist != NULL) { 333 if (strcmp(vfsname, *vfslist) == 0) 334 return (skip); 335 ++vfslist; 336 } 337 return (!skip); 338 } 339 340 /* 341 * Without -l and -t option, all file system types are enabled. 342 * The -l option selects the local file systems, if present. 343 * A -t option modifies the selection by adding or removing further 344 * file system types, based on the argument that is passed. 345 */ 346 static int 347 checkvfsselected(char *fstypename) 348 { 349 int result; 350 351 if (vfslist_t) { 352 /* if -t option used then select passed types */ 353 result = checkvfsname(fstypename, vfslist_t, skipvfs_t); 354 if (vfslist_l) { 355 /* if -l option then adjust selection */ 356 if (checkvfsname(fstypename, vfslist_l, skipvfs_l) == skipvfs_t) 357 result = skipvfs_t; 358 } 359 } else { 360 /* no -t option then -l decides */ 361 result = checkvfsname(fstypename, vfslist_l, skipvfs_l); 362 } 363 return (result); 364 } 365 366 /* 367 * Make a pass over the file system info in ``mntbuf'' filtering out 368 * file system types not in vfslist_{l,t} and possibly re-stating to get 369 * current (not cached) info. Returns the new count of valid statfs bufs. 370 */ 371 static size_t 372 regetmntinfo(struct statfs **mntbufp, long mntsize) 373 { 374 int error, i, j; 375 struct statfs *mntbuf; 376 377 if (vfslist_l == NULL && vfslist_t == NULL) 378 return (nflag ? mntsize : getmntinfo(mntbufp, MNT_WAIT)); 379 380 mntbuf = *mntbufp; 381 for (j = 0, i = 0; i < mntsize; i++) { 382 if (checkvfsselected(mntbuf[i].f_fstypename) != 0) 383 continue; 384 /* 385 * XXX statfs(2) can fail for various reasons. It may be 386 * possible that the user does not have access to the 387 * pathname, if this happens, we will fall back on 388 * "stale" filesystem statistics. 389 */ 390 error = statfs(mntbuf[i].f_mntonname, &mntbuf[j]); 391 if (nflag || error < 0) 392 if (i != j) { 393 if (error < 0) 394 xo_warnx("%s stats possibly stale", 395 mntbuf[i].f_mntonname); 396 mntbuf[j] = mntbuf[i]; 397 } 398 j++; 399 } 400 return (j); 401 } 402 403 static void 404 prthuman(const struct statfs *sfsp, int64_t used) 405 { 406 407 prthumanval(" {:blocks/%6s}", sfsp->f_blocks * sfsp->f_bsize); 408 prthumanval(" {:used/%6s}", used * sfsp->f_bsize); 409 prthumanval(" {:available/%6s}", sfsp->f_bavail * sfsp->f_bsize); 410 } 411 412 static void 413 prthumanval(const char *fmt, int64_t bytes) 414 { 415 char buf[6]; 416 int flags; 417 418 flags = HN_B | HN_NOSPACE | HN_DECIMAL; 419 if (hflag == UNITS_SI) 420 flags |= HN_DIVISOR_1000; 421 422 humanize_number(buf, sizeof(buf) - (bytes < 0 ? 0 : 1), 423 bytes, "", HN_AUTOSCALE, flags); 424 425 xo_attr("value", "%lld", (long long) bytes); 426 xo_emit(fmt, buf); 427 } 428 429 /* 430 * Print an inode count in "human-readable" format. 431 */ 432 static void 433 prthumanvalinode(const char *fmt, int64_t bytes) 434 { 435 char buf[6]; 436 int flags; 437 438 flags = HN_NOSPACE | HN_DECIMAL | HN_DIVISOR_1000; 439 440 humanize_number(buf, sizeof(buf) - (bytes < 0 ? 0 : 1), 441 bytes, "", HN_AUTOSCALE, flags); 442 443 xo_attr("value", "%lld", (long long) bytes); 444 xo_emit(fmt, buf); 445 } 446 447 /* 448 * Convert statfs returned file system size into BLOCKSIZE units. 449 */ 450 static intmax_t 451 fsbtoblk(int64_t num, uint64_t fsbs, u_long bs) 452 { 453 return (num * (intmax_t) fsbs / (int64_t) bs); 454 } 455 456 /* 457 * Print out status about a file system. 458 */ 459 static void 460 prtstat(struct statfs *sfsp, struct maxwidths *mwp) 461 { 462 static long blocksize; 463 static int headerlen, timesthrough = 0; 464 static const char *header; 465 int64_t used, availblks, inodes; 466 const char *format; 467 468 if (++timesthrough == 1) { 469 mwp->mntfrom = imax(mwp->mntfrom, (int)strlen("Filesystem")); 470 mwp->fstype = imax(mwp->fstype, (int)strlen("Type")); 471 if (thousands) { /* make space for commas */ 472 mwp->total += (mwp->total - 1) / 3; 473 mwp->used += (mwp->used - 1) / 3; 474 mwp->avail += (mwp->avail - 1) / 3; 475 mwp->iused += (mwp->iused - 1) / 3; 476 mwp->ifree += (mwp->ifree - 1) / 3; 477 } 478 if (hflag) { 479 header = " Size"; 480 mwp->total = mwp->used = mwp->avail = 481 (int)strlen(header); 482 } else { 483 header = getbsize(&headerlen, &blocksize); 484 mwp->total = imax(mwp->total, headerlen); 485 } 486 mwp->used = imax(mwp->used, (int)strlen("Used")); 487 mwp->avail = imax(mwp->avail, (int)strlen("Avail")); 488 489 xo_emit("{T:/%-*s}", mwp->mntfrom, "Filesystem"); 490 if (Tflag) 491 xo_emit(" {T:/%-*s}", mwp->fstype, "Type"); 492 xo_emit(" {T:/%*s} {T:/%*s} {T:/%*s} {T:Capacity}", 493 mwp->total, header, 494 mwp->used, "Used", mwp->avail, "Avail"); 495 if (iflag) { 496 mwp->iused = imax(hflag ? 0 : mwp->iused, 497 (int)strlen(" iused")); 498 mwp->ifree = imax(hflag ? 0 : mwp->ifree, 499 (int)strlen("ifree")); 500 xo_emit(" {T:/%*s} {T:/%*s} {T:\%iused}", 501 mwp->iused - 2, "iused", mwp->ifree, "ifree"); 502 } 503 xo_emit(" {T:Mounted on}\n"); 504 } 505 506 xo_open_instance("filesystem"); 507 /* Check for 0 block size. Can this happen? */ 508 if (sfsp->f_bsize == 0) { 509 xo_warnx ("File system %s does not have a block size, assuming 512.", 510 sfsp->f_mntonname); 511 sfsp->f_bsize = 512; 512 } 513 xo_emit("{tk:name/%-*s}", mwp->mntfrom, sfsp->f_mntfromname); 514 if (Tflag) 515 xo_emit(" {:type/%-*s}", mwp->fstype, sfsp->f_fstypename); 516 used = sfsp->f_blocks - sfsp->f_bfree; 517 availblks = sfsp->f_bavail + used; 518 if (hflag) { 519 prthuman(sfsp, used); 520 } else { 521 if (thousands) 522 format = " {t:total-blocks/%*j'd} {t:used-blocks/%*j'd} " 523 "{t:available-blocks/%*j'd}"; 524 else 525 format = " {t:total-blocks/%*jd} {t:used-blocks/%*jd} " 526 "{t:available-blocks/%*jd}"; 527 xo_emit(format, 528 mwp->total, fsbtoblk(sfsp->f_blocks, 529 sfsp->f_bsize, blocksize), 530 mwp->used, fsbtoblk(used, sfsp->f_bsize, blocksize), 531 mwp->avail, fsbtoblk(sfsp->f_bavail, 532 sfsp->f_bsize, blocksize)); 533 } 534 xo_emit(" {:used-percent/%5.0f}{U:%%}", 535 availblks == 0 ? 100.0 : (double)used / (double)availblks * 100.0); 536 if (iflag) { 537 inodes = sfsp->f_files; 538 used = inodes - sfsp->f_ffree; 539 if (hflag) { 540 xo_emit(" "); 541 prthumanvalinode(" {:inodes-used/%5s}", used); 542 prthumanvalinode(" {:inodes-free/%5s}", sfsp->f_ffree); 543 } else { 544 if (thousands) 545 format = " {:inodes-used/%*j'd} {:inodes-free/%*j'd}"; 546 else 547 format = " {:inodes-used/%*jd} {:inodes-free/%*jd}"; 548 xo_emit(format, mwp->iused, (intmax_t)used, 549 mwp->ifree, (intmax_t)sfsp->f_ffree); 550 } 551 if (inodes == 0) 552 xo_emit(" {:inodes-used-percent/ -}{U:} "); 553 else { 554 xo_emit(" {:inodes-used-percent/%4.0f}{U:%%} ", 555 (double)used / (double)inodes * 100.0); 556 } 557 } else 558 xo_emit(" "); 559 if (strncmp(sfsp->f_mntfromname, "total", MNAMELEN) != 0) 560 xo_emit(" {:mounted-on}", sfsp->f_mntonname); 561 xo_emit("\n"); 562 xo_close_instance("filesystem"); 563 } 564 565 static void 566 addstat(struct statfs *totalfsp, struct statfs *statfsp) 567 { 568 uint64_t bsize; 569 570 bsize = statfsp->f_bsize / totalfsp->f_bsize; 571 totalfsp->f_blocks += statfsp->f_blocks * bsize; 572 totalfsp->f_bfree += statfsp->f_bfree * bsize; 573 totalfsp->f_bavail += statfsp->f_bavail * bsize; 574 totalfsp->f_files += statfsp->f_files; 575 totalfsp->f_ffree += statfsp->f_ffree; 576 } 577 578 /* 579 * Update the maximum field-width information in `mwp' based on 580 * the file system specified by `sfsp'. 581 */ 582 static void 583 update_maxwidths(struct maxwidths *mwp, const struct statfs *sfsp) 584 { 585 static long blocksize = 0; 586 int dummy; 587 588 if (blocksize == 0) 589 getbsize(&dummy, &blocksize); 590 591 mwp->mntfrom = imax(mwp->mntfrom, (int)strlen(sfsp->f_mntfromname)); 592 mwp->fstype = imax(mwp->fstype, (int)strlen(sfsp->f_fstypename)); 593 mwp->total = imax(mwp->total, int64width( 594 fsbtoblk((int64_t)sfsp->f_blocks, sfsp->f_bsize, blocksize))); 595 mwp->used = imax(mwp->used, 596 int64width(fsbtoblk((int64_t)sfsp->f_blocks - 597 (int64_t)sfsp->f_bfree, sfsp->f_bsize, blocksize))); 598 mwp->avail = imax(mwp->avail, int64width(fsbtoblk(sfsp->f_bavail, 599 sfsp->f_bsize, blocksize))); 600 mwp->iused = imax(mwp->iused, int64width((int64_t)sfsp->f_files - 601 sfsp->f_ffree)); 602 mwp->ifree = imax(mwp->ifree, int64width(sfsp->f_ffree)); 603 } 604 605 /* Return the width in characters of the specified value. */ 606 static int 607 int64width(int64_t val) 608 { 609 int len; 610 611 len = 0; 612 /* Negative or zero values require one extra digit. */ 613 if (val <= 0) { 614 val = -val; 615 len++; 616 } 617 while (val > 0) { 618 len++; 619 val /= 10; 620 } 621 622 return (len); 623 } 624 625 static void 626 usage(void) 627 { 628 629 xo_error( 630 "usage: df [-b | -g | -H | -h | -k | -m | -P] [-acilnT] [-t type] [-,]\n" 631 " [file | filesystem ...]\n"); 632 exit(EX_USAGE); 633 } 634 635 static char * 636 makenetvfslist(void) 637 { 638 char *str, *strptr, **listptr; 639 struct xvfsconf *xvfsp, *keep_xvfsp; 640 size_t buflen; 641 int cnt, i, maxvfsconf; 642 643 if (sysctlbyname("vfs.conflist", NULL, &buflen, NULL, 0) < 0) { 644 xo_warn("sysctl(vfs.conflist)"); 645 return (NULL); 646 } 647 xvfsp = malloc(buflen); 648 if (xvfsp == NULL) { 649 xo_warnx("malloc failed"); 650 return (NULL); 651 } 652 keep_xvfsp = xvfsp; 653 if (sysctlbyname("vfs.conflist", xvfsp, &buflen, NULL, 0) < 0) { 654 xo_warn("sysctl(vfs.conflist)"); 655 free(keep_xvfsp); 656 return (NULL); 657 } 658 maxvfsconf = buflen / sizeof(struct xvfsconf); 659 660 if ((listptr = malloc(sizeof(char*) * maxvfsconf)) == NULL) { 661 xo_warnx("malloc failed"); 662 free(keep_xvfsp); 663 return (NULL); 664 } 665 666 for (cnt = 0, i = 0; i < maxvfsconf; i++) { 667 if (xvfsp->vfc_flags & VFCF_NETWORK) { 668 listptr[cnt++] = strdup(xvfsp->vfc_name); 669 if (listptr[cnt-1] == NULL) { 670 xo_warnx("malloc failed"); 671 free(listptr); 672 free(keep_xvfsp); 673 return (NULL); 674 } 675 } 676 xvfsp++; 677 } 678 679 if (cnt == 0 || 680 (str = malloc(sizeof(char) * (32 * cnt + cnt + 2))) == NULL) { 681 if (cnt > 0) 682 xo_warnx("malloc failed"); 683 free(listptr); 684 free(keep_xvfsp); 685 return (NULL); 686 } 687 688 *str = 'n'; *(str + 1) = 'o'; 689 for (i = 0, strptr = str + 2; i < cnt; i++, strptr++) { 690 strlcpy(strptr, listptr[i], 32); 691 strptr += strlen(listptr[i]); 692 *strptr = ','; 693 free(listptr[i]); 694 } 695 *(--strptr) = '\0'; 696 697 free(keep_xvfsp); 698 free(listptr); 699 return (str); 700 } 701