lineedit: do not hang on error, but return error indicator.
[oweals/busybox.git] / coreutils / chmod.c
index c4f8fa0b257c8228d3857e62011ef2e3c1e58598..f07a49bd3b0922efae9865d98fa875aefcb8123e 100644 (file)
@@ -7,22 +7,25 @@
  * Reworked by (C) 2002 Vladimir Oleynik <dzo@simtreas.ru>
  *  to correctly parse '-rwxgoa'
  *
- * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
 
 /* BB_AUDIT SUSv3 compliant */
 /* BB_AUDIT GNU defects - unsupported long options. */
 /* http://www.opengroup.org/onlinepubs/007904975/utilities/chmod.html */
 
-#include "busybox.h"
+#include "libbb.h"
+
+/* This is a NOEXEC applet. Be very careful! */
+
 
 #define OPT_RECURSE (option_mask32 & 1)
-#define OPT_VERBOSE (USE_DESKTOP(option_mask32 & 2) SKIP_DESKTOP(0))
-#define OPT_CHANGED (USE_DESKTOP(option_mask32 & 4) SKIP_DESKTOP(0))
-#define OPT_QUIET   (USE_DESKTOP(option_mask32 & 8) SKIP_DESKTOP(0))
-#define OPT_STR     ("-R" USE_DESKTOP("vcf"))
+#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")
 
-/* TODO:
+/* 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.
  * symbolic links encountered during recursive directory traversals.
  */
 
-static int fileAction(const char *fileName, struct stat *statbuf, void* junk)
+static int FAST_FUNC fileAction(const char *fileName, struct stat *statbuf, void* param, int depth)
 {
-       mode_t newmode = statbuf->st_mode;
+       mode_t newmode;
 
-       // TODO: match GNU behavior:
-       // if (depth > 0 && S_ISLNK(statbuf->st_mode)) return TRUE;
-       // if (depth == 0) follow link
+       /* 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 = statbuf->st_mode;
 
-       if (!bb_parse_mode((char *)junk, &newmode))
-               bb_error_msg_and_die("invalid mode: %s", (char *)junk);
+       if (!bb_parse_mode((char *)param, &newmode))
+               bb_error_msg_and_die("invalid mode '%s'", (char *)param);
 
-       if (chmod(fileName, statbuf->st_mode) == 0) {
-               if (OPT_VERBOSE /* -v verbose? or -c changed? */
+       if (chmod(fileName, newmode) == 0) {
+               if (OPT_VERBOSE
                 || (OPT_CHANGED && statbuf->st_mode != newmode)
                ) {
                        printf("mode of '%s' changed to %04o (%s)\n", fileName,
@@ -51,49 +61,53 @@ static int fileAction(const char *fileName, struct stat *statbuf, void* junk)
                }
                return TRUE;
        }
-       if (!OPT_QUIET) /* not silent (-f)? */
-               bb_perror_msg("%s", fileName);
+ err:
+       if (!OPT_QUIET)
+               bb_simple_perror_msg(fileName);
        return FALSE;
 }
 
-int chmod_main(int argc, char **argv)
+int chmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int chmod_main(int argc UNUSED_PARAM, char **argv)
 {
        int retval = EXIT_SUCCESS;
        char *arg, **argp;
        char *smode;
 
-       /* Convert first encountered -r into a-r, -w into a-w etc */
-       argp = argv + 1;
-       while ((arg = *argp)) {
+       /* 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] != '-')
+               if (arg[0] != '-') {
+                       arg = NULL;
                        break;
+               }
                /* An option. Not a -- or valid option? */
-               if (arg[1] && !strchr(OPT_STR, arg[1])) {
-                       argp[0] = xasprintf("a%s", arg);
+               if (arg[1] && !strchr("-"OPT_STR, arg[1])) {
+                       arg[0] = 'a';
                        break;
                }
-               argp++;
        }
-       /* "chmod -rzzz abc" will say "invalid mode: a-rzzz"!
-        * It is easily fixable, but deemed not worth the code */
 
+       /* Parse options */
        opt_complementary = "-2";
-       getopt32(argc, argv, OPT_STR + 1); /* Reuse string */
+       getopt32(argv, ("-"OPT_STR) + 1); /* Reuse string */
        argv += optind;
 
-       smode = *argv++;
+       /* 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
-                               FALSE,          // follow links: GNU doesn't
-                               FALSE,          // depth first
-                               fileAction,     // file action
-                               fileAction,     // dir action
-                               smode)          // user data
+                       OPT_RECURSE,    // recurse
+                       fileAction,     // file action
+                       fileAction,     // dir action
+                       smode,          // user data
+                       0)              // depth
                ) {
                        retval = EXIT_FAILURE;
                }
@@ -101,3 +115,46 @@ int chmod_main(int argc, char **argv)
 
        return retval;
 }
+
+/*
+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"
+*/