+/* vi: set sw=4 ts=4: */
/*
* Mini chmod implementation for busybox
*
- * Copyright (C) 1998 by Erik Andersen <andersee@debian.org>
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU 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
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * Reworked by (C) 2002 Vladimir Oleynik <dzo@simtreas.ru>
+ * to correctly parse '-rwxgoa'
*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+//config:config CHMOD
+//config: bool "chmod (5.1 kb)"
+//config: default y
+//config: help
+//config: chmod is used to change the access permission of files.
+
+//applet:IF_CHMOD(APPLET_NOEXEC(chmod, chmod, BB_DIR_BIN, BB_SUID_DROP, chmod))
+
+//kbuild:lib-$(CONFIG_CHMOD) += chmod.o
+
+/* BB_AUDIT SUSv3 compliant */
+/* BB_AUDIT GNU defects - unsupported long options. */
+/* http://www.opengroup.org/onlinepubs/007904975/utilities/chmod.html */
+
+//usage:#define chmod_trivial_usage
+//usage: "[-R"IF_DESKTOP("cvf")"] MODE[,MODE]... FILE..."
+//usage:#define chmod_full_usage "\n\n"
+//usage: "Each MODE is one or more of the letters ugoa, one of the\n"
+//usage: "symbols +-= and one or more of the letters rwxst\n"
+//usage: "\n -R Recurse"
+//usage: IF_DESKTOP(
+//usage: "\n -c List changed files"
+//usage: "\n -v List all files"
+//usage: "\n -f Hide errors"
+//usage: )
+//usage:
+//usage:#define chmod_example_usage
+//usage: "$ ls -l /tmp/foo\n"
+//usage: "-rw-rw-r-- 1 root root 0 Apr 12 18:25 /tmp/foo\n"
+//usage: "$ chmod u+x /tmp/foo\n"
+//usage: "$ ls -l /tmp/foo\n"
+//usage: "-rwxrw-r-- 1 root root 0 Apr 12 18:25 /tmp/foo*\n"
+//usage: "$ chmod 444 /tmp/foo\n"
+//usage: "$ ls -l /tmp/foo\n"
+//usage: "-r--r--r-- 1 root root 0 Apr 12 18:25 /tmp/foo\n"
+
+#include "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
+
+#define OPT_RECURSE (option_mask32 & 1)
+#define OPT_VERBOSE (IF_DESKTOP(option_mask32 & 2) IF_NOT_DESKTOP(0))
+#define OPT_CHANGED (IF_DESKTOP(option_mask32 & 4) IF_NOT_DESKTOP(0))
+#define OPT_QUIET (IF_DESKTOP(option_mask32 & 8) IF_NOT_DESKTOP(0))
+#define OPT_STR "R" IF_DESKTOP("vcf")
+
+/* coreutils:
+ * chmod never changes the permissions of symbolic links; the chmod
+ * system call cannot change their permissions. This is not a problem
+ * since the permissions of symbolic links are never used.
+ * However, for each symbolic link listed on the command line, chmod changes
+ * the permissions of the pointed-to file. In contrast, chmod ignores
+ * symbolic links encountered during recursive directory traversals.
*/
-#include <stdio.h>
-#include <grp.h>
-#include <pwd.h>
-#include "internal.h"
-
-
-static mode_t mode=7777;
-
-
-static const char chmod_usage[] = "[-R] MODE[,MODE]... FILE...\n"
-"Each MODE is one or more of the letters ugoa, one of the symbols +-= and\n"
-"one or more of the letters rwxst.\n\n"
- "\t-R\tchange files and directories recursively.\n";
-
+static int FAST_FUNC fileAction(const char *fileName, struct stat *statbuf, void* param, int depth)
+{
+ mode_t newmode;
+
+ /* match coreutils behavior */
+ if (depth == 0) {
+ /* statbuf holds lstat result, but we need stat (follow link) */
+ if (stat(fileName, statbuf))
+ goto err;
+ } else { /* depth > 0: skip links */
+ if (S_ISLNK(statbuf->st_mode))
+ return TRUE;
+ }
+ newmode = bb_parse_mode((char *)param, statbuf->st_mode);
+ if (newmode == (mode_t)-1)
+ bb_error_msg_and_die("invalid mode '%s'", (char *)param);
-static int fileAction(const char *fileName)
-{
- struct stat statBuf;
- if ((stat(fileName, &statBuf) < 0) ||
- (chmod(fileName, mode)) < 0) {
- perror(fileName);
- return( FALSE);
- }
- return( TRUE);
+ if (chmod(fileName, newmode) == 0) {
+ if (OPT_VERBOSE
+ || (OPT_CHANGED && statbuf->st_mode != newmode)
+ ) {
+ printf("mode of '%s' changed to %04o (%s)\n", fileName,
+ newmode & 07777, bb_mode_string(newmode)+1);
+ }
+ return TRUE;
+ }
+ err:
+ if (!OPT_QUIET)
+ bb_simple_perror_msg(fileName);
+ return FALSE;
}
-/* [ugoa]{+|-|=}[rwxstl] */
-int parse_mode( const char* s, mode_t* or, mode_t* and, int* group_execute)
+int chmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chmod_main(int argc UNUSED_PARAM, char **argv)
{
- mode_t mode = 0;
- mode_t groups = S_ISVTX;
- char type;
- char c;
-
- do {
- for ( ; ; ) {
- switch ( c = *s++ ) {
- case '\0':
- return (FALSE);
- case 'u':
- groups |= S_ISUID|S_IRWXU;
- continue;
- case 'g':
- groups |= S_ISGID|S_IRWXG;
- continue;
- case 'o':
- groups |= S_IRWXO;
- continue;
- case 'a':
- groups |= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
- continue;
- case '+':
- case '=':
- case '-':
- type = c;
- if ( groups == S_ISVTX ) /* The default is "all" */
- groups |= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
- break;
- default:
- if ( c >= '0' && c <= '7' && mode == 0 && groups == S_ISVTX ) {
- *and = 0;
- *or = strtol(--s, 0, 010);
- return (TRUE);
- }
- else
- return (FALSE);
- }
+ int retval = EXIT_SUCCESS;
+ char *arg, **argp;
+ char *smode;
+
+ /* Convert first encountered -r into ar, -w into aw etc
+ * so that getopt would not eat it */
+ argp = argv;
+ while ((arg = *++argp)) {
+ /* Mode spec must be the first arg (sans -R etc) */
+ /* (protect against mishandling e.g. "chmod 644 -r") */
+ if (arg[0] != '-') {
+ arg = NULL;
break;
}
-
- while ( (c = *s++) != '\0' ) {
- switch ( c ) {
- case ',':
- break;
- case 'r':
- mode |= S_IRUSR|S_IRGRP|S_IROTH;
- continue;
- case 'w':
- mode |= S_IWUSR|S_IWGRP|S_IWOTH;
- continue;
- case 'x':
- mode |= S_IXUSR|S_IXGRP|S_IXOTH;
- continue;
- case 's':
- if ( group_execute != 0 && (groups & S_IRWXG) ) {
- if ( *group_execute < 0 )
- return (FALSE);
- if ( type != '-' ) {
- mode |= S_IXGRP;
- *group_execute = 1;
- }
- }
- mode |= S_ISUID|S_ISGID;
- continue;
- case 'l':
- if ( *group_execute > 0 )
- return (FALSE);
- if ( type != '-' ) {
- *and &= ~S_IXGRP;
- *group_execute = -1;
- }
- mode |= S_ISGID;
- groups |= S_ISGID;
- continue;
- case 't':
- mode |= S_ISVTX;
- continue;
- default:
- return (FALSE);
- }
+ /* An option. Not a -- or valid option? */
+ if (arg[1] && !strchr("-"OPT_STR, arg[1])) {
+ arg[0] = 'a';
break;
}
- switch ( type ) {
- case '=':
- *and &= ~(groups);
- /* fall through */
- case '+':
- *or |= mode & groups;
- break;
- case '-':
- *and &= ~(mode & groups);
- *or &= *and;
- break;
+ }
+
+ /* Parse options */
+ getopt32(argv, "^" OPT_STR "\0" "-2");
+ argv += optind;
+
+ /* Restore option-like mode if needed */
+ if (arg) arg[0] = '-';
+
+ /* Ok, ready to do the deed now */
+ smode = *argv++;
+ do {
+ if (!recursive_action(*argv,
+ OPT_RECURSE, // recurse
+ fileAction, // file action
+ fileAction, // dir action
+ smode, // user data
+ 0) // depth
+ ) {
+ retval = EXIT_FAILURE;
}
- } while ( c == ',' );
- return (TRUE);
-}
+ } while (*++argv);
+ return retval;
+}
-int chmod_main(int argc, char **argv)
-{
- int recursiveFlag=FALSE;
- mode_t andWithMode = S_ISVTX|S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
- mode_t orWithMode = 0;
- char *invocationName=*argv;
-
- if (argc < 3) {
- fprintf(stderr, "Usage: %s %s", invocationName, chmod_usage);
- exit( FALSE);
- }
- argc--;
- argv++;
-
- /* Parse options */
- while (**argv == '-') {
- while (*++(*argv)) switch (**argv) {
- case 'R':
- recursiveFlag = TRUE;
- break;
- default:
- fprintf(stderr, "Unknown option: %c\n", **argv);
- exit( FALSE);
- }
- argc--;
- argv++;
- }
-
- /* Find the selected group */
- if ( parse_mode(*argv, &orWithMode, &andWithMode, 0) == FALSE ) {
- fprintf(stderr, "%s: Unknown mode: %s\n", invocationName, *argv);
- exit( FALSE);
- }
- mode &= andWithMode;
- mode |= orWithMode;
-
- /* Ok, ready to do the deed now */
- if (argc <= 1) {
- fprintf(stderr, "%s: too few arguments", invocationName);
- exit( FALSE);
- }
- while (argc-- > 1) {
- argv++;
- recursiveAction( *argv, recursiveFlag, TRUE, fileAction, fileAction);
- }
- exit(TRUE);
+/*
+Security: chmod is too important and too subtle.
+This is a test script (busybox chmod versus coreutils).
+Run it in empty directory.
+
+#!/bin/sh
+t1="/tmp/busybox chmod"
+t2="/usr/bin/chmod"
+create() {
+ rm -rf $1; mkdir $1
+ (
+ cd $1 || exit 1
+ mkdir dir
+ >up
+ >file
+ >dir/file
+ ln -s dir linkdir
+ ln -s file linkfile
+ ln -s ../up dir/up
+ )
+}
+tst() {
+ (cd test1; $t1 $1)
+ (cd test2; $t2 $1)
+ (cd test1; ls -lR) >out1
+ (cd test2; ls -lR) >out2
+ echo "chmod $1" >out.diff
+ if ! diff -u out1 out2 >>out.diff; then exit 1; fi
+ rm out.diff
}
+echo "If script produced 'out.diff' file, then at least one testcase failed"
+create test1; create test2
+tst "a+w file"
+tst "a-w dir"
+tst "a+w linkfile"
+tst "a-w linkdir"
+tst "-R a+w file"
+tst "-R a-w dir"
+tst "-R a+w linkfile"
+tst "-R a-w linkdir"
+tst "a-r,a+x linkfile"
+*/