diff options
Diffstat (limited to 'usr/src/lib/libshell/common/bltins/test.c')
-rw-r--r-- | usr/src/lib/libshell/common/bltins/test.c | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/usr/src/lib/libshell/common/bltins/test.c b/usr/src/lib/libshell/common/bltins/test.c new file mode 100644 index 0000000000..f21db8a75c --- /dev/null +++ b/usr/src/lib/libshell/common/bltins/test.c @@ -0,0 +1,629 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2007 AT&T Knowledge Ventures * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Knowledge Ventures * +* * +* A copy of the License is available at * +* http://www.opensource.org/licenses/cpl1.0.txt * +* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * test expression + * [ expression ] + * + * David Korn + * AT&T Labs + * + */ + + +#include "defs.h" +#include <ctype.h> +#include <error.h> +#include <ls.h> +#include "io.h" +#include "terminal.h" +#include "test.h" +#include "builtins.h" +#include "FEATURE/externs" +#include "FEATURE/poll" +#include <tmx.h> + +#if !_lib_setregid +# undef _lib_setreuid +#endif /* _lib_setregid */ + +#ifdef S_ISSOCK +# if _pipe_socketpair +# if _socketpair_shutdown_mode +# define isapipe(f,p) (test_stat(f,p)>=0&&S_ISFIFO((p)->st_mode)||S_ISSOCK((p)->st_mode)&&(p)->st_ino&&((p)->st_mode&(S_IRUSR|S_IWUSR))!=(S_IRUSR|S_IWUSR)) +# else +# define isapipe(f,p) (test_stat(f,p)>=0&&S_ISFIFO((p)->st_mode)||S_ISSOCK((p)->st_mode)&&(p)->st_ino) +# endif +# else +# define isapipe(f,p) (test_stat(f,p)>=0&&S_ISFIFO((p)->st_mode)||S_ISSOCK((p)->st_mode)&&(p)->st_ino) +# endif +# define isasock(f,p) (test_stat(f,p)>=0&&S_ISSOCK((p)->st_mode)) +#else +# define isapipe(f,p) (test_stat(f,p)>=0&&S_ISFIFO((p)->st_mode)) +# define isasock(f,p) (0) +#endif + +#define permission(a,f) (sh_access(a,f)==0) +static time_t test_time(const char*, const char*); +static int test_stat(const char*, struct stat*); +static int test_mode(const char*); + +/* single char string compare */ +#define c_eq(a,c) (*a==c && *(a+1)==0) +/* two character string compare */ +#define c2_eq(a,c1,c2) (*a==c1 && *(a+1)==c2 && *(a+2)==0) + +struct test +{ + Shell_t *sh; + int ap; + int ac; + char **av; +}; + +static char *nxtarg(struct test*,int); +static int expr(struct test*,int); +static int e3(struct test*); + +static int test_strmatch(const char *str, const char *pat) +{ + int match[2*(MATCH_MAX+1)],n; + register int c, m=0; + register const char *cp=pat; + while(c = *cp++) + { + if(c=='(') + m++; + if(c=='\\' && *cp) + cp++; + } + if(m) + m++; + else + match[0] = 0; + if(m > elementsof(match)/2) + m = elementsof(match)/2; + n = strgrpmatch(str, pat, match, m, STR_MAXIMAL|STR_LEFT|STR_RIGHT); + if(m==0 && n==1) + match[1] = strlen(str); + if(n) + sh_setmatch(str, -1, n, match); + return(n); +} + +int b_test(int argc, char *argv[],void *extra) +{ + struct test tdata; + register char *cp = argv[0]; + register int not; + tdata.sh = (Shell_t*)extra; + tdata.av = argv; + tdata.ap = 1; + if(c_eq(cp,'[')) + { + cp = argv[--argc]; + if(!c_eq(cp, ']')) + errormsg(SH_DICT,ERROR_exit(2),e_missing,"']'"); + } + if(argc <= 1) + return(1); + cp = argv[1]; + not = c_eq(cp,'!'); + /* posix portion for test */ + switch(argc) + { + case 5: + if(!not) + break; + argv++; + /* fall through */ + case 4: + { + register int op = sh_lookup(cp=argv[2],shtab_testops); + if(op&TEST_BINOP) + break; + if(!op) + { + if(argc==5) + break; + if(not && cp[0]=='-' && cp[2]==0) + return(test_unop(cp[1],argv[3])!=0); + else if(argv[1][0]=='-' && argv[1][2]==0) + return(!test_unop(argv[1][1],cp)); + errormsg(SH_DICT,ERROR_exit(2),e_badop,cp); + } + return(test_binop(op,argv[1],argv[3])^(argc!=5)); + } + case 3: + if(not) + return(*argv[2]!=0); + if(cp[0] != '-' || cp[2] || cp[1]=='?') + { + if(cp[0]=='-' && (cp[1]=='-' || cp[1]=='?') && + strcmp(argv[2],"--")==0) + { + char *av[3]; + av[0] = argv[0]; + av[1] = argv[1]; + av[2] = 0; + optget(av,sh_opttest); + errormsg(SH_DICT,ERROR_usage(2), "%s",opt_info.arg); + return(2); + } + break; + } + return(!test_unop(cp[1],argv[2])); + case 2: + return(*cp==0); + } + if(argc==5) + argv--; + tdata.ac = argc; + return(!expr(&tdata,0)); +} + +/* + * evaluate a test expression. + * flag is 0 on outer level + * flag is 1 when in parenthesis + * flag is 2 when evaluating -a + */ +static int expr(struct test *tp,register int flag) +{ + register int r; + register char *p; + r = e3(tp); + while(tp->ap < tp->ac) + { + p = nxtarg(tp,0); + /* check for -o and -a */ + if(flag && c_eq(p,')')) + { + tp->ap--; + break; + } + if(*p=='-' && *(p+2)==0) + { + if(*++p == 'o') + { + if(flag==2) + { + tp->ap--; + break; + } + r |= expr(tp,3); + continue; + } + else if(*p == 'a') + { + r &= expr(tp,2); + continue; + } + } + if(flag==0) + break; + errormsg(SH_DICT,ERROR_exit(2),e_badsyntax); + } + return(r); +} + +static char *nxtarg(struct test *tp,int mt) +{ + if(tp->ap >= tp->ac) + { + if(mt) + { + tp->ap++; + return(0); + } + errormsg(SH_DICT,ERROR_exit(2),e_argument); + } + return(tp->av[tp->ap++]); +} + + +static int e3(struct test *tp) +{ + register char *arg, *cp; + register int op; + char *binop; + arg=nxtarg(tp,0); + if(arg && c_eq(arg, '!')) + return(!e3(tp)); + if(c_eq(arg, '(')) + { + op = expr(tp,1); + cp = nxtarg(tp,0); + if(!cp || !c_eq(cp, ')')) + errormsg(SH_DICT,ERROR_exit(2),e_missing,"')'"); + return(op); + } + cp = nxtarg(tp,1); + if(cp!=0 && (c_eq(cp,'=') || c2_eq(cp,'!','='))) + goto skip; + if(c2_eq(arg,'-','t')) + { + if(cp && isdigit(*cp)) + return(*(cp+1)?0:tty_check(*cp-'0')); + else + { + /* test -t with no arguments */ + tp->ap--; + return(tty_check(1)); + } + } + if(*arg=='-' && arg[2]==0) + { + op = arg[1]; + if(!cp) + { + /* for backward compatibility with new flags */ + if(op==0 || !strchr(test_opchars+10,op)) + return(1); + errormsg(SH_DICT,ERROR_exit(2),e_argument); + } + if(strchr(test_opchars,op)) + return(test_unop(op,cp)); + } + if(!cp) + { + tp->ap--; + return(*arg!=0); + } +skip: + op = sh_lookup(binop=cp,shtab_testops); + if(!(op&TEST_BINOP)) + cp = nxtarg(tp,0); + if(!op) + errormsg(SH_DICT,ERROR_exit(2),e_badop,binop); + if(op==TEST_AND | op==TEST_OR) + tp->ap--; + return(test_binop(op,arg,cp)); +} + +int test_unop(register int op,register const char *arg) +{ + struct stat statb; + int f; + switch(op) + { + case 'r': + return(permission(arg, R_OK)); + case 'w': + return(permission(arg, W_OK)); + case 'x': + return(permission(arg, X_OK)); + case 'V': +#if SHOPT_FS_3D + { + register int offset = staktell(); + if(stat(arg,&statb)<0 || !S_ISREG(statb.st_mode)) + return(0); + /* add trailing / */ + stakputs(arg); + stakputc('/'); + stakputc(0); + arg = (const char*)stakptr(offset); + stakseek(offset); + /* FALL THRU */ + } +#else + return(0); +#endif /* SHOPT_FS_3D */ + case 'd': + return(test_stat(arg,&statb)>=0 && S_ISDIR(statb.st_mode)); + case 'c': + return(test_stat(arg,&statb)>=0 && S_ISCHR(statb.st_mode)); + case 'b': + return(test_stat(arg,&statb)>=0 && S_ISBLK(statb.st_mode)); + case 'f': + return(test_stat(arg,&statb)>=0 && S_ISREG(statb.st_mode)); + case 'u': + return(test_mode(arg)&S_ISUID); + case 'g': + return(test_mode(arg)&S_ISGID); + case 'k': +#ifdef S_ISVTX + return(test_mode(arg)&S_ISVTX); +#else + return(0); +#endif /* S_ISVTX */ +#if SHOPT_TEST_L + case 'l': +#endif + case 'L': + case 'h': /* undocumented, and hopefully will disappear */ + if(*arg==0 || arg[strlen(arg)-1]=='/' || lstat(arg,&statb)<0) + return(0); + return(S_ISLNK(statb.st_mode)); + + case 'C': +#ifdef S_ISCTG + return(test_stat(arg,&statb)>=0 && S_ISCTG(statb.st_mode)); +#else + return(0); +#endif /* S_ISCTG */ + case 'H': +#ifdef S_ISCDF + { + register int offset = staktell(); + if(test_stat(arg,&statb)>=0 && S_ISCDF(statb.st_mode)) + return(1); + stakputs(arg); + stakputc('+'); + stakputc(0); + arg = (const char*)stakptr(offset); + stakseek(offset); + return(test_stat(arg,&statb)>=0 && S_ISCDF(statb.st_mode)); + } +#else + return(0); +#endif /* S_ISCDF */ + + case 'S': + return(isasock(arg,&statb)); + case 'N': + return(test_stat(arg,&statb)>=0 && tmxgetmtime(&statb) > tmxgetatime(&statb)); + case 'p': + return(isapipe(arg,&statb)); + case 'n': + return(*arg != 0); + case 'z': + return(*arg == 0); + case 's': + sfsync(sfstdout); + case 'O': + case 'G': + if(*arg==0 || test_stat(arg,&statb)<0) + return(0); + if(op=='s') + return(statb.st_size>0); + else if(op=='O') + return(statb.st_uid==sh.userid); + return(statb.st_gid==sh.groupid); + case 'a': + case 'e': + return(permission(arg, F_OK)); + case 'o': + f=1; + if(*arg=='?') + return(sh_lookopt(arg+1,&f)>0); + op = sh_lookopt(arg,&f); + return(op && (f==(sh_isoption(op)!=0))); + case 't': + if(isdigit(*arg) && arg[1]==0) + return(tty_check(*arg-'0')); + return(0); + default: + { + static char a[3] = "-?"; + a[1]= op; + errormsg(SH_DICT,ERROR_exit(2),e_badop,a); + /* NOTREACHED */ + return(0); + } + } +} + +int test_binop(register int op,const char *left,const char *right) +{ + register double lnum,rnum; + if(op&TEST_ARITH) + { + while(*left=='0') + left++; + while(*right=='0') + right++; + lnum = sh_arith(left); + rnum = sh_arith(right); + } + switch(op) + { + /* op must be one of the following values */ + case TEST_AND: + case TEST_OR: + return(*left!=0); + case TEST_PEQ: + return(test_strmatch(left, right)); + case TEST_PNE: + return(!test_strmatch(left, right)); + case TEST_SGT: + return(strcoll(left, right)>0); + case TEST_SLT: + return(strcoll(left, right)<0); + case TEST_SEQ: + return(strcmp(left, right)==0); + case TEST_SNE: + return(strcmp(left, right)!=0); + case TEST_EF: + return(test_inode(left,right)); + case TEST_NT: + return(test_time(left,right)>0); + case TEST_OT: + return(test_time(left,right)<0); + case TEST_EQ: + return(lnum==rnum); + case TEST_NE: + return(lnum!=rnum); + case TEST_GT: + return(lnum>rnum); + case TEST_LT: + return(lnum<rnum); + case TEST_GE: + return(lnum>=rnum); + case TEST_LE: + return(lnum<=rnum); + } + /* NOTREACHED */ + return(0); +} + +/* + * returns the modification time of f1 - modification time of f2 + */ + +static time_t test_time(const char *file1,const char *file2) +{ + Time_t t1, t2; + struct stat statb1,statb2; + int r=test_stat(file2,&statb2); + if(test_stat(file1,&statb1)<0) + return(r<0?0:-1); + if(r<0) + return(1); + t1 = tmxgetmtime(&statb1); + t2 = tmxgetmtime(&statb2); + if (t1 > t2) + return(1); + if (t1 < t2) + return(-1); + return(0); +} + +/* + * return true if inode of two files are the same + */ + +int test_inode(const char *file1,const char *file2) +{ + struct stat stat1,stat2; + if(test_stat(file1,&stat1)>=0 && test_stat(file2,&stat2)>=0) + if(stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino) + return(1); + return(0); +} + + +/* + * This version of access checks against effective uid/gid + * The static buffer statb is shared with test_mode. + */ + +int sh_access(register const char *name, register int mode) +{ + struct stat statb; + if(*name==0) + return(-1); + if(strmatch(name,(char*)e_devfdNN)) + return(sh_ioaccess((int)strtol(name+8, (char**)0, 10),mode)); + /* can't use access function for execute permission with root */ + if(mode==X_OK && sh.euserid==0) + goto skip; + if(sh.userid==sh.euserid && sh.groupid==sh.egroupid) + return(access(name,mode)); +#ifdef _lib_setreuid + /* swap the real uid to effective, check access then restore */ + /* first swap real and effective gid, if different */ + if(sh.groupid==sh.euserid || setregid(sh.egroupid,sh.groupid)==0) + { + /* next swap real and effective uid, if needed */ + if(sh.userid==sh.euserid || setreuid(sh.euserid,sh.userid)==0) + { + mode = access(name,mode); + /* restore ids */ + if(sh.userid!=sh.euserid) + setreuid(sh.userid,sh.euserid); + if(sh.groupid!=sh.egroupid) + setregid(sh.groupid,sh.egroupid); + return(mode); + } + else if(sh.groupid!=sh.egroupid) + setregid(sh.groupid,sh.egroupid); + } +#endif /* _lib_setreuid */ +skip: + if(test_stat(name, &statb) == 0) + { + if(mode == F_OK) + return(mode); + else if(sh.euserid == 0) + { + if(!S_ISREG(statb.st_mode) || mode!=X_OK) + return(0); + /* root needs execute permission for someone */ + mode = (S_IXUSR|S_IXGRP|S_IXOTH); + } + else if(sh.euserid == statb.st_uid) + mode <<= 6; + else if(sh.egroupid == statb.st_gid) + mode <<= 3; +#ifdef _lib_getgroups + /* on some systems you can be in several groups */ + else + { + static int maxgroups; + gid_t *groups; + register int n; + if(maxgroups==0) + { + /* first time */ + if((maxgroups=getgroups(0,(gid_t*)0)) <= 0) + { + /* pre-POSIX system */ + maxgroups=NGROUPS_MAX; + } + } + groups = (gid_t*)stakalloc((maxgroups+1)*sizeof(gid_t)); + n = getgroups(maxgroups,groups); + while(--n >= 0) + { + if(groups[n] == statb.st_gid) + { + mode <<= 3; + break; + } + } + } +# endif /* _lib_getgroups */ + if(statb.st_mode & mode) + return(0); + } + return(-1); +} + +/* + * Return the mode bits of file <file> + * If <file> is null, then the previous stat buffer is used. + * The mode bits are zero if the file doesn't exist. + */ + +static int test_mode(register const char *file) +{ + struct stat statb; + if(file && (*file==0 || test_stat(file,&statb)<0)) + return(0); + return(statb.st_mode); +} + +/* + * do an fstat() for /dev/fd/n, otherwise stat() + */ +static int test_stat(const char *name,struct stat *buff) +{ + if(*name==0) + { + errno = ENOENT; + return(-1); + } + if(strmatch(name,(char*)e_devfdNN)) + return(fstat((int)strtol(name+8, (char**)0, 10),buff)); + else + return(stat(name,buff)); +} |