jaime@killer.UUCP (10/03/87)
I have written a chmod for Minix that interprets the symbolic parameters supported by UNIX chmod and used extensively in shar files. The source and a Minix-style man page are included at the end of this posting. I tried to implement chmod as described in the 7th Edition Unix Programmers Manual, Volume 1, published by Holt, Rinehart and Wilson in 1983. (You know; that big blue book with the children's block letters spelling out UNIX on the front) Since I don't have access to an actual V7 Unix implementation, I don't know if I've duplicated all the quirks. I was able to test my chmod on a 3B2 running System V, and a Sun running Sun OS 4.2 Release 3.2. It works fine as far as I can tell. Two differences: 1. System V chmod ignores the current umask in all cases. Version 7 (and Berkeley) look at the umask if you leave off a <who> specification. For example: 'chmod +w file' will have no effect if the umask is 0222. I followed the V7 convention. 2. I took the manual literally when it said, concerning <who>, that "The letter 'a' stands for 'ugo'. If <who> is omitted, the default is 'a' ...". Therefore, all the following should be equivalent (except for the umask business): ugo+s a+s +s My chmod treats all the above equally, setting the SETUID and SETGID bits in each case. Both the Berkeley and System V chmods took 'ugo+s' but ignored 'a+s' and '+s'. Since this inconsistent quirk (bug?) (feature?) exists in both 4BSD and SYSV, I imagine it comes from V7 chmod. Anyone with a real V7 care to see if 'chmod +s file' does anything? - James da Silva ------------------------------- chmod.man ------------------------------ Command: chmod - change access mode for files Syntax: chmod octal-number files chmod [augo][+-=][rwxst] files Flags: (none) Examples: chmod 755 file # Owner: rwx Group: r-x Others: r-x chmod +x file1 file2 # Make file1 and file2 executable chmod a-w file # Make file read only chmod u+s file # Turn on SETUID for file chmod +t file # sticky - file stays in swap space (haha) chmod g=u # group perms set to same as user perms. The given mode is applied to each file in the file list. The mode can be either absolute or symbolic. Absolute modes are given as an octal number that represents the new file mode. The mode bits are defined as follows: 4000 Set effective user id on execution to file's owner id 2000 Set effective group id on execution to file's group id 0400 file is readable by the owner of the file 0200 writeable by owner 0100 executable by owner 0070 same as above, for other users in the same group 0007 same as above, for all other users Symbolic modes modify the current file mode in a specified way. They take the form: [who] op permissions { op permissions } The possibilities for [who] are 'u', 'g', 'o', and 'a'; standing for user, group, other and all, respectively. If who is omitted, 'a' is assumed, but the current umask is used. The op can be '+', '-', or '='; '+' turns on the given permissions, '-' turns them off; '=' sets the permissions exclusively for the given who. For example 'g=x' sets the group permissions to '--x'. The possible permissions are 'r', 'w', 'x'; which stand for read, write, and execute permission; 's' turns on the set effective user/group id bits. 'u', 'g' and 'o' in the permissions field stand for the current user, group, or other permission bits, respectively. Only one of these may be used at a time. The 't' permission is included for compatibility with 7th Edition Unix. It normally sets the sticky bit, to keep a file in the swap space after execution, but it has no effect in Minix. 's' only makes sense with 'u' and 'g'; 'o+s' is harmless and does nothing. Multiple symbolic modes may be specified, separated by commas. -------------------------------- chmod.c ------------------------------- /* * chmod.c Author: James da Silva * (ihnp4!killer!jaime) * * a (hopefully) 7th Edition Unix compatible chmod for Minix. */ #include <stat.h> /* for Minix */ /* #include <sys/types.h> /* for Berkeley, System V */ /* #include <sys/stat.h> /* (and Version 7 ?) */ #define isop(c) ((c=='+')||(c=='-')||(c=='=')) #define isperm(c) ((c=='r')||(c=='w')||(c=='x')||(c=='s')||(c=='t')||\ (c=='u')||(c=='g')||(c=='o')) /* the bits associated with user, group, other */ #define U_MSK (0700 | S_ISUID) #define G_MSK (0070 | S_ISGID) #define O_MSK 0007 typedef unsigned short bitset; /* type used for modes */ struct stat st; /* structure returned by stat() */ char *pname, *arg; bitset newmode, absolute(), symbolic(); int isabsolute; main(argc, argv) int argc; char **argv; { int i; pname = *(argv++); if(argc < 3) usage(); arg = *argv; /* save pointer to mode arg */ /* check for octal mode */ if( isabsolute = ((*arg>='0') && (*arg<='7')) ) newmode = absolute(); /* apply the mode to all files listed */ for(i=2; i<argc; i++) { argv++; if(stat(*argv, &st)) /* get current file mode */ { printf("%s: cannot find `%s'\n", pname, *argv); exit(1); } /* calculate new mode for this file */ if( !isabsolute ) newmode = symbolic(st.st_mode); if(chmod(*argv, newmode)) /* change the mode */ { printf("%s: cannot chmod `%s'\n", pname, *argv); exit(1); } } } /* * absolute * * Interprets an octal mode. * The file modes will be set to this value. */ bitset absolute() { bitset m; char *s; m = 0; s = arg; /* convert octal string to integer */ while((*s>='0') && (*s<='7')) m = m * 8 + (*(s++) - '0'); /* if something else is there, choke */ if(*s) badmode(s); return m; } /* * symbolic * * Processes symbolic mode of the form (in EBNF): * <symbolic> ::= <pgroup> { ',' <pgroup> }. * <pgroup> ::= [ <who> ] <op> <permissions> { <op> <permissions> }. * * <who> ::= <whoch> { <whoch> }. * <whoch> ::= 'a' | 'u' | 'g' | 'o'. * * <op> ::= '+' | '-' | '='. * * <permissions> ::= <permch> { <permch> }. * <permch> ::= 'r' | 'w' | 'x' | 's' | 't' | 'u' | 'g' | 'o'. * * If <who> is omitted, 'a' is assumed, BUT umask()ed bits are uneffected. * If <op> is '=', all unspecified permissions are turned off for this <who>. * For permissions 'u', 'g', and 'o', the permissions are taken from the * specified set. i.e. o=g sets the permissions for other the same as for * group. * * Pain in the duff, isn't it? */ bitset symbolic(mode) bitset mode; { int g,o,u, haswho, haspcopy; bitset u_mask, emask, partial, other, applyop(); char *s, c, op; s = arg; u_mask = umask(0); /* get the umasked bits */ do /* pgroup */ { haswho = u = g = o = 0; while(!isop(*s)) { /* we must have a 'who' then */ haswho = 1; switch(*s) { case 'a': u=g=o=1; break; case 'u': u = 1; break; case 'g': g = 1; break; case 'o': o = 1; break; default: badmode(s); } s++; } if(!haswho) { u=g=o=1; /* assume all */ emask = ~u_mask; /* effective umask */ } else emask = ~0; /* process each given operator */ while(isop(*s)) { op = *(s++); other = partial = haspcopy = 0; /* collect the specified permissions */ while(isperm(*s)) { /* Berkeley only allows one of 'u' 'g' or 'o' as permissions */ if((*s=='u')||(*s=='g')||(*s=='o')) if( haspcopy ) badmode(s); else haspcopy = 1; switch(*s) { case 'r': partial |= 4; break; case 'w': partial |= 2; break; case 'x': partial |= 1; break; case 'u': partial |= (mode & U_MSK &~S_ISUID) >> 6; other |= mode & S_ISUID; break; case 'g': partial |= (mode & G_MSK &~S_ISGID) >> 3; other |= mode & S_ISGID; break; case 'o': partial |= (mode & O_MSK); break; case 't': other |= S_ISVTX; break; case 's': if(u) other |= S_ISUID; if(g) other |= S_ISGID; break; default: badmode(s); } s++; } /* apply the op using the affected bits and masks */ if(u) mode = applyop(mode,op,(other | (partial << 6)),emask,U_MSK); if(g) mode = applyop(mode,op,(other | (partial << 3)),emask,G_MSK); if(o) mode = applyop(mode,op,(other | partial),emask,O_MSK); } } while(*(s++) == ','); /* not at end - choke */ if(*(--s)) badmode(s); return mode; } /* * applyop * * applies the operator to the current mode using the specified bitset * and mask. 'bits' will contain 1's in every bit affected by the * operator '+', '-', or '='. In the case of '=', msk is used to * determine which bits will be forced off. 'emask' is the effective * umask. */ bitset applyop(mode, op, bits, emask, msk) char op; bitset mode, bits, emask, msk; { switch(op) { case '+': mode |= bits & emask; /* turn these bits on */ break; case '-': mode &= ~(bits & emask); /* turn these off */ break; case '=': mode |= bits & emask; /* turn these bits on */ mode &= ~(~bits & msk & emask); /* others off */ break; default: /* should never get here (famous last words) */ printf("%s: panic: bad op `%c' passed\n", pname, op); } return mode; } /* * usage * * Prints a terse usage message and exits. */ usage() { printf("Usage: %s [absolute-mode | symbolic-mode] files\n", pname); exit(1); } /* * badmode * * Called when the parser chokes on the given mode. * Prints a message showing the offending character and exits. */ badmode(s) char *s; { int i,sp; char buffer[80], *bp; sp = s - arg + strlen(pname) + 21; sp = sp > 79 ? 79 : sp; /* check for buffer overflow */ for(i=0, bp = buffer; i<sp; i++, bp++) *bp = ' '; *bp = '\0'; printf("%s: badly formed mode `%s'\n", pname, arg); printf("%s^\n", buffer); exit(1); } ------------------------------------------------------------------------ James A. da Silva 14102 Bramble Court #203 ihnp4!killer!jaime Laurel, Maryland 20708