3 Written by Corinna Vinschen <vinschen@redhat.com>
5 This file is part of Cygwin.
7 This software is a copyrighted work licensed under the terms of the
8 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
20 #include <cygwin/acl.h>
21 #include <cygwin/version.h>
36 #define ILLEGAL_MODE ((mode_t)0xffffffff)
39 static char *prog_name
;
43 DeleteExt
= 1, /* The values 1,2,3 allow bitmasking below. */
55 mode_t
getperm (char *in
)
57 if (isdigit ((unsigned char) *in
) && !in
[1])
62 return i
<< 6 | i
<< 3 | i
;
64 if (strlen (in
) > 3 && strchr (" \t\n\r#", in
[3]))
68 if (!strchr ("r-", in
[0])
69 || !strchr ("w-", in
[1])
70 || !strchr ("x-", in
[2]))
72 return (in
[0] == 'r' ? S_IROTH
: 0)
73 | (in
[1] == 'w' ? S_IWOTH
: 0)
74 | (in
[2] == 'x' ? S_IXOTH
: 0);
78 getaclentry (action_t action
, char *c
, aclent_t
*ace
)
83 ace
->a_id
= (uid_t
) -1;
86 /* First, check if we're handling a default entry. */
87 if (!strncmp (c
, "default:", 8) || !strncmp (c
, "d:", 2))
89 ace
->a_type
= ACL_DEFAULT
;
90 c
= strchr (c
, ':') + 1;
92 /* c now points to the type. Check for next colon. If we find a colon,
93 NUL it. Otherwise the string is invalid, except when deleting. */
94 c2
= strchrnul (c
, ':');
97 else if (action
!= Delete
)
100 if (!strcmp (c
, "u") || !strcmp (c
, "user"))
101 ace
->a_type
|= USER_OBJ
;
102 else if (!strcmp (c
, "g") || !strcmp (c
, "group"))
103 ace
->a_type
|= GROUP_OBJ
;
104 else if (!strcmp (c
, "m") || !strcmp (c
, "mask"))
105 ace
->a_type
|= CLASS_OBJ
;
106 else if (!strcmp (c
, "o") || !strcmp (c
, "other"))
107 ace
->a_type
|= OTHER_OBJ
;
110 /* Skip to next field. */
114 /* Nothing follows. This is only valid if action is Delete and the
115 type is CLASS_OBJ, or if ACL_DEFAULT is set. */
117 || (!(ace
->a_type
& (CLASS_OBJ
| ACL_DEFAULT
))))
120 else if (!(ace
->a_type
& (USER_OBJ
| GROUP_OBJ
)))
122 /* Mask and other entries may contain one or two colons. */
126 /* If this is a user or group entry, check if next char is a colon char.
127 If so, skip it, otherwise it's the name of a user or group. */
132 /* c now points to the id. Check for next colon. If we find a colon,
133 NUL it. Otherwise the string is invalid, except when deleting.
134 If we delete, it must be a default entry since standard ugo entries
136 c2
= strchrnul (c
+ 1, ':');
139 else if (action
!= Delete
)
141 /* Fetch user/group id. */
142 if (isdigit ((unsigned char) *c
))
146 ace
->a_id
= strtol (c
, &c3
, 10);
150 else if (ace
->a_type
& USER_OBJ
)
152 struct passwd
*pw
= getpwnam (c
);
155 ace
->a_id
= pw
->pw_uid
;
159 struct group
*gr
= getgrnam (c
);
162 ace
->a_id
= gr
->gr_gid
;
164 if (ace
->a_type
& USER_OBJ
)
166 ace
->a_type
&= ~USER_OBJ
;
171 ace
->a_type
&= ~GROUP_OBJ
;
172 ace
->a_type
|= GROUP
;
174 /* Skip to next field. */
177 if (action
== Delete
)
179 /* Trailing garbage? */
182 /* No, we're good. */
183 ace
->a_perm
= ILLEGAL_MODE
;
187 if ((ace
->a_perm
= getperm (c
)) == ILLEGAL_MODE
)
193 getaclentries (action_t action
, char *buf
, aclent_t
*acls
, int *idx
)
197 if (action
== SetFromFile
)
202 if (!strcmp (buf
, "-"))
204 else if (! (fp
= fopen (buf
, "r")))
206 while ((fb
= fgets (fbuf
, 256, fp
)))
208 while (strchr (" \t", *fb
))
210 if (strchr ("\n\r#", *fb
))
212 if (!getaclentry (action
, fb
, acls
+ (*idx
)++))
222 for (c
= strtok (buf
, ","); c
; c
= strtok (NULL
, ","))
223 if (!getaclentry (action
, c
, acls
+ (*idx
)++))
229 searchace (aclent_t
*aclp
, int nentries
, int type
, int id
)
233 for (i
= 0; i
< nentries
; ++i
)
234 if ((aclp
[i
].a_type
== type
&& (id
< 0 || aclp
[i
].a_id
== id
))
241 delace (aclent_t
*tgt
, int tcnt
, int t
)
245 for (i
= t
+ 1; i
< tcnt
; ++i
)
252 delacl (aclent_t
*tgt
, int tcnt
, aclent_t
*src
, int scnt
)
256 for (s
= 0; s
< scnt
; ++s
)
258 t
= searchace (tgt
, MAX_ACL_ENTRIES
, src
[s
].a_type
,
259 (src
[s
].a_type
& (USER
| GROUP
)) ? src
[s
].a_id
: -1);
263 tcnt
= delace (tgt
, tcnt
, t
);
269 modacl (aclent_t
*tgt
, int tcnt
, aclent_t
*src
, int scnt
)
273 /* Delete, replace or add given acl entries. */
274 for (s
= 0; s
< scnt
; ++s
)
276 t
= searchace (tgt
, MAX_ACL_ENTRIES
, src
[s
].a_type
,
277 (src
[s
].a_type
& (USER
| GROUP
)) ? src
[s
].a_id
: -1);
280 /* ILLEGAL_MODE means "delete". */
281 if (src
[s
].a_perm
== ILLEGAL_MODE
&& t
< tcnt
)
282 tcnt
= delace (tgt
, tcnt
, t
);
294 check_got_mask (aclent_t
*src
, int scnt
, int *got_mask
, int *got_def_mask
)
296 *got_mask
= searchace (src
, scnt
, CLASS_OBJ
, -1) >= 0;
297 *got_def_mask
= searchace (src
, scnt
, DEF_CLASS_OBJ
, -1) >= 0;
301 recompute_mask (aclent_t
*tgt
, int tcnt
, int got_mask
, int got_def_mask
)
304 int need_mask
= 0, need_def_mask
= 0;
305 int mask_idx
= -1, def_mask_idx
= -1;
306 mode_t mask
= 0, def_mask
= 0;
308 /* Now recompute mask, if requested (default) */
309 for (t
= 0; t
< tcnt
; ++t
)
311 switch (tgt
[t
].a_type
)
315 /* Do we need a CLASS_OBJ at all? */
319 /* Compute resulting maximum mask. */
320 mask
|= tgt
[t
].a_perm
;
323 /* Do we already have a CLASS_OBJ? */
328 /* Do we need a DEF_CLASS_OBJ at all? */
332 /* Compute resulting maximum default mask. */
333 def_mask
|= tgt
[t
].a_perm
;
336 /* Do we already have a DEF_CLASS_OBJ? */
341 /* Recompute mask, if requested
342 - If we got a mask in the input string, recompute only if --mask has been
344 - If we got no mask in the input, but we either need a mask or we already
345 have one, and --no-mask has *not* been specified, recompute. */
346 if ((got_mask
&& mask_opt
> 0)
347 || (!got_mask
&& mask_opt
>= 0 && (need_mask
|| mask_idx
>= 0)))
352 t
= searchace (tgt
, MAX_ACL_ENTRIES
, CLASS_OBJ
, -1);
357 tgt
[t
].a_type
= CLASS_OBJ
;
359 tgt
[t
].a_perm
= mask
;
361 /* Recompute default mask, if requested */
362 if ((got_def_mask
&& mask_opt
> 0)
363 || (!got_def_mask
&& mask_opt
>= 0
364 && (need_def_mask
|| def_mask_idx
>= 0)))
366 if (def_mask_idx
>= 0)
369 t
= searchace (tgt
, MAX_ACL_ENTRIES
, DEF_CLASS_OBJ
, -1);
374 tgt
[t
].a_type
= DEF_CLASS_OBJ
;
376 tgt
[t
].a_perm
= def_mask
;
382 addmissing (aclent_t
*tgt
, int tcnt
)
385 int types
= 0, def_types
= 0;
386 int perm
= 0, def_perm
= 0;
388 /* Check if we have all the required entries now. */
389 for (t
= 0; t
< tcnt
; ++t
)
390 if (tgt
[t
].a_type
& ACL_DEFAULT
)
392 def_types
|= tgt
[t
].a_type
;
393 if (tgt
[t
].a_type
& GROUP_OBJ
)
394 def_perm
|= tgt
[t
].a_perm
;
395 else if ((tgt
[t
].a_type
& (USER
| GROUP
)) && mask_opt
>= 0)
396 def_perm
|= tgt
[t
].a_perm
;
400 types
|= tgt
[t
].a_type
;
401 if (tgt
[t
].a_type
& GROUP_OBJ
)
402 perm
|= tgt
[t
].a_perm
;
403 else if ((tgt
[t
].a_type
& (USER
| GROUP
)) && mask_opt
>= 0)
404 perm
|= tgt
[t
].a_perm
;
406 /* Add missing CLASS_OBJ */
407 if ((types
& (USER
| GROUP
)) && !(types
& CLASS_OBJ
))
409 tgt
[tcnt
].a_type
= CLASS_OBJ
;
410 tgt
[tcnt
].a_id
= (uid_t
) -1;
411 tgt
[tcnt
++].a_perm
= perm
;
415 /* Add missing default entries. */
416 if (!(def_types
& USER_OBJ
) && tcnt
< MAX_ACL_ENTRIES
)
418 t
= searchace (tgt
, tcnt
, USER_OBJ
, -1);
419 tgt
[tcnt
].a_type
= DEF_USER_OBJ
;
420 tgt
[tcnt
].a_id
= (uid_t
) -1;
421 tgt
[tcnt
++].a_perm
= t
>= 0 ? tgt
[t
].a_perm
: S_IRWXO
;
423 if (!(def_types
& GROUP_OBJ
) && tcnt
< MAX_ACL_ENTRIES
)
425 t
= searchace (tgt
, tcnt
, GROUP_OBJ
, -1);
426 tgt
[tcnt
].a_type
= DEF_GROUP_OBJ
;
427 tgt
[tcnt
].a_id
= (uid_t
) -1;
428 tgt
[tcnt
].a_perm
= t
>= 0 ? tgt
[t
].a_perm
: (S_IROTH
| S_IXOTH
);
429 def_perm
|= tgt
[tcnt
++].a_perm
;
431 if (!(def_types
& OTHER_OBJ
) && tcnt
< MAX_ACL_ENTRIES
)
433 t
= searchace (tgt
, tcnt
, OTHER_OBJ
, -1);
434 tgt
[tcnt
].a_type
= DEF_OTHER_OBJ
;
435 tgt
[tcnt
].a_id
= (uid_t
) -1;
436 tgt
[tcnt
++].a_perm
= t
>= 0 ? tgt
[t
].a_perm
: (S_IROTH
| S_IXOTH
);
438 /* Add missing DEF_CLASS_OBJ */
439 if ((def_types
& (USER
| GROUP
)) && !(def_types
& CLASS_OBJ
))
441 tgt
[tcnt
].a_type
= DEF_CLASS_OBJ
;
442 tgt
[tcnt
].a_id
= (uid_t
) -1;
443 tgt
[tcnt
++].a_perm
= def_perm
;
450 delallacl (aclent_t
*tgt
, int tcnt
, action_t action
)
454 for (t
= 0; t
< tcnt
; ++t
)
455 /* -b (DeleteExt): Remove all extended ACL entries.
456 -k (DeleteDef): Remove all default ACL entries.
457 -b -k (DeleteAll): Remove extended and remove defaults. That means,
458 only preserve standard POSIX perms. */
459 if (((action
& DeleteExt
) && (tgt
[t
].a_type
& (USER
| GROUP
| CLASS_OBJ
)))
460 || ((action
& DeleteDef
) && (tgt
[t
].a_type
& ACL_DEFAULT
)))
464 memmove (&tgt
[t
], &tgt
[t
+ 1], (tcnt
- t
) * sizeof (aclent_t
));
471 setfacl (action_t action
, const char *path
, aclent_t
*acls
, int cnt
)
473 aclent_t lacl
[MAX_ACL_ENTRIES
];
474 int lcnt
, got_mask
= 0, got_def_mask
= 0;
476 memset (lacl
, 0, sizeof lacl
);
480 check_got_mask (acls
, cnt
, &got_mask
, &got_def_mask
);
481 memcpy (lacl
, acls
, (lcnt
= cnt
) * sizeof (aclent_t
));
482 if ((lcnt
= recompute_mask (lacl
, lcnt
, got_mask
, got_def_mask
)) < 0
483 || (lcnt
= addmissing (lacl
, lcnt
)) < 0
484 || acl (path
, SETACL
, lcnt
, lacl
) < 0)
491 check_got_mask (acls
, cnt
, &got_mask
, &got_def_mask
);
492 if ((lcnt
= acl (path
, GETACL
, MAX_ACL_ENTRIES
, lacl
)) < 0
493 || (lcnt
= delacl (lacl
, lcnt
, acls
, cnt
)) < 0
494 || (lcnt
= recompute_mask (lacl
, lcnt
, got_mask
, got_def_mask
)) < 0
495 || acl (path
, SETACL
, lcnt
, lacl
) < 0)
504 if ((lcnt
= acl (path
, GETACL
, MAX_ACL_ENTRIES
, lacl
)) < 0
505 || (lcnt
= delallacl (lacl
, lcnt
, action
)) < 0
506 || acl (path
, SETACL
, lcnt
, lacl
) < 0)
513 check_got_mask (acls
, cnt
, &got_mask
, &got_def_mask
);
514 if ((lcnt
= acl (path
, GETACL
, MAX_ACL_ENTRIES
, lacl
)) < 0
515 || (lcnt
= modacl (lacl
, lcnt
, acls
, cnt
)) < 0
516 || (lcnt
= recompute_mask (lacl
, lcnt
, got_mask
, got_def_mask
)) < 0
517 || (lcnt
= addmissing (lacl
, lcnt
)) < 0
518 || acl (path
, SETACL
, lcnt
, lacl
) < 0)
528 static void __attribute__ ((__noreturn__
))
532 "Usage: %s [-n] {-f ACL_FILE | -s acl_entries} FILE...\n"
533 " %s [-n] {[-bk]|[-x acl_entries] [-m acl_entries]} FILE...\n"
535 "Modify file and directory access control lists (ACLs)\n"
537 " -b, --remove-all remove all extended ACL entries\n"
538 " -x, --delete delete one or more specified ACL entries\n"
539 " -f, --set-file set ACL entries for FILE to ACL entries read\n"
541 " -k, --remove-default remove all default ACL entries\n"
542 " -m, --modify modify one or more specified ACL entries\n"
543 " -n, --no-mask don't recalculate the effective rights mask\n"
544 " --mask do recalculate the effective rights mask\n"
545 " -s, --set set specified ACL entries on FILE\n"
546 " -V, --version print version and exit\n"
547 " -h, --help this help text\n"
549 "At least one of (-b, -x, -f, -k, -m, -s) must be specified\n"
550 "\n", prog_name
, prog_name
);
551 if (stream
== stdout
)
554 " Acl_entries are one or more comma-separated ACL entries from the following\n"
560 " g[roup]:gid:perm\n"
564 " Default entries are like the above with the additional default identifier.\n"
567 " d[efault]:u[ser]:uid:perm\n"
569 " 'perm' is either a 3-char permissions string in the form \"rwx\" with the\n"
570 " character - for no permission, or it is the octal representation of the\n"
571 " permissions, a value from 0 (equivalent to \"---\") to 7 (\"rwx\").\n"
572 " 'uid' is a user name or a numerical uid.\n"
573 " 'gid' is a group name or a numerical gid.\n"
575 "For each file given as parameter, %s will either replace its complete ACL\n"
576 "(-s, -f), or it will add, modify, or delete ACL entries.\n"
578 "The following options are supported:\n"
581 " Remove all extended ACL entries. The base ACL entries of the owner, group\n"
582 " and others are retained. This option can be combined with the\n"
583 " -k,--remove-default option to delete all non-standard POSIX permissions.\n"
586 " Delete one or more specified entries from the file's ACL. The owner, group\n"
587 " and others entries must not be deleted. Acl_entries to be deleted should\n"
588 " be specified without permissions, as in the following list:\n"
593 " d[efault]:u[ser][:uid]\n"
594 " d[efault]:g[roup][:gid]\n"
595 " d[efault]:m[ask][:]\n"
596 " d[efault]:o[ther][:]\n"
599 " Take the Acl_entries from ACL_FILE one per line. Whitespace characters are\n"
600 " ignored, and the character \"#\" may be used to start a comment. The special\n"
601 " filename \"-\" indicates reading from stdin.\n"
602 " Required entries are\n"
603 " - One user entry for the owner of the file.\n"
604 " - One group entry for the group of the file.\n"
605 " - One other entry.\n"
606 " If additional user and group entries are given:\n"
607 " - A mask entry for the file group class of the file.\n"
608 " - No duplicate user or group entries with the same uid/gid.\n"
609 " If it is a directory:\n"
610 " - One default user entry for the owner of the file.\n"
611 " - One default group entry for the group of the file.\n"
612 " - One default mask entry for the file group class.\n"
613 " - One default other entry.\n"
615 "-k, --remove-default\n"
616 " Remove all default ACL entries. If no default ACL entries exist, no\n"
617 " warnings are issued. This option can be combined with the -b,--remove-all\n"
618 " option to delete all non-standard POSIX permissions.\n"
621 " Add or modify one or more specified ACL entries. Acl_entries is a\n"
622 " comma-separated list of entries from the same list as above.\n"
625 " Valid in conjunction with -m. Do not recalculate the effective rights\n"
626 " mask. The default behavior of setfacl is to recalculate the ACL mask entry,\n"
627 " unless a mask entry was explicitly given. The mask entry is set to the\n"
628 " union of all permissions of the owning group, and all named user and group\n"
629 " entries. (These are exactly the entries affected by the mask entry).\n"
632 " Valid in conjunction with -m. Do recalculate the effective rights mask,\n"
633 " even if an ACL mask entry was explicitly given. (See the -n option.)\n"
636 " Like -f, but set the file's ACL with ACL entries specified in a\n"
637 " comma-separated list on the command line.\n"
639 "While the -x and -m options may be used in the same command, the -f and -s\n"
640 "options may be used only exclusively.\n"
642 "Directories may contain default ACL entries. Files created in a directory\n"
643 "that contains default ACL entries will have permissions according to the\n"
644 "combination of the current umask, the explicit permissions requested and\n"
645 "the default ACL entries.\n"
649 fprintf(stream
, "Try '%s --help' for more information.\n", prog_name
);
650 exit (stream
== stdout
? 0 : 1);
653 struct option longopts
[] = {
654 {"remove-all", no_argument
, NULL
, 'b'},
655 {"delete", required_argument
, NULL
, 'x'},
656 {"set-file", required_argument
, NULL
, 'f'},
657 {"file", required_argument
, NULL
, 'f'},
658 {"remove-default", no_argument
, NULL
, 'k'},
659 {"modify", required_argument
, NULL
, 'm'},
660 {"no-mask", no_argument
, NULL
, 'n'},
661 {"mask", no_argument
, NULL
, '\n'},
662 {"replace", no_argument
, NULL
, 'r'},
663 {"set", required_argument
, NULL
, 's'},
664 {"substitute", required_argument
, NULL
, 's'},
665 {"help", no_argument
, NULL
, 'h'},
666 {"version", no_argument
, NULL
, 'V'},
667 {0, no_argument
, NULL
, 0}
669 const char *opts
= "bd:f:hkm:nrs:Vx:";
674 printf ("setfacl (cygwin) %d.%d.%d\n"
675 "POSIX ACL modification utility\n"
676 "Copyright (C) 2000 - %s Cygwin Authors\n"
677 "This is free software; see the source for copying conditions. There is NO\n"
678 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
679 CYGWIN_VERSION_DLL_MAJOR
/ 1000,
680 CYGWIN_VERSION_DLL_MAJOR
% 1000,
681 CYGWIN_VERSION_DLL_MINOR
,
682 strrchr (__DATE__
, ' ') + 1);
686 main (int argc
, char **argv
)
689 action_t action
= NoAction
;
690 aclent_t acls
[MAX_ACL_ENTRIES
];
694 prog_name
= program_invocation_short_name
;
696 memset (acls
, 0, sizeof acls
);
697 while ((c
= getopt_long (argc
, argv
, opts
, longopts
, NULL
)) != EOF
)
701 if (action
== NoAction
)
703 else if (action
== DeleteDef
)
708 case 'd': /* Backward compat */
710 if (action
== NoAction
)
712 else if (action
== Modify
)
716 if (! getaclentries (Delete
, optarg
, acls
, &aclidx
))
718 fprintf (stderr
, "%s: illegal acl entries\n", prog_name
);
723 if (action
== NoAction
)
727 if (! getaclentries (SetFromFile
, optarg
, acls
, &aclidx
))
729 fprintf (stderr
, "%s: illegal acl entries\n", prog_name
);
736 if (action
== NoAction
)
738 else if (action
== DeleteExt
)
744 if (action
== NoAction
)
746 else if (action
== Delete
)
750 if (! getaclentries (Modify
, optarg
, acls
, &aclidx
))
752 fprintf (stderr
, "%s: illegal acl entries\n", prog_name
);
765 if (action
== NoAction
)
769 if (! getaclentries (Set
, optarg
, acls
, &aclidx
))
771 fprintf (stderr
, "%s: illegal acl entries\n", prog_name
);
779 fprintf (stderr
, "Try `%s --help' for more information.\n", prog_name
);
782 if (action
== NoAction
)
784 if (optind
> argc
- 1)
787 switch (aclcheck (acls
, aclidx
, NULL
))
790 fprintf (stderr
, "%s: more than one group entry.\n", prog_name
);
793 fprintf (stderr
, "%s: more than one user entry.\n", prog_name
);
796 fprintf (stderr
, "%s: more than one mask entry.\n", prog_name
);
799 fprintf (stderr
, "%s: more than one other entry.\n", prog_name
);
801 case DUPLICATE_ERROR
:
802 fprintf (stderr
, "%s: duplicate additional user or group.\n", prog_name
);
805 fprintf (stderr
, "%s: invalid entry type.\n", prog_name
);
808 fprintf (stderr
, "%s: missing entries.\n", prog_name
);
811 fprintf (stderr
, "%s: out of memory.\n", prog_name
);
816 for (c
= optind
; c
< argc
; ++c
)
817 ret
|= setfacl (action
, argv
[c
], acls
, aclidx
);