1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25 /*
26 * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
27 */
28
29 #include <sys/types.h>
30 #include <sys/errno.h>
31 #include <sys/tiuser.h>
32 #include <setjmp.h>
33 #include <pwd.h>
34 #include <grp.h>
35
36 #include <rpc/types.h>
37 #include <rpc/xdr.h>
38 #include <rpc/auth.h>
39 #include <rpc/clnt.h>
40 #include <rpc/rpc_msg.h>
41 #include <string.h>
42 #include "snoop.h"
43
44 #include <sys/stat.h>
45
46 extern char *get_sum_line();
47 extern void check_retransmit();
48 extern char *sum_nfsfh();
49 extern int sum_nfsstat();
50 extern int detail_nfsstat();
51 extern void detail_nfsfh();
52 extern void detail_fattr();
53 extern void skip_fattr();
54 extern char *sum_nfsfh3();
55 extern int sum_nfsstat3();
56 extern int detail_nfsstat3();
57 extern void detail_post_op_attr();
58 extern void detail_nfsfh3();
59 extern int sum_nfsstat4();
60 extern int detail_nfsstat4();
61
62 extern jmp_buf xdr_err;
63
64 static void aclcall2();
65 static void aclreply2();
66 static void aclcall3();
67 static void aclreply3();
68 static void aclcall4();
69 static void aclreply4();
70 static void detail_access2();
71 static char *sum_access2();
72 static void detail_mask();
73 static void detail_secattr();
74 static void detail_aclent();
75 static char *detail_uname();
76 static char *detail_gname();
77 static char *detail_perm(ushort_t);
78 static void interpret_nfs_acl2(int, int, int, int, int, char *, int);
79 static void interpret_nfs_acl3(int, int, int, int, int, char *, int);
80 static void interpret_nfs_acl4(int, int, int, int, int, char *, int);
81
82 #define ACLPROC2_NULL ((unsigned long)(0))
83 #define ACLPROC2_GETACL ((unsigned long)(1))
84 #define ACLPROC2_SETACL ((unsigned long)(2))
85 #define ACLPROC2_GETATTR ((unsigned long)(3))
86 #define ACLPROC2_ACCESS ((unsigned long)(4))
87 #define ACLPROC2_GETXATTRDIR ((unsigned long)(5))
88
89 #define ACLPROC3_NULL ((unsigned long)(0))
90 #define ACLPROC3_GETACL ((unsigned long)(1))
91 #define ACLPROC3_SETACL ((unsigned long)(2))
92 #define ACLPROC3_GETXATTRDIR ((unsigned long)(3))
93
94 #define ACLPROC4_NULL ((unsigned long)(0))
95 #define ACLPROC4_GETACL ((unsigned long)(1))
96 #define ACLPROC4_SETACL ((unsigned long)(2))
97
98 #define NA_USER_OBJ 0x1
99 #define NA_USER 0x2
100 #define NA_GROUP_OBJ 0x4
101 #define NA_GROUP 0x8
102 #define NA_CLASS_OBJ 0x10
103 #define NA_OTHER_OBJ 0x20
104 #define NA_ACL_DEFAULT 0x1000
105
106 #define NA_DEF_USER_OBJ (NA_USER_OBJ | NA_ACL_DEFAULT)
107 #define NA_DEF_USER (NA_USER | NA_ACL_DEFAULT)
108 #define NA_DEF_GROUP_OBJ (NA_GROUP_OBJ | NA_ACL_DEFAULT)
109 #define NA_DEF_GROUP (NA_GROUP | NA_ACL_DEFAULT)
110 #define NA_DEF_CLASS_OBJ (NA_CLASS_OBJ | NA_ACL_DEFAULT)
111 #define NA_DEF_OTHER_OBJ (NA_OTHER_OBJ | NA_ACL_DEFAULT)
112
113 #define NA_ACL 0x1
114 #define NA_ACLCNT 0x2
115 #define NA_DFACL 0x4
116 #define NA_DFACLCNT 0x8
117
118 #define ACCESS2_READ 0x0001
119 #define ACCESS2_LOOKUP 0x0002
120 #define ACCESS2_MODIFY 0x0004
121 #define ACCESS2_EXTEND 0x0008
122 #define ACCESS2_DELETE 0x0010
123 #define ACCESS2_EXECUTE 0x0020
124
125 static char *procnames_short_v2[] = {
126 "NULL2", /* 0 */
127 "GETACL2", /* 1 */
128 "SETACL2", /* 2 */
129 "GETATTR2", /* 3 */
130 "ACCESS2", /* 4 */
131 "GETXATTRDIR2", /* 5 */
132 };
133 static char *procnames_short_v3[] = {
134 "NULL3", /* 0 */
135 "GETACL3", /* 1 */
136 "SETACL3", /* 2 */
137 "GETXATTRDIR3", /* 3 */
138 };
139 static char *procnames_short_v4[] = {
140 "NULL4", /* 0 */
141 "GETACL4", /* 1 */
142 "SETACL4", /* 2 */
143 };
144
145 static char *procnames_long_v2[] = {
146 "Null procedure", /* 0 */
147 "Get file access control list", /* 1 */
148 "Set file access control list", /* 2 */
149 "Get file attributes", /* 3 */
150 "Check access permission", /* 4 */
151 "Get extended attribute directory", /* 5 */
152 };
153 static char *procnames_long_v3[] = {
154 "Null procedure", /* 0 */
155 "Get file access control list", /* 1 */
156 "Set file access control list", /* 2 */
157 "Get extended attribute directory", /* 3 */
158 };
159 static char *procnames_long_v4[] = {
160 "Null procedure", /* 0 */
161 "Get file access control list", /* 1 */
162 "Set file access control list", /* 2 */
163 };
164
165 #define MAXPROC_V2 5
166 #define MAXPROC_V3 3
167 #define MAXPROC_V4 2
168
169 /* ARGSUSED */
170 void
interpret_nfs_acl(flags,type,xid,vers,proc,data,len)171 interpret_nfs_acl(flags, type, xid, vers, proc, data, len)
172 int flags, type, xid, vers, proc;
173 char *data;
174 int len;
175 {
176
177 if (vers == 2) {
178 interpret_nfs_acl2(flags, type, xid, vers, proc, data, len);
179 return;
180 }
181
182 if (vers == 3) {
183 interpret_nfs_acl3(flags, type, xid, vers, proc, data, len);
184 return;
185 }
186
187 if (vers == 4) {
188 interpret_nfs_acl4(flags, type, xid, vers, proc, data, len);
189 return;
190 }
191 }
192
193 static void
interpret_nfs_acl2(int flags,int type,int xid,int vers,int proc,char * data,int len)194 interpret_nfs_acl2(int flags, int type, int xid, int vers, int proc,
195 char *data, int len)
196 {
197 char *line;
198 char buff[2048];
199 int off, sz;
200 char *fh;
201 ulong_t mask;
202
203 if (proc < 0 || proc > MAXPROC_V2)
204 return;
205
206 if (flags & F_SUM) {
207 line = get_sum_line();
208
209 if (type == CALL) {
210 (void) sprintf(line, "NFS_ACL C %s",
211 procnames_short_v2[proc]);
212 line += strlen(line);
213 switch (proc) {
214 case ACLPROC2_GETACL:
215 fh = sum_nfsfh();
216 mask = getxdr_u_long();
217 (void) sprintf(line, "%s mask=0x%lx", fh, mask);
218 break;
219 case ACLPROC2_SETACL:
220 (void) sprintf(line, sum_nfsfh());
221 break;
222 case ACLPROC2_GETATTR:
223 (void) sprintf(line, sum_nfsfh());
224 break;
225 case ACLPROC2_ACCESS:
226 fh = sum_nfsfh();
227 (void) sprintf(line, "%s (%s)", fh,
228 sum_access2());
229 break;
230 case ACLPROC2_GETXATTRDIR:
231 fh = sum_nfsfh();
232 (void) sprintf(line, "%s create=%s", fh,
233 getxdr_bool() ? "true" : "false");
234 break;
235 default:
236 break;
237 }
238
239 check_retransmit(line, (ulong_t)xid);
240 } else {
241 (void) sprintf(line, "NFS_ACL R %s ",
242 procnames_short_v2[proc]);
243 line += strlen(line);
244 switch (proc) {
245 case ACLPROC2_GETACL:
246 (void) sum_nfsstat(line);
247 break;
248 case ACLPROC2_SETACL:
249 (void) sum_nfsstat(line);
250 break;
251 case ACLPROC2_GETATTR:
252 (void) sum_nfsstat(line);
253 break;
254 case ACLPROC2_ACCESS:
255 if (sum_nfsstat(line) == 0) {
256 skip_fattr();
257 line += strlen(line);
258 (void) sprintf(line, " (%s)",
259 sum_access2());
260 }
261 break;
262 case ACLPROC2_GETXATTRDIR:
263 if (sum_nfsstat(line) == 0) {
264 line += strlen(line);
265 (void) sprintf(line, sum_nfsfh());
266 }
267 break;
268 default:
269 break;
270 }
271 }
272 }
273
274 if (flags & F_DTAIL) {
275 show_header("NFS_ACL: ", "Sun NFS_ACL", len);
276 show_space();
277 (void) sprintf(get_line(0, 0), "Proc = %d (%s)",
278 proc, procnames_long_v2[proc]);
279 if (type == CALL)
280 aclcall2(proc);
281 else
282 aclreply2(proc);
283 show_trailer();
284 }
285 }
286
287 static void
interpret_nfs_acl3(int flags,int type,int xid,int vers,int proc,char * data,int len)288 interpret_nfs_acl3(int flags, int type, int xid, int vers, int proc,
289 char *data, int len)
290 {
291 char *line;
292 char buff[2048];
293 int off, sz;
294 char *fh;
295 ulong_t mask;
296
297 if (proc < 0 || proc > MAXPROC_V3)
298 return;
299
300 if (flags & F_SUM) {
301 line = get_sum_line();
302
303 if (type == CALL) {
304 (void) sprintf(line, "NFS_ACL C %s",
305 procnames_short_v3[proc]);
306 line += strlen(line);
307 switch (proc) {
308 case ACLPROC3_GETACL:
309 fh = sum_nfsfh3();
310 mask = getxdr_u_long();
311 (void) sprintf(line, "%s mask=0x%lx", fh, mask);
312 break;
313 case ACLPROC3_SETACL:
314 (void) sprintf(line, sum_nfsfh3());
315 break;
316 case ACLPROC3_GETXATTRDIR:
317 fh = sum_nfsfh3();
318 (void) sprintf(line, "%s create=%s", fh,
319 getxdr_bool() ? "true" : "false");
320 break;
321 default:
322 break;
323 }
324
325 check_retransmit(line, (ulong_t)xid);
326 } else {
327 (void) sprintf(line, "NFS_ACL R %s ",
328 procnames_short_v3[proc]);
329 line += strlen(line);
330 switch (proc) {
331 case ACLPROC3_GETACL:
332 (void) sum_nfsstat3(line);
333 break;
334 case ACLPROC3_SETACL:
335 (void) sum_nfsstat3(line);
336 break;
337 case ACLPROC3_GETXATTRDIR:
338 if (sum_nfsstat3(line) == 0) {
339 line += strlen(line);
340 (void) sprintf(line, sum_nfsfh3());
341 }
342 break;
343 default:
344 break;
345 }
346 }
347 }
348
349 if (flags & F_DTAIL) {
350 show_header("NFS_ACL: ", "Sun NFS_ACL", len);
351 show_space();
352 (void) sprintf(get_line(0, 0), "Proc = %d (%s)",
353 proc, procnames_long_v3[proc]);
354 if (type == CALL)
355 aclcall3(proc);
356 else
357 aclreply3(proc);
358 show_trailer();
359 }
360 }
361
362 static void
interpret_nfs_acl4(int flags,int type,int xid,int vers,int proc,char * data,int len)363 interpret_nfs_acl4(int flags, int type, int xid, int vers, int proc,
364 char *data, int len)
365 {
366 char *line;
367 char buff[2048];
368 int off, sz;
369 char *fh;
370 ulong_t mask;
371
372 if (proc < 0 || proc > MAXPROC_V4)
373 return;
374
375 if (flags & F_SUM) {
376 line = get_sum_line();
377
378 if (type == CALL) {
379 (void) sprintf(line, "NFS_ACL C %s",
380 procnames_short_v4[proc]);
381 line += strlen(line);
382 switch (proc) {
383 case ACLPROC4_GETACL:
384 fh = sum_nfsfh3();
385 mask = getxdr_u_long();
386 (void) sprintf(line, "%s mask=0x%lx", fh, mask);
387 break;
388 case ACLPROC4_SETACL:
389 (void) sprintf(line, sum_nfsfh3());
390 break;
391 default:
392 break;
393 }
394
395 check_retransmit(line, (ulong_t)xid);
396 } else {
397 (void) sprintf(line, "NFS_ACL R %s ",
398 procnames_short_v4[proc]);
399 line += strlen(line);
400 switch (proc) {
401 case ACLPROC4_GETACL:
402 (void) sum_nfsstat4(line);
403 break;
404 case ACLPROC4_SETACL:
405 (void) sum_nfsstat4(line);
406 break;
407 default:
408 break;
409 }
410 }
411 }
412
413 if (flags & F_DTAIL) {
414 show_header("NFS_ACL: ", "Sun NFS_ACL", len);
415 show_space();
416 (void) sprintf(get_line(0, 0), "Proc = %d (%s)",
417 proc, procnames_long_v4[proc]);
418 if (type == CALL)
419 aclcall4(proc);
420 else
421 aclreply4(proc);
422 show_trailer();
423 }
424 }
425
426 int
sum_nfsstat4(char * line)427 sum_nfsstat4(char *line)
428 {
429 ulong_t status;
430 char *p, *nfsstat4_to_name(int);
431
432 status = getxdr_long();
433 p = nfsstat4_to_name(status);
434 (void) strcpy(line, p);
435 return (status);
436 }
437
438 int
detail_nfsstat4()439 detail_nfsstat4()
440 {
441 ulong_t status;
442 char buff[64];
443 int pos;
444
445 pos = getxdr_pos();
446 status = sum_nfsstat4(buff);
447
448 (void) sprintf(get_line(pos, getxdr_pos()), "Status = %d (%s)",
449 status, buff);
450
451 return ((int)status);
452 }
453
454 /*
455 * Print out version 2 NFS_ACL call packets
456 */
457 static void
aclcall2(proc)458 aclcall2(proc)
459 int proc;
460 {
461
462 switch (proc) {
463 case ACLPROC2_GETACL:
464 detail_nfsfh();
465 detail_mask();
466 break;
467 case ACLPROC2_SETACL:
468 detail_nfsfh();
469 detail_secattr();
470 break;
471 case ACLPROC2_GETATTR:
472 detail_nfsfh();
473 break;
474 case ACLPROC2_ACCESS:
475 detail_nfsfh();
476 detail_access2();
477 break;
478 default:
479 break;
480 }
481 }
482
483 /*
484 * Print out version 2 NFS_ACL reply packets
485 */
486 static void
aclreply2(proc)487 aclreply2(proc)
488 int proc;
489 {
490
491 switch (proc) {
492 case ACLPROC2_GETACL:
493 if (detail_nfsstat() == 0) {
494 detail_fattr();
495 detail_secattr();
496 }
497 break;
498 case ACLPROC2_SETACL:
499 if (detail_nfsstat() == 0)
500 detail_fattr();
501 break;
502 case ACLPROC2_GETATTR:
503 if (detail_nfsstat() == 0)
504 detail_fattr();
505 break;
506 case ACLPROC2_ACCESS:
507 if (detail_nfsstat() == 0) {
508 detail_fattr();
509 detail_access2();
510 }
511 break;
512 default:
513 break;
514 }
515 }
516
517 /*
518 * Print out version 3 NFS_ACL call packets
519 */
520 static void
aclcall3(proc)521 aclcall3(proc)
522 int proc;
523 {
524
525 switch (proc) {
526 case ACLPROC3_GETACL:
527 detail_nfsfh3();
528 detail_mask();
529 break;
530 case ACLPROC3_SETACL:
531 detail_nfsfh3();
532 detail_secattr();
533 break;
534 default:
535 break;
536 }
537 }
538
539 /*
540 * Print out version 3 NFS_ACL reply packets
541 */
542 static void
aclreply3(proc)543 aclreply3(proc)
544 int proc;
545 {
546
547 switch (proc) {
548 case ACLPROC3_GETACL:
549 if (detail_nfsstat3() == 0) {
550 detail_post_op_attr("");
551 detail_secattr();
552 }
553 break;
554 case ACLPROC3_SETACL:
555 if (detail_nfsstat3() == 0)
556 detail_post_op_attr("");
557 break;
558 default:
559 break;
560 }
561 }
562
563 /*
564 * Print out version 4 NFS_ACL call packets
565 */
566 static void
aclcall4(proc)567 aclcall4(proc)
568 int proc;
569 {
570
571 switch (proc) {
572 case ACLPROC4_GETACL:
573 detail_nfsfh3();
574 detail_mask();
575 break;
576 case ACLPROC4_SETACL:
577 detail_nfsfh3();
578 detail_secattr();
579 break;
580 default:
581 break;
582 }
583 }
584
585 /*
586 * Print out version 4 NFS_ACL reply packets
587 */
588 static void
aclreply4(proc)589 aclreply4(proc)
590 int proc;
591 {
592
593 switch (proc) {
594 case ACLPROC4_GETACL:
595 if (detail_nfsstat4() == 0) {
596 detail_post_op_attr("");
597 detail_secattr();
598 }
599 break;
600 case ACLPROC4_SETACL:
601 if (detail_nfsstat4() == 0)
602 detail_post_op_attr("");
603 break;
604 default:
605 break;
606 }
607 }
608
609 static void
detail_access2()610 detail_access2()
611 {
612 uint_t bits;
613
614 bits = showxdr_u_long("Access bits = 0x%08x");
615 (void) sprintf(get_line(0, 0), " %s",
616 getflag(bits, ACCESS2_READ, "Read", "(no read)"));
617 (void) sprintf(get_line(0, 0), " %s",
618 getflag(bits, ACCESS2_LOOKUP, "Lookup", "(no lookup)"));
619 (void) sprintf(get_line(0, 0), " %s",
620 getflag(bits, ACCESS2_MODIFY, "Modify", "(no modify)"));
621 (void) sprintf(get_line(0, 0), " %s",
622 getflag(bits, ACCESS2_EXTEND, "Extend", "(no extend)"));
623 (void) sprintf(get_line(0, 0), " %s",
624 getflag(bits, ACCESS2_DELETE, "Delete", "(no delete)"));
625 (void) sprintf(get_line(0, 0), " %s",
626 getflag(bits, ACCESS2_EXECUTE, "Execute", "(no execute)"));
627 }
628
629 static char *
sum_access2()630 sum_access2()
631 {
632 int bits;
633 static char buff[22];
634
635 bits = getxdr_u_long();
636 buff[0] = '\0';
637
638 if (bits & ACCESS2_READ)
639 (void) strcat(buff, "read,");
640 if (bits & ACCESS2_LOOKUP)
641 (void) strcat(buff, "lookup,");
642 if (bits & ACCESS2_MODIFY)
643 (void) strcat(buff, "modify,");
644 if (bits & ACCESS2_EXTEND)
645 (void) strcat(buff, "extend,");
646 if (bits & ACCESS2_DELETE)
647 (void) strcat(buff, "delete,");
648 if (bits & ACCESS2_EXECUTE)
649 (void) strcat(buff, "execute,");
650 if (buff[0] != '\0')
651 buff[strlen(buff) - 1] = '\0';
652
653 return (buff);
654 }
655
656 static void
detail_mask()657 detail_mask()
658 {
659 ulong_t mask;
660
661 mask = showxdr_u_long("Mask = 0x%lx");
662 (void) sprintf(get_line(0, 0), " %s",
663 getflag(mask, NA_ACL, "aclent", "(no aclent)"));
664 (void) sprintf(get_line(0, 0), " %s",
665 getflag(mask, NA_ACLCNT, "aclcnt", "(no aclcnt)"));
666 (void) sprintf(get_line(0, 0), " %s",
667 getflag(mask, NA_DFACL, "dfaclent", "(no dfaclent)"));
668 (void) sprintf(get_line(0, 0), " %s",
669 getflag(mask, NA_DFACLCNT, "dfaclcnt", "(no dfaclcnt)"));
670 }
671
672 static void
detail_secattr()673 detail_secattr()
674 {
675
676 detail_mask();
677 showxdr_long("Aclcnt = %d");
678 detail_aclent();
679 showxdr_long("Dfaclcnt = %d");
680 detail_aclent();
681 }
682
683 static void
detail_aclent()684 detail_aclent()
685 {
686 int count;
687 int type;
688 int id;
689 ushort_t perm;
690
691 count = getxdr_long();
692 while (count-- > 0) {
693 type = getxdr_long();
694 id = getxdr_long();
695 perm = getxdr_u_short();
696 switch (type) {
697 case NA_USER:
698 (void) sprintf(get_line(0, 0), "\tuser:%s:%s",
699 detail_uname(id), detail_perm(perm));
700 break;
701 case NA_USER_OBJ:
702 (void) sprintf(get_line(0, 0), "\tuser::%s",
703 detail_perm(perm));
704 break;
705 case NA_GROUP:
706 (void) sprintf(get_line(0, 0), "\tgroup:%s:%s",
707 detail_gname(id), detail_perm(perm));
708 break;
709 case NA_GROUP_OBJ:
710 (void) sprintf(get_line(0, 0), "\tgroup::%s",
711 detail_perm(perm));
712 break;
713 case NA_CLASS_OBJ:
714 (void) sprintf(get_line(0, 0), "\tmask:%s",
715 detail_perm(perm));
716 break;
717 case NA_OTHER_OBJ:
718 (void) sprintf(get_line(0, 0), "\tother:%s",
719 detail_perm(perm));
720 break;
721 case NA_DEF_USER:
722 (void) sprintf(get_line(0, 0), "\tdefault:user:%s:%s",
723 detail_uname(id), detail_perm(perm));
724 break;
725 case NA_DEF_USER_OBJ:
726 (void) sprintf(get_line(0, 0), "\tdefault:user::%s",
727 detail_perm(perm));
728 break;
729 case NA_DEF_GROUP:
730 (void) sprintf(get_line(0, 0), "\tdefault:group:%s:%s",
731 detail_gname(id), detail_perm(perm));
732 break;
733 case NA_DEF_GROUP_OBJ:
734 (void) sprintf(get_line(0, 0), "\tdefault:group::%s",
735 detail_perm(perm));
736 break;
737 case NA_DEF_CLASS_OBJ:
738 (void) sprintf(get_line(0, 0), "\tdefault:mask:%s",
739 detail_perm(perm));
740 break;
741 case NA_DEF_OTHER_OBJ:
742 (void) sprintf(get_line(0, 0), "\tdefault:other:%s",
743 detail_perm(perm));
744 break;
745 default:
746 (void) sprintf(get_line(0, 0), "\tunrecognized entry");
747 break;
748 }
749 }
750 }
751
752 static char *
detail_uname(uid_t uid)753 detail_uname(uid_t uid)
754 {
755 struct passwd *pwd;
756 static char uidp[10];
757
758 pwd = getpwuid(uid);
759 if (pwd == NULL) {
760 sprintf(uidp, "%d", uid);
761 return (uidp);
762 }
763 return (pwd->pw_name);
764 }
765
766 static char *
detail_gname(gid_t gid)767 detail_gname(gid_t gid)
768 {
769 struct group *grp;
770 static char gidp[10];
771
772 grp = getgrgid(gid);
773 if (grp == NULL) {
774 sprintf(gidp, "%d", gid);
775 return (gidp);
776 }
777 return (grp->gr_name);
778 }
779
780 static char *perms[] = {
781 "---",
782 "--x",
783 "-w-",
784 "-wx",
785 "r--",
786 "r-x",
787 "rw-",
788 "rwx"
789 };
790 static char *
detail_perm(ushort_t perm)791 detail_perm(ushort_t perm)
792 {
793
794 if (perm >= sizeof (perms) / sizeof (perms[0]))
795 return ("?");
796 return (perms[perm]);
797 }
798