/* sysunix.c Copyright (C) 1989-2002 Codemist Ltd */ /* * General Unix system-specific code */ /* * This code may be used and modified, and redistributed in binary * or source form, subject to the "CCL Public License", which should * accompany it. This license is a variant on the BSD license, and thus * permits use of code derived from this in either open and commercial * projects: but it does require that updates to this code be made * available back to the originators of the package. * Before merging other code in with this or linking this code * with other packages or libraries please check that the license terms * of the other material are compatible with those of this. */ /* Signature: 3035e8da 08-Apr-2002 */ #include "machine.h" #include <sys/stat.h> #ifndef NO_UNISTD_AVAILABLE /* * Posix mandates a header <unistd.h>, which is why I feel entitled to * include it here. But for systems that do not I can assert * NO_UNISTD_AVAILABLE in machine.h and worry about other ways to * reference the relevant facilities... */ #include <unistd.h> #endif #ifdef __hpux /* * Get the regular headers to define a few more things... */ #define _SYSCALL_INCLUDED #endif #include <stdarg.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <sys/types.h> #include <dirent.h> #include <errno.h> #include "tags.h" #include "externs.h" #include "sys.h" #ifdef TIMEOUT #include "timeout.h" #endif #include "filename.c" int change_directory(char *filename, char *old, size_t n) { process_file_name(filename, old, n); if (*filename == 0) return 1; if (chdir(filename)) { char err_buf[LONGEST_LEGAL_FILENAME+100]; char *msg; switch (errno) { case ENOTDIR: msg = "A component of %s is not a directory."; break; case ENOENT: msg = "The directory %s does not exist."; break; case EACCES: msg = "Insufficient permission for %s."; break; #ifndef HP_UNIX /* * This symbol seems not to be available under HP versions of Unix. * Since I am just producing pretty error messages here the loss of * functionality missing it out is pretty minor... */ case ELOOP: msg = "Pathname %s has too many symbolic links."; break; #endif case ENAMETOOLONG: msg = "The pathname %s is too long."; break; default: msg = "Cannot change directory to %s."; break; } sprintf(err_buf, msg, filename); aerror0(err_buf); return 1; } else return 0; } int create_directory(char *filename, char *old, size_t n) { process_file_name(filename, old, n); if (*filename == 0) return 1; return mkdir(filename, 0770); } static void remove_files(char *name, int dirp, long int size) /* Remove a file, or a directory and all its contents */ { switch (dirp) { case 0: /* SCAN_FILE */ remove(name); return; case 2: /* SCAN_ENDDIR */ rmdir(name); return; default: /* 1 == SCAN_STARTDIR */ return; } } int delete_file(char *filename, char *old, size_t n) { process_file_name(filename, old, n); if (*filename == 0) return 0; /* * We cannot simply use remove here, since this will not * work with directories and their contents. Hence the * use of scan_directory. */ scan_directory(filename, remove_files); return 0; } /* extern char *getcwd(char *s, size_t n); in case unistd not used */ int get_current_directory(char *s, int n) { #ifdef NO_GETCWD aerror0("cannot get current directory name."); *s = 0; return 0; #else if (getcwd(s, n) == 0) { switch(errno) { case ERANGE: aerror0("the pathname of the current directory is too long."); break; case EACCES: aerror0("insufficient permission to get pathname."); break; default: aerror0("cannot get current directory name."); break; } *s = 0; return 0; } else return strlen(s); #endif } long file_length(char *filename, char *old, size_t n) { struct stat buf; process_file_name(filename, old, n); if (*filename == 0) return 0; if (stat(filename,&buf) == -1) return -1; return (long)(buf.st_size); } #ifndef S_IFMT # ifdef __S_IFMT # define S_IFMT __S_IFMT # endif #endif #ifndef S_IFDIR # ifdef __S_IFDIR # define S_IFDIR __S_IFDIR # endif #endif #ifndef S_IFREG # ifdef __S_IFREG # define S_IFREG __S_IFREG # endif #endif #ifndef S_ISLNK # ifdef S_IFLNK # ifdef S_IFMT # define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) # endif # endif #endif int directoryp(char *filename, char *old, size_t n) { struct stat buf; process_file_name(filename, old, n); if (*filename == 0) return 0; if (stat(filename,&buf) == -1) return 0; return ((buf.st_mode & S_IFMT) == S_IFDIR); } char *get_truename(char *filename, char *old, size_t n) { struct stat buf; char *temp, *fn, *dir, *pwd; process_file_name(filename, old, n); if (*filename == 0) aerror("truename"); /* Find out whether we have a file or a directory */ if (stat(filename,&buf) == -1) aerror0("truename: cannot stat file"); /* Store current directory */ /* /* * The next line is UNSATISFACTORY because Posix explicitly says (at least in * in the copy of 1003.1 that I have) that getcwd has undefined behaviour * if its first argument is NULL. */ if ((pwd = (char *)getcwd((char *)NULL, LONGEST_LEGAL_FILENAME)) == NULL) aerror0("truename: cannot get current working directory"); if ((buf.st_mode & S_IFMT) == S_IFDIR) { /* We have a directory */ char *dir = (char*)(*malloc_hook)(LONGEST_LEGAL_FILENAME); if (chdir(filename) != 0) aerror0("truename: cannot change directory"); if (getcwd(dir,LONGEST_LEGAL_FILENAME) == NULL) aerror0("truename: cannot get current working directory"); if (chdir(pwd) != 0) aerror0("truename: cannot change directory"); (*free_hook)(pwd); /* * Axiom-specific hack: truename preserves '/' at the end of * a path */ if (old[n-1] == '/' && dir[strlen(dir)-1] != '/') { n = strlen(dir); dir[n] = '/'; dir[n+1] = '\0'; } return dir; } else { /* Assume we have some kind of file */ temp = strrchr(filename,'/'); if (temp) { /* Found a directory component */ fn = (char *)(*malloc_hook)(1+strlen(temp)); strcpy(fn, temp); /* strdup(temp); */ *temp = '\0'; /* fn is now "/file" and filename is the directory */ if (chdir(filename) != 0) aerror0("truename: cannot change directory"); /* /* getcwd(NULL,...) invalid */ if ((temp = (char *)getcwd((char *)NULL,LONGEST_LEGAL_FILENAME)) == NULL) aerror0("truename: cannot get current working directory"); if (chdir(pwd) != 0) dir = (char *)malloc((strlen(temp) + strlen(fn) + 1)*sizeof(char)); if (dir == NULL) { aerror0("truename: malloc failed"); (*free_hook)(fn); return NULL; } aerror0("truename: cannot change directory"); dir = (char *)(*malloc_hook)((strlen(temp) + strlen(fn) + 1)*sizeof(char)); /* /* * No check for malloc failure... */ strcpy(dir, temp); (*free_hook)(temp); (*free_hook)(pwd); strcat(dir, fn); (*free_hook)(fn); return dir; } else { dir = (char *)(*malloc_hook)((strlen(pwd) + strlen(filename) + 2)*sizeof(char)); /* /* No check for malloc failure */ strcpy(dir,pwd); strcat(dir, "/"); strcat(dir, filename); (*free_hook)(pwd); return dir; } } } #ifndef DO_NOT_USE_STAT int file_readable(char *filename, char *old, size_t n) { struct stat buf; process_file_name(filename, old, n); if (*filename == 0) return 0; if (stat(filename,&buf) == -1) return 0; /* File probably does not exist */ else if (geteuid() == buf.st_uid) return (buf.st_mode & S_IRUSR); else if (getegid() == buf.st_gid) return (buf.st_mode & S_IRGRP); else return (buf.st_mode & S_IROTH); } int file_writeable(char *filename, char *old, size_t n) { struct stat buf; process_file_name(filename, old, n); if (*filename == 0) return 0; if (stat(filename,&buf) == -1) return 0; /* Should we check to see if the directory is writeable? */ else if (geteuid() == buf.st_uid) return (buf.st_mode & S_IWUSR); else if (getegid() == buf.st_gid) return (buf.st_mode & S_IWGRP); else return (buf.st_mode & S_IWOTH); } #else int file_readable(char *filename, char *old, size_t n) { FILE *fp; process_file_name(filename, old, n); if (*filename == 0) return 0; /* The "correct" way to do this is via stat, but this is much simpler! */ fp = fopen(filename,"r"); if (fp == NULL) return 0; else { fclose(fp); return 1; } } int file_writeable(char *filename, char *old, size_t n) { FILE *fp; process_file_name(filename, old, n); if (*filename == 0) return 0; fp = fopen(filename,"a"); if (fp == NULL) return 0; else { fclose(fp); return 1; } } #endif int rename_file(char *from_name, char *from_old, size_t from_size, char *to_name, char *to_old, size_t to_size) { process_file_name(from_name, from_old, from_size); process_file_name(to_name, to_old, to_size); if (*from_name == 0 || *to_name == 0) return 0; return rename(from_name,to_name); } #ifdef NAG_VERSION int list_directory_members(char *filename, char *old, char **filelist[], size_t n) { struct dirent **namelist; int number_of_entries, i; char **files; process_file_name(filename, old, n); /* scandir expects "." for the current directory */ if (*filename == 0) number_of_entries = scandir(".",&namelist,NULL,NULL); else number_of_entries = scandir(filename,&namelist,NULL,NULL); /* * If the scandir failed then return now, since we make an assumption later * that we found at least two entries: "." and "..". */ if (number_of_entries == -1) return -1; files=(char **)(*malloc_hook)(number_of_entries*sizeof(char *)); for (i=0;i<number_of_entries;++i) { files[i] = strdup(namelist[i]->d_name); (*free_hook)(namelist[i]); } (*free_hook)(namelist); *filelist = files; /* * When we return we will prepend the directory name to the files, so we * must make sure it is suitable for that. This is done here since it is * platform dependent (i.e. in DOS we would need to ensure the last * character was "\"). */ /* i=strlen(filename); if (i > 0 && filename[i-1] != '/') { filename[i]='/'; filename[i+1]='\0'; } */ return number_of_entries; } #else void list_directory_members(char *filename, char *old, size_t n, directory_callback *fn) { process_file_name(filename, old, n); scan_files(filename, fn); } #endif CSLbool file_exists(char *filename, char *old, size_t n, char *tt) /* * This returns YES if the file exists, and as a side-effect copies a * textual form of the last-changed-time of the file into the buffer tt. */ { struct stat statbuff; process_file_name(filename, old, n); if (*filename == 0) return NO; if (stat(filename, &statbuff) != 0) return NO; strcpy(tt, ctime(&(statbuff.st_mtime))); return YES; } /* * getenv() is a mild pain in two respects - firstly Ultrix uses * a non-ANSI definition (using 2 args not 1), and the MSDOS seems * to have a strong preference for upper case names. To allow for * all this I do not call getenv() directly but go via the following * code that can patch things up. */ #ifdef TWO_ARG_GETENV char *my_getenv(char *s) { static char value[LONGEST_LEGAL_FILENAME]; getenv(s, value); return value; } #else char *my_getenv(char *s) { return getenv(s); } #endif int my_system(char *s) { return system(s); } FILE *my_popen(char *a, char *b) { #ifdef NCC_LIB return NULL; #else return (FILE *)popen(a, b); #endif } void my_pclose(FILE *a) { #ifndef NCC_LIB pclose(a); #endif } #ifndef DO_NOT_USE_GETUID /* * "machine.h" should set DO_NOT_USE_GETUID if that function is not * properly available. Not having it will make the treatment of * (eg) "~xxx/..." in filenames less satisfactory. */ #include <pwd.h> int get_home_directory(char *b, int len) { int i; struct passwd *pw = getpwuid(getuid()); strcpy(b, pw->pw_dir); i = strlen(b); /* Here the directory handed back has "/" forced in as its final character */ if ( b[i-1] != '/') { b[i++] = '/'; b[i] = 0; } return i; } int get_users_home_directory(char *b, int len) { struct passwd *pw = getpwnam(b); if (pw != NULL) strcpy(b, pw->pw_dir); else strcpy(b, "."); /* use current directory if getpwnam() fails */ return strlen(b); } #else /* USE_GETUID */ int get_home_directory(char *b, int len) { int i; strcpy(b, getenv("HOME")); /* Probably works with most shells */ i = strlen(b); if ( b[i-1] != '/') { b[i++] = '/'; b[i] = 0; } return i; } int get_users_home_directory(char *b, int len) { strcpy(b, "."); /* use current directory if getpwnam() no available */ return 1; } #endif /* USE_GETUID */ #ifdef UNIX_TIMES /* * This is a BSD-style clock facility, possibly giving a resolution of * only 1/100 second. I believe that Portable Standard Lisp typically * reports user time, which is why I do this. A further nasty here * is that I am probably compiling this file in ANSI mode, and on * at least some computers this makes #include <sys/times.h> fairly * ineffective (ugh), so I declare all the structures and functions I * want directly (ugh ugh) and hope they are as needed. Consider this * when you port to a new machine. */ clock_t read_clock(void) { struct my_tms { clock_t tms_utime; clock_t tms_stime; clock_t tms_cutime; clock_t tms_cstime; } tmsbuf; clock_t w1, w2, w3; extern void times(/*struct my_tms * */); times(&tmsbuf); w1 = tmsbuf.tms_utime; /* User time in UNIX_TIMES ticks */ w2 = CLOCKS_PER_SEC; w3 = UNIX_TIMES; return (clock_t)((double)w1 * ((double)w2/(double)w3)); } #endif void accept_tick() { } #ifdef __kcm extern int _ttyhandle; int batchp() { return (_ttyhandle != 0); } #else #ifdef NCC_LIB int batchp() { extern int _fisatty(FILE*); return !_fisatty(stdin); } #else #if BSD_LIB int batchp() { return !isatty(fileno(stdin)); } #else #error "Unknown Library type" #endif /* BSD_LIB */ #endif /* NCC_LIB */ #endif /* __kcm */ /* * The next procedure is responsible for establishing information about * where the main checkpoint image should be recovered from, and where * and fasl files should come from. */ char *find_image_directory(int argc, char *argv[]) { char image[LONGEST_LEGAL_FILENAME]; char pgmname[LONGEST_LEGAL_FILENAME]; char *w; /* * If the main reduce executable is has a full path-name /xxx/yyy/zzz then * I will try to make /xxx/yyy/zzz.img the default image file. To do * this I need to find the full path for the executable. I ATTEMPT to follow * the bahaviour of "sh", "bash" and "csh". But NOTE WELL that if anybody * launches this code in an unusual manner (eg using an "exec" style * function) that could confuse it substantially. Thus users are expected * EITHER to run this code directly via the shell OR to provide explicit * command-line options specifying what image files should be used. */ char *myname = argv[0]; /* * If the name of the executable starts with a "/" it is already an * absolute path name. I believe that if the user types (to the shell) * something like $REDUCE/bin/reduce or ~user/subdir/csl then the environment * variable or user-name key gets expanded out by the shell before the command * is actually launched. */ if (myname == NULL || myname[0] == 0) pgmname[0] = 0; else if (myname[0] == '/') { #ifdef TRACE_NAME printf("Absolute path executable %s\n", myname); #endif strcpy(pgmname, myname); } else { int n; for (w=myname; *w!=0; w++) if (*w == '/') break; /* * Now if the program name has a "/" anywhere within it it is treated as * a path starting from the current directory. */ if (*w == '/') { if (myname[0] == '.' && myname[1] == '/') myname += 2; #ifdef TRACE_NAME printf("local path with \"/\" in it %s\n", myname); #endif n = get_current_directory(pgmname, sizeof(pgmname)); if (n + strlen(myname) + 2 >= sizeof(pgmname) || pgmname[0] == 0) pgmname[0] = 0; /* Current dir unavailable or full name is too long */ else { pgmname[n] = '/'; strcpy(&pgmname[n+1], myname); #ifdef TRACE_NAME printf("Full name: %s\n", pgmname); #endif } } else { char *path = my_getenv("PATH"); /* * I omit checks for names of shell built-in functions, since my code is * actually being executed by here. So I get my search path and look * for an executable file somewhere on it. I note that the shells back this * up with hash tables, and so in cases where "rehash" might be needed this * code may become confused. */ struct stat buf; uid_t myuid = geteuid(), hisuid; gid_t mygid = getegid(), hisgid; int protection; int ok = 0; if (path != NULL) { while (*path != 0) { while (*path == ':') path++; n = 0; while (*path != 0 && *path != ':') pgmname[n++] = *path++; /* Here I have separated off the next segment of my PATH */ pgmname[n++] = '/'; strcpy(&pgmname[n], myname); #ifdef TRACE_NAME printf("Try looking for %s\n", pgmname); #endif if (stat(pgmname, &buf) == -1) continue; hisuid = buf.st_uid; hisgid = buf.st_gid; protection = buf.st_mode; #ifdef TRACE_NAME printf("uids %d %d gids %d %d protection %o\n", myuid, hisuid, mygid, hisgid, protection); #endif /* * I now want to check if there is a file of the right name that is * executable by the current (effective) user. */ if (protection & S_IXOTH || mygid == hisgid && protection & S_IXGRP || myuid == hisuid && protection & S_IXUSR) { ok = 1; break; } } } if (ok) { #ifdef TRACE_NAME printf("Success %s\n", pgmname); #endif } else pgmname[0] = 0; } } #ifndef DO_NOT_FOLLOW_SYMBOLIC_LINKS #ifdef S_ISLNK /* * Now if I have a program name I will try to see if it is a symbolic link * and if so I will follow it. */ if (pgmname[0] != 0) { struct stat buf; int n1; if (lstat(pgmname, &buf) != -1 && S_ISLNK(buf.st_mode) && (n1 = readlink(pgmname, image, sizeof(image)-1)) > 0) { image[n1] = 0; #ifdef TRACE_NAME printf("Symbolic link to: %s\n", image); #endif strcpy(pgmname, image); } } #endif /* S_ISLNK */ #endif /* DO_NOT_FOLLOW_SYMBOLIC_LINKS */ if (pgmname[0] == 0) { /* * If I could not find the name of the current executable I will * look in a shell variable "reduceimg". If that is not set I will * really consider that I have failed and use an image "csl.img" in the * current dircectory. */ w = my_getenv("reduceimg"); if (w != NULL) strcpy(image, w); else strcpy(image, "csl.img"); } else { strcpy(image, pgmname); /* * If the fully-rooted name of the program was available I just * append ".img" to get the default image name. */ w = image + strlen(image); strcpy(w, ".img"); } #ifdef OLD_CODE_NOW_REPLACED /* * Here I assume Unix, or something sufficiently like it, and * if the current program is called xxx, then I want an environment * variable called xxx.img to tell me where to find the image file * and the fasl directory. */ #ifdef PUBLIC strcpy(pgmname, "/usr/local/lib/reduce"); /* fixed name */ w = my_getenv("reduceimg"); if (w != NULL) strcpy(image, w); else strcpy(image, pgmname); #else if (argc > 0 && argv[0] != NULL) { int i; w = argv[0]; i = strlen(w); while (i > 0 && w[i-1] != '/') i--; sprintf(pgmname, "%s.img", &w[i]); /* final component of argv[0] */ strncpy(program_name, &w[i], 63); program_name[63] = 0; } else strcpy(pgmname, "csl.img"); /* even argv[0] is not available! */ w = my_getenv(pgmname); #endif if (w != NULL) strcpy(image, w); else strcpy(image, pgmname); #endif /* OLD_CODE_NOW_REPLACED */ /* * I copy from local vectors into malloc'd space to hand my * answer back. */ w = (char *)(*malloc_hook)(1+strlen(image)); /* * The error exit here seem unsatisfactory... */ if (w == NULL) { fprintf(stderr, "\n+++ Panic - run out of space\n"); exit(EXIT_FAILURE); } strcpy(w, image); return w; } /* * The following function controls memory allocation policy */ int32 ok_to_grab_memory(int32 current) { #ifdef COMMON return current; #else return 3*current + 2; #endif } #include "fileops.c" #include "scandir.c" /* end of sysunix.c */