Tizen 2.0 Release
[framework/base/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;
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
132         memset(&st, 0, sizeof(st));
133
134         for(;;) {
135                 backup_line = line;
136                 error = read_acl_comments(file, &line, &path_p, &uid, &gid,
137                                           &flags);
138                 if (error < 0)
139                         goto fail;
140                 if (error == 0)
141                         return status;
142
143                 if (path_p == NULL) {
144                         if (filename) {
145                                 fprintf(stderr, _("%s: %s: No filename found "
146                                                   "in line %d, aborting\n"),
147                                         progname, xquote(filename, "\n\r"),
148                                         backup_line);
149                         } else {
150                                 fprintf(stderr, _("%s: No filename found in "
151                                                  "line %d of standard input, "
152                                                  "aborting\n"),
153                                         progname, backup_line);
154                         }
155                         status = 1;
156                         goto getout;
157                 }
158
159                 if (!(args.seq = seq_init()))
160                         goto fail;
161                 if (seq_append_cmd(args.seq, CMD_REMOVE_ACL, ACL_TYPE_ACCESS) ||
162                     seq_append_cmd(args.seq, CMD_REMOVE_ACL, ACL_TYPE_DEFAULT))
163                         goto fail;
164
165                 error = read_acl_seq(file, args.seq, CMD_ENTRY_REPLACE,
166                                      SEQ_PARSE_WITH_PERM |
167                                      SEQ_PARSE_DEFAULT |
168                                      SEQ_PARSE_MULTI,
169                                      &line, NULL);
170                 if (error != 0) {
171                         fprintf(stderr, _("%s: %s: %s in line %d\n"),
172                                 progname, xquote(filename, "\n\r"), strerror(errno),
173                                 line);
174                         status = 1;
175                         goto getout;
176                 }
177
178                 error = stat(path_p, &st);
179                 if (opt_test && error != 0) {
180                         fprintf(stderr, "%s: %s: %s\n", progname,
181                                 xquote(path_p, "\n\r"), strerror(errno));
182                         status = 1;
183                 }
184
185                 args.mode = 0;
186                 error = do_set(path_p, &st, 0, &args);
187                 if (error != 0) {
188                         status = 1;
189                         goto resume;
190                 }
191
192                 if (uid != ACL_UNDEFINED_ID && uid != st.st_uid)
193                         st.st_uid = uid;
194                 else
195                         st.st_uid = -1;
196                 if (gid != ACL_UNDEFINED_ID && gid != st.st_gid)
197                         st.st_gid = gid;
198                 else
199                         st.st_gid = -1;
200                 if (!opt_test &&
201                     (st.st_uid != -1 || st.st_gid != -1)) {
202                         if (chown(path_p, st.st_uid, st.st_gid) != 0) {
203                                 fprintf(stderr, _("%s: %s: Cannot change "
204                                                   "owner/group: %s\n"),
205                                         progname, xquote(path_p, "\n\r"),
206                                         strerror(errno));
207                                 status = 1;
208                         }
209                 }
210
211                 mask = S_ISUID | S_ISGID | S_ISVTX;
212                 if ((st.st_mode & mask) != (flags & mask)) {
213                         if (!args.mode)
214                                 args.mode = st.st_mode;
215                         args.mode &= (S_IRWXU | S_IRWXG | S_IRWXO);
216                         if (chmod(path_p, flags | args.mode) != 0) {
217                                 fprintf(stderr, _("%s: %s: Cannot change "
218                                                   "mode: %s\n"),
219                                         progname, xquote(path_p, "\n\r"),
220                                         strerror(errno));
221                                 status = 1;
222                         }
223                 }
224 resume:
225                 if (path_p) {
226                         free(path_p);
227                         path_p = NULL;
228                 }
229                 if (args.seq) {
230                         seq_free(args.seq);
231                         args.seq = NULL;
232                 }
233         }
234
235 getout:
236         if (path_p) {
237                 free(path_p);
238                 path_p = NULL;
239         }
240         if (args.seq) {
241                 seq_free(args.seq);
242                 args.seq = NULL;
243         }
244         return status;
245
246 fail:
247         fprintf(stderr, "%s: %s: %s\n", progname, xquote(filename, "\n\r"),
248                 strerror(errno));
249         status = 1;
250         goto getout;
251 }
252 #endif
253
254
255 void help(void)
256 {
257         printf(_("%s %s -- set file access control lists\n"),
258                 progname, VERSION);
259         printf(_("Usage: %s %s\n"),
260                 progname, cmd_line_spec);
261         printf(_(
262 "  -m, --modify=acl        modify the current ACL(s) of file(s)\n"
263 "  -M, --modify-file=file  read ACL entries to modify from file\n"
264 "  -x, --remove=acl        remove entries from the ACL(s) of file(s)\n"
265 "  -X, --remove-file=file  read ACL entries to remove from file\n"
266 "  -b, --remove-all        remove all extended ACL entries\n"
267 "  -k, --remove-default    remove the default ACL\n"));
268 #if !POSIXLY_CORRECT
269         if (!posixly_correct) {
270                 printf(_(
271 "      --set=acl           set the ACL of file(s), replacing the current ACL\n"
272 "      --set-file=file     read ACL entries to set from file\n"
273 "      --mask              do recalculate the effective rights mask\n"));
274         }
275 #endif
276         printf(_(
277 "  -n, --no-mask           don't recalculate the effective rights mask\n"
278 "  -d, --default           operations apply to the default ACL\n"));
279 #if !POSIXLY_CORRECT
280         if (!posixly_correct) {
281                 printf(_(
282 "  -R, --recursive         recurse into subdirectories\n"
283 "  -L, --logical           logical walk, follow symbolic links\n"
284 "  -P, --physical          physical walk, do not follow symbolic links\n"
285 "      --restore=file      restore ACLs (inverse of `getfacl -R')\n"
286 "      --test              test mode (ACLs are not modified)\n"));
287         }
288 #endif
289         printf(_(
290 "  -v, --version           print version and exit\n"
291 "  -h, --help              this help text\n"));
292 }
293
294
295 int next_file(const char *arg, seq_t seq)
296 {
297         char *line;
298         int errors = 0;
299         struct do_set_args args;
300
301         args.seq = seq;
302
303         if (strcmp(arg, "-") == 0) {
304                 while ((line = next_line(stdin)))
305                         errors = walk_tree(line, walk_flags, 0, do_set, &args);
306                 if (!feof(stdin)) {
307                         fprintf(stderr, _("%s: Standard input: %s\n"),
308                                 progname, strerror(errno));
309                         errors = 1;
310                 }
311         } else {
312                 errors = walk_tree(arg, walk_flags, 0, do_set, &args);
313         }
314         return errors ? 1 : 0;
315 }
316
317
318 #define ERRNO_ERROR(s) \
319         ({status = (s); goto errno_error; })
320
321
322 int main(int argc, char *argv[])
323 {
324         int opt;
325         int saw_files = 0;
326         int status = 0;
327         FILE *file;
328         int which;
329         int lineno;
330         int error;
331         seq_t seq;
332         int seq_cmd, parse_mode;
333         
334         progname = basename(argv[0]);
335
336 #if POSIXLY_CORRECT
337         cmd_line_options = POSIXLY_CMD_LINE_OPTIONS;
338         cmd_line_spec = _(POSIXLY_CMD_LINE_SPEC);
339 #else
340         if (getenv(POSIXLY_CORRECT_STR))
341                 posixly_correct = 1;
342         if (!posixly_correct) {
343                 cmd_line_options = CMD_LINE_OPTIONS;
344                 cmd_line_spec = _(CMD_LINE_SPEC);
345         } else {
346                 cmd_line_options = POSIXLY_CMD_LINE_OPTIONS;
347                 cmd_line_spec = _(POSIXLY_CMD_LINE_SPEC);
348         }
349 #endif
350
351         setlocale(LC_CTYPE, "");
352         setlocale(LC_MESSAGES, "");
353         bindtextdomain(PACKAGE, LOCALEDIR);
354         textdomain(PACKAGE);
355
356         seq = seq_init();
357         if (!seq)
358                 ERRNO_ERROR(1);
359
360         while ((opt = getopt_long(argc, argv, cmd_line_options,
361                                   long_options, NULL)) != -1) {
362                 /* we remember the two REMOVE_ACL commands of the set
363                    operations because we may later need to delete them.  */
364                 cmd_t seq_remove_default_acl_cmd = NULL;
365                 cmd_t seq_remove_acl_cmd = NULL;
366
367                 if (opt != '\1' && saw_files) {
368                         seq_free(seq);
369                         seq = seq_init();
370                         if (!seq)
371                                 ERRNO_ERROR(1);
372                         saw_files = 0;
373                 }
374
375                 switch (opt) {
376                         case 'b':  /* remove all extended entries */
377                                 if (seq_append_cmd(seq, CMD_REMOVE_EXTENDED_ACL,
378                                                         ACL_TYPE_ACCESS) ||
379                                     seq_append_cmd(seq, CMD_REMOVE_ACL,
380                                                         ACL_TYPE_DEFAULT))
381                                         ERRNO_ERROR(1);
382                                 break;
383
384                         case 'k':  /* remove default ACL */
385                                 if (seq_append_cmd(seq, CMD_REMOVE_ACL,
386                                                         ACL_TYPE_DEFAULT))
387                                         ERRNO_ERROR(1);
388                                 break;
389
390                         case 'n':  /* do not recalculate mask */
391                                 opt_recalculate = -1;
392                                 break;
393
394                         case 'r':  /* force recalculate mask */
395                                 opt_recalculate = 1;
396                                 break;
397
398                         case 'd':  /*  operations apply to default ACL */
399                                 opt_promote = 1;
400                                 break;
401
402                         case 's':  /* set */
403                                 if (seq_append_cmd(seq, CMD_REMOVE_ACL,
404                                                         ACL_TYPE_ACCESS))
405                                         ERRNO_ERROR(1);
406                                 seq_remove_acl_cmd = seq->s_last;
407                                 if (seq_append_cmd(seq, CMD_REMOVE_ACL,
408                                                         ACL_TYPE_DEFAULT))
409                                         ERRNO_ERROR(1);
410                                 seq_remove_default_acl_cmd = seq->s_last;
411
412                                 seq_cmd = CMD_ENTRY_REPLACE;
413                                 parse_mode = SEQ_PARSE_WITH_PERM;
414                                 goto set_modify_delete;
415
416                         case 'm':  /* modify */
417                                 seq_cmd = CMD_ENTRY_REPLACE;
418                                 parse_mode = SEQ_PARSE_WITH_PERM;
419                                 goto set_modify_delete;
420
421                         case 'x':  /* delete */
422                                 seq_cmd = CMD_REMOVE_ENTRY;
423 #if POSIXLY_CORRECT
424                                 parse_mode = SEQ_PARSE_ANY_PERM;
425 #else
426                                 if (posixly_correct)
427                                         parse_mode = SEQ_PARSE_ANY_PERM;
428                                 else
429                                         parse_mode = SEQ_PARSE_NO_PERM;
430 #endif
431                                 goto set_modify_delete;
432
433                         set_modify_delete:
434                                 if (!posixly_correct)
435                                         parse_mode |= SEQ_PARSE_DEFAULT;
436                                 if (opt_promote)
437                                         parse_mode |= SEQ_PROMOTE_ACL;
438                                 if (parse_acl_seq(seq, optarg, &which,
439                                                   seq_cmd, parse_mode) != 0) {
440                                         if (which < 0 ||
441                                             (size_t) which >= strlen(optarg)) {
442                                                 fprintf(stderr, _(
443                                                         "%s: Option "
444                                                         "-%c incomplete\n"),
445                                                         progname, opt);
446                                         } else {
447                                                 fprintf(stderr, _(
448                                                         "%s: Option "
449                                                         "-%c: %s near "
450                                                         "character %d\n"),
451                                                         progname, opt,
452                                                         strerror(errno),
453                                                         which+1);
454                                         }
455                                         status = 2;
456                                         goto cleanup;
457                                 }
458                                 break;
459
460                         case 'S':  /* set from file */
461                                 if (seq_append_cmd(seq, CMD_REMOVE_ACL,
462                                                         ACL_TYPE_ACCESS))
463                                         ERRNO_ERROR(1);
464                                 seq_remove_acl_cmd = seq->s_last;
465                                 if (seq_append_cmd(seq, CMD_REMOVE_ACL,
466                                                         ACL_TYPE_DEFAULT))
467                                         ERRNO_ERROR(1);
468                                 seq_remove_default_acl_cmd = seq->s_last;
469
470                                 seq_cmd = CMD_ENTRY_REPLACE;
471                                 parse_mode = SEQ_PARSE_WITH_PERM;
472                                 goto set_modify_delete_from_file;
473
474                         case 'M':  /* modify from file */
475                                 seq_cmd = CMD_ENTRY_REPLACE;
476                                 parse_mode = SEQ_PARSE_WITH_PERM;
477                                 goto set_modify_delete_from_file;
478
479                         case 'X':  /* delete from file */
480                                 seq_cmd = CMD_REMOVE_ENTRY;
481 #if POSIXLY_CORRECT
482                                 parse_mode = SEQ_PARSE_ANY_PERM;
483 #else
484                                 if (posixly_correct)
485                                         parse_mode = SEQ_PARSE_ANY_PERM;
486                                 else
487                                         parse_mode = SEQ_PARSE_NO_PERM;
488 #endif
489                                 goto set_modify_delete_from_file;
490
491                         set_modify_delete_from_file:
492                                 if (!posixly_correct)
493                                         parse_mode |= SEQ_PARSE_DEFAULT;
494                                 if (opt_promote)
495                                         parse_mode |= SEQ_PROMOTE_ACL;
496                                 if (strcmp(optarg, "-") == 0) {
497                                         file = stdin;
498                                 } else {
499                                         file = fopen(optarg, "r");
500                                         if (file == NULL) {
501                                                 fprintf(stderr, "%s: %s: %s\n",
502                                                         progname,
503                                                         xquote(optarg, "\n\r"),
504                                                         strerror(errno));
505                                                 status = 2;
506                                                 goto cleanup;
507                                         }
508                                 }
509
510                                 lineno = 0;
511                                 error = read_acl_seq(file, seq, seq_cmd,
512                                                      parse_mode, &lineno, NULL);
513                                 
514                                 if (file != stdin) {
515                                         fclose(file);
516                                 }
517
518                                 if (error) {
519                                         if (!errno)
520                                                 errno = EINVAL;
521
522                                         if (file != stdin) {
523                                                 fprintf(stderr, _(
524                                                         "%s: %s in line "
525                                                         "%d of file %s\n"),
526                                                         progname,
527                                                         strerror(errno),
528                                                         lineno,
529                                                         xquote(optarg, "\n\r"));
530                                         } else {
531                                                 fprintf(stderr, _(
532                                                         "%s: %s in line "
533                                                         "%d of standard "
534                                                         "input\n"), progname,
535                                                         strerror(errno),
536                                                         lineno);
537                                         }
538                                         status = 2;
539                                         goto cleanup;
540                                 }
541                                 break;
542
543
544                         case '\1':  /* file argument */
545                                 if (seq_empty(seq))
546                                         goto synopsis;
547                                 saw_files = 1;
548
549                                 status = next_file(optarg, seq);
550                                 break;
551
552                         case 'B':  /* restore ACL backup */
553                                 saw_files = 1;
554
555                                 if (strcmp(optarg, "-") == 0)
556                                         file = stdin;
557                                 else {
558                                         file = fopen(optarg, "r");
559                                         if (file == NULL) {
560                                                 fprintf(stderr, "%s: %s: %s\n",
561                                                         progname,
562                                                         xquote(optarg, "\n\r"),
563                                                         strerror(errno));
564                                                 status = 2;
565                                                 goto cleanup;
566                                         }
567                                 }
568
569                                 status = restore(file,
570                                                (file == stdin) ? NULL : optarg);
571
572                                 if (file != stdin)
573                                         fclose(file);
574                                 if (status != 0)
575                                         goto cleanup;
576                                 break;
577
578                         case 'R':  /* recursive */
579                                 walk_flags |= WALK_TREE_RECURSIVE;
580                                 break;
581
582                         case 'L':  /* follow symlinks */
583                                 walk_flags |= WALK_TREE_LOGICAL;
584                                 walk_flags &= ~WALK_TREE_PHYSICAL;
585                                 break;
586
587                         case 'P':  /* do not follow symlinks */
588                                 walk_flags |= WALK_TREE_PHYSICAL;
589                                 walk_flags &= ~WALK_TREE_LOGICAL;
590                                 break;
591
592                         case 't':  /* test mode */
593                                 opt_test = 1;
594                                 break;
595
596                         case 'v':  /* print version and exit */
597                                 printf("%s " VERSION "\n", progname);
598                                 status = 0;
599                                 goto cleanup;
600
601                         case 'h':  /* help! */
602                                 help();
603                                 status = 0;
604                                 goto cleanup;
605
606                         case ':':  /* option missing */
607                         case '?':  /* unknown option */
608                         default:
609                                 goto synopsis;
610                 }
611                 if (seq_remove_acl_cmd) {
612                         /* This was a set operation. Check if there are
613                            actually entries of ACL_TYPE_ACCESS; if there
614                            are none, we need to remove this command! */
615                         if (!has_any_of_type(seq_remove_acl_cmd->c_next,
616                                             ACL_TYPE_ACCESS))
617                                 seq_delete_cmd(seq, seq_remove_acl_cmd);
618                 }
619                 if (seq_remove_default_acl_cmd) {
620                         /* This was a set operation. Check if there are
621                            actually entries of ACL_TYPE_DEFAULT; if there
622                            are none, we need to remove this command! */
623                         if (!has_any_of_type(seq_remove_default_acl_cmd->c_next,
624                                             ACL_TYPE_DEFAULT))
625                                 seq_delete_cmd(seq, seq_remove_default_acl_cmd);
626                 }
627         }
628         while (optind < argc) {
629                 if(!seq)
630                         goto synopsis;
631                 if (seq_empty(seq))
632                         goto synopsis;
633                 saw_files = 1;
634
635                 status = next_file(argv[optind++], seq);
636         }
637         if (!saw_files)
638                 goto synopsis;
639
640         goto cleanup;
641
642 synopsis:
643         fprintf(stderr, _("Usage: %s %s\n"),
644                 progname, cmd_line_spec);
645         fprintf(stderr, _("Try `%s --help' for more information.\n"),
646                 progname);
647         status = 2;
648         goto cleanup;
649
650 errno_error:
651         fprintf(stderr, "%s: %s\n", progname, strerror(errno));
652         goto cleanup;
653
654 cleanup:
655         if (seq)
656                 seq_free(seq);
657         return status;
658 }
659