File: [Development] / xfs-cmds / acl / setfacl / setfacl.c (download)
Revision 1.10, Tue Jul 29 01:55:43 2003 UTC (14 years, 2 months ago) by nathans
Branch: MAIN
Changes since 1.9: +27 -10
lines
ACL update from AG - libmisc routines, numerous test updates
|
/*
File: setfacl.c
(Linux Access Control List Management)
Copyright (C) 1999-2002
Andreas Gruenbacher, <a.gruenbacher@computer.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <dirent.h>
#include <libgen.h>
#include <ftw.h>
#include <getopt.h>
#include <locale.h>
#include "config.h"
#include "sequence.h"
#include "parse.h"
#include "misc.h"
extern int
do_set(
const char *path_p,
const struct stat *stat_p,
const seq_t seq);
#define POSIXLY_CORRECT_STR "POSIXLY_CORRECT"
/* '-' stands for `process non-option arguments in loop' */
#if !POSIXLY_CORRECT
# define CMD_LINE_OPTIONS "-:bkndm:M:x:X:RLP"
# define CMD_LINE_SPEC "[-bkndRLP] { -m|-M|-x|-X ... } file ..."
#endif
#define POSIXLY_CMD_LINE_OPTIONS "-:bkndm:M:x:X:"
#define POSIXLY_CMD_LINE_SPEC "[-bknd] {-m|-M|-x|-X ... } file ..."
struct option long_options[] = {
#if !POSIXLY_CORRECT
{ "set", 1, 0, 's' },
{ "set-file", 1, 0, 'S' },
{ "mask", 0, 0, 'r' },
{ "recursive", 0, 0, 'R' },
{ "logical", 0, 0, 'L' },
{ "physical", 0, 0, 'P' },
{ "restore", 1, 0, 'B' },
{ "test", 0, 0, 't' },
#endif
{ "modify", 1, 0, 'm' },
{ "modify-file", 1, 0, 'M' },
{ "remove", 1, 0, 'x' },
{ "remove-file", 1, 0, 'X' },
{ "default", 0, 0, 'd' },
{ "no-mask", 0, 0, 'n' },
{ "remove-all", 0, 0, 'b' },
{ "remove-default", 0, 0, 'k' },
{ "version", 0, 0, 'v' },
{ "help", 0, 0, 'h' },
{ NULL, 0, 0, 0 },
};
const char *progname;
const char *cmd_line_options, *cmd_line_spec;
int opt_recursive; /* recurse into sub-directories? */
int opt_walk_logical; /* always follow symbolic links */
int opt_walk_physical; /* never follow symbolic links */
int opt_recalculate; /* recalculate mask entry (0=default, 1=yes, -1=no) */
int opt_promote; /* promote access ACL to default ACL */
int opt_test; /* do not write to the file system.
Print what would happen instead. */
#if POSIXLY_CORRECT
const int posixly_correct = 1; /* Posix compatible behavior! */
#else
int posixly_correct; /* Posix compatible behavior? */
#endif
int chown_error;
int promote_warning;
static const char *xquote(const char *str)
{
const char *q = quote(str);
if (q == NULL) {
fprintf(stderr, "%s: %s\n", progname, strerror(errno));
exit(1);
}
return q;
}
int
has_any_of_type(
cmd_t cmd,
acl_type_t acl_type)
{
while (cmd) {
if (cmd->c_type == acl_type)
return 1;
cmd = cmd->c_next;
}
return 0;
}
#if !POSIXLY_CORRECT
int
restore(
FILE *file,
const char *filename)
{
char *path_p;
struct stat stat;
uid_t uid;
gid_t gid;
seq_t seq = NULL;
int line = 0, backup_line;
int error, status = 0;
memset(&stat, 0, sizeof(stat));
for(;;) {
backup_line = line;
error = read_acl_comments(file, &line, &path_p, &uid, &gid);
if (error < 0)
goto fail;
if (error == 0)
return 0;
if (path_p == NULL) {
if (filename) {
fprintf(stderr, _("%s: %s: No filename found "
"in line %d, aborting\n"),
progname, xquote(filename),
backup_line);
} else {
fprintf(stderr, _("%s: No filename found in "
"line %d of standard input, "
"aborting\n"),
progname, backup_line);
}
goto getout;
}
if (!(seq = seq_init()))
goto fail;
if (seq_append_cmd(seq, CMD_REMOVE_ACL, ACL_TYPE_ACCESS) ||
seq_append_cmd(seq, CMD_REMOVE_ACL, ACL_TYPE_DEFAULT))
goto fail;
error = read_acl_seq(file, seq, CMD_ENTRY_REPLACE,
SEQ_PARSE_WITH_PERM |
SEQ_PARSE_NO_RELATIVE |
SEQ_PARSE_DEFAULT |
SEQ_PARSE_MULTI,
&line, NULL);
if (error != 0) {
fprintf(stderr, _("%s: %s: %s in line %d\n"),
progname, xquote(filename), strerror(errno),
line);
goto getout;
}
error = lstat(path_p, &stat);
if (opt_test && error != 0) {
fprintf(stderr, "%s: %s: %s\n", progname,
xquote(path_p), strerror(errno));
status = 1;
}
stat.st_uid = uid;
stat.st_gid = gid;
error = do_set(path_p, &stat, seq);
if (error != 0) {
status = 1;
goto resume;
}
if (!opt_test &&
(uid != ACL_UNDEFINED_ID || gid != ACL_UNDEFINED_ID)) {
if (chown(path_p, uid, gid) != 0) {
fprintf(stderr, _("%s: %s: Cannot change "
"owner/group: %s\n"),
progname, xquote(path_p),
strerror(errno));
status = 1;
}
}
resume:
if (path_p) {
free(path_p);
path_p = NULL;
}
if (seq) {
seq_free(seq);
seq = NULL;
}
}
getout:
if (path_p) {
free(path_p);
path_p = NULL;
}
if (seq) {
seq_free(seq);
seq = NULL;
}
return status;
fail:
fprintf(stderr, "%s: %s: %s\n", progname, xquote(filename),
strerror(errno));
status = 1;
goto getout;
}
#endif
void help(void)
{
printf(_("%s %s -- set file access control lists\n"),
progname, VERSION);
printf(_("Usage: %s %s\n"),
progname, cmd_line_spec);
printf(_(
" -m, --modify=acl modify the current ACL(s) of file(s)\n"
" -M, --modify-file=file read ACL entries to modify from file\n"
" -x, --remove=acl remove entries from the ACL(s) of file(s)\n"
" -X, --remove-file=file read ACL entries to remove from file\n"
" -b, --remove-all remove all extended ACL entries\n"
" -k, --remove-default remove the default ACL\n"));
#if !POSIXLY_CORRECT
if (!posixly_correct) {
printf(_(
" --set=acl set the ACL of file(s), replacing the current ACL\n"
" --set-file=file read ACL entries to set from file\n"
" --mask do recalculate the effective rights mask\n"));
}
#endif
printf(_(
" -n, --no-mask don't recalculate the effective rights mask\n"
" -d, --default operations apply to the default ACL\n"));
#if !POSIXLY_CORRECT
if (!posixly_correct) {
printf(_(
" -R, --recursive recurse into subdirectories\n"
" -L, --logical logical walk, follow symbolic links\n"
" -P, --physical physical walk, do not follow symbolic links\n"
" --restore=file restore ACLs (inverse of `getfacl -R')\n"
" --test test mode (ACLs are not modified)\n"));
}
#endif
printf(_(
" --version print version and exit\n"
" --help this help text\n"));
}
char *next_line(FILE *file)
{
static char line[_POSIX_PATH_MAX], *c;
if (!fgets(line, sizeof(line), file))
return NULL;
c = strrchr(line, '\0');
while (c > line && (*(c-1) == '\n' ||
*(c-1) == '\r')) {
c--;
*c = '\0';
}
return line;
}
static int __errors;
static seq_t __seq;
int __do_set(const char *file, const struct stat *stat,
int flag, struct FTW *ftw)
{
if (flag & FTW_DNR) {
/* Item is a directory which can't be read. */
fprintf(stderr, "%s: %s: %s\n",
progname, file, strerror(errno));
return 0;
}
/* Process the target of a symbolic link, and traverse the link,
only if doing a logical walk, or if the symbolic link was
specified on the command line. Always skip symbolic links if
doing a physical walk. */
if (S_ISLNK(stat->st_mode) &&
(opt_walk_physical || (ftw->level > 0 && !opt_walk_logical)))
return 0;
if (do_set(file, stat, __seq))
__errors++;
/* We also get here in non-recursive mode. In that case,
return something != 0 to abort nftw. */
if (!opt_recursive)
return 1;
return 0;
}
int walk_tree(const char *file, seq_t seq)
{
__errors = 0;
__seq = seq;
if (nftw(file, __do_set, 0, opt_walk_physical * FTW_PHYS) < 0) {
fprintf(stderr, "%s: %s: %s\n", progname,
xquote(file), strerror(errno));
__errors++;
}
return __errors;
}
int next_file(const char *arg, seq_t seq)
{
char *line;
int errors = 0;
if (strcmp(arg, "-") == 0) {
while ((line = next_line(stdin)))
errors = walk_tree(line, seq);
} else {
errors = walk_tree(arg, seq);
}
return errors ? 1 : 0;
}
#define ERRNO_ERROR(s) \
({status = (s); goto errno_error; })
int main(int argc, char *argv[])
{
int opt;
int saw_files = 0;
int status = 0;
FILE *file;
int which;
int lineno;
int error;
seq_t seq = NULL;
int seq_cmd, parse_mode;
progname = basename(argv[0]);
#if POSIXLY_CORRECT
cmd_line_options = POSIXLY_CMD_LINE_OPTIONS;
cmd_line_spec = _(POSIXLY_CMD_LINE_SPEC);
#else
if (getenv(POSIXLY_CORRECT_STR))
posixly_correct = 1;
if (!posixly_correct) {
cmd_line_options = CMD_LINE_OPTIONS;
cmd_line_spec = _(CMD_LINE_SPEC);
} else {
cmd_line_options = POSIXLY_CMD_LINE_OPTIONS;
cmd_line_spec = _(POSIXLY_CMD_LINE_SPEC);
}
#endif
setlocale(LC_CTYPE, "");
setlocale(LC_MESSAGES, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
while ((opt = getopt_long(argc, argv, cmd_line_options,
long_options, NULL)) != -1) {
/* we remember the two REMOVE_ACL commands of the set
operations because we may later need to delete them. */
cmd_t seq_remove_default_acl_cmd = NULL;
cmd_t seq_remove_acl_cmd = NULL;
if (opt != '\1' && saw_files) {
if (seq) {
seq_free(seq);
seq = NULL;
}
saw_files = 0;
}
if (seq == NULL) {
if (!(seq = seq_init()))
ERRNO_ERROR(1);
}
switch (opt) {
case 'b': /* remove all extended entries */
if (seq_append_cmd(seq, CMD_REMOVE_EXTENDED_ACL,
ACL_TYPE_ACCESS) ||
seq_append_cmd(seq, CMD_REMOVE_ACL,
ACL_TYPE_DEFAULT))
ERRNO_ERROR(1);
break;
case 'k': /* remove default ACL */
if (seq_append_cmd(seq, CMD_REMOVE_ACL,
ACL_TYPE_DEFAULT))
ERRNO_ERROR(1);
break;
case 'n': /* do not recalculate mask */
opt_recalculate = -1;
break;
case 'r': /* force recalculate mask */
opt_recalculate = 1;
break;
case 'd': /* operations apply to default ACL */
opt_promote = 1;
break;
case 's': /* set */
if (seq_append_cmd(seq, CMD_REMOVE_ACL,
ACL_TYPE_ACCESS))
ERRNO_ERROR(1);
seq_remove_acl_cmd = seq->s_last;
if (seq_append_cmd(seq, CMD_REMOVE_ACL,
ACL_TYPE_DEFAULT))
ERRNO_ERROR(1);
seq_remove_default_acl_cmd = seq->s_last;
seq_cmd = CMD_ENTRY_REPLACE;
parse_mode = SEQ_PARSE_WITH_PERM |
SEQ_PARSE_NO_RELATIVE;
goto set_modify_delete;
case 'm': /* modify */
seq_cmd = CMD_ENTRY_REPLACE;
parse_mode = SEQ_PARSE_WITH_PERM;
#if POSIXLY_CORRECT || 1
parse_mode |= SEQ_PARSE_NO_RELATIVE;
#else
if (posixly_correct)
parse_mode |= SEQ_PARSE_NO_RELATIVE;
else
parse_mode |= SEQ_PARSE_ANY_RELATIVE;
#endif
goto set_modify_delete;
case 'x': /* delete */
seq_cmd = CMD_REMOVE_ENTRY;
parse_mode = SEQ_PARSE_NO_RELATIVE;
#if POSIXLY_CORRECT
parse_mode |= SEQ_PARSE_ANY_PERM;
#else
if (posixly_correct)
parse_mode |= SEQ_PARSE_ANY_PERM;
else
parse_mode |= SEQ_PARSE_NO_PERM;
#endif
goto set_modify_delete;
set_modify_delete:
if (!posixly_correct)
parse_mode |= SEQ_PARSE_DEFAULT;
if (opt_promote)
parse_mode |= SEQ_PROMOTE_ACL;
if (parse_acl_seq(seq, optarg, &which,
seq_cmd, parse_mode) != 0) {
if (which < 0 ||
(size_t) which >= strlen(optarg)) {
fprintf(stderr, _(
"%s: Option "
"-%c incomplete\n"),
progname, opt);
} else {
fprintf(stderr, _(
"%s: Option "
"-%c: %s near "
"character %d\n"),
progname, opt,
strerror(errno),
which+1);
}
status = 2;
goto cleanup;
}
break;
case 'S': /* set from file */
if (seq_append_cmd(seq, CMD_REMOVE_ACL,
ACL_TYPE_ACCESS))
ERRNO_ERROR(1);
seq_remove_acl_cmd = seq->s_last;
if (seq_append_cmd(seq, CMD_REMOVE_ACL,
ACL_TYPE_DEFAULT))
ERRNO_ERROR(1);
seq_remove_default_acl_cmd = seq->s_last;
seq_cmd = CMD_ENTRY_REPLACE;
parse_mode = SEQ_PARSE_WITH_PERM |
SEQ_PARSE_NO_RELATIVE;
goto set_modify_delete_from_file;
case 'M': /* modify from file */
seq_cmd = CMD_ENTRY_REPLACE;
parse_mode = SEQ_PARSE_WITH_PERM;
#if POSIXLY_CORRECT || 1
parse_mode |= SEQ_PARSE_NO_RELATIVE;
#else
if (posixly_correct)
parse_mode |= SEQ_PARSE_NO_RELATIVE;
else
parse_mode |= SEQ_PARSE_ANY_RELATIVE;
#endif
goto set_modify_delete_from_file;
case 'X': /* delete from file */
seq_cmd = CMD_REMOVE_ENTRY;
parse_mode = SEQ_PARSE_NO_RELATIVE;
#if POSIXLY_CORRECT
parse_mode |= SEQ_PARSE_ANY_PERM;
#else
if (posixly_correct)
parse_mode |= SEQ_PARSE_ANY_PERM;
else
parse_mode |= SEQ_PARSE_NO_PERM;
#endif
goto set_modify_delete_from_file;
set_modify_delete_from_file:
if (!posixly_correct)
parse_mode |= SEQ_PARSE_DEFAULT;
if (opt_promote)
parse_mode |= SEQ_PROMOTE_ACL;
if (strcmp(optarg, "-") == 0) {
file = stdin;
} else {
file = fopen(optarg, "r");
if (file == NULL) {
fprintf(stderr, "%s: %s: %s\n",
progname,
xquote(optarg),
strerror(errno));
status = 2;
goto cleanup;
}
}
lineno = 0;
error = read_acl_seq(file, seq, seq_cmd,
parse_mode, &lineno, NULL);
if (file != stdin) {
fclose(file);
}
if (error) {
if (!errno)
errno = EINVAL;
if (file != stdin) {
fprintf(stderr, _(
"%s: %s in line "
"%d of file %s\n"),
progname,
strerror(errno),
lineno,
xquote(optarg));
} else {
fprintf(stderr, _(
"%s: %s in line "
"%d of standard "
"input\n"), progname,
strerror(errno),
lineno);
}
status = 2;
goto cleanup;
}
break;
case '\1': /* file argument */
if (seq_empty(seq))
goto synopsis;
saw_files = 1;
status = next_file(optarg, seq);
break;
case 'B': /* restore ACL backup */
saw_files = 1;
if (strcmp(optarg, "-") == 0)
file = stdin;
else {
file = fopen(optarg, "r");
if (file == NULL) {
fprintf(stderr, "%s: %s: %s\n",
progname,
xquote(optarg),
strerror(errno));
status = 2;
goto cleanup;
}
}
status = restore(file,
(file == stdin) ? NULL : optarg);
if (file != stdin)
fclose(file);
if (status != 0)
goto cleanup;
break;
case 'R': /* recursive */
opt_recursive = 1;
break;
case 'L': /* follow symlinks */
opt_walk_logical = 1;
opt_walk_physical = 0;
break;
case 'P': /* do not follow symlinks */
opt_walk_logical = 0;
opt_walk_physical = 1;
break;
case 't': /* test mode */
opt_test = 1;
break;
case 'v': /* print version and exit */
printf("%s " VERSION "\n", progname);
status = 0;
goto cleanup;
case 'h': /* help! */
help();
status = 0;
goto cleanup;
case ':': /* option missing */
case '?': /* unknown option */
default:
goto synopsis;
}
if (seq_remove_acl_cmd) {
/* This was a set operation. Check if there are
actually entries of ACL_TYPE_ACCESS; if there
are none, we need to remove this command! */
if (!has_any_of_type(seq_remove_acl_cmd->c_next,
ACL_TYPE_ACCESS))
seq_delete_cmd(seq, seq_remove_acl_cmd);
}
if (seq_remove_default_acl_cmd) {
/* This was a set operation. Check if there are
actually entries of ACL_TYPE_DEFAULT; if there
are none, we need to remove this command! */
if (!has_any_of_type(seq_remove_default_acl_cmd->c_next,
ACL_TYPE_DEFAULT))
seq_delete_cmd(seq, seq_remove_default_acl_cmd);
}
}
while (optind < argc) {
if (seq_empty(seq))
goto synopsis;
saw_files = 1;
status = next_file(argv[optind++], seq);
}
if (!saw_files)
goto synopsis;
goto cleanup;
synopsis:
fprintf(stderr, _("Usage: %s %s\n"),
progname, cmd_line_spec);
fprintf(stderr, _("Try `%s --help' for more information.\n"),
progname);
status = 2;
goto cleanup;
errno_error:
fprintf(stderr, "%s: %s\n", progname, strerror(errno));
goto cleanup;
cleanup:
if (seq)
seq_free(seq);
return status;
}