libbb: remove glibc-style bloat from sha_crypt. -1130 bytes.
[oweals/busybox.git] / coreutils / chmod.c
index 9f84d0d37e290b14af7e3988e58b5d7e35f98e5a..40f681fb6ab0797da76d044e3148ef150924ee1c 100644 (file)
+/* 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 tarball for details.
  */
 
-#include <stdio.h>
-#include <grp.h>
-#include <pwd.h>
-#include "internal.h"
+/* BB_AUDIT SUSv3 compliant */
+/* BB_AUDIT GNU defects - unsupported long options. */
+/* http://www.opengroup.org/onlinepubs/007904975/utilities/chmod.html */
 
+#include "libbb.h"
 
-static mode_t mode=7777;
+/* This is a NOEXEC applet. Be very careful! */
 
 
-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";
-
+#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")
 
+/* 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.
+ */
 
-static int fileAction(const char *fileName)
+static int FAST_FUNC fileAction(const char *fileName, struct stat *statbuf, void* param, int depth)
 {
-    struct stat statBuf;
-    if ((stat(fileName, &statBuf) < 0) || 
-           (chmod(fileName, mode)) < 0) { 
-       perror(fileName);
-       return( FALSE);
-    }
-    return( TRUE);
-}
+       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 = statbuf->st_mode;
 
-/* [ugoa]{+|-|=}[rwxstl] */
-int parse_mode( const char* s, mode_t* or, mode_t* and, int* group_execute) 
-{
-       mode_t  mode = 0;
-       mode_t  groups = S_ISVTX;
-       char    type;
-       char    c;
+       if (!bb_parse_mode((char *)param, &newmode))
+               bb_error_msg_and_die("invalid mode: %s", (char *)param);
 
-       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);
-                       }
-                       break;
+       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;
+}
 
-               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);
-                       }
+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 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;
                }
-               switch ( type ) {
-               case '=':
-                       *and &= ~(groups);
-                       /* fall through */
-               case '+':
-                       *or |= mode & groups;
-                       break;
-               case '-':
-                       *and &= ~(mode & groups);
-                       *or &= *and;
+               /* An option. Not a -- or valid option? */
+               if (arg[1] && !strchr("-"OPT_STR, arg[1])) {
+                       arg[0] = 'a';
                        break;
                }
-       } while ( c == ',' );
-       return (TRUE);
-}
+       }
 
+       /* Parse options */
+       opt_complementary = "-2";
+       getopt32(argv, ("-"OPT_STR) + 1); /* Reuse string */
+       argv += optind;
 
-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);
+       /* 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 (*++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"
+*/