/* * 9xa: execute arguments * usage: 9xa [-n number] [-f file] program arg ... * number: nuber of args given to the program. the default is 20 * file: file names in the file is used instead of standard input. * coded by: Kenji arisawa * email: arisawa@aichi-u.ac.jp * * 9xa is a program that is similar to Xargs in UNIX. * Xargs allows multiple file names per line. * However such tolerance makes other problems. * Think that file name contains spaces. * Xargs has "-0" option for such case. * However the option cannot not collaborate with other familiar tools such as sort. * 9xa's input is a list of file names like Xargs but 9xa insists one file name per line. * That is NL('\n') is the only separator of the list in 9xa. * File names must be plain form, that is, * without single/double quotations nor '\' escapes for spaces. */ #include #include #include #ifndef UNIX #define PLAN9 #endif typedef struct Str Str; struct Str { char *base; int n; }; char *usage="usage: 9xa [-n number] [-f file] program arg ..."; #ifdef PLAN9 static Waitmsg * waitfor(int pid) { Waitmsg *w; while((w = wait()) != nil){ if(w->pid == pid){ return w; } free(w); } return nil; } #endif static int execute(char *path, char **args) { int pid; Waitmsg *msg; switch(pid = fork()) {/* assign = */ case -1: sysfatal("fork: %r"); case 0: close(0); exec(path, args); sysfatal("exec: %r"); default: break; } msg = waitfor(pid); if(msg == nil){ werrstr("waitfor: %r"); return -1; } free(msg); return 0; } /* we make a string list for path. the format is: erm% xd -c /env/path 0000000 . 00 / b i n 00 0000007 term% the goal is to get a string list like: {".","/bin",nil} any /env/foo should have a '\0' at the end. */ /* * mkstrlist: * strlist = mkstrlist(mem); * free(mem); free(strlist); */ static char ** mkstrlist(Str *mem) { int m,i,n; char *s,**argv; s = mem->base; n = mem->n; i = 0; while(n-- > 0){ if(*s++ == 0) i++; } m = i; // we over-count '\0' /* in calloc, we should have a room for the last nil */ argv = malloc(sizeof(char**)*(m + 1) + mem->n); memmove(&argv[m+1],mem->base,mem->n); s = (char*)&argv[m+1]; i = 0; argv[i++] = s; while(i < m){ if(*s++ == 0){ argv[i++] = s; } } argv[i] = nil; return argv; } #ifdef PLAN9 /* Str *mem; * mem = readfile(file) * free: free(mem); */ static Str * readfile(char *file) { int fd,n; Dir *dir; Str *p; fd = open(file,OREAD); if(fd < 0) return nil; dir = dirfstat(fd); p = malloc(sizeof(Str) + dir->length); if(p == nil) return nil; p->base = (char*)&p[1]; n = read(fd,p->base,dir->length); if(n != dir->length){ free(p); return nil; } p->n = n; return p; } #endif #ifdef UNIX /* * simplified translate */ static char * tr(char *s, char ch0, char ch1) { char *s0; s0 = s; while(*s){ if(*s == ch0) *s = ch1; s++; } return s0; } /* * return: * nil: if no such env * else: nil terminated string list * pathlist = getpathlist(); * free(pathlist); */ static char ** getpathlist(void) { char *path; int n; char **pathlist; Str *mem; path = getenv("PATH"); n = strlen(path); path = strdup(path); tr(path,':','\0'); mem = malloc(sizeof(Str)); mem->base = path; mem->n = n; pathlist = mkstrlist(mem); free(mem); return pathlist; } #else /*Plan9 */ /* * designed for $path * return: * nil: if no such env * else: nil terminated string list * pathlist = getpathlist(); * free(pathlist); */ static char ** getpathlist(void) { Str *mem; char **pathlist; mem = readfile("/env/path"); pathlist = mkstrlist(mem); free(mem); return pathlist; } #endif /* get exec path * return: * nil: * else: execuatble path. free is needed */ static char * getexecpath(char **pathlist, char *cmd) { char *compath; while(*pathlist){ compath = smprint("%s/%s",*pathlist++,cmd); if(access(compath, AEXEC) == 0) return compath; free(compath); } return nil; } static int strlistlen(char **args) { int n = 0; while(*args++) n++; return n; } static int instrlist(char *str,char **strlist) { while(*strlist){ if(strncmp(str,*strlist,strlen(*strlist)) == 0) return 1; strlist++; } return 0; } #define NMAX 52 void main(int argc, char **argv) { int i,n; Biobuf bin; char *s; char **args; // args that is passed to exec char *path; char **pathlist; char *abspathhead[]={"/","./","../",nil}; int nmax = 20; // not effective even if more than 20 int fd = 0; char *file = nil; ARGBEGIN{ case 'n': nmax = atoi(ARGF()); if(nmax < 1) nmax = 1; break; case 'f': file = ARGF(); break; default: sysfatal(usage); }ARGEND /* Let's assume we execute: * 9xa prog a_1 a_2 .. a_n * extra arguments will come from pipe, they are typically file names * therefore in effect we must execute with args * prog a_1 a_2 .. a_n x_1 x_2 .. x_i nil * args[0] is path to prog * argc is n + 1 * i must be <= nmax * array size of args must be >= nmax + argc + 1 * * Memory for args in executing exec(path,args) is supplied as stack. * The limit is ARG_MAX in UNIX. The value is defined in "limit.h". * However ARG_MAX is worthless for coding. * That makes things complicated and, in addition, debug is hard. * And more, consuming too much memoy for args causes other problems. * Thus it is wise not consult with ARG_MAX */ args = calloc(nmax + argc + 1,sizeof(char*)); if(args == nil) sysfatal("no enough memory"); /* examine if we have extra *argv */ if(*argv == nil){ fprint(2,"%s\n",usage); exits("usage"); } if(file){ fd = open(file,OREAD); if(fd < 0) sysfatal("no such file"); }; Binit(&bin, fd, OREAD); pathlist = getpathlist(); //path = nil; // might be required to avoid warning by poor compiler if(instrlist(*argv,abspathhead) == 0){ int n; n = strlistlen(pathlist); path = getexecpath(pathlist, *argv); if(path == nil){ char *msg; msg = smprint("'%s/%s' file does not exist",pathlist[n-1],*argv); free(msg); exits(msg); } } else path = *argv; if(path == nil){ fprint(2,"something wrong\n"); exits("???"); } argv++; args[0] = path; n = 1; while(*argv) args[n++] = *argv++; args[n] = nil; while(1){ s = nil; for(i = 0; i < nmax; i++){ s = Brdstr(&bin, '\n', 1); if(s == nil) break; args[n + i] = s; // need not strdup(s) } args[n + i] = nil; if(i > 0) execute(path,args); if(s == nil) break; for(i = 0; i < nmax; i++) free(args[n + i]); } exits(nil); }