packaging files
[platform/upstream/acl.git] / setfacl / setfacl.c
1 /*
2   File: setfacl.c
3   (Linux Access Control List Management)
4
5   Copyright (C) 1999-2002
6   Andreas Gruenbacher, <a.gruenbacher@bestbits.at>
7
8   This program is free software; you can redistribute it and/or
9   modify it under the terms of the GNU Lesser General Public
10   License as published by the Free Software Foundation; either
11   version 2.1 of the License, or (at your option) any later version.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public
19   License along with this library; if not, write to the Free Software
20   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23 #include <limits.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <errno.h>
28 #include <sys/stat.h>
29 #include <dirent.h>
30 #include <libgen.h>
31 #include <getopt.h>
32 #include <locale.h>
33 #include "config.h"
34 #include "sequence.h"
35 #include "parse.h"
36 #include "do_set.h"
37 #include "walk_tree.h"
38 #include "misc.h"
39
40 #define POSIXLY_CORRECT_STR "POSIXLY_CORRECT"
41
42 /* '-' stands for `process non-option arguments in loop' */
43 #if !POSIXLY_CORRECT
44 #  define CMD_LINE_OPTIONS "-:bkndvhm:M:x:X:RLP"
45 #  define CMD_LINE_SPEC "[-bkndRLP] { -m|-M|-x|-X ... } file ..."
46 #endif
47 #define POSIXLY_CMD_LINE_OPTIONS "-:bkndvhm:M:x:X:"
48 #define POSIXLY_CMD_LINE_SPEC "[-bknd] {-m|-M|-x|-X ... } file ..."
49
50 struct option long_options[] = {
51 #if !POSIXLY_CORRECT
52         { "set",                1, 0, 's' },
53         { "set-file",           1, 0, 'S' },
54
55         { "mask",               0, 0, 'r' },
56         { "recursive",          0, 0, 'R' },
57         { "logical",            0, 0, 'L' },
58         { "physical",           0, 0, 'P' },
59         { "restore",            1, 0, 'B' },
60         { "test",               0, 0, 't' },
61 #endif
62         { "modify",             1, 0, 'm' },
63         { "modify-file",        1, 0, 'M' },
64         { "remove",             1, 0, 'x' },
65         { "remove-file",        1, 0, 'X' },
66
67         { "default",            0, 0, 'd' },
68         { "no-mask",            0, 0, 'n' },
69         { "remove-all",         0, 0, 'b' },
70         { "remove-default",     0, 0, 'k' },
71         { "version",            0, 0, 'v' },
72         { "help",               0, 0, 'h' },
73         { NULL,                 0, 0, 0   },
74 };
75
76 const char *progname;
77 const char *cmd_line_options, *cmd_line_spec;
78
79 int walk_flags = WALK_TREE_DEREFERENCE_TOPLEVEL;
80 int opt_recalculate;  /* recalculate mask entry (0=default, 1=yes, -1=no) */
81 int opt_promote;  /* promote access ACL to default ACL */
82 int opt_test;  /* do not write to the file system.
83                       Print what would happen instead. */
84 #if POSIXLY_CORRECT
85 const int posixly_correct = 1;  /* Posix compatible behavior! */
86 #else
87 int posixly_correct;  /* Posix compatible behavior? */
88 #endif
89 int chown_error;
90 int promote_warning;
91
92
93 static const char *xquote(const char *str, const char *quote_chars)
94 {
95         const char *q = quote(str, quote_chars);
96         if (q == NULL) {
97                 fprintf(stderr, "%s: %s\n", progname, strerror(errno));
98                 exit(1);
99         }
100         return q;
101 }
102
103 int
104 has_any_of_type(
105         cmd_t cmd,
106         acl_type_t acl_type)
107 {
108         while (cmd) {
109                 if (cmd->c_type == acl_type)
110                         return 1;
111                 cmd = cmd->c_next;
112         }
113         return 0;
114 }
115         
116
117 #if !POSIXLY_CORRECT
118 int
119 restore(
120         FILE *file,
121         const char *filename)
122 {
123         char *path_p;
124         struct stat st;
125         uid_t uid;
126         gid_t gid;
127         mode_t mask, flags;
128         struct do_set_args args = { };
129         int line = 0, backup_line;
130         int error, status = 0;
131         int chmod_required = 0;
132
133         memset(&st, 0, sizeof(st));
134
135         for(;;) {
136                 backup_line = line;
137                 error = read_acl_comments(file, &line, &path_p, &uid, &gid,
138                                           &flags);
139                 if (error < 0) {
140                         error = -error;
141                         goto fail;
142                 }
143                 if (error == 0)
144                         return status;
145
146                 if (path_p == NULL) {
147                         if (filename) {
148                                 fprintf(stderr, _("%s: %s: No filename found "
149                                                   "in line %d, aborting\n"),
150                                         progname, xquote(filename, "\n\r"),
151                                         backup_line);
152                         } else {
153                                 fprintf(stderr, _("%s: No filename found in "
154                                                  "line %d of standard input, "
155                                                  "aborting\n"),
156                                         progname, backup_line);
157                         }
158                         status = 1;
159                         goto getout;
160                 }
161
162                 if (!(args.seq = seq_init()))
163                         goto fail_errno;
164                 if (seq_append_cmd(args.seq, CMD_REMOVE_ACL, ACL_TYPE_ACCESS) ||
165                     seq_append_cmd(args.seq, CMD_REMOVE_ACL, ACL_TYPE_DEFAULT))
166                         goto fail_errno;
167
168                 error = read_acl_seq(file, args.seq, CMD_ENTRY_REPLACE,
169                                      SEQ_PARSE_WITH_PERM |
170                                      SEQ_PARSE_DEFAULT |
171                                      SEQ_PARSE_MULTI,
172                                      &line, NULL);
173                 if (error != 0) {
174                         fprintf(stderr, _("%s: %s: %s in line %d\n"),
175                                 progname, xquote(filename, "\n\r"), strerror(errno),
176                                 line);
177                         status = 1;
178                         goto getout;
179                 }
180
181                 error = stat(path_p, &st);
182                 if (opt_test && error != 0) {
183                         fprintf(stderr, "%s: %s: %s\n", progname,
184                                 xquote(path_p, "\n\r"), strerror(errno));
185                         status = 1;
186                 }
187
188                 args.mode = 0;
189                 error = do_set(path_p, &st, 0, &args);
190                 if (error != 0) {
191                         status = 1;
192                         goto resume;
193                 }
194
195                 if (uid != ACL_UNDEFINED_ID && uid != st.st_uid)
196                         st.st_uid = uid;
197                 else
198                         st.st_uid = -1;
199                 if (gid != ACL_UNDEFINED_ID && gid != st.st_gid)
200                         st.st_gid = gid;
201                 else
202                         st.st_gid = -1;
203                 if (!opt_test &&
204                     (st.st_uid != -1 || st.st_gid != -1)) {
205                         if (chown(path_p, st.st_uid, st.st_gid) != 0) {
206                                 fprintf(stderr, _("%s: %s: Cannot change "
207                                                   "owner/group: %s\n"),
208                                         progname, xquote(path_p, "\n\r"),
209                                         strerror(errno));
210                                 status = 1;
211                         }
212
213                         /* chown() clears setuid/setgid so force a chmod if
214                          * S_ISUID/S_ISGID was expected */
215                         if ((st.st_mode & flags) & (S_ISUID | S_ISGID))
216                                 chmod_required = 1;
217                 }
218
219                 mask = S_ISUID | S_ISGID | S_ISVTX;
220                 if (chmod_required || ((st.st_mode & mask) != (flags & mask))) {
221                         if (!args.mode)
222                                 args.mode = st.st_mode;
223                         args.mode &= (S_IRWXU | S_IRWXG | S_IRWXO);
224                         if (chmod(path_p, flags | args.mode) != 0) {
225                                 fprintf(stderr, _("%s: %s: Cannot change "
226                                                   "mode: %s\n"),
227                                         progname, xquote(path_p, "\n\r"),
228                                         strerror(errno));
229                                 status = 1;
230                         }
231                 }
232 resume:
233                 if (path_p) {
234                         free(path_p);
235                         path_p = NULL;
236                 }
237                 if (args.seq) {
238                         seq_free(args.seq);
239                         args.seq = NULL;
240                 }
241         }
242
243 getout:
244         if (path_p) {
245                 free(path_p);
246                 path_p = NULL;
247         }
248         if (args.seq) {
249                 seq_free(args.seq);
250                 args.seq = NULL;
251         }
252         return status;
253
254 fail_errno:
255         error = errno;
256 fail:
257         fprintf(stderr, "%s: %s: %s\n", progname, xquote(filename, "\n\r"),
258                 strerror(error));
259         status = 1;
260         goto getout;
261 }
262 #endif
263
264
265 void help(void)
266 {
267         printf(_("%s %s -- set file access control lists\n"),
268                 progname, VERSION);
269         printf(_("Usage: %s %s\n"),
270                 progname, cmd_line_spec);
271         printf(_(
272 "  -m, --modify=acl        modify the current ACL(s) of file(s)\n"
273 "  -M, --modify-file=file  read ACL entries to modify from file\n"
274 "  -x, --remove=acl        remove entries from the ACL(s) of file(s)\n"
275 "  -X, --remove-file=file  read ACL entries to remove from file\n"
276 "  -b, --remove-all        remove all extended ACL entries\n"
277 "  -k, --remove-default    remove the default ACL\n"));
278 #if !POSIXLY_CORRECT
279         if (!posixly_correct) {
280                 printf(_(
281 "      --set=acl           set the ACL of file(s), replacing the current ACL\n"
282 "      --set-file=file     read ACL entries to set from file\n"
283 "      --mask              do recalculate the effective rights mask\n"));
284         }
285 #endif
286         printf(_(
287 "  -n, --no-mask           don't recalculate the effective rights mask\n"
288 "  -d, --default           operations apply to the default ACL\n"));
289 #if !POSIXLY_CORRECT
290         if (!posixly_correct) {
291                 printf(_(
292 "  -R, --recursive         recurse into subdirectories\n"
293 "  -L, --logical           logical walk, follow symbolic links\n"
294 "  -P, --physical          physical walk, do not follow symbolic links\n"
295 "      --restore=file      restore ACLs (inverse of `getfacl -R')\n"
296 "      --test              test mode (ACLs are not modified)\n"));
297         }
298 #endif
299         printf(_(
300 "  -v, --version           print version and exit\n"
301 "  -h, --help              this help text\n"));
302 }
303
304
305 int next_file(const char *arg, seq_t seq)
306 {
307         char *line;
308         int errors = 0;
309         struct do_set_args args;
310
311         args.seq = seq;
312
313         if (strcmp(arg, "-") == 0) {
314                 while ((line = next_line(stdin)))
315                         errors = walk_tree(line, walk_flags, 0, do_set, &args);
316                 if (!feof(stdin)) {
317                         fprintf(stderr, _("%s: Standard input: %s\n"),
318                                 progname, strerror(errno));
319                         errors = 1;
320                 }
321         } else {
322                 errors = walk_tree(arg, walk_flags, 0, do_set, &args);
323         }
324         return errors ? 1 : 0;
325 }
326
327
328 #define ERRNO_ERROR(s) \
329         ({status = (s); goto errno_error; })
330
331
332 int main(int argc, char *argv[])
333 {
334         int opt;
335         int saw_files = 0;
336         int status = 0;
337         FILE *file;
338         int which;
339         int lineno;
340         int error;
341         seq_t seq;
342         int seq_cmd, parse_mode;
343         
344         progname = basename(argv[0]);
345
346 #if POSIXLY_CORRECT
347         cmd_line_options = POSIXLY_CMD_LINE_OPTIONS;
348         cmd_line_spec = _(POSIXLY_CMD_LINE_SPEC);
349 #else
350         if (getenv(POSIXLY_CORRECT_STR))
351                 posixly_correct = 1;
352         if (!posixly_correct) {
353                 cmd_line_options = CMD_LINE_OPTIONS;
354                 cmd_line_spec = _(CMD_LINE_SPEC);
355         } else {
356                 cmd_line_options = POSIXLY_CMD_LINE_OPTIONS;
357                 cmd_line_spec = _(POSIXLY_CMD_LINE_SPEC);
358         }
359 #endif
360
361         setlocale(LC_CTYPE, "");
362         setlocale(LC_MESSAGES, "");
363         bindtextdomain(PACKAGE, LOCALEDIR);
364         textdomain(PACKAGE);
365
366         seq = seq_init();
367         if (!seq)
368                 ERRNO_ERROR(1);
369
370         while ((opt = getopt_long(argc, argv, cmd_line_options,
371                                   long_options, NULL)) != -1) {
372                 /* we remember the two REMOVE_ACL commands of the set
373                    operations because we may later need to delete them.  */
374                 cmd_t seq_remove_default_acl_cmd = NULL;
375                 cmd_t seq_remove_acl_cmd = NULL;
376
377                 if (opt != '\1' && saw_files) {
378                         seq_free(seq);
379                         seq = seq_init();
380                         if (!seq)
381                                 ERRNO_ERROR(1);
382                         saw_files = 0;
383                 }
384
385                 switch (opt) {
386                         case 'b':  /* remove all extended entries */
387                                 if (seq_append_cmd(seq, CMD_REMOVE_EXTENDED_ACL,
388                                                         ACL_TYPE_ACCESS) ||
389                                     seq_append_cmd(seq, CMD_REMOVE_ACL,
390                                                         ACL_TYPE_DEFAULT))
391                                         ERRNO_ERROR(1);
392                                 break;
393
394                         case 'k':  /* remove default ACL */
395                                 if (seq_append_cmd(seq, CMD_REMOVE_ACL,
396                                                         ACL_TYPE_DEFAULT))
397                                         ERRNO_ERROR(1);
398                                 break;
399
400                         case 'n':  /* do not recalculate mask */
401                                 opt_recalculate = -1;
402                                 break;
403
404                         case 'r':  /* force recalculate mask */
405                                 opt_recalculate = 1;
406                                 break;
407
408                         case 'd':  /*  operations apply to default ACL */
409                                 opt_promote = 1;
410                                 break;
411
412                         case 's':  /* set */
413                                 if (seq_append_cmd(seq, CMD_REMOVE_ACL,
414                                                         ACL_TYPE_ACCESS))
415                                         ERRNO_ERROR(1);
416                                 seq_remove_acl_cmd = seq->s_last;
417                                 if (seq_append_cmd(seq, CMD_REMOVE_ACL,
418                                                         ACL_TYPE_DEFAULT))
419                                         ERRNO_ERROR(1);
420                                 seq_remove_default_acl_cmd = seq->s_last;
421
422                                 seq_cmd = CMD_ENTRY_REPLACE;
423                                 parse_mode = SEQ_PARSE_WITH_PERM;
424                                 goto set_modify_delete;
425
426                         case 'm':  /* modify */
427                                 seq_cmd = CMD_ENTRY_REPLACE;
428                                 parse_mode = SEQ_PARSE_WITH_PERM;
429                                 goto set_modify_delete;
430
431                         case 'x':  /* delete */
432                                 seq_cmd = CMD_REMOVE_ENTRY;
433 #if POSIXLY_CORRECT
434                                 parse_mode = SEQ_PARSE_ANY_PERM;
435 #else
436                                 if (posixly_correct)
437                                         parse_mode = SEQ_PARSE_ANY_PERM;
438                                 else
439                                         parse_mode = SEQ_PARSE_NO_PERM;
440 #endif
441                                 goto set_modify_delete;
442
443                         set_modify_delete:
444                                 if (!posixly_correct)
445                                         parse_mode |= SEQ_PARSE_DEFAULT;
446                                 if (opt_promote)
447                                         parse_mode |= SEQ_PROMOTE_ACL;
448                                 if (parse_acl_seq(seq, optarg, &which,
449                                                   seq_cmd, parse_mode) != 0) {
450                                         if (which < 0 ||
451                                             (size_t) which >= strlen(optarg)) {
452                                                 fprintf(stderr, _(
453                                                         "%s: Option "
454                                                         "-%c incomplete\n"),
455                                                         progname, opt);
456                                         } else {
457                                                 fprintf(stderr, _(
458                                                         "%s: Option "
459                                                         "-%c: %s near "
460                                                         "character %d\n"),
461                                                         progname, opt,
462                                                         strerror(errno),
463                                                         which+1);
464                                         }
465                                         status = 2;
466                                         goto cleanup;
467                                 }
468                                 break;
469
470                         case 'S':  /* set from file */
471                                 if (seq_append_cmd(seq, CMD_REMOVE_ACL,
472                                                         ACL_TYPE_ACCESS))
473                                         ERRNO_ERROR(1);
474                                 seq_remove_acl_cmd = seq->s_last;
475                                 if (seq_append_cmd(seq, CMD_REMOVE_ACL,
476                                                         ACL_TYPE_DEFAULT))
477                                         ERRNO_ERROR(1);
478                                 seq_remove_default_acl_cmd = seq->s_last;
479
480                                 seq_cmd = CMD_ENTRY_REPLACE;
481                                 parse_mode = SEQ_PARSE_WITH_PERM;
482                                 goto set_modify_delete_from_file;
483
484                         case 'M':  /* modify from file */
485                                 seq_cmd = CMD_ENTRY_REPLACE;
486                                 parse_mode = SEQ_PARSE_WITH_PERM;
487                                 goto set_modify_delete_from_file;
488
489                         case 'X':  /* delete from file */
490                                 seq_cmd = CMD_REMOVE_ENTRY;
491 #if POSIXLY_CORRECT
492                                 parse_mode = SEQ_PARSE_ANY_PERM;
493 #else
494                                 if (posixly_correct)
495                                         parse_mode = SEQ_PARSE_ANY_PERM;
496                                 else
497                                         parse_mode = SEQ_PARSE_NO_PERM;
498 #endif
499                                 goto set_modify_delete_from_file;
500
501                         set_modify_delete_from_file:
502                                 if (!posixly_correct)
503                                         parse_mode |= SEQ_PARSE_DEFAULT;
504                                 if (opt_promote)
505                                         parse_mode |= SEQ_PROMOTE_ACL;
506                                 if (strcmp(optarg, "-") == 0) {
507                                         file = stdin;
508                                 } else {
509                                         file = fopen(optarg, "r");
510                                         if (file == NULL) {
511                                                 fprintf(stderr, "%s: %s: %s\n",
512                                                         progname,
513                                                         xquote(optarg, "\n\r"),
514                                                         strerror(errno));
515                                                 status = 2;
516                                                 goto cleanup;
517                                         }
518                                 }
519
520                                 lineno = 0;
521                                 error = read_acl_seq(file, seq, seq_cmd,
522                                                      parse_mode, &lineno, NULL);
523                                 
524                                 if (file != stdin) {
525                                         fclose(file);
526                                 }
527
528                                 if (error) {
529                                         if (!errno)
530                                                 errno = EINVAL;
531
532                                         if (file != stdin) {
533                                                 fprintf(stderr, _(
534                                                         "%s: %s in line "
535                                                         "%d of file %s\n"),
536                                                         progname,
537                                                         strerror(errno),
538                                                         lineno,
539                                                         xquote(optarg, "\n\r"));
540                                         } else {
541                                                 fprintf(stderr, _(
542                                                         "%s: %s in line "
543                                                         "%d of standard "
544                                                         "input\n"), progname,
545                                                         strerror(errno),
546                                                         lineno);
547                                         }
548                                         status = 2;
549                                         goto cleanup;
550                                 }
551                                 break;
552
553
554                         case '\1':  /* file argument */
555                                 if (seq_empty(seq))
556                                         goto synopsis;
557                                 saw_files = 1;
558
559                                 status = next_file(optarg, seq);
560                                 break;
561
562                         case 'B':  /* restore ACL backup */
563                                 saw_files = 1;
564
565                                 if (strcmp(optarg, "-") == 0)
566                                         file = stdin;
567                                 else {
568                                         file = fopen(optarg, "r");
569                                         if (file == NULL) {
570                                                 fprintf(stderr, "%s: %s: %s\n",
571                                                         progname,
572                                                         xquote(optarg, "\n\r"),
573                                                         strerror(errno));
574                                                 status = 2;
575                                                 goto cleanup;
576                                         }
577                                 }
578
579                                 status = restore(file,
580                                                (file == stdin) ? NULL : optarg);
581
582                                 if (file != stdin)
583                                         fclose(file);
584                                 if (status != 0)
585                                         goto cleanup;
586                                 break;
587
588                         case 'R':  /* recursive */
589                                 walk_flags |= WALK_TREE_RECURSIVE;
590                                 break;
591
592                         case 'L':  /* follow symlinks */
593                                 walk_flags |= WALK_TREE_LOGICAL | WALK_TREE_DEREFERENCE;
594                                 walk_flags &= ~WALK_TREE_PHYSICAL;
595                                 break;
596
597                         case 'P':  /* do not follow symlinks */
598                                 walk_flags |= WALK_TREE_PHYSICAL;
599                                 walk_flags &= ~(WALK_TREE_LOGICAL | WALK_TREE_DEREFERENCE |
600                                                 WALK_TREE_DEREFERENCE_TOPLEVEL);
601                                 break;
602
603                         case 't':  /* test mode */
604                                 opt_test = 1;
605                                 break;
606
607                         case 'v':  /* print version and exit */
608                                 printf("%s " VERSION "\n", progname);
609                                 status = 0;
610                                 goto cleanup;
611
612                         case 'h':  /* help! */
613                                 help();
614                                 status = 0;
615                                 goto cleanup;
616
617                         case ':':  /* option missing */
618                         case '?':  /* unknown option */
619                         default:
620                                 goto synopsis;
621                 }
622                 if (seq_remove_acl_cmd) {
623                         /* This was a set operation. Check if there are
624                            actually entries of ACL_TYPE_ACCESS; if there
625                            are none, we need to remove this command! */
626                         if (!has_any_of_type(seq_remove_acl_cmd->c_next,
627                                             ACL_TYPE_ACCESS))
628                                 seq_delete_cmd(seq, seq_remove_acl_cmd);
629                 }
630                 if (seq_remove_default_acl_cmd) {
631                         /* This was a set operation. Check if there are
632                            actually entries of ACL_TYPE_DEFAULT; if there
633                            are none, we need to remove this command! */
634                         if (!has_any_of_type(seq_remove_default_acl_cmd->c_next,
635                                             ACL_TYPE_DEFAULT))
636                                 seq_delete_cmd(seq, seq_remove_default_acl_cmd);
637                 }
638         }
639         while (optind < argc) {
640                 if(!seq)
641                         goto synopsis;
642                 if (seq_empty(seq))
643                         goto synopsis;
644                 saw_files = 1;
645
646                 status = next_file(argv[optind++], seq);
647         }
648         if (!saw_files)
649                 goto synopsis;
650
651         goto cleanup;
652
653 synopsis:
654         fprintf(stderr, _("Usage: %s %s\n"),
655                 progname, cmd_line_spec);
656         fprintf(stderr, _("Try `%s --help' for more information.\n"),
657                 progname);
658         status = 2;
659         goto cleanup;
660
661 errno_error:
662         fprintf(stderr, "%s: %s\n", progname, strerror(errno));
663         goto cleanup;
664
665 cleanup:
666         if (seq)
667                 seq_free(seq);
668         return status;
669 }
670