1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1989, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Michael Fischbein. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #if 0 36 #ifndef lint 37 static char sccsid[] = "@(#)print.c 8.4 (Berkeley) 4/17/94"; 38 #endif /* not lint */ 39 #endif 40 #include <sys/cdefs.h> 41 __FBSDID("$FreeBSD$"); 42 43 #include <sys/param.h> 44 #include <sys/stat.h> 45 #include <sys/acl.h> 46 47 #include <err.h> 48 #include <errno.h> 49 #include <fts.h> 50 #include <langinfo.h> 51 #include <libutil.h> 52 #include <limits.h> 53 #include <stdio.h> 54 #include <stdint.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <time.h> 58 #include <unistd.h> 59 #include <wchar.h> 60 #ifdef COLORLS 61 #include <ctype.h> 62 #include <termcap.h> 63 #include <signal.h> 64 #endif 65 66 #include "ls.h" 67 #include "extern.h" 68 69 static int printaname(const FTSENT *, u_long, u_long); 70 static void printdev(size_t, dev_t); 71 static void printlink(const FTSENT *); 72 static void printtime(time_t); 73 static int printtype(u_int); 74 static void printsize(size_t, off_t); 75 #ifdef COLORLS 76 static void endcolor(int); 77 static int colortype(mode_t); 78 #endif 79 static void aclmode(char *, const FTSENT *); 80 81 #define IS_NOPRINT(p) ((p)->fts_number == NO_PRINT) 82 83 #ifdef COLORLS 84 /* Most of these are taken from <sys/stat.h> */ 85 typedef enum Colors { 86 C_DIR, /* directory */ 87 C_LNK, /* symbolic link */ 88 C_SOCK, /* socket */ 89 C_FIFO, /* pipe */ 90 C_EXEC, /* executable */ 91 C_BLK, /* block special */ 92 C_CHR, /* character special */ 93 C_SUID, /* setuid executable */ 94 C_SGID, /* setgid executable */ 95 C_WSDIR, /* directory writeble to others, with sticky 96 * bit */ 97 C_WDIR, /* directory writeble to others, without 98 * sticky bit */ 99 C_NUMCOLORS /* just a place-holder */ 100 } Colors; 101 102 static const char *defcolors = "exfxcxdxbxegedabagacad"; 103 104 /* colors for file types */ 105 static struct { 106 int num[2]; 107 int bold; 108 } colors[C_NUMCOLORS]; 109 #endif 110 111 static size_t padding_for_month[12]; 112 static size_t month_max_size = 0; 113 114 void 115 printscol(const DISPLAY *dp) 116 { 117 FTSENT *p; 118 119 for (p = dp->list; p; p = p->fts_link) { 120 if (IS_NOPRINT(p)) 121 continue; 122 (void)printaname(p, dp->s_inode, dp->s_block); 123 (void)putchar('\n'); 124 } 125 } 126 127 /* 128 * print name in current style 129 */ 130 int 131 printname(const char *name) 132 { 133 if (f_octal || f_octal_escape) 134 return prn_octal(name); 135 else if (f_nonprint) 136 return prn_printable(name); 137 else 138 return prn_normal(name); 139 } 140 141 static const char * 142 get_abmon(int mon) 143 { 144 145 switch (mon) { 146 case 0: return (nl_langinfo(ABMON_1)); 147 case 1: return (nl_langinfo(ABMON_2)); 148 case 2: return (nl_langinfo(ABMON_3)); 149 case 3: return (nl_langinfo(ABMON_4)); 150 case 4: return (nl_langinfo(ABMON_5)); 151 case 5: return (nl_langinfo(ABMON_6)); 152 case 6: return (nl_langinfo(ABMON_7)); 153 case 7: return (nl_langinfo(ABMON_8)); 154 case 8: return (nl_langinfo(ABMON_9)); 155 case 9: return (nl_langinfo(ABMON_10)); 156 case 10: return (nl_langinfo(ABMON_11)); 157 case 11: return (nl_langinfo(ABMON_12)); 158 } 159 160 /* should never happen */ 161 abort(); 162 } 163 164 static size_t 165 mbswidth(const char *month) 166 { 167 wchar_t wc; 168 size_t width, donelen, clen, w; 169 170 width = donelen = 0; 171 while ((clen = mbrtowc(&wc, month + donelen, MB_LEN_MAX, NULL)) != 0) { 172 if (clen == (size_t)-1 || clen == (size_t)-2) 173 return (-1); 174 donelen += clen; 175 if ((w = wcwidth(wc)) == (size_t)-1) 176 return (-1); 177 width += w; 178 } 179 180 return (width); 181 } 182 183 static void 184 compute_abbreviated_month_size(void) 185 { 186 int i; 187 size_t width; 188 size_t months_width[12]; 189 190 for (i = 0; i < 12; i++) { 191 width = mbswidth(get_abmon(i)); 192 if (width == (size_t)-1) { 193 month_max_size = -1; 194 return; 195 } 196 months_width[i] = width; 197 if (width > month_max_size) 198 month_max_size = width; 199 } 200 201 for (i = 0; i < 12; i++) 202 padding_for_month[i] = month_max_size - months_width[i]; 203 } 204 205 void 206 printlong(const DISPLAY *dp) 207 { 208 struct stat *sp; 209 FTSENT *p; 210 NAMES *np; 211 char buf[20]; 212 #ifdef COLORLS 213 int color_printed = 0; 214 #endif 215 216 if ((dp->list == NULL || dp->list->fts_level != FTS_ROOTLEVEL) && 217 (f_longform || f_size)) { 218 (void)printf("total %lu\n", howmany(dp->btotal, blocksize)); 219 } 220 221 for (p = dp->list; p; p = p->fts_link) { 222 if (IS_NOPRINT(p)) 223 continue; 224 sp = p->fts_statp; 225 if (f_inode) 226 (void)printf("%*ju ", 227 dp->s_inode, (uintmax_t)sp->st_ino); 228 if (f_size) 229 (void)printf("%*jd ", 230 dp->s_block, howmany(sp->st_blocks, blocksize)); 231 strmode(sp->st_mode, buf); 232 aclmode(buf, p); 233 np = p->fts_pointer; 234 (void)printf("%s %*ju %-*s %-*s ", buf, dp->s_nlink, 235 (uintmax_t)sp->st_nlink, dp->s_user, np->user, dp->s_group, 236 np->group); 237 if (f_flags) 238 (void)printf("%-*s ", dp->s_flags, np->flags); 239 if (f_label) 240 (void)printf("%-*s ", dp->s_label, np->label); 241 if (S_ISCHR(sp->st_mode) || S_ISBLK(sp->st_mode)) 242 printdev(dp->s_size, sp->st_rdev); 243 else 244 printsize(dp->s_size, sp->st_size); 245 if (f_accesstime) 246 printtime(sp->st_atime); 247 else if (f_birthtime) 248 printtime(sp->st_birthtime); 249 else if (f_statustime) 250 printtime(sp->st_ctime); 251 else 252 printtime(sp->st_mtime); 253 #ifdef COLORLS 254 if (f_color) 255 color_printed = colortype(sp->st_mode); 256 #endif 257 (void)printname(p->fts_name); 258 #ifdef COLORLS 259 if (f_color && color_printed) 260 endcolor(0); 261 #endif 262 if (f_type) 263 (void)printtype(sp->st_mode); 264 if (S_ISLNK(sp->st_mode)) 265 printlink(p); 266 (void)putchar('\n'); 267 } 268 } 269 270 void 271 printstream(const DISPLAY *dp) 272 { 273 FTSENT *p; 274 int chcnt; 275 276 for (p = dp->list, chcnt = 0; p; p = p->fts_link) { 277 if (p->fts_number == NO_PRINT) 278 continue; 279 /* XXX strlen does not take octal escapes into account. */ 280 if (strlen(p->fts_name) + chcnt + 281 (p->fts_link ? 2 : 0) >= (unsigned)termwidth) { 282 putchar('\n'); 283 chcnt = 0; 284 } 285 chcnt += printaname(p, dp->s_inode, dp->s_block); 286 if (p->fts_link) { 287 printf(", "); 288 chcnt += 2; 289 } 290 } 291 if (chcnt) 292 putchar('\n'); 293 } 294 295 void 296 printcol(const DISPLAY *dp) 297 { 298 static FTSENT **array; 299 static int lastentries = -1; 300 FTSENT *p; 301 FTSENT **narray; 302 int base; 303 int chcnt; 304 int cnt; 305 int col; 306 int colwidth; 307 int endcol; 308 int num; 309 int numcols; 310 int numrows; 311 int row; 312 int tabwidth; 313 314 if (f_notabs) 315 tabwidth = 1; 316 else 317 tabwidth = 8; 318 319 /* 320 * Have to do random access in the linked list -- build a table 321 * of pointers. 322 */ 323 if (dp->entries > lastentries) { 324 if ((narray = 325 realloc(array, dp->entries * sizeof(FTSENT *))) == NULL) { 326 warn(NULL); 327 printscol(dp); 328 return; 329 } 330 lastentries = dp->entries; 331 array = narray; 332 } 333 for (p = dp->list, num = 0; p; p = p->fts_link) 334 if (p->fts_number != NO_PRINT) 335 array[num++] = p; 336 337 colwidth = dp->maxlen; 338 if (f_inode) 339 colwidth += dp->s_inode + 1; 340 if (f_size) 341 colwidth += dp->s_block + 1; 342 if (f_type) 343 colwidth += 1; 344 345 colwidth = (colwidth + tabwidth) & ~(tabwidth - 1); 346 if (termwidth < 2 * colwidth) { 347 printscol(dp); 348 return; 349 } 350 numcols = termwidth / colwidth; 351 numrows = num / numcols; 352 if (num % numcols) 353 ++numrows; 354 355 if ((dp->list == NULL || dp->list->fts_level != FTS_ROOTLEVEL) && 356 (f_longform || f_size)) { 357 (void)printf("total %lu\n", howmany(dp->btotal, blocksize)); 358 } 359 360 base = 0; 361 for (row = 0; row < numrows; ++row) { 362 endcol = colwidth; 363 if (!f_sortacross) 364 base = row; 365 for (col = 0, chcnt = 0; col < numcols; ++col) { 366 chcnt += printaname(array[base], dp->s_inode, 367 dp->s_block); 368 if (f_sortacross) 369 base++; 370 else 371 base += numrows; 372 if (base >= num) 373 break; 374 while ((cnt = ((chcnt + tabwidth) & ~(tabwidth - 1))) 375 <= endcol) { 376 if (f_sortacross && col + 1 >= numcols) 377 break; 378 (void)putchar(f_notabs ? ' ' : '\t'); 379 chcnt = cnt; 380 } 381 endcol += colwidth; 382 } 383 (void)putchar('\n'); 384 } 385 } 386 387 /* 388 * print [inode] [size] name 389 * return # of characters printed, no trailing characters. 390 */ 391 static int 392 printaname(const FTSENT *p, u_long inodefield, u_long sizefield) 393 { 394 struct stat *sp; 395 int chcnt; 396 #ifdef COLORLS 397 int color_printed = 0; 398 #endif 399 400 sp = p->fts_statp; 401 chcnt = 0; 402 if (f_inode) 403 chcnt += printf("%*ju ", 404 (int)inodefield, (uintmax_t)sp->st_ino); 405 if (f_size) 406 chcnt += printf("%*jd ", 407 (int)sizefield, howmany(sp->st_blocks, blocksize)); 408 #ifdef COLORLS 409 if (f_color) 410 color_printed = colortype(sp->st_mode); 411 #endif 412 chcnt += printname(p->fts_name); 413 #ifdef COLORLS 414 if (f_color && color_printed) 415 endcolor(0); 416 #endif 417 if (f_type) 418 chcnt += printtype(sp->st_mode); 419 return (chcnt); 420 } 421 422 /* 423 * Print device special file major and minor numbers. 424 */ 425 static void 426 printdev(size_t width, dev_t dev) 427 { 428 429 (void)printf("%#*jx ", (u_int)width, (uintmax_t)dev); 430 } 431 432 static size_t 433 ls_strftime(char *str, size_t len, const char *fmt, const struct tm *tm) 434 { 435 char *posb, nfmt[BUFSIZ]; 436 const char *format = fmt; 437 size_t ret; 438 439 if ((posb = strstr(fmt, "%b")) != NULL) { 440 if (month_max_size == 0) { 441 compute_abbreviated_month_size(); 442 } 443 if (month_max_size > 0) { 444 snprintf(nfmt, sizeof(nfmt), "%.*s%s%*s%s", 445 (int)(posb - fmt), fmt, 446 get_abmon(tm->tm_mon), 447 (int)padding_for_month[tm->tm_mon], 448 "", 449 posb + 2); 450 format = nfmt; 451 } 452 } 453 ret = strftime(str, len, format, tm); 454 return (ret); 455 } 456 457 static void 458 printtime(time_t ftime) 459 { 460 char longstring[80]; 461 static time_t now = 0; 462 const char *format; 463 static int d_first = -1; 464 465 if (d_first < 0) 466 d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 467 if (now == 0) 468 now = time(NULL); 469 470 #define SIXMONTHS ((365 / 2) * 86400) 471 if (f_timeformat) /* user specified format */ 472 format = f_timeformat; 473 else if (f_sectime) 474 /* mmm dd hh:mm:ss yyyy || dd mmm hh:mm:ss yyyy */ 475 format = d_first ? "%e %b %T %Y" : "%b %e %T %Y"; 476 else if (ftime + SIXMONTHS > now && ftime < now + SIXMONTHS) 477 /* mmm dd hh:mm || dd mmm hh:mm */ 478 format = d_first ? "%e %b %R" : "%b %e %R"; 479 else 480 /* mmm dd yyyy || dd mmm yyyy */ 481 format = d_first ? "%e %b %Y" : "%b %e %Y"; 482 ls_strftime(longstring, sizeof(longstring), format, localtime(&ftime)); 483 fputs(longstring, stdout); 484 fputc(' ', stdout); 485 } 486 487 static int 488 printtype(u_int mode) 489 { 490 491 if (f_slash) { 492 if ((mode & S_IFMT) == S_IFDIR) { 493 (void)putchar('/'); 494 return (1); 495 } 496 return (0); 497 } 498 499 switch (mode & S_IFMT) { 500 case S_IFDIR: 501 (void)putchar('/'); 502 return (1); 503 case S_IFIFO: 504 (void)putchar('|'); 505 return (1); 506 case S_IFLNK: 507 (void)putchar('@'); 508 return (1); 509 case S_IFSOCK: 510 (void)putchar('='); 511 return (1); 512 case S_IFWHT: 513 (void)putchar('%'); 514 return (1); 515 default: 516 break; 517 } 518 if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { 519 (void)putchar('*'); 520 return (1); 521 } 522 return (0); 523 } 524 525 #ifdef COLORLS 526 static int 527 putch(int c) 528 { 529 (void)putchar(c); 530 return 0; 531 } 532 533 static int 534 writech(int c) 535 { 536 char tmp = (char)c; 537 538 (void)write(STDOUT_FILENO, &tmp, 1); 539 return 0; 540 } 541 542 static void 543 printcolor(Colors c) 544 { 545 char *ansiseq; 546 547 if (colors[c].bold) 548 tputs(enter_bold, 1, putch); 549 550 if (colors[c].num[0] != -1) { 551 ansiseq = tgoto(ansi_fgcol, 0, colors[c].num[0]); 552 if (ansiseq) 553 tputs(ansiseq, 1, putch); 554 } 555 if (colors[c].num[1] != -1) { 556 ansiseq = tgoto(ansi_bgcol, 0, colors[c].num[1]); 557 if (ansiseq) 558 tputs(ansiseq, 1, putch); 559 } 560 } 561 562 static void 563 endcolor(int sig) 564 { 565 tputs(ansi_coloff, 1, sig ? writech : putch); 566 tputs(attrs_off, 1, sig ? writech : putch); 567 } 568 569 static int 570 colortype(mode_t mode) 571 { 572 switch (mode & S_IFMT) { 573 case S_IFDIR: 574 if (mode & S_IWOTH) 575 if (mode & S_ISTXT) 576 printcolor(C_WSDIR); 577 else 578 printcolor(C_WDIR); 579 else 580 printcolor(C_DIR); 581 return (1); 582 case S_IFLNK: 583 printcolor(C_LNK); 584 return (1); 585 case S_IFSOCK: 586 printcolor(C_SOCK); 587 return (1); 588 case S_IFIFO: 589 printcolor(C_FIFO); 590 return (1); 591 case S_IFBLK: 592 printcolor(C_BLK); 593 return (1); 594 case S_IFCHR: 595 printcolor(C_CHR); 596 return (1); 597 default:; 598 } 599 if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { 600 if (mode & S_ISUID) 601 printcolor(C_SUID); 602 else if (mode & S_ISGID) 603 printcolor(C_SGID); 604 else 605 printcolor(C_EXEC); 606 return (1); 607 } 608 return (0); 609 } 610 611 void 612 parsecolors(const char *cs) 613 { 614 int i; 615 int j; 616 size_t len; 617 char c[2]; 618 short legacy_warn = 0; 619 620 if (cs == NULL) 621 cs = ""; /* LSCOLORS not set */ 622 len = strlen(cs); 623 for (i = 0; i < (int)C_NUMCOLORS; i++) { 624 colors[i].bold = 0; 625 626 if (len <= 2 * (size_t)i) { 627 c[0] = defcolors[2 * i]; 628 c[1] = defcolors[2 * i + 1]; 629 } else { 630 c[0] = cs[2 * i]; 631 c[1] = cs[2 * i + 1]; 632 } 633 for (j = 0; j < 2; j++) { 634 /* Legacy colours used 0-7 */ 635 if (c[j] >= '0' && c[j] <= '7') { 636 colors[i].num[j] = c[j] - '0'; 637 if (!legacy_warn) { 638 warnx("LSCOLORS should use " 639 "characters a-h instead of 0-9 (" 640 "see the manual page)"); 641 } 642 legacy_warn = 1; 643 } else if (c[j] >= 'a' && c[j] <= 'h') 644 colors[i].num[j] = c[j] - 'a'; 645 else if (c[j] >= 'A' && c[j] <= 'H') { 646 colors[i].num[j] = c[j] - 'A'; 647 colors[i].bold = 1; 648 } else if (tolower((unsigned char)c[j]) == 'x') 649 colors[i].num[j] = -1; 650 else { 651 warnx("invalid character '%c' in LSCOLORS" 652 " env var", c[j]); 653 colors[i].num[j] = -1; 654 } 655 } 656 } 657 } 658 659 void 660 colorquit(int sig) 661 { 662 endcolor(sig); 663 664 (void)signal(sig, SIG_DFL); 665 (void)kill(getpid(), sig); 666 } 667 668 #endif /* COLORLS */ 669 670 static void 671 printlink(const FTSENT *p) 672 { 673 int lnklen; 674 char name[MAXPATHLEN + 1]; 675 char path[MAXPATHLEN + 1]; 676 677 if (p->fts_level == FTS_ROOTLEVEL) 678 (void)snprintf(name, sizeof(name), "%s", p->fts_name); 679 else 680 (void)snprintf(name, sizeof(name), 681 "%s/%s", p->fts_parent->fts_accpath, p->fts_name); 682 if ((lnklen = readlink(name, path, sizeof(path) - 1)) == -1) { 683 (void)fprintf(stderr, "\nls: %s: %s\n", name, strerror(errno)); 684 return; 685 } 686 path[lnklen] = '\0'; 687 (void)printf(" -> "); 688 (void)printname(path); 689 } 690 691 static void 692 printsize(size_t width, off_t bytes) 693 { 694 695 if (f_humanval) { 696 /* 697 * Reserve one space before the size and allocate room for 698 * the trailing '\0'. 699 */ 700 char buf[HUMANVALSTR_LEN - 1 + 1]; 701 702 humanize_number(buf, sizeof(buf), (int64_t)bytes, "", 703 HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL); 704 (void)printf("%*s ", (u_int)width, buf); 705 } else if (f_thousands) { /* with commas */ 706 /* This format assignment needed to work round gcc bug. */ 707 const char *format = "%*j'd "; 708 (void)printf(format, (u_int)width, bytes); 709 } else 710 (void)printf("%*jd ", (u_int)width, bytes); 711 } 712 713 /* 714 * Add a + after the standard rwxrwxrwx mode if the file has an 715 * ACL. strmode() reserves space at the end of the string. 716 */ 717 static void 718 aclmode(char *buf, const FTSENT *p) 719 { 720 char name[MAXPATHLEN + 1]; 721 int ret, trivial; 722 static dev_t previous_dev = NODEV; 723 static int supports_acls = -1; 724 static int type = ACL_TYPE_ACCESS; 725 acl_t facl; 726 727 /* 728 * XXX: ACLs are not supported on whiteouts and device files 729 * residing on UFS. 730 */ 731 if (S_ISCHR(p->fts_statp->st_mode) || S_ISBLK(p->fts_statp->st_mode) || 732 S_ISWHT(p->fts_statp->st_mode)) 733 return; 734 735 if (previous_dev == p->fts_statp->st_dev && supports_acls == 0) 736 return; 737 738 if (p->fts_level == FTS_ROOTLEVEL) 739 snprintf(name, sizeof(name), "%s", p->fts_name); 740 else 741 snprintf(name, sizeof(name), "%s/%s", 742 p->fts_parent->fts_accpath, p->fts_name); 743 744 if (previous_dev != p->fts_statp->st_dev) { 745 previous_dev = p->fts_statp->st_dev; 746 supports_acls = 0; 747 748 ret = lpathconf(name, _PC_ACL_NFS4); 749 if (ret > 0) { 750 type = ACL_TYPE_NFS4; 751 supports_acls = 1; 752 } else if (ret < 0 && errno != EINVAL) { 753 warn("%s", name); 754 return; 755 } 756 if (supports_acls == 0) { 757 ret = lpathconf(name, _PC_ACL_EXTENDED); 758 if (ret > 0) { 759 type = ACL_TYPE_ACCESS; 760 supports_acls = 1; 761 } else if (ret < 0 && errno != EINVAL) { 762 warn("%s", name); 763 return; 764 } 765 } 766 } 767 if (supports_acls == 0) 768 return; 769 facl = acl_get_link_np(name, type); 770 if (facl == NULL) { 771 warn("%s", name); 772 return; 773 } 774 if (acl_is_trivial_np(facl, &trivial)) { 775 acl_free(facl); 776 warn("%s", name); 777 return; 778 } 779 if (!trivial) 780 buf[10] = '+'; 781 acl_free(facl); 782 } 783