/* Fulist version 2.0 (C) by Ian Collier, September 1994. This file may be reproduced freely in whole or in part, provided this comment is duplicated unchanged on all copies. If the code is modified, a notice indicating the modification must be added. This software comes with no warranty. */ /* Version 2.1: Aided portability by adding the get_terminal etc. macros and adding the sgi code to the "suspend" routine */ /* Version 2.2: Ported to DEC OSF/1 */ /* Version 2.25: Ported to Solaris 2.5 */ /* Version 2.3: Fixed some compile warnings and compiled on Red Hat 5.2; AMD fixes; always follow if sole argument is a symlink */ /* Version 2.3a: Renamed "lines" and "columns" variables to compile on AIX 4.2.1 */ /* Version 2.31: added curses keys KEY_BACKSPACE and KEY_DC; fixed some issues with single quote characters */ /* Version 2.32: fixed silly double-name bug introduced in 2.31; fixed Solaris hanging problem */ #define VERSION "2.32" #define VLEN 4 #define SHORTHELP "/usr/local/man/cat1/fulist.shorthelp" #ifdef AMD /* Select strings to trim off the beginnings of directories */ # ifndef AMDFS # define AMDFS "/amd/fs/" # define AMDFSLEN 8 # endif # ifndef AMDMNT # define AMDMNT "/amd/tmp_mnt/" /* plus any string */ # define AMDMNTLEN 13 # endif # ifndef AMDADD # define AMDADD "/fs" # define AMDADDLEN 3 # endif # ifndef AMDFSLEN # define AMDFSLEN (strlen(AMDFS)) # endif # ifndef AMDMNTLEN # define AMDMNTLEN (strlen(AMDMNT)) # endif # ifndef AMDADDLEN # define AMDADDLEN (strlen(AMDADD)) # endif #endif /* AMD */ #undef __STRICT_ANSI__ /* A paranoid Solaris detector. */ #if (defined(__svr4__)||defined(__SVR4)||defined(__SVR4)) && (defined(sun)||defined(__sun)||defined(__sun__)) #define Solaris #include /* for pushing streams modules onto ptys */ #endif #include #include #include #include #include #define _INCLUDE_TERMIO #include #undef ECHO /* on a certain broken operating system which shall remain */ #undef NL0 /* nameless, there are some constants which are defined in */ #undef NL1 /* several places, and one of these is termios.h. These */ #undef TAB0 /* lines silence the compiler and cause no harm since the */ #undef TAB1 /* constants are not used in the code. */ #undef TAB2 #undef XTABS #undef CR0 #undef CR1 #undef CR2 #undef CR3 #undef FF0 #undef FF1 #undef BS0 #undef BS1 #undef TOSTOP #undef FLUSHO #undef PENDIN #undef NOFLSH #undef HUPCL #undef TIOCHPCL #undef TIOCGETP #undef TIOCSETP #undef TIOCEXCL #undef TIOCNXCL #include #include #undef memcpy /* one broken curses.h defines this */ #include #include #include #include #include #include #include #include #include #include #include #include #define isprint8(c) (isprint((unsigned char)(c)&0x7f)) #define isspace8(c) (isspace((unsigned char)(c))) #define isdigit8(c) (isdigit((unsigned char)(c))) #define isalnum8(c) (isalnum((unsigned char)(c))) #include /* SunOS 4 does not provide strerror or declare errno; other systems seem to be OK. */ #if (defined(sun) || defined(__sun__)) && !defined(Solaris) extern int errno; extern int sys_nerr; extern char *sys_errlist[]; #define strerror(x) ((x)>=0 && (x) # define get_terminal(fd,params) (ioctl(fd,TCGETA,params)) # define set_terminal(fd,params) (ioctl(fd,TCSETAW,params)) # define set_terminal_now(fd,params) (ioctl(fd,TCSETA,params)) # define termios termio # undef VMIN # undef VTIME # define VMIN VEOF # define VTIME VEOL #endif #endif #ifdef KEY_UP /* must be sys v curses */ #define SYSVCURSES # ifndef SA_INTERRUPT /* sigaction() madness */ # define SA_INTERRUPT 0 # endif static void winch_handler(); static int winchanged=0; /* whether WINCH signal has been received */ static int sizechanged=0; /* whether the size has actually changed */ static SCREEN *screen; #else /* must be BSD curses */ #undef SYSVCURSES static void beep(); #define KEY_DOWN 0402 #define KEY_UP 0403 #define KEY_LEFT 0404 #define KEY_RIGHT 0405 #define KEY_F0 0410 #define KEY_F(n) (KEY_F0+(n)) #define KEY_IC 0513 #define KEY_EOL 0517 #define KEY_NPAGE 0522 #define KEY_PPAGE 0523 #define KEY_BTAB 0541 #endif /* end of curses selection */ #define correction 1 /* this defines how many lines to leave at the bottom of the screen. When curses exits from display mode it seems to place the cursor on the bottom line without clearing on some terminals, so it helps to leave the bottom line free. Also, BSD curses seems to get stuck if you write in the bottom right-hand corner of the screen. */ /* Some extra keys */ #define KEY_REDRAW 512 /* redraw screen */ #define KEY_DELLEFT 513 /* delete-char-left */ #define KEY_DELRIGHT 514 /* delete-char-right */ #define KEY_TAB 515 /* go to next input field */ #define KEY_INTERRUPT 516 /* end */ #ifndef KEY_RESIZE #define KEY_RESIZE 517 /* value returned when the screen changes size */ #endif #define KEY_BAD 0 /* anything that looks suspicious */ #ifndef KEY_HELP #define KEY_HELP 0553 #endif #ifndef KEY_SUSPEND #define KEY_SUSPEND 0627 #endif /* some parameters */ #undef MAXNAMELEN /* in case it's in param.h */ #define MAXNAMELEN 80 /* max length of a file name */ #define MAXDIRLEN 200 /* max length of a full directory name */ #define MAXUIDLEN 9 /* max length of a username + nul char */ #define UIDTABSIZE 64 /* size of the uid hash table */ #define MAXCOMMANDLEN 120 /* max length of the command input area */ #define MAXESCLEN 9 /* max length of an escape sequence + nul */ #define WORKLEN 1024 /* length of a workspace string */ #define HASHSIZE 251 /* number of elements in command hash table */ /* some constants */ #define use_mtime 0 #define use_ctime 1 #define use_atime 2 /* Profile commands */ #define Cterm 1 #define Ceterm 2 #define Cescchar 3 #define Callow 4 #define Ckey 5 #define Csynonym 6 #define Csort 7 #define Cshell 8 #define Cf1 10 /* Fulist commands and modifiers */ #define Fb 1 #define Fbot 2 #define Ff 3 #define Fh 4 #define Fo 5 #define FQ 6 #define Fr 7 #define Ftop 8 #define Fsb 20 #define Fsd 21 #define Fsf 22 #define Fsn 23 #define Fst 24 #define Fsrb 25 #define Fsrd 26 #define Fsrf 27 #define Fsrn 28 #define Fsrt 29 #define Freverse (Fsrb-Fsb) #define Fslash 0x20 #define Fd 4 #define Fn 2 #define Ft 1 #define Fposition 0x40 #define Fissort(x) ((unsigned int)((x)-Fsb)<10) /* is x a sorting command? */ /* Command characteristics */ #define Xnodelay 1 /* don't ask for keypress */ #define Xrename 2 /* look at arg 2 for new name of file */ #define Xprotect 4 /* has 2 arguments and arg 2 shouldn't exist */ #define Xcompress 8 /* might add .Z */ #define Xuncompress 16 /* might take away .Z */ #define Xgzip 32 /* might add .gz or .z */ #define Xgunzip 64 /* might take away .gz or .z */ #define Xoutput 128 /* exit curses mode before executing */ /* Errors */ #define Eargs 1 /* problem with command line arguments */ #define Einit 2 /* initialisation problem */ #define Escreen 3 /* screen too small */ #define Emem 4 /* Memory problem */ #define Eprofile 5 /* Syntax error in profile */ #define Enofiles 6 /* No files to list */ #define Ekey 7 /* problem with reading the keyboard */ #define Eintr 99 /* interrupted */ #define Mrealloc "Couldn't realloc enough memory" #define EOF_MAGIC 12345 /* errno number for "EOF occurred" */ /* some types */ struct details { /* details of a file */ int dir; /* number of directory it's in */ char name[MAXNAMELEN]; /* name of file */ ino_t inode; /* inode number */ mode_t mode; /* file mode */ nlink_t nlink; /* number of links */ uid_t uidno; /* uid number */ char *uid; /* uid character string */ gid_t gidno; /* gid number */ char *gid; /* gid character string */ dev_t device; /* device number of file, if any */ off_t size; /* size of file */ time_t time; /* mtime, ctime or atime as appropriate */ char date[13]; /* ascii version of the above time */ char cmd[MAXCOMMANDLEN]; /* command typed against this file */ }; struct uidentry { /* entry in the uid table */ struct uidentry *next; /* next entry in bucket */ int uidno; /* number of uid */ char uid[MAXUIDLEN]; /* ascii version of user name */ }; struct keytable { /* map from escape sequences to keys */ char esc[MAXESCLEN]; /* an escape sequence */ int len; /* length of the escape sequence */ int key; /* the key token */ }; struct pfkey { /* PF key definition */ int num; /* command or key number */ char *str; /* string definition, if it isn't a number */ }; struct syn { /* synonym definition */ char *name; /* name of synonym */ int abbr; /* minimum abbreviation */ int flags; /* characteristics of command */ char *fullname; /* expansion of synonym */ }; struct hash { /* command hash table element */ struct hash *next; /* next element in bucket */ int dot; /* whether "." came before this in path */ int data; /* offset from end of header to data */ char name[1]; /* the name being hashed followed by data */ }; static char *default_profile[]={ "f1 \\h", "f2 \\r", "f3 \\Q", "f4 \\top", "f5 REDRAW", "f6 \\bot", "f7 \\b", "f8 \\f", "key \\ei INSERT", "key ^A BACKTAB", "key ^I TAB", "key ^K EOL", "key ^L REDRAW", "key ^V PAGEDN", "key \\ev PAGEUP", "key \\e[224z F1", "key \\e[225z F2", "key \\e[226z F3", "key \\e[227z F4", "key \\e[228z F5", "key \\e[229z F6", "key \\e[230z F7", "key \\e[231z F8", "key \\e[232z F9", "key \\e[233z F10", "key \\e[192z F11", "key \\e[193z F12", "key \\e[196z HELP", "key \\e[2z INSERT", "syn -p copy 1 cp", "syn -pr rename 1 mv", "syn -pr mv 2 mv", "syn -no less 4 less", "syn -z compress 4 compress", "syn -Z uncompress 6 uncompress", "syn -g gzip 2 gzip", "syn -G gunzip 4 gunzip", "syn erase 5 rm" }; static char *programname; /* name of the program (for error messages) */ /* Parameters for the file listing utility */ static int followlinks=0; /* (try to) follow symbolic links */ static int allfiles=1; /* list file names starting with '.' */ static int timetype=use_mtime; /* which of three dates to display */ static time_t current_time; /* today's date (to decide whether to print the year of a file's date) */ static int recurse=0; /* descend into subdirectories */ static int listdirs=1; /* descend into directories named in args */ static int showgroup=0; /* show group as well as owner of file */ static int showinode=0; /* show inode number of file */ static int numberuids=0; /* show number of uid/gid instead of name */ static int maxnamelen; /* max length of any file name */ static int newnamelen; /* same for next pass */ static int commandlen; /* length of command in display */ /* Lists of files */ static struct details *files;/* the list of files */ static int nfiles=0; /* number of files so far listed */ static int maxfiles=100; /* number of files for which space allocated */ static char *dirs; /* the list of directories */ static int ndirs=0; /* number of dirs so far */ static int maxdirs=5; /* number of dirs for which space allocated */ /* terminal variables */ static char *term; /* name of the terminal */ static int incurses=0; /* whether curses has been started */ static int nkeys=0; /* number of key definitions */ static int maxkeys=32; /* number of keys for which space allocated */ static struct keytable *extrakeys; /* key definition table */ static struct pfkey pfkeys[20]; /* pf key definitions */ static int width,height; /* terminal size */ static struct termios shellmode; /* normal terminal mode */ static struct termios rawmode; /* raw no-echo terminal mode */ static struct winsize winsz; /* for examining terminal size */ static int xpos,ypos; /* cursor position */ static int insmode=0; /* whether fulist is in insert mode */ /* shell variables */ static struct syn *synonyms; /* a table of defined synonyms */ static int nsynonyms=0; /* number of defined synonyms */ static int maxsynonyms=32; /* number of synonyms for which space allocated */ static struct hash *hashtable[HASHSIZE]; /* a table of hashed commands */ /* pty variables */ static char *ptyname; /* name of the pty or tty */ static int ptyfd; /* file handle of the open pty */ /* misc variables */ static char work[WORKLEN]; /* a workspace */ static char work2[WORKLEN]; /* another workspace */ static struct uidentry *uidtable[UIDTABSIZE]; /* uid hash table */ static struct uidentry *gidtable[UIDTABSIZE]; /* gid hash table */ static char *oneline=0; /* buffer to print a line on the screen */ static int *usage=0; /* buffer to store line usage info */ static char *profile; /* name of profile file, if any */ static int *commands=0; /* list of fulist commands to do */ static int ncmds=0; /* number of fulist cmds to do */ static int maxcmds=10; /* number of fulist cmds for which space allocated */ static int currentdir=0; /* number of current directory */ static int lastdir=0; /* number of directory most recently listed */ static char escchar='\\'; /* escape character to use */ static char allowslash=0; /* allow '/' as alternative escchar or not */ static int useshell=0; /* invoke shell to interpret metacharacters or not */ static int dividers; /* print directory dividers */ static int topfile=0; /* which file is at the top of the display */ static int botfile=0; /* which file is at the bottom of the display */ static int delay; /* give prompt before returning to curses mode */ static char *newname; /* new name of a file that might have been renamed */ static int sorttype=Fsf; /* sort command to execute on startup */ /* functions */ #ifdef __STDC__ /* define Args macro so that the arguments of each */ #define Args(a) a /* function can be stated without confusing the */ #else /* compiler */ #define Args(a) () #endif static void interpret Args((char *line,char *source)); static char *getsynonym Args((char *name,int len,int *flags)); static void *search Args((char *name,int *exist)); static char *locate Args((char *name)); static int cmd_to_num Args((char *cmd,int len)); static int key_to_num Args((char *key,int len)); static int hexdigit Args((int c)); static char *get_escape Args((char *esc,int len,int *anslen)); static void addkey Args((char *esc,int len,int key,int replace)); static int fulist_cmd Args((char *string)); static void addsynonym Args((char *name,int nlen,int abbr,int flags,char *fullname,int flen)); static void addcommand Args((int cmd)); static void dosort Args((int cmd)); static int getmodifier Args((char *line,int *len)); static void docommand Args((int line,int save)); static int shell Args((char *cmd,int special,int flags)); static void blkwrite Args((int fd,char *buf,int nbyte)); static void makelist Args((int argc,char **argv)); static int listfile Args((int dir,char *name)); static int adddir Args((int dirno,char *name,int len)); static int statfile Args((int num,int verbose)); static void listdir Args((int dir,int showdotdot)); static void addlistdir Args((int dir,char *name,int showdotdot)); static void printfile Args((int file,char *buffer,int size)); static int getwinsize Args((void)); static int getinstant Args((void)); static int getkey Args((void)); static char *xmalloc Args((size_t size)); static void endcurses Args((void)); static void restartcurses Args((void)); static void emsg Args((int rc,char *msg1,char *msg2)); static void die Args((int rc)); #define min(x,y) ((x)<(y)?(x):(y)) #define nelem(a) ((sizeof a)/(sizeof *a)) /* number of elements in an array */ static void emptyfunction(){} int main(argc,argv) int argc; char **argv; { int i,j,k,l; int dorefresh; int error; int opt; char c; FILE *fp; int dir; int file; char *p; char *type; static sigset_t set,oset; char *myname; static char manname[MAXPATHLEN]; #ifdef SYSVCURSES static struct sigaction act; #endif /* initialise */ if((programname=strrchr(argv[0],'/'))) /* set programname to the */ programname++; /* basename of this program */ else programname=argv[0]; files=(struct details *)xmalloc(maxfiles*sizeof(struct details)); dirs=xmalloc(maxdirs*MAXDIRLEN); for(i=0;i= 0) { if((ptyname=ptsname(ptyfd))==0)close(ptyfd); else grantpt(ptyfd); } } # else /* BSD Unix uses pty/tty pairs. */ { static char _ptyname[]="/dev/ptyp0"; int tmpfd; ptyfd=-1; for(i='p';ptyfd<0 && i<='r';i++){ _ptyname[8]=i; _ptyname[9]='0'; if(!access(_ptyname,F_OK))for(j=0;j<16;j++){ if((ptyfd=open(_ptyname,O_RDWR|O_NDELAY))>=0) { /* pty is open but check that the other side is OK too */ _ptyname[5]='t'; tmpfd=open(_ptyname,O_RDWR|O_NDELAY); if (tmpfd>=0) { close(tmpfd); break; } close(ptyfd); ptyfd=-1; _ptyname[5]='p'; } if(i==9)_ptyname[9]='a'; else _ptyname[9]++; } } if(ptyfd>=0)ptyname=_ptyname; else ptyname=0; } # endif /* Solaris */ # endif /* __osf__ */ if(!ptyname){ fprintf(stderr,"%s: Couldn't find a pty\n",programname); die(Einit); } #endif /* end of open-pty code */ profile=getenv("FULIST_PROFILE"); /* name the default profile */ if(!profile){ static char tmp[MAXPATHLEN]; char *home=getenv("HOME"); if(home){ strcpy(tmp,home); strcat(tmp,"/"); strcat(tmp,".fulistrc"); profile=tmp; } } /* dirs[0] will be the current directory */ #ifdef GETCWD /* getcwd has a buffer length parameter */ if(!getcwd(dirs,MAXDIRLEN)) emsg(Einit,"Couldn't name current directory",(char *)0); #else /* getwd does not... */ { static char tmp[MAXPATHLEN]; if(!getwd(tmp)){ fprintf(stderr,"%s: Couldn't name current directory: %s\n", programname,tmp); die(Einit); } else if(strlen(tmp)>=MAXDIRLEN){ fprintf(stderr,"%s: Internal buffer too small for current directory\n", programname); die(Einit); } else strcpy(dirs,tmp); } #endif #ifdef AMD /* Strip off any leading path names which were put there by "amd", */ { /* such as /amd/fs/ and /amd/tmp_mnt/server/ */ static char tmp[MAXDIRLEN]; l=0; /* l is the number of characters to strip off */ if(!memcmp(dirs,AMDFS,AMDFSLEN))l=AMDFSLEN-1; else if(!memcmp(dirs,AMDMNT,AMDMNTLEN)){ p=strchr(dirs+AMDMNTLEN,'/'); if(p)l=p-dirs; } /* Replace the first l characters with the given replacement text */ if(l && strlen(dirs)+AMDADDLEN-l0) strcpy(tmp,AMDADD); else tmp[0]=0; strcat(tmp,dirs+l); /* check that the result exists before confirming the replacement */ if (!access(tmp,R_OK|X_OK)) strcpy(dirs,tmp); } } #endif /* try to find out the full path name of this program */ if(argv[0][0]=='/' && !access(argv[0],X_OK))myname=argv[0]; else{ myname=locate(programname); if(!myname)myname=argv[0]; if(myname[0]!='/' && !access(myname,X_OK)){ /* prepend current directory */ strcpy(work,dirs); strcat(work,"/"); strcat(work,myname); myname=work; } } /* next try to find the path name of the short help file */ strcpy(manname,myname); strcat(manname,".shorthelp"); if(access(manname,R_OK))strcpy(manname,SHORTHELP); /* Define the default synonyms "fulist" and "type" */ addsynonym("fulist",6,1,Xnodelay|Xoutput,myname,strlen(myname)); l=Xoutput; type=getenv("PAGER"); if(type && strstr(type,"less"))l=Xnodelay; else if(!type){ if(locate("less"))type="less",l=Xnodelay; else type="more"; } addsynonym("type",4,2,l,type,strlen(type)); /* Interpret flag arguments */ error=0; for(opt=1;optWORKLEN)l=WORKLEN-12; memcpy(work2,profile,l); strcpy(work2+l,", line "); l+=7; i=1; while(fgets(work,WORKLEN,fp)){ sprintf(work2+l,"%d",i++); interpret(work,work2); } sprintf(work2+l,"%d",i); interpret((char*)0,work2); /* signal 'end of profile' */ } /* start curses */ #ifdef SYSVCURSES screen=newterm(term,stdout,stdin); if(!screen){ fprintf(stderr,"%s: curses couldn't initialise the terminal\n",programname); die(Einit); } incurses=1; set_term(screen); intrflush(stdscr,0); keypad(stdscr,1); sigemptyset(&act.sa_mask); act.sa_handler=winch_handler; act.sa_flags=SA_INTERRUPT; sigaction(SIGWINCH,&act,(struct sigaction*)0); #else if(!initscr()){ fprintf(stderr,"%s: curses couldn't initialise the terminal\n",programname); die(Einit); } incurses=1; /* BSD curses doesn't have key recognition, so we have to print out the keypad_xmit sequence manually, and register some of the keys from termcap for our own key-recogniser. */ if(KS)fputs(KS,stdout),fflush(stdout); #define newkey(esc,key) if(esc)addkey(esc,strlen(esc),key,0) newkey(KD,KEY_DOWN); newkey(KU,KEY_UP); newkey(KL,KEY_LEFT); newkey(KR,KEY_RIGHT); newkey(K1,KEY_F(1)); newkey(K2,KEY_F(2)); newkey(K3,KEY_F(3)); newkey(K4,KEY_F(4)); newkey(K5,KEY_F(5)); newkey(K6,KEY_F(6)); newkey(K7,KEY_F(7)); newkey(K8,KEY_F(8)); newkey(K9,KEY_F(9)); newkey(K0,KEY_F(10)); #undef newkey #endif nonl(); noecho(); raw(); leaveok(stdscr,0); refresh(); /* this makes sure we are in curses mode so that we can exit curses mode if we want (if you see what I mean) */ getwinsize(); oneline=xmalloc(width+1); usage=(int*)xmalloc((height+1)*sizeof(int)); while(1){ /* Entering at this point refreshes the whole file list. */ if(currentdir) /* change back to the original current dir */ if(chdir(dirs))emsg(0,"warning: couldn't chdir to",dirs); makelist(argc,argv); maxnamelen++; /* allow a space after the file name column */ newnamelen=0; dosort(sorttype); xpos=maxnamelen; ypos=0; insmode=0; delay=1; topfile=0; while(1){ /* entry here displays the screen & starts key processing */ if(!nfiles){ endcurses(); fprintf(stderr,"%s: no files to list\n",programname); die(Enofiles); } if(!incurses && delay){ fflush(stdout); fprintf(stderr,"%s: press Return to continue\n",programname); fgets(work,WORKLEN,stdin); } delay=0; #ifdef SYSVCURSES if(sizechanged)getkey(); /* this accepts any change of size */ #endif if(!incurses)restartcurses(); /* As the screen might have changed size, the following size logic is positioned here. */ if(newnamelen>=maxnamelen)maxnamelen=newnamelen+1; if(xpos=width)xpos=maxnamelen; if(ypos>=height)ypos=1; /* Now the screen is printed */ for(i=0;imaxnamelen)move(ypos,--xpos); else break; /* then delete... */ #ifdef KEY_DC case KEY_DC: #endif case KEY_DELRIGHT: delch(); p=files[usage[ypos]].cmd; i=xpos-maxnamelen; l=strlen(p); for(j=i;j255 || k==KEY_BAD || !isprint8(k) || xpos>=maxnamelen+MAXCOMMANDLEN){ beep(); break; } if(insmode)insch(k); else addch(k); i=xpos-maxnamelen; if(xposl)p[l++]=' '; if(insmode)for(j=l++;j>i;j--)p[j]=p[j-1]; p[i]=k; p[l+1]=0; } /* end of key switch */ } /* end of get-key loop */ if(k==KEY_RESIZE)continue; insmode=0; ncmds=0; /* this loop examines all typed input and executes the Unix commands */ for(i=1;usage[i]>-2;i++)docommand(i,1); /* next take care of a PF key defined to a string */ if(k>=KEY_F(1) && k<=KEY_F(20)){ if((p=pfkeys[k-KEY_F(1)].str)){ if(strlen(p)>=MAXCOMMANDLEN)p[MAXCOMMANDLEN]=0; strcpy(files[usage[ypos]].cmd,p); docommand(ypos,0); } /* next remember a PF key defined to a fulist command */ else if((i=pfkeys[k-KEY_F(1)].num)){ if(i==Fslash)i=Fposition+usage[ypos]; addcommand(i); } } /* next remember any other keypress implemented by a fulist command */ else if(k==KEY_NPAGE)addcommand(Ff); else if(k==KEY_PPAGE)addcommand(Fb); else if(k==KEY_HELP)addcommand(Fh); /* now do all the remembered commands */ dorefresh=0; for(i=0;i0 && topfile;j--,topfile--) if(files[topfile].dir!=files[topfile-1].dir)j--; if(j<0 || (j==0 && files[topfile].dir!=currentdir)) topfile++; } else if((topfile-=height-2)<0)topfile=0; if(commands[i]==Fb)ypos=1; else ypos=height-1; break; case Ff: /* moving forward is easy because of botfile */ topfile=botfile; ypos=1; break; case Fh: /* construct a help command and execute it */ p=getsynonym("type",4,&i); sprintf(work,"%s '%s'",p,manname); if(!(i&Xnodelay))delay=1; endcurses(); shell(work,0,0); break; case FQ: die(0); case Fr: dorefresh=1; break; case Ftop: topfile=0; ypos=1; break; default: /* all others are positioning or sorting commands */ if(commands[i]>=Fposition){ topfile=commands[i]-Fposition; ypos=1; } else{ dosort(commands[i]); topfile=0; ypos=1; } } xpos=maxnamelen; if(dorefresh)break; } /* end of wait-for-commands loop */ } /* end of list-files loop */ } /* This function interprets a line from the profile */ static void interpret(line,source) char *line; char *source; /* identification of the source for an error message */ { static int error=0; static int in_if=0; static int interpreting=1; static int done_if=0; int i; int c,k; int l; int flags; char *esc; char *name; int nlen; int flen; if(!line){ /* check for errors */ if(in_if){ error=1; fprintf(stderr,"%s: %s: unterminated 'term' directive\n", programname,source); } if(error)die(Eprofile); return; } while(line[0] && isspace8(line[0]))line++; /* skip space */ if(!line[0] || line[0]=='#')return; /* ignore blank lines */ for(i=0;line[i] && !isspace8(line[i]);i++);/* find end of first word */ c=cmd_to_num(line,i); while(line[i] && isspace8(line[i]))i++; /* find second word */ line+=i; switch(c){ /* check for missing parameters */ case Cterm: case Cescchar: case Callow: case Ckey: case Csynonym: if(!line[0]){ error=1; fprintf(stderr,"%s: %s: missing parameter\n",programname,source); return; } } /* if interpreting, interpret the command. The term and eterm commands are always interpreted. */ if(c==Cterm || c==Ceterm || interpreting)switch(c){ case Cterm: if(in_if){ error=1; interpreting=1; fprintf(stderr,"%s: %s: unterminated 'term' directive\n", programname,source); } in_if=1; /* "term *" means continue if no previous "term" matched. */ if(line[0]=='*' && (!line[1] || isspace8(line[1]))){ interpreting=!done_if; return; } /* otherwise compare each word with the terminal name */ l=strlen(term); i=0; while(line[i]){ if(!strncmp(term,line+i,l) && (!line[i+l] || isspace8(line[i+l]))){ done_if=1; return; } while(line[i] && !isspace8(line[i]))i++; /* skip each word */ while(line[i] && isspace8(line[i]))i++; } interpreting=0; return; case Ceterm: if(!in_if){ error=1; fprintf(stderr,"%s: %s: extra 'eterm' directive\n", programname,source); } in_if=0; interpreting=1; return; case Cescchar: escchar=line[0]; if(line[1] && !isspace8(line[1])){ error=1; fprintf(stderr,"%s: %s: parameter should be a single character\n", programname,source); } return; case Callow: case Cshell: /* both have on|off parameter so interpret together */ if(!strncmp(line,"on",2))i=1; else if(!strncmp(line,"off",3))i=0; else { error=1; fprintf(stderr,"%s: %s: parameter should be 'on' or 'off'\n", programname,source); } if(c==Callow)allowslash=i; else useshell=i; return; case Ckey: for(i=0;line[i] && !isspace8(line[i]);i++); esc=get_escape(line,i,&l); /* convert escape sequence */ if(!esc){ error=1; fprintf(stderr,"%s: %s: escape sequence too long or malformed\n", programname,source); return; } while(line[i] && isspace8(line[i]))i++; line+=i; if(!line[0]){ error=1; fprintf(stderr,"%s: %s: missing parameter\n",programname,source); return; } for(i=0;line[i] && !isspace8(line[i]);i++); k=key_to_num(line,i); /* convert key name */ if(!k){ error=1; fprintf(stderr,"%s: %s: invalid key name\n",programname,source); return; } addkey(esc,l,k,1); return; case Csort: /* collect a fulist sort command */ i=fulist_cmd(line); if(i<0)i= -i; if(!Fissort(i)){ error=1; fprintf(stderr,"%s: %s: invalid sort command\n",programname,source); return; } sorttype=i; break; case Csynonym: flags=0; /* First collect any flags that might be present */ if(line[0]=='-')while(*++line && !isspace8(line[0]))switch(line[0]){ case 'n': flags|=Xnodelay; break; case 'r': flags|=Xrename; break; case 'p': flags|=Xprotect; break; case 'z': flags|=Xcompress; break; case 'Z': flags|=Xuncompress; break; case 'g': flags|=Xgzip; break; case 'G': flags|=Xgunzip; break; case 'o': flags|=Xoutput; break; default: error=1; fprintf(stderr,"%s: %s: invalid flag letter '%c'\n", programname,source,line[0]); return; } while(line[0] && isspace8(line[0]))line++; /* skip spaces */ name=line; /* collect the name */ for(nlen=0;name[nlen] && !isspace8(name[nlen]);nlen++); line+=nlen; while(line[0] && isspace8(line[0]))line++; /* skip spaces */ l=0; /* now attempt to collect a number */ for(i=0;isdigit8(line[i]);i++)l=l*10+line[i]-'0'; if(l==0 || (line[i] && !isspace8(line[i])))l=nlen; /* unsuccessful */ else line+=i; /* successful - skip the number */ if(l>nlen){ /* test the number */ error=1; fprintf(stderr,"%s: %s: number %d exceeds the length of the name\n", programname,source,l); return; } while(line[0] && isspace8(line[0]))line++; /* skip spaces */ flen=strlen(line); while(flen>0 && isspace8(line[flen-1]))flen--;/* remove trailing spaces*/ if(!flen){ /* if anything was missing flen will be zero. */ error=1; fprintf(stderr,"%s: %s: mising parameter\n",programname,source); return; } addsynonym(name,nlen,l,flags,line,flen); /* at last! */ break; default: if(c>=Cf1 && c=KEY_F(1) && k<=KEY_F(20)){ error=1; fprintf(stderr,"%s: %s: Can't map F key to another F key\n", programname,source); return; } if(k==0){ k=fulist_cmd(line); if(k==Fo)k=0; } if(k>0){ pfkeys[c].num=k; if(pfkeys[c].str)free(pfkeys[c].str); pfkeys[c].str=0; return; } pfkeys[c].num=0; if(pfkeys[c].str)free(pfkeys[c].str); for(i=strlen(line);isspace8(line[i-1]);i--); pfkeys[c].str=xmalloc(i+1); memcpy(pfkeys[c].str,line,i); pfkeys[c].str[i]=0; return; } /* Any other command is an error */ error=1; fprintf(stderr,"%s: %s: invalid command\n",programname,source); return; } } /* This function looks a synonym up and returns its full name */ static char *getsynonym(name,len,flags) char *name; int len; int *flags; { int i; for(i=0;i=synonyms[i].abbr && !strncmp(synonyms[i].name,name,len)){ *flags=synonyms[i].flags; return synonyms[i].fullname; } } *flags=0; return 0; } static unsigned hashfn(string) /* A hash function */ char *string; { register unsigned i=0; while(*string)i+=(i<<3)+*string++; return i%HASHSIZE; } static void *search(name,exist) /* Search for a name in the hash table */ char *name; /* if exist=1, the result is a pointer to */ int *exist; /* the item; if exist=0 the result is a */ /* "next" field where the new item would */ { /* be inserted */ int h=hashfn(name); struct hash **i=&hashtable[h]; struct hash *j; int c; if(!(j=*i)) return *exist=0,(void *)i; /* No elements in this bucket */ while((c=strcmp(name,j->name))){ /* stop when correct element found */ if(c<0) return *exist=0,(void *)i; /* gone too far down the list */ i=&(j->next); if(!(j=*i)) return *exist=0,(void *)i; /* no next element in list */ } return *exist=1,(void *)j; } static char *locate(name) /* Locate the executable file "name" */ char *name; { char *path=getenv("PATH"); void *hash; int exist; int dot=0; int i; struct hash *new,**old; char *ans; static char test[MAXPATHLEN+1]; if(!strchr(name,'/')){ /* only search if the name has no '/'s */ hash=search(name,&exist); /* first search the hash table */ if(!exist&&path){ while(path[0]){ /* then search the path */ for(i=0;(test[i]=path[0])&&path++[0]!=':';i++); /* Copy next dir */ if(i==1&&test[0]=='.'){dot=1;continue;} /* Test for "." */ test[i]='/'; strcpy(test+i+1,name); /* add slash and name */ if(!access(test,X_OK)){ /* if it is executable... */ new=(struct hash *) /* make a new hash item */ xmalloc(sizeof(struct hash)+strlen(name)+strlen(test)+1); old=(struct hash **)hash; /* this points to previous link field */ new->next=*old; *old=new; new->dot=dot; new->data=strlen(name)+1; strcpy(new->name,name); strcpy(new->name+new->data,test); exist=1; hash=(void*)new; break; } } /* If the command was not found but a "." was skipped over, and if the command is in ".", then record a not-found entry with dot==1 */ if(dot && !exist && !access(name,X_OK)){ new=(struct hash *)xmalloc(sizeof(struct hash)+strlen(name)+1); old=(struct hash **)hash; new->next=*old; *old=new; new->dot=1; new->data=strlen(name)+1; strcpy(new->name,name); new->name[new->data]=0; exist=1; hash=(void*)new; return name; } } if(exist){ /* Now, if the hash item was found or newly created, use it */ new=(struct hash *)hash; ans=new->name+new->data; if(new->dot&&!access(name,X_OK)) /* If "." came in the path before */ return name; /* the named directory, check "." */ if(ans[0])return ans; /* first, then return the stored */ } /* name. */ } return 0; /* if the name contains '/' or wasn't found in the path, return "not found". The caller will usually then just use the name unchanged. */ } /* This function maps a profile command to a number */ static int cmd_to_num(cmd,len) char *cmd; int len; { int i; static struct {char *word; int abbr; int num;} table[]={ {"alias", 5, Csynonym}, {"allowslash", 5, Callow}, {"escchar", 3, Cescchar}, {"eterm", 5, Ceterm}, {"key", 3, Ckey}, {"shell", 5, Cshell}, {"sorttype", 4, Csort}, {"synonym", 3, Csynonym}, {"term", 4, Cterm} }; if(cmd[0]=='f' || cmd[0]=='F'){ /* command might be "f1" etc */ if(len==2 && cmd[1]>='1' && cmd[1]<='9') return Cf1+cmd[1]-'1'; if(len==3 && cmd[1]=='1' && cmd[2]>='0' && cmd[2]<='9') return Cf1+9+cmd[2]-'0'; if(len==3 && cmd[1]=='2' && cmd[2]=='0') return Cf1+19; } for(i=0;i=table[i].abbr && !strncasecmp(cmd,table[i].word,len)) return table[i].num; return 0; } /* This function maps a key name to its number */ static int key_to_num(key,len) char *key; int len; { int i; static struct {char *word; int abbr; int num;} table[]={ {"BACKSPACE", 6, KEY_DELLEFT}, {"BACKTAB", 7, KEY_BTAB}, {"DELETE", 3, KEY_DELRIGHT}, {"DELLEFT", 4, KEY_DELLEFT}, {"DELRIGHT", 4, KEY_DELRIGHT}, {"DOWN", 4, KEY_DOWN}, {"EOL", 3, KEY_EOL}, {"ERASEEOL", 5, KEY_EOL}, {"HELP", 4, KEY_HELP}, {"INSERT", 3, KEY_IC}, {"LEFT", 4, KEY_LEFT}, {"PAGEDN", 5, KEY_NPAGE}, {"PAGEUP", 5, KEY_PPAGE}, {"REDRAW", 3, KEY_REDRAW}, {"RIGHT", 5, KEY_RIGHT}, {"TAB", 3, KEY_TAB}, {"UP", 2, KEY_UP} }; if(key[0]=='f' || key[0]=='F'){ if(len==2 && key[1]>='1' && key[1]<='9') return KEY_F(key[1]-'0'); if(len==3 && key[1]=='1' && key[2]>='0' && key[2]<='9') return KEY_F(key[2]-'0'+10); if(len==3 && key[1]=='2' && key[2]=='0') return KEY_F(20); } for(i=0;i=table[i].abbr && !strncasecmp(key,table[i].word,len)) return table[i].num; return 0; } static int hexdigit(c)/* convert character into hex digit or >15 if invalid */ char c; { if(c>='0' && c<='9')return c-'0'; if(c>='A' && c<='F')return c-'A'+10; if(c>='a' && c<='f')return c-'a'+10; return 16; } /* This function interprets an escape sequence */ static char *get_escape(esc,len,anslen) char *esc; int len; int *anslen; { static char ans[MAXESCLEN]; int i,j; int c,d; i=j=0; while(i=len)return 0; /* two hex digits follow */ c=hexdigit(esc[i++]); d=hexdigit(esc[i++]); if(c>15 || d>15)return 0; ans[j++]=c*16+d; break; default: i--; if(esc[i]>='0' && esc[i]<='7'){ /* 3 octal digits follow */ if(i+2>=len)return 0; c=esc[i++]-'0'; d=esc[i++]-'0'; if(d<0 || d>7)return 0; c=c*8+d; d=esc[i++]-'0'; if(d<0 || d>7)return 0; ans[j++]=c*8+d; } else ans[j++]=esc[i++]; /* literal next char */ } } if(ielem) static int sort_sb(s1,s2) char *s1,*s2; { char c1=getentry(s1,name)[0],c2=getentry(s2,name)[0]; if(!c1)return c2 ? 1 : 0; if(!c2)return -1; return getentry(s2,size)-getentry(s1,size); } static int sort_sd(s1,s2) char *s1,*s2; { char c1=getentry(s1,name)[0],c2=getentry(s2,name)[0]; if(!c1)return c2 ? 1 : 0; if(!c2)return -1; return getentry(s2,time)-getentry(s1,time); } static int sort_sf(s1,s2) char *s1,*s2; { int d1=getentry(s1,dir); int d2=getentry(s2,dir); char c1=getentry(s1,name)[0],c2=getentry(s2,name)[0]; if(!c1)return c2 ? 1 : 0; if(!c2)return -1; if(d1!=d2)return strcmp(dirs+d1*MAXDIRLEN,dirs+d2*MAXDIRLEN); return strcmp(getentry(s1,name),getentry(s2,name)); } static int sort_sn(s1,s2) char *s1,*s2; { int i; char c1=getentry(s1,name)[0],c2=getentry(s2,name)[0]; if(!c1)return c2 ? 1 : 0; if(!c2)return -1; i=strcmp(getentry(s1,name),getentry(s2,name)); if(i)return i; return strcmp(dirs+getentry(s1,dir)*MAXDIRLEN,dirs+getentry(s2,dir)*MAXDIRLEN); } static int sort_st(s1,s2) char *s1,*s2; { char *t1=getentry(s1,name); char *t2=getentry(s2,name); char *t; int i; char c1=getentry(s1,name)[0],c2=getentry(s2,name)[0]; if(!c1)return c2 ? 1 : 0; if(!c2)return -1; if((t=strchr(t1,'.')))t1=t+1; else t1=""; if((t=strchr(t2,'.')))t2=t+1; else t2=""; i=strcmp(t1,t2); if(i)return i; else return sort_sn(s1,s2); } static int sort_srb(s1,s2) char *s1,*s2; { char c1=getentry(s1,name)[0],c2=getentry(s2,name)[0]; if(!c1)return c2 ? 1 : 0; if(!c2)return -1; return getentry(s1,size)-getentry(s2,size); } static int sort_srd(s1,s2) char *s1,*s2; { char c1=getentry(s1,name)[0],c2=getentry(s2,name)[0]; if(!c1)return c2 ? 1 : 0; if(!c2)return -1; return getentry(s1,time)-getentry(s2,time); } static int sort_srf(s1,s2) char *s1,*s2; { int d1=getentry(s1,dir); int d2=getentry(s2,dir); char c1=getentry(s1,name)[0],c2=getentry(s2,name)[0]; if(!c1)return c2 ? 1 : 0; if(!c2)return -1; if(d1!=d2)return strcmp(dirs+d2*MAXDIRLEN,dirs+d1*MAXDIRLEN); return strcmp(getentry(s2,name),getentry(s1,name)); } static int sort_srn(s1,s2) char *s1,*s2; { int i; char c1=getentry(s1,name)[0],c2=getentry(s2,name)[0]; if(!c1)return c2 ? 1 : 0; if(!c2)return -1; i=strcmp(getentry(s2,name),getentry(s1,name)); if(i)return i; return strcmp(dirs+getentry(s2,dir)*MAXDIRLEN,dirs+getentry(s1,dir)*MAXDIRLEN); } static int sort_srt(s1,s2) char *s1,*s2; { char c1=getentry(s1,name)[0],c2=getentry(s2,name)[0]; if(!c1)return c2 ? 1 : 0; if(!c2)return -1; return sort_st(s2,s1); } #undef getentry /* This function sorts the list of files according to "cmd" */ static void dosort(cmd) int cmd; { int (*sorter)()=NULL; switch(cmd){ case Fsb: sorter=sort_sb; dividers=0; break; case Fsd: sorter=sort_sd; dividers=0; break; case Fsf: sorter=sort_sf; dividers=1; break; case Fsn: sorter=sort_sn; dividers=0; break; case Fst: sorter=sort_st; dividers=0; break; case Fsrb: sorter=sort_srb; dividers=0; break; case Fsrd: sorter=sort_srd; dividers=0; break; case Fsrf: sorter=sort_srf; dividers=1; break; case Fsrn: sorter=sort_srn; dividers=0; break; case Fsrt: sorter=sort_srt; dividers=0; break; default:; /* not reached (we hope!) */ } qsort((char *)files,nfiles,sizeof files[0],sorter); /* If any blank files went to the bottom, remove them */ while(nfiles>0 && files[nfiles-1].name[0]==0)nfiles--; } /* This function tests for a fulist modifier and returns its number if any. The escape character is assumed to have been already skipped. */ static int getmodifier(line,len) char *line; int *len; { int ans=Fslash; char *p=line; char ch; for(;(ch=p[0]);p++){ if(ch=='d' && !(ans&Fd))ans|=Fd; else if(ch=='n' && !(ans&Fn))ans|=Fn; else if(ch=='t' && !(ans&Ft))ans|=Ft; else break; } if(line[-1]==escchar){ /* command is always allowed if started by */ *len=p-line; /* the true escape character */ return ans; } if(isalnum8(ch) || ch=='-' || ch=='+' || ch=='_' || ch=='@' || ch=='\\'){ *len=0; return 0; /* command is not allowed if alphanumeric or -+_@\ follows */ } *len=p-line; return ans; } /* This function copies a string (like memcpy) but expands each single-quote character into '\'' (as this function is always invoked within quotes). Returns the number of characters copied. */ static int qmemcpy(dst,src,len) char *dst; char *src; int len; { int i=0, j=0; char c; if (len==0) len=strlen(src); while (len--) { c=dst[i++]=src[j++]; if (c=='\'') { dst[i++]='\\'; dst[i++]=c; dst[i++]=c; } } return i; } /* This function inserts a file name into a command to be executed */ static void insertname(file,buffer,flag) int file; /* number of the file whose name is being inserted */ char *buffer; /* where to insert */ int flag; /* which modifiers to insert */ { struct details *d=&files[file]; char *name,*type; int nlen; if(flag==Fslash){ /* insert directory or not */ if(currentdir!=d->dir)flag=Fd|Fn|Ft; else flag=Fn|Ft; } if(flag&Fd){ /* insert directory */ buffer+=qmemcpy(buffer,dirs+(d->dir)*MAXDIRLEN,0); if((flag&(Fn|Ft)) && buffer[-1]!='/')buffer++[0]='/'; } name=d->name; type=strchr(name,'.'); if(type)nlen=type++ -name; else nlen=strlen(name); if(flag&Fn){ /* insert name */ if (nlen) buffer+=qmemcpy(buffer,name,nlen); if((flag&Ft) && type)buffer++[0]='.'; } if((flag&Ft) && type){ /* insert type */ buffer+=qmemcpy(buffer,type,0); } buffer[0]=0; } /* This function interprets any command typed in on screen line "line". A unix command will be executed, while a fulist command will be remembered for later. */ static void docommand(line,save) int line; int save; /* if nonzero, this command will be remembered by '=' and '?'. */ { static char lastcommand[MAXCOMMANDLEN]; int i,l; int rc; int omit=0; int c=usage[line]; char *cmd; char *pos; char *p; char ch; int rlen; int flags=0; int special=0; char quote=0; char bquote=0; int lastletter=0; if(c<0)return; cmd=files[c].cmd; if(cmd[0]=='*'){ /* no command */ cmd[0]=0; return; } /* Find the first nonblank character */ for(pos=cmd;pos[0] && isspace8(pos[0]);pos++); if(!pos[0]){ /* no command */ cmd[0]=0; return; } if(pos[0]=='?'){ /* get last command back */ strcpy(cmd,lastcommand); return; } if(pos[0]=='='){ /* point to the last command */ save=0; for(pos=lastcommand;pos[0] && isspace8(pos[0]);pos++); if(!pos[0]){ cmd[0]='*'; return; } } for(i=strlen(cmd);i>0 && isspace8(cmd[i-1]);i--); cmd[i]=0; /* strip off trailing blanks */ if(save)strcpy(lastcommand,cmd);/* save for future '=' and '?' commands */ i=fulist_cmd(pos); if(i<0 && pos[0]==escchar){ /* invalid fulist command */ cmd[0]='*'; if(!cmd[1])cmd[2]=0; cmd[1]='?'; return; } if(i==Fo){ /* skip the \o command but set omit=1 */ omit=1; for(pos+=2;pos[0] && isspace8(pos[0]);pos++); } else if(i>0) { /* remember the fulist command and return */ cmd[0]='*'; if(i==Fslash)i=Fposition+usage[line]; addcommand(i); return; } if(!files[c].name[0] && !omit){ /* no command on an empty file */ cmd[0]='*'; if(!cmd[1])cmd[2]=0; cmd[1]='?'; return; } /* Construct the command to be executed in a work area. */ /* first expand any synonym at the start of the command. */ for(i=0;pos[i] && !isspace8(pos[i]);i++); if((p=getsynonym(pos,i,&flags))){ strcpy(work2,p); strcat(work2,pos+i); pos=work2; } /* copy characters, taking care of special characters and escapes */ p=work; while(pos[0]){ ch=pos++[0]; /* Quoting madness... if we are in simple quotes then "quote" contains the quote character. If we are in back-quotes then "bquote" contains either '`' or '"' (the latter being when the back-quotes were inside double-quotes) and "quote" contains either zero or "'" (the latter being when we are inside single quotes within the back-quotes). Shell seems to have weird quoting behaviour when quotes are not properly nested so I ignore that and treat a matching quote as always ending the quotation. */ if(ch==quote)quote=0; else switch(ch){ case '`': if(bquote=='`')bquote=quote=0; else if(bquote)quote=bquote,bquote=0; else if(quote=='"')special=bquote=quote,quote=0; else if(!quote)special=bquote=ch; break; case '"': if(ch==bquote)quote=bquote=0; else if(!quote)quote=ch; break; case '\'': if(!quote)quote=ch; break; case '#': case '^': /* these chars need quoting as shell thinks they are special */ if(quote!='\''){ if(quote)p++[0]=quote; p++[0]='\''; p++[0]=ch; p++[0]='\''; if(quote)p++[0]=quote; ch=0; } break; case '*': case '(': case ')': case '>': case '<': case '&': case '|': case ';': case '?': case '[': case '{': case '}': /* these chars are special unless quoted */ if(!quote)special=1; break; case '$': /* this char is special unless single-quoted */ if(quote!='\'')special=1; break; } if(ch==escchar || (ch=='/' && allowslash && !lastletter)){ /* this may be a modifier or an escaped special character */ i=getmodifier(pos,&rlen); if(i==Fslash && ch==escchar && pos[0] && strchr("\\\"\'`#^*()<>&|;?][{}$/%",pos[0])) i=0; /* it is an escaped special character */ if(i){ /* it is a modifier */ pos+=rlen; omit=1; lastletter=1; if(quote!='\''){ if(quote)p++[0]=quote; p++[0]='\''; } insertname(c,p,i); p+=strlen(p); if(quote!='\''){ p++[0]='\''; if(quote)p++[0]=quote; } ch=0; } else if(ch==escchar){ /* it is an escaped special character */ ch=pos++[0]; /* (this is never the end of the string) */ if(ch=='\''){ /* special treatment for quote character */ if (quote!='\"'){ /* doesn't need quoting in double quotes */ if (quote) p++[0]=quote; /* end quotes */ p++[0]='\\'; /* escape */ p++[0]=ch; /* place the literal quote */ ch=0; if (quote) p++[0]=quote; /* begin quotes */ } } else if(quote!='\''){/* needs quoting if not in single quotes */ if(quote)p++[0]=quote; p++[0]='\''; p++[0]=ch; p++[0]='\''; if(quote)p++[0]=quote; ch=0; lastletter=0; } } } lastletter=0; if(!ch)continue; /* this char has been done already */ if(isalnum8(ch) || ch=='-' || ch=='+' || ch=='_' || ch=='@')lastletter=1; p++[0]=ch; } if(!omit){ p++[0]=' '; if(quote!='\''){ if(quote)p++[0]=quote; p++[0]='\''; } insertname(c,p,Fslash); p+=strlen(p); if(quote!='\''){ if(quote)p++[0]=quote; p++[0]='\''; } } p[0]=0; newname=0; if(flags&Xoutput)endcurses(); rc=shell(work,special,flags); if(!incurses && !(flags&Xnodelay))delay=1; cmd[0]='*'; if(rc){ /* print the return code then copy to the command line */ if(rc==-1)work2[0]='?',work2[1]=0; else sprintf(work2,"%d",rc); i=0; for(l=0;work2[l];l++){ if(!cmd[l+1])i=1; /* remember if a nul is overwritten */ cmd[l+1]=work2[l]; } if(i)cmd[l+1]=0; } /* See what happened to the file */ p=files[c].name; rc=!p[0] || statfile(c,0);/* (rc==1 means the file still exists) */ if(flags && !rc){ /* it disappeared, so it might have been renamed */ l=strlen(p); if(flags&Xcompress){ /* try with ".Z" */ strcpy(p+l,".Z"); rc=statfile(c,0); } if(!rc && (flags&Xgzip)){ /* try with ".gz" or ".z" */ strcpy(p+l,".gz"); rc=statfile(c,0); if(!rc){ strcpy(p+l,".z"); rc=statfile(c,0); } } if(!rc && (flags&Xuncompress) && /* try with .Z removed */ l>2 && p[l-1]=='Z' && p[l-2]=='.'){ p[l-2]=0; rc=statfile(c,0); if(!rc)p[l-2]='.'; } if(!rc && (flags&Xgunzip) && /* try with .z or .gz removed */ l>1 && p[l-1]=='z'){ /* mimic old & new versions of gunzip: */ if(l>3 && p[l-2]=='g' && p[l-3]=='.'){ /* new version removes .gz */ p[l-3]=0; rc=statfile(c,0); if(!rc)p[l-3]='.'; } if(rc && l>2 && p[l-2]=='.'){ /* old & new both remove .z */ p[l-2]=0; rc=statfile(c,0); if(!rc)p[l-2]='.'; } if(rc && p[l-2]!='.'){ /* old removes any trailing z */ ch=p[l-1]; p[l-1]=0; rc=statfile(c,0); if(!rc)p[l-1]=ch; } } if(!rc && (flags&Xrename) && newname && /* try under new name */ listfile(currentdir,newname)){ /* separate off leading dirs */ p=files[c].name; /* then copy new name here */ strcpy(p,files[nfiles-1].name); files[c].dir=files[nfiles-1].dir; nfiles--; rc=statfile(c,0); } if(rc && strlen(p)>newnamelen)newnamelen=strlen(p); } if(!rc)p[0]=0; /* if file is not found, remove it from display */ if(incurses){ /* if the display is still up, update the display */ printfile(c,oneline,width); mvaddstr(line,0,oneline); refresh(); } } /* This function executes a command. The command buffer will be corrupted. */ static int shell(cmd,special,flags) char *cmd; int special,flags; { static char **argv=0; static int maxargs=0; fd_set readfds; int status; int rc=0; static struct stat st; int argc,i,j; int pid; int tty; int fileflags=0; char quote; char *name; char ch; struct timeval tv; if(!maxargs)argv=(char **)xmalloc((maxargs=32)*sizeof(char*)); if(useshell && special){ /* the command requires a shell */ argv[0]=name="/bin/sh"; argv[1]="-c"; argv[2]=cmd; argv[3]=0; newname=0; /* all bets are off regarding what the shell might do */ } else { /* parse the command into arguments */ while(cmd[0] && isspace8(cmd[0]))cmd++; for(argc=0;cmd[0];argc++){ for(quote=i=j=0;(ch=cmd[i]) && (quote || !isspace8(cmd[i]));i++){ if(ch==quote)quote=0; else if(ch=='\'' || ch=='\"')quote=ch; else if(!quote && ch=='\\' && cmd[i+1]) cmd[j++]=cmd[++i]; else cmd[j++]=ch; } if(cmd[i])i++; cmd[j]=0; if(argc==maxargs-1){ argv=(char **)realloc((char *)argv,(maxargs+=32)*sizeof(char *)); if(!argv)emsg(Emem,Mrealloc,(char *)0); } argv[argc]=cmd; for(cmd+=i;cmd[0] && isspace8(cmd[0]);cmd++); } argv[argc]=0; name=locate(argv[0]); if(!name)name=argv[0]; if(flags&Xprotect){ /* the command must have two parameters of which the second must either not exist or be a directory */ if(argc!=3)return -1; if(!stat(argv[2],&st)){ if(!S_ISDIR(st.st_mode))return -1; } /* store the second non-directory parameter of a rename command */ else if((flags&Xrename) && argc==3)newname=argv[2]; } /* store the second parameter of a rename command */ else if((flags&Xrename) && argc==3)newname=argv[2]; } /* Execute the command */ if(incurses){ /* prepare to run command in a pty in order to monitor output */ #ifdef sgi /* SGI needs a new pty for each command */ ptyname=_getpty(&ptyfd,O_RDWR|O_NDELAY,0600,0); if(!ptyname){ endcurses(); fprintf(stderr,"%s: warning: couldn't get a pty",programname); } #endif #ifdef Solaris /* This may or may not be needed each time */ unlockpt(ptyfd); #endif fileflags=fcntl(0,F_GETFL,0); if(fileflags==-1 || fcntl(0,F_SETFL,fileflags|O_NDELAY)==-1) emsg(0,"warning: couldn't make stdin non-blocking",(char*)0); } /* if an error occurred, we are no longer in curses mode. Otherwise continue */ if(incurses){ signal(SIGCHLD,emptyfunction); /* this makes sure the SIGCHLD interrupts the select(). */ if((pid=fork())<0){ emsg(0,"couldn't perform fork",(char*)0); return -1; } if(!pid){ /* child */ close(ptyfd); close(0); incurses=0; if(setsid()==-1){ emsg(0,"couldn't perform setsid",(char*)0); _exit(-1); } if((tty=open(ptyname,O_RDWR))<0){ emsg(0,"couldn't open",ptyname); _exit(-1); } #ifdef Solaris /* Solaris wants these modules pushing each time */ ioctl(tty, I_PUSH, "ptem"); ioctl(tty, I_PUSH, "ldterm"); #endif #ifdef __osf__ /* OSF/1 requires an ioctl as well as a setsid */ if(ioctl(tty,TIOCSCTTY,0)) emsg(0,"couldn't set controlling terminal",(char*)0); #endif set_terminal_now(tty,&shellmode); winsz.ws_row=height+correction; winsz.ws_col=width; ioctl(tty,TIOCSWINSZ,&winsz); close(1); close(2); if(dup(tty)<0 || dup(tty)<0 || (tty>2 && dup(tty)<0)){ emsg(0,"couldn't dup tty descriptor",(char*)0); _exit(-1); } if(tty>2)close(tty); execv(name,argv); emsg(0,"couldn't execute",name); _exit(-1); } /* parent */ FD_ZERO(&readfds); while(1){ FD_SET(0,&readfds); FD_SET(ptyfd,&readfds); if(waitpid(pid,&status,WNOHANG)>0)break; if(select(FD_SETSIZE,&readfds,(fd_set*)0,(fd_set*)0,(struct timeval*)0)<0){ if(errno!=EINTR){ emsg(0,"select call failed",(char*)0); rc=-1; break; } #ifdef SYSVCURSES else if(winchanged && (winchanged=0,getwinsize())){ /* tell the new win size to the pty */ sizechanged=1; if((tty=open(ptyname,O_RDWR))>=0){ winsz.ws_row=height+correction; winsz.ws_col=width; ioctl(tty,TIOCSWINSZ,&winsz); close(tty); } } #endif else continue; } if(FD_ISSET(ptyfd,&readfds)) if((i=read(ptyfd,work2,WORKLEN))>0){ if(incurses){ /* end curses but keep raw no-echo mode because */ endcurses(); /* stdin is still being piped verbatim to the pty */ memcpy((char*)&rawmode,(char*)&shellmode,sizeof rawmode); rawmode.c_iflag=0; rawmode.c_oflag &= ~OPOST; rawmode.c_lflag=0; rawmode.c_cc[VMIN]=1; rawmode.c_cc[VTIME]=0; set_terminal(0,&rawmode); } blkwrite(1,work2,i); } if(FD_ISSET(0,&readfds)) if((i=read(0,work2,WORKLEN))>0)blkwrite(ptyfd,work2,i); } if(rc==0)rc=WIFSTOPPED(status)?1000+WSTOPSIG(status): WIFSIGNALED(status)?1000+WTERMSIG(status): (char)WEXITSTATUS(status); /* If any data is left in the pty, print it */ /* Note: Solaris blocks on read if we have already got to EOF so use a non-blocking select */ FD_ZERO(&readfds); FD_SET(ptyfd,&readfds); while (1) { tv.tv_sec=0; tv.tv_usec=0; if (select(FD_SETSIZE,&readfds,(fd_set*)0,(fd_set*)0,&tv)<0 || !FD_ISSET(ptyfd,&readfds)) break; if ((i=read(ptyfd,work2,WORKLEN))>0) { if(incurses)endcurses(); blkwrite(1,work2,i); } else break; } #ifdef sgi close(ptyfd); #endif fcntl(0,F_SETFL,fileflags&~O_NDELAY); if(!incurses)set_terminal(0,&shellmode); } else { /* run the command not in a pty */ if((pid=fork())<0){ emsg(0,"couldn't perform fork",(char*)0); return -1; } if(!pid){ /* child */ close(ptyfd); execvp(name,argv); emsg(0,"couldn't execute",name); _exit(-1); } /* parent */ while(1){ if(waitpid(pid,&status,0)>0){ rc=WIFSTOPPED(status)?1000+WSTOPSIG(status): WIFSIGNALED(status)?1000+WTERMSIG(status): (char)WEXITSTATUS(status); break; } } } return rc; } /* This function is like write() but always writes the whole block. */ static void blkwrite(fd,buf,nbyte) int fd,nbyte; char *buf; { int i; fd_set writefd; while(nbyte>0){ i=write(fd,buf,nbyte); if(i==-1 && errno!=EWOULDBLOCK && errno!=EAGAIN)return;/* permanent error */ if(!(nbyte-=i))return; /* write completed */ buf+=i; FD_ZERO(&writefd); FD_SET(fd,&writefd); if(select(FD_SETSIZE,(fd_set*)0,&writefd,(fd_set*)0,(struct timeval*)0)<0 && errno!=EINTR) return; /* some sort of error */ } } /* This function makes a list of files. */ static void makelist(argc,argv) int argc; char **argv; { int i; int statted; int follow=followlinks; nfiles=0; ndirs=1; statted=0; maxnamelen=0; if(argc==2)followlinks=1; /* always follow on first time */ if(argc==1)listdir(0,1); /* for no args list current directory */ else { /* else go through args twice */ char *used=xmalloc(argc); for(i=1;i=0){ /* if single directory given as */ if(chdir(dirs+lastdir*MAXDIRLEN)) /* argument then try to chdir it */ emsg(0,"warning: couldn't chdir to",dirs+lastdir*MAXDIRLEN); else currentdir=lastdir; } free(used); } /* make sure each file is statted & list recursively */ while(stattedMAXNAMELEN){ endcurses(); fprintf(stderr,"%s: warning: file name %s is too long\n", programname,basename); return 0; } namelen=strlen(basename); if(!namelen) /* a null name arises from, e.g. "/tmp/", which means "/tmp/." */ files[nfiles].name[0]='.',files[nfiles].name[namelen=1]=0; else memcpy(files[nfiles].name,basename,namelen+1); files[nfiles].dir=dir; files[nfiles].cmd[0]=0; nfiles++; if(namelen>maxnamelen)maxnamelen=namelen; return 1; } /* Add a new directory name to the list or search for an existing one */ static int adddir(dirno,name,len) int dirno,len; char *name; { int i; int newlen=0; char *newdir; if(ndirs==maxdirs){ maxdirs+=10; dirs=realloc(dirs,maxdirs*MAXDIRLEN); if(!dirs)emsg(Emem,Mrealloc,(char *)0); } if(len && name[len-1]=='/')len--; /* deduct a trailing '/' */ if(len){ if(name[0]=='.') {/* deduct a "/./" from the middle or end of a path */ if(!name[1])return dirno; else if(name[1]=='/')name+=2,len-=2; } if(name[0]!='/')newlen=1+strlen(dirs+dirno*MAXDIRLEN); if(len+newlen>=MAXDIRLEN){ endcurses(); fprintf(stderr,"%s: warning: path of file name %s is too long\n", programname,name); return -1; } } newdir=dirs+ndirs*MAXDIRLEN; if(!len){ newdir[0]='/'; newdir[1]=0; len++; } else if(name[0]=='/'){ memcpy(newdir,name,len); newdir[len]=0; } else { memcpy(newdir,dirs+dirno*MAXDIRLEN,newlen); if(newdir[newlen-2]=='/')newlen--; newdir[newlen-1]='/'; memcpy(newdir+newlen,name,len); newdir[newlen+len]=0; } for(i=0;idir)*MAXDIRLEN); memcpy(tmp,dirs+(d->dir)*MAXDIRLEN,s); if(tmp[s-1]!='/')tmp[s++]='/'; strcpy(tmp+s,d->name); if(followlinks)s=stat(tmp,&st); else s=lstat(tmp,&st); if(s && followlinks)s=lstat(tmp,&st); /* in case destination of a link doesn't exist */ if(s && !verbose)return 0; if(s){ emsg(0,"Can't stat",tmp); if(num==nfiles-1)nfiles--; else /* d->inode=d->mode=d->nlink=d->uidno=d->gidno=d->device=d->size =d->time=0, d->uid=d->gid=0, strcpy(d->date,"Xxx xx xxxx"); */ d->name[0]=0; /* this effectively erases the file from memory */ return 0; } d->inode=st.st_ino; d->mode=st.st_mode; d->nlink=st.st_nlink; d->uidno=uid=st.st_uid; d->gidno=gid=st.st_gid; d->device=st.st_rdev; d->size=st.st_size; d->time=t=timetype==use_mtime?st.st_mtime: timetype==use_ctime?st.st_ctime: st.st_atime; /* print the date */ tp=localtime(&d->time); if(tcurrent_time+3600) /* if older than 182 days (6 months) or younger than minus 1 hour */ sprintf(d->date,"%s %2d %4d",month[tp->tm_mon],tp->tm_mday,tp->tm_year+1900); else sprintf(d->date,"%s %2d %02d:%02d", month[tp->tm_mon],tp->tm_mday,tp->tm_hour,tp->tm_min); /* print the uid/gid if necessary */ if(numberuids)d->uid=d->gid=0; else{ uidp=uidtable[uid%UIDTABSIZE]; while(uidp && uidp->uidno!=uid && uidp->next)uidp=uidp->next; if(!uidp || uidp->uidno!=uid){ uidp2=(struct uidentry *)xmalloc(sizeof(struct uidentry)); if(uidp)uidp->next=uidp2; else uidtable[uid%UIDTABSIZE]=uidp2; uidp=uidp2; uidp->next=0; uidp->uidno=uid; pwd=getpwuid(uid); if(pwd)strncpy(uidp->uid,pwd->pw_name,MAXUIDLEN-1), uidp->uid[MAXUIDLEN-1]=0; else sprintf(uidp->uid,"%ld",(long)uid); } d->uid=uidp->uid; if(showgroup){ uidp=gidtable[gid%UIDTABSIZE]; while(uidp && uidp->uidno!=gid && uidp->next)uidp=uidp->next; if(!uidp || uidp->uidno!=gid){ uidp2=(struct uidentry *)xmalloc(sizeof(struct uidentry)); if(uidp)uidp->next=uidp2; else gidtable[gid%UIDTABSIZE]=uidp2; uidp=uidp2; uidp->next=0; uidp->uidno=gid; grp=getgrgid(gid); if(grp)strncpy(uidp->uid,grp->gr_name,MAXUIDLEN-1), uidp->uid[MAXUIDLEN-1]=0; else sprintf(uidp->uid,"%ld",(long)gid); } d->gid=uidp->uid; } } return 1; } /* List a directory, adding each file to the end of the list */ static void listdir(dir,showdotdot) int dir,showdotdot; { DIR *dirp; struct dirent *entry; dirp=opendir(dirs+dir*MAXDIRLEN); if(!dirp){ emsg(0,"Couldn't open",dirs+dir*MAXDIRLEN); return; } if(showdotdot)listfile(dir,".."); while((entry=readdir(dirp))){ if(entry->d_name[0]=='.'){ if(!allfiles || entry->d_name[1]==0 || (entry->d_name[1]=='.' && entry->d_name[2]==0)) continue; } listfile(dir,entry->d_name); } closedir(dirp); } /* Add a directory and then list it */ static void addlistdir(dir,name,showdotdot) int dir,showdotdot; char *name; { lastdir=adddir(dir,name,strlen(name)); if(lastdir<0)endcurses(), fprintf(stderr,"%s: - directory not listed\n",programname); else listdir(lastdir,showdotdot); } /* Print the details of a file. The given size does not include the terminating null of the details which will be printed into the buffer. */ static void printfile(file,buffer,size) int file,size; char *buffer; { struct details *fd=&files[file]; int len=strlen(fd->name); int showdev=0; int i; int c; /* print the file name, truncated or padded up to maxnamelen with spaces */ if(len>maxnamelen)len=maxnamelen; for(i=0;iname[i])?fd->name[i]:'?'; while(lencmd); if(c>commandlen)if(fd->cmd[0]=='*') c=commandlen; /* truncate commands which have been executed */ for(i=0;icmd[i]; while(lenname[0]){ /* print the inode */ if(leninode),len+=7; /* print the mode */ #define nxtchar if(lenmode)? 'd': S_ISBLK(fd->mode)?(showdev='b'): S_ISCHR(fd->mode)?(showdev='c'): S_ISLNK(fd->mode)? 'l': S_ISFIFO(fd->mode)? 'p': S_ISSOCK(fd->mode)? 's': '-'; nxtchar (fd->mode&S_IRUSR)?'r':'-'; nxtchar (fd->mode&S_IWUSR)?'w':'-'; nxtchar (fd->mode&S_ISUID)? ((fd->mode&S_IXUSR)?'s':'S'): ((fd->mode&S_IXUSR)?'x':'-'); nxtchar (fd->mode&S_IRGRP)?'r':'-'; nxtchar (fd->mode&S_IWGRP)?'w':'-'; nxtchar (fd->mode&S_ISGID)? ((fd->mode&S_IXGRP)?'s':'S'): ((fd->mode&S_IXGRP)?'x':'-'); nxtchar (fd->mode&S_IROTH)?'r':'-'; nxtchar (fd->mode&S_IWOTH)?'w':'-'; nxtchar (fd->mode&01000)? ((fd->mode&S_IXOTH)?'t':'T'): ((fd->mode&S_IXOTH)?'x':'-'); /* print the number of links */ if(lennlink),len+=3; /* print the uid */ if(lenuid)sprintf(buffer+len," %-8s",fd->uid),len+=9; else sprintf(buffer+len," %-8ld",(long)fd->uidno),len+=9; } /* print the gid */ if(lengid)sprintf(buffer+len," %-8s",fd->gid),len+=9; else sprintf(buffer+len," %-8ld",(long)fd->gidno),len+=9; } /* print the size or device number */ if(lensize),len+=9; if(lendevice/256,(long)fd->device&255), len+=9; /* print the date */ nxtchar ' '; for(i=0;i<12 && lendate[i]; } /* fill with spaces */ while(len255)return k; /* a key token from curses */ pre=0; for(i=0;i255){ /* a curses key has arrived. Let's ignore the other junk. */ bstart=bend=0; return k; } buffer[j++]=k; delay=0; } } buffer[j]=0; bstart=bend=0; /* assume all characters will be used up */ /* Test for an exact match */ for(i=0;iextrakeys[i].len && !memcmp(extrakeys[i].esc,buffer,extrakeys[i].len)){ bend=j; bstart=extrakeys[i].len; return extrakeys[i].key; } /* No match has been found. Check for escape+digit, then return KEY_BAD. */ if(buffer[0]==27 && j==2){ if(buffer[1]>='1' && buffer[1]<='9')return KEY_F(buffer[1]-'0'); else switch(buffer[1]){ case '0': return KEY_F(10); case '-': return KEY_F(11); case '=': return KEY_F(12); } } return KEY_BAD; } static char *xmalloc(size) size_t size; { register char *ans=malloc(size); if(ans)return ans; emsg(Emem,"Couldn't allocate memory",(char *)0); return 0; /* not reached */ } #ifndef SYSVCURSES static struct termios cursesmode; #endif /* This function ends curses, so we can print an error message and/or suspend/terminate the program */ static void endcurses() { if(!incurses)return; #ifndef SYSVCURSES if(KE)fputs(KE,stdout); get_terminal(0,&cursesmode); /* save the terminal mode */ #endif endwin(); fflush(stdout); incurses=0; } /* This function restarts curses, after endcurses() has been called */ static void restartcurses() { if(incurses)return; wrefresh(curscr); #ifndef SYSVCURSES if(KS)fputs(KS,stdout),fflush(stdout); set_terminal(0,&cursesmode); /* restore the terminal mode. I can't think why this should be necessary. It is supposed to be the job of curses... */ #endif incurses=1; } /* This function prints an error message after a system call failed. If rc is nonzero it exits the program. The two input strings are just concatenated together, which saves the caller doing it (as in: "couldn't open"+file) */ static void emsg(rc,msg1,msg2) int rc; char *msg1; char *msg2; { char *err; if(errno==EOF_MAGIC)err="end of file"; else if(!(err=strerror(errno))) err="Unknown error occurred"; endcurses(); if(msg2)fprintf(stderr,"%s: %s %s: %s\n",programname,msg1,msg2,err); else fprintf(stderr,"%s: %s: %s\n",programname,msg1,err); if(rc)die(rc); } static void die(rc) int rc; { endcurses(); if(rc)fprintf(stderr,"%s: exited with return code %d\n",programname,rc); exit(rc); }