/*--------------------------------------------------------------------------- * File and directory functions and efuns. * *--------------------------------------------------------------------------- */ #include "driver.h" #include "typedefs.h" #include "my-alloca.h" #include #include #include #include #include #if defined(HAVE_DIRENT_H) || defined(_POSIX_VERSION) # include # define generic_dirent dirent # define DIRENT_NLENGTH(dirent) (strlen((dirent)->d_name)) #else /* not (DIRENT or _POSIX_VERSION) */ # define generic_dirent direct # define DIRENT_NLENGTH(dirent) ((dirent)->d_namlen) # ifdef HAVE_SYS_NDIR_H # include # endif /* SYSNDIR */ # ifdef HAVE_SYS_DIR_H # include # endif /* SYSDIR */ # ifdef HAVE_NDIR_H # include # endif /* NDIR */ #endif /* not (HAVE_DIRENT_H or _POSIX_VERSION) */ #if defined(CYGWIN) extern int lstat(const char *, struct stat *); #endif #ifndef S_ISDIR # define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) #endif #ifndef S_ISREG # define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) #endif #ifdef SunOS4 # if !defined (__GNUC__) || __GNUC__ < 2 || __GNUC__ == 2 && __GNUC_MINOR__ < 7 extern int lstat (CONST char *, struct stat *); # endif extern int fchmod(int, int); #endif #if defined(OS2) || defined(__EMX__) # define lstat stat #endif /*-------------------------------------------------------------------------*/ #include "files.h" #include "array.h" #include "comm.h" #include "filestat.h" #include "interpret.h" #include "lex.h" /* lex_close() */ #include "main.h" #include "mempools.h" #include "mstrings.h" #include "simulate.h" #include "stdstrings.h" #include "svalue.h" #include "xalloc.h" #include "../mudlib/sys/files.h" /*-------------------------------------------------------------------------*/ static Bool isdir (const char *path) /* Helper function for copy and move: test if is a directory. */ { struct stat stats; return ixstat (path, &stats) == 0 && S_ISDIR (stats.st_mode); } /* isdir() */ /*-------------------------------------------------------------------------*/ static void strip_trailing_slashes (char *path) /* Strip trailing slashed from , which is modified in-place. */ { int last; last = strlen (path) - 1; while (last > 0 && path[last] == '/') path[last--] = '\0'; } /* strip_trailing_slashes() */ /*-------------------------------------------------------------------------*/ static int copy_file (const char *from, const char *to, int mode) /* Copy the file to with access . * Return 0 on success, 1 or errno on failure. */ { int fromfd, tofd; ssize_t count; char buf[4096]; fromfd = ixopen(from, O_RDONLY); if (fromfd < 0) { debug_message("copy_file(): can't open '%s': %s\n", from, strerror(errno)); return 1; } /* We have to unlink 'to', because it may be a symlink. O_CREAT won't remove that. */ if (unlink(to) < 0 && errno != ENOENT) { debug_message("copy_file(): can't unlink '%s': %s\n", to, strerror(errno)); close(fromfd); return 1; } tofd = ixopen3(to, O_WRONLY|O_CREAT|O_TRUNC, mode); if (tofd < 0) { debug_message("copy_file(): can't open '%s': %s\n", to, strerror(errno)); close(fromfd); return 1; } #ifdef HAVE_FCHMOD /* We have given the file mode to ixopen3, this is just to counter umask. So don't worry if this fchmod fails. */ fchmod(tofd, mode); #endif do { ssize_t written; count = read(fromfd, buf, sizeof(buf)); if (count < 0) { debug_message("copy_file(): can't read from '%s': %s\n", from, strerror(errno)); close(fromfd); close(tofd); unlink(to); return 1; } written = 0; while (written < count) { ssize_t len; len = write(tofd, buf + written, count - written); if (len <= 0) { debug_message("copy_file(): can't write to '%s': %s\n", to, strerror(errno)); close(fromfd); close(tofd); unlink(to); return 1; } written += len; } } while (count > 0); FCOUNT_READ(from); FCOUNT_WRITE(to); close(fromfd); close(tofd); #ifndef HAVE_FCHMOD chmod(to, mode); #endif return 0; } /* copy_file() */ /*-------------------------------------------------------------------------*/ static int move_file (const char *from, const char *to) /* Move the file or directory to , copying it if necessary. * Result is 0 on success, 1 or errno on failure. */ { struct stat fromstat, tostat; if (lstat(from, &fromstat) < 0) { debug_message("move_file(): can't lstat '%s': %s\n", from, strerror(errno)); return 1; } if (!lstat(to, &tostat)) { if (fromstat.st_dev == tostat.st_dev && fromstat.st_ino == tostat.st_ino) { /* Same file. */ debug_message("move_file(): '%s' and '%s' are the same.\n", from, to); return 1; } if (S_ISDIR(tostat.st_mode)) { debug_message("move_file(): destination '%s' is a directory.\n", to); return 1; } } if (rename(from, to)) { if (errno == EXDEV) { if (!S_ISREG(fromstat.st_mode)) { debug_message("move_file(): can't move '%s' across filesystems: Not a regular file.\n", to); return 1; } if (copy_file(from, to, fromstat.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO))) return 1; unlink(from); } else { debug_message("move_file(): can't rename '%s' to '%s': %s\n", to, from, strerror(errno)); return 1; } } FCOUNT_DEL(from); return 0; } /* move_file() */ /*-------------------------------------------------------------------------*/ /* If your system's native methods can provide a speedier directory access * than opendir/readdir/closedir(), implement them as xopendir() and friends, * and define XDIR to the datastructure you need. */ #ifndef XDIR /* Use the normal Posix calls */ struct xdirect { /* inode and position in directory aren't usable in a portable way, * so why support them anyway? */ short d_namlen; char *d_name; int size; int time; int atime; int mode; }; #define XOPENDIR(dest, path) (\ (!chdir(path) &&\ NULL != ((dest) = opendir("."))) ||\ (chdir(mud_lib),MY_FALSE)\ ) #define xclosedir(dir_ptr) (chdir(mud_lib),closedir(dir_ptr)) #define xrewinddir(dir_ptr) rewinddir(dir_ptr) #define XDIR DIR /*-------------------------------------------------------------------------*/ static struct xdirect * xreaddir (XDIR *dir_ptr, int mask) /* Read the next entry from and return it via a pointer * to a static xdirect structure. * is tested for GETDIR_SIZES, GETDIR_DATES, GETDIR_ACCESS, * GETDIR_MODES - only the data for requested items is returned. */ { static struct xdirect xde; struct generic_dirent *de; int namelen; struct stat st; de = readdir(dir_ptr); if (!de) return NULL; namelen = DIRENT_NLENGTH(de); xde.d_namlen = namelen; xde.d_name = de->d_name; if (mask & (GETDIR_SIZES|GETDIR_DATES|GETDIR_ACCESS|GETDIR_MODES) ) { if (ixstat(xde.d_name, &st) == -1) /* who knows... */ { xde.size = FSIZE_NOFILE; xde.time = 0; xde.atime = 0; xde.mode = 0; } else { if (S_IFDIR & st.st_mode) xde.size = FSIZE_DIR; else xde.size = st.st_size; xde.time = st.st_mtime; xde.atime = st.st_atime; xde.mode = st.st_mode; } } return &xde; } /* xreaddir() */ #endif /* XDIR */ /*-------------------------------------------------------------------------*/ static int pstrcmp (const void *p1, const void *p2) /* qsort() comparison function: strcmp() on two svalue-strings. */ { return mstrcmp(((svalue_t*)p1)->u.str, ((svalue_t*)p2)->u.str); } /* pstrcmp() */ /*-------------------------------------------------------------------------*/ struct get_dir_error_context { svalue_t head; XDIR *dirp; vector_t *v; }; /*-------------------------------------------------------------------------*/ static void get_dir_error_handler (svalue_t *arg) /* T_ERROR_HANDLER function: is a (struct get_dir_error_context*) * with the directory which needs to be closed. */ { struct get_dir_error_context *ecp; ecp = (struct get_dir_error_context *)arg; xclosedir(ecp->dirp); if (ecp->v) free_array(ecp->v); } /* get_dir_error_handler() */ /*-------------------------------------------------------------------------*/ svalue_t * v_cat (svalue_t *sp, int num_arg) /* EFUN cat() * * int cat(string pathi [, int start [, int num]]) * * List the file found at path. * The optional arguments start and num are start line * number and number of lines. If they are not given the whole * file is printed from the beginning. * * Result is the number of lines printed. */ { int rc; string_t *path; svalue_t *arg; int start, len; arg = sp- num_arg + 1; /* Get and test the arguments */ start = 0; len = 0; if (num_arg > 1) { start = arg[1].u.number; if (num_arg == 3) { len = arg[2].u.number; } } /* Print the file */ rc = 0; path = NULL; do{ # define MAX_LINES 50 char buff[1000]; FILE *f; int i; if (len < 0) break; path = check_valid_path(arg[0].u.str, current_object, STR_PRINT_FILE, MY_FALSE); if (path == 0) break; if (start < 0) break; f = fopen(get_txt(path), "r"); if (f == NULL) break; FCOUNT_READ(path); if (len == 0) len = MAX_LINES; if (len > MAX_LINES) len = MAX_LINES; if (start == 0) start = 1; for (i = 1; i < start + len; i++) { if (fgets(buff, sizeof buff, f) == 0) break; if (i >= start) add_message("%s", buff); } fclose(f); if (i <= start) break; if (i == MAX_LINES + start) add_message("*****TRUNCATED****\n"); rc = i-start; # undef MAX_LINES }while(0); if (path) free_mstring(path); sp = pop_n_elems(num_arg, sp); push_number(sp, rc); return sp; } /* v_cat() */ /*-------------------------------------------------------------------------*/ svalue_t * f_copy_file (svalue_t *sp) /* EFUN copy_file() * * int copy_file(string from, string to) * * The efun rename() will copy the file to the new name . * If is a directory, then will be placed in that * directory and keep its original name. * * You must have read permission for and write permission * for the target name to copy the file. * * On successfull completion copy_file() will return 0. If any error * occurs, a non-0 value is returned. * * TODO: Add two more args: start, length to implement slicing? * TODO:: See f-981229-10 "truncate_file()". * TODO: Return useful error messages. */ { struct stat to_stats, from_stats; string_t *path; char *cp, *to; int result; /* Check the arguments */ do { char fromB[MAXPATHLEN+1]; char toB[MAXPATHLEN+1]; result = 1; /* Default: failure */ path = check_valid_path(sp[-1].u.str, current_object, STR_COPY_FILE , MY_FALSE); if (!path) break; /* We need our own copy of the result */ extract_cstr(fromB, path, sizeof(fromB)); free_mstring(path); if (isdir(fromB)) break; path = check_valid_path(sp->u.str, current_object, STR_COPY_FILE , MY_TRUE); if (!path) break; if (!mstrsize(path) && mstreq(sp->u.str, STR_SLASH)) { strcpy(toB, "./"); } else { extract_cstr(toB, path, sizeof(toB)); } to = toB; free_mstring(path); strip_trailing_slashes(fromB); if (isdir(toB)) { /* Target is a directory; build full target filename. */ char *newto; cp = strrchr(fromB, '/'); if (cp) cp++; else cp = fromB; newto = alloca(strlen(toB) + 1 + strlen(cp) + 1); strcpy(newto, toB); strcat(newto, "/"); strcat(newto, cp); to = newto; } /* Now copy the file */ if (lstat(fromB, &from_stats) != 0) { errorf("%s: lstat failed\n", fromB); break; } if (lstat(to, &to_stats) == 0) { if (from_stats.st_dev == to_stats.st_dev && from_stats.st_ino == to_stats.st_ino) { errorf("'%s' and '%s' are the same file\n", fromB, to); break; } if (S_ISDIR(to_stats.st_mode)) { errorf("%s: cannot overwrite directory\n", to); break; } } else if (errno != ENOENT) { perror("copy_file"); errorf("%s: unknown error\n", to); break; } if (!S_ISREG(from_stats.st_mode)) { errorf("cannot copy '%s': Not a regular file\n", fromB); break; } result = copy_file(fromB, to, from_stats.st_mode & 0777); }while(0); /* Clean up the stack and return the result */ free_svalue(sp); free_svalue(sp-1); put_number(sp-1, result); return sp-1; } /* f_copy_file() */ /*-------------------------------------------------------------------------*/ svalue_t * f_file_size (svalue_t *sp) /* EFUN file_size() * * int file_size(string file) * * Returns the size of the file in bytes. * * Size FSIZE_NOFILE (-1) indicates that the file either does not * exist, or that it is not readable for the calling object/user. * Size FSIZE_DIR (-2) indicates that it is a directory. */ { long len; struct stat st; string_t * file; st.st_mode = 0; /* Silences ZeroFault/AIX under high optimizations */ file = check_valid_path(sp->u.str, current_object, STR_FILE_SIZE, MY_FALSE); if (!file || ixstat(get_txt(file), &st) == -1) len = FSIZE_NOFILE; else if (S_IFDIR & st.st_mode) len = FSIZE_DIR; else len = (long)st.st_size; free_mstring(file); free_svalue(sp); put_number(sp, len); return sp; } /* f_file_size() */ /*-------------------------------------------------------------------------*/ svalue_t * f_get_dir (svalue_t *sp) /* EFUN get_dir() * * string *get_dir(string str) * string *get_dir(string str, int mask) * * This function takes a path as argument and returns an array of file * names and attributes in that directory. * * Returns 0 if the directory to search in does not exist. * * The filename part of the path may contain '*' or '?' as wildcards: * every '*' matches an arbitrary amount of characters (or just itself). * Thus get_dir("/path/ *") would return an alphabetically sorted array * of all files in directory "/path/", or just ({ "/path/ *" }) if this * file happens to exist. * * To query the content of a directory, use the directory name with a * trailing '/' or '/.', for example get_dir("/path/."). Use the * directory name as it is to get information about the directory itself. * * The optional second argument mask can be used to get * information about the specified files. * * GETDIR_EMPTY (0x00) get_dir returns an empty array (not very * useful). * GETDIR_NAMES (0x01) put the alphabetically sorted file names into * the returned array. * GETDIR_SIZES (0x02) put the file sizes unsorted into the returned * array. directories have size FSIZE_DIR (-2). * GETDIR_DATES (0x04) put the file modification dates unsorted into * the returned array. * GETDIR_ACCESS (0x40) put the file access dates unsorted into * the returned array. * GETDIR_MODES (0x80) put the unix file modes unsorted into * the returned array. * * GETDIR_ALL (0xDF) Return all. * * GETDIR_PATH (0x10) if this mask bit is set, the filenames with * the full path will be returned * (GETDIR_NAMES is implied). * GETDIR_UNSORTED (0x20) if this mask bit is set, the result of will * _not_ be sorted. * * Note: You should use GETDIR_NAMES|GETDIR_UNSORTED to get the entries * in the same order as with GETDIR_SIZES and GETDIR_DATES. * * The values of mask can be added together. */ { vector_t *v; char path[MAXPATHLEN+1]; int mask; string_t *fpath = NULL; mask = sp->u.number; v = NULL; do { static struct get_dir_error_context ec; /* must survive errors */ vector_t *w; int i, j, count = 0; XDIR *dirp; int namelen; Bool do_match = MY_FALSE; size_t pathlen; Bool in_top_dir = MY_FALSE; struct xdirect *de; struct stat st; char *p; char *regexpr = 0; int nqueries; /* Adjust the mask for implied bits */ if (mask & GETDIR_PATH) mask |= GETDIR_NAMES; if (!sp[-1].u.str) break; fpath = check_valid_path(sp[-1].u.str, current_object, STR_GET_DIR, MY_FALSE); if (fpath == NULL) break; /* We need to modify the returned path, and thus to make a * writeable copy. */ extract_cstr(path, fpath, sizeof(path)); /* Convert the empty path to '.' */ if (strlen(path) < 2) { path[0] = path[0] ? path[0] : '.'; path[1] = '\0'; p = path; in_top_dir = MY_TRUE; } else { /* If path ends with '/' or "/." remove it */ if ((p = strrchr(path, '/')) == NULL) p = path; if ((p[0] == '/' && p[1] == '.' && p[2] == '\0') || (p[0] == '/' && p[1] == '\0') ) *p = '\0'; in_top_dir = (p == path); } /* Number of data items per file */ nqueries = ((mask & GETDIR_NAMES) != 0) + ((mask & GETDIR_SIZES) != 0) + ((mask & GETDIR_DATES) != 0) + ((mask & GETDIR_ACCESS) != 0) + ((mask & GETDIR_MODES) != 0) ; if (strchr(p, '*') || ixstat(path, &st) < 0) { /* We got a wildcard and/or a directory: * prepare to match. */ if (*p == '\0') break; regexpr = alloca(strlen(p)+2); if (p != path) { strcpy(regexpr, p + 1); *p = '\0'; } else { strcpy(regexpr, p); strcpy(path, "."); in_top_dir = MY_TRUE; } do_match = MY_TRUE; } else if (*p != '\0' && strcmp(path, ".")) { /* We matched a single file */ svalue_t *stmp; if (*p == '/' && *(p + 1) != '\0') p++; v = allocate_array(nqueries); stmp = v->item; if (mask & GETDIR_NAMES) { if (mask & GETDIR_PATH) { put_c_string(stmp, path); if (!compat_mode) { string_t *tmp = stmp->u.str; stmp->u.str = add_slash(tmp); free_mstring(tmp); } } else { put_c_string(stmp, p); } stmp++; } if (mask & GETDIR_SIZES){ put_number(stmp, (S_IFDIR & st.st_mode) ? FSIZE_DIR : st.st_size); stmp++; } if (mask & GETDIR_DATES) { put_number(stmp, st.st_mtime); stmp++; } if (mask & GETDIR_ACCESS) { put_number(stmp, st.st_atime); stmp++; } if (mask & GETDIR_MODES) { put_number(stmp, st.st_mode); stmp++; } break; } pathlen = strlen(path); if ( XOPENDIR(dirp, path) == 0) break; /* Prepare the error handler to do clean up. */ ec.dirp = dirp; ec.v = NULL; inter_sp = sp+1; push_error_handler(get_dir_error_handler, &ec.head); /* Count files */ for (de = xreaddir(dirp, 1); de; de = xreaddir(dirp, 1)) { namelen = de->d_namlen; if (do_match) { if ( !match_string(regexpr, de->d_name, namelen) ) continue; } else { if (namelen <= 2 && *de->d_name == '.' && (namelen == 1 || de->d_name[1] == '.' ) ) continue; } count += nqueries; if (max_array_size && count >= (long)max_array_size) break; } if (nqueries) count /= nqueries; /* Make array and put files on it. */ v = allocate_array(count * nqueries); if (count == 0) { /* This is the easy case :-) */ inter_sp--; xclosedir(dirp); break; } ec.v = v; xrewinddir(dirp); w = v; j = 0; /* Taken into account that files might be added/deleted from outside. */ for(i = 0, de = xreaddir(dirp,mask); de; de = xreaddir(dirp,mask)) { namelen = de->d_namlen; if (do_match) { if ( !match_string(regexpr, de->d_name, namelen) ) continue; } else { if (namelen <= 2 && *de->d_name == '.' && (namelen == 1 || de->d_name[1] == '.' ) ) continue; } if (i >= count) { /* New file. Don't need efficience here, but consistence. */ vector_t *tmp, *new; count++; tmp = allocate_array(nqueries); new = add_array(v, tmp); free_array(v); free_array(tmp); ec.v = v = new; w = v; } if (mask & GETDIR_NAMES) { string_t *result; if ((mask & GETDIR_PATH) && !in_top_dir) { char * name; if (compat_mode) { memsafe(result = alloc_mstring(namelen+pathlen+1) , namelen+pathlen+2 , "getdir() names"); name = get_txt(result); } else { memsafe(result = alloc_mstring(namelen+pathlen+2) , namelen+pathlen+3 , "getdir() names"); name = get_txt(result); *name++ = '/'; } memcpy(name, path, pathlen); name += pathlen; *name++ = '/'; if (namelen) memcpy(name, de->d_name, namelen); name[namelen] = '\0'; } else { memsafe(result = new_n_mstring(de->d_name, namelen), namelen , "getdir() names"); } put_string(w->item+j, result); j++; } if (mask & GETDIR_SIZES) { put_number(w->item + j, de->size); j++; } if (mask & GETDIR_DATES) { put_number(w->item + j, de->time); j++; } if (mask & GETDIR_ACCESS) { put_number(w->item + j, de->atime); j++; } if (mask & GETDIR_MODES) { put_number(w->item + j, de->mode); j++; } i++; } xclosedir(dirp); inter_sp--; if ( !((mask ^ 1) & (GETDIR_NAMES|GETDIR_UNSORTED)) ) { /* Sort by names. */ qsort(v->item, i, sizeof v->item[0] * nqueries, pstrcmp); } }while(0); if (fpath) free_mstring(fpath); sp--; /* just an int */ free_string_svalue(sp); if (v) put_array(sp, v); else put_number(sp, 0); return sp; } /* f_get_dir() */ /*-------------------------------------------------------------------------*/ svalue_t * f_mkdir (svalue_t *sp) /* EFUN mkdir() * * int mkdir(string path) * * Make a directory named path. Return 1 for success and 0 for * failure. */ { int i; string_t *path; path = check_valid_path(sp->u.str, current_object, STR_MKDIR, MY_TRUE); i = !(path == 0 || mkdir(get_txt(path), 0775) == -1); free_mstring(path); free_svalue(sp); put_number(sp, i); return sp; } /* f_mkdir() */ /*-------------------------------------------------------------------------*/ svalue_t * v_read_bytes (svalue_t *sp, int num_arg) /* EFUN read_bytes() * * string read_bytes (string file, int start, int number) * * Reads a given amount of bytes from file. * If is not given or 0, the file is read from the * beginning, else from the th byte on. If is * negative, it is counted from the end of the file. * is the number of bytes to read. 0 or negative values * are possible, but not useful. * If would be outside the actual size of the file, 0 is * returned instead of a string. */ { string_t *rc; string_t *file; svalue_t *arg; int start, len; arg = sp- num_arg + 1; /* Get the arguments */ start = 0; len = 0; if (num_arg > 1) { start = arg[1].u.number; if (num_arg == 3) { len = arg[2].u.number; sp--; } sp--; } /* Read the file */ rc = NULL; file = NULL; do{ struct stat st; char *str; int f; long size; /* TODO: fpos_t? */ /* Perform some sanity checks */ if (len < 0 || (max_byte_xfer && len > max_byte_xfer)) break;; file = check_valid_path(arg[0].u.str, current_object, STR_READ_BYTES, MY_FALSE); if (!file) break;; /* Open the file and determine its size */ f = ixopen(get_txt(file), O_RDONLY); if (f < 0) break;; FCOUNT_READ(file); if (fstat(f, &st) == -1) fatal("Could not stat an open file.\n"); size = (long)st.st_size; /* Determine the proper start and len to use */ if (start < 0) start = size + start; if (start >= size) { close(f); break;; } if ((start+len) > size) len = (size - start); if (len <= 0) { close(f); break;; } /* Seek and read */ if ((size = (long)lseek(f,start, 0)) < 0) { close(f); break;; } str = mb_alloc(mbFile, (size_t)len); if (!str) { close(f); break; } size = read(f, str, (size_t)len); close(f); if (size <= 0) { mb_free(mbFile); break; } /* We return a copy of the life parts of the buffer, and get rid * of the largish buffer itself. */ rc = new_n_mstring(str, size); mb_free(mbFile); }while(0); if (file) free_mstring(file); free_svalue(sp--); if (rc == NULL) push_number(sp, 0); else push_string(sp, rc); return sp; } /* v_read_bytes() */ /*-------------------------------------------------------------------------*/ svalue_t * v_read_file (svalue_t *sp, int num_arg) /* EFUN read_file() * * string read_file(string file, int start, int number) * * Reads lines from file. * If is not given or 0, the file is read from the * beginning, else from the numbered line on. * If is not given or 0, the whole file is read, else * just the given amount of lines. * If would be outside the actual size of the file, 0 is * returned instead of a string. */ { string_t *rc; string_t *file; svalue_t *arg; int start, len; arg = sp- num_arg + 1; /* Get the arguments */ start = 0; len = 0; if (num_arg > 1) { start = arg[1].u.number; if (num_arg == 3) { len = arg[2].u.number; sp--; } sp--; } /* Read the file */ rc = NULL; file = NULL; do { struct stat st; FILE *f; char *str, *p, *p2, *end, c; long size; /* TODO: fpos_t? */ p = NULL; /* Silence spurious warnings */ end = NULL; if (len < 0 && len != -1) break; file = check_valid_path(arg[0].u.str, current_object, STR_READ_FILE, MY_FALSE); if (!file) break; /* If the file would be opened in text mode, the size from fstat would * not match the number of characters that we can read. */ f = fopen(get_txt(file), "rb"); if (f == NULL) break;; FCOUNT_READ(get_txt(file)); /* Check if the file is small enough to be read. */ if (fstat(fileno(f), &st) == -1) { fatal("Could not stat an open file.\n"); /* NOTREACHED */ break;; } size = (long)st.st_size; if (max_file_xfer && size > max_file_xfer) { if ( start || len ) size = max_file_xfer; else { fclose(f); break;; } } /* Make the arguments sane */ if (!start) start = 1; if (!len) len = size; /* Get the memory */ str = mb_alloc(mbFile, (size_t)size + 1); /* allow a leading ' ' */ if (!str) { fclose(f); free_mstring(file); errorf("(read_file) Out of memory (%ld bytes) for buffer\n", size+1); /* NOTREACHED */ break; } *str++ = ' '; /* this way, we can always read the 'previous' char... */ /* Search for the first line to read. * For this, the file is read in chunks of bytes, st.st_size * records the remaining length of the file. */ do { /* Read the next chunk */ if (size > st.st_size) /* Happens with the last block */ size = (long)st.st_size; if ((!size && start > 1) || fread(str, (size_t)size, 1, f) != 1) { fclose(f); f = NULL; mb_free(mbFile); break; } st.st_size -= size; end = str+size; /* Find all the '\n' in the chunk and count them */ for (p = str; NULL != ( p2 = memchr(p, '\n', (size_t)(end-p)) ) && --start; ) p = p2+1; } while ( start > 1 ); if (f == NULL) /* then the inner loop aborted and we have to, too */ break; /* p now points to the first requested line. * st.st_size is the remaining size of the file. */ /* Shift the found lines back to the front of the buffer, and * count them. * Also convert \r\n pairs into \n on MS-DOS filesystems. */ for (p2 = str; p != end; ) { c = *p++; if ( c == '\n' ) { #ifdef MSDOS_FS if ( p2[-1] == '\r' ) p2--; #endif if (!--len) { *p2++=c; break; } } *p2++ = c; } /* If there are still some lines missing, and parts of the file * are not read yet, read and scan those remaining parts. */ if ( len && st.st_size ) { /* Read the remaining file, but only as much as there is * space left in the buffer. As that one is max_file_xfer * long, it has to be sufficient. */ size -= ( p2-str) ; if (size > st.st_size) size = (long)st.st_size; if (fread(p2, (size_t)size, 1, f) != 1) { fclose(f); mb_free(mbFile); break; } st.st_size -= size; end = p2+size; /* Count the remaining lines, again converting \r\n into \n * when necessary. */ for (p = p2; p != end; ) { c = *p++; if ( c == '\n' ) { #ifdef MSDOS_FS if ( p2[-1] == '\r' ) p2--; #endif if (!--len) { *p2++ = c; break; } } *p2++ = c; } /* If there are lines missing and the file is not at its end, * we have a failure. */ if ( st.st_size && len > 0) { /* tried to read more than READ_MAX_FILE_SIZE */ fclose(f); mb_free(mbFile); break; } } fclose(f); /* Make a copy of the valid parts of the str buffer, then * get rid of the largish buffer itself. */ rc = new_n_mstring(str, p2-str); mb_free(mbFile); if (!rc) { free_mstring(file); errorf("(read_file) Out of memory for result\n"); } } while(0); if (file) free_mstring(file); free_svalue(sp--); if (rc == NULL) push_number(sp, 0); else push_string(sp, rc); return sp; } /* v_read_file() */ /*-------------------------------------------------------------------------*/ svalue_t * f_rename (svalue_t *sp) /* EFUN rename() * * int rename(string from, string to) * * The efun rename() will move from to the new name to. If from * is a file, then to may be either a file or a directory. If * from is a directory, then to has to be a directory. If to * exists and is a directory, then from will be placed in that * directory and keep its original name. * * You must have write permission for from to rename the file. * * On successfull completion rename() will return 0. If any error * occurs, a non-0 value is returned. * TODO: Return useful error messages. */ { int rc; char from[MAXPATHLEN+1], to[MAXPATHLEN+1]; rc = 1; do { string_t *path; path = check_valid_path(sp[-1].u.str, current_object, STR_RENAME_FROM, MY_TRUE); if (!path) break; extract_cstr(from, path, sizeof(from)); free_mstring(path); path = check_valid_path(sp->u.str, current_object, STR_RENAME_TO, MY_TRUE); if (!path) { break; } if (!mstrsize(path) && mstreq(sp->u.str, STR_SLASH)) { strcpy(to, "./"); } else { extract_cstr(to, path, sizeof(to)); } free_mstring(path); strip_trailing_slashes(from); if (isdir(to)) { /* Target is a directory; build full target filename. */ char *cp; char *newto; cp = strrchr(from, '/'); if (cp) cp++; else cp = from; newto = alloca(strlen(to) + 1 + strlen(cp) + 1); sprintf(newto, "%s/%s", to, cp); rc = move_file(from, newto); break; } /* File to file move */ rc = move_file(from, to); }while(0); free_svalue(sp--); free_svalue(sp); put_number(sp, rc); return sp; } /* f_rename() */ /*-------------------------------------------------------------------------*/ svalue_t * f_rm (svalue_t *sp) /* EFUN rm() * * int rm(string file) * * Remove the file. Returns 0 for failure and 1 for success. */ { int i; string_t *path; path = check_valid_path(sp->u.str, current_object, STR_REMOVE_FILE, MY_TRUE); i = 0; if (path != 0 && unlink(get_txt(path)) != -1) { FCOUNT_DEL(get_txt(path)); i = 1; } if (path != NULL) free_mstring(path); free_svalue(sp); put_number(sp, i); return sp; } /* f_rm() */ /*-------------------------------------------------------------------------*/ svalue_t * f_rmdir (svalue_t *sp) /* EFUN rmdir() * * int rmdir(string dir) * * Remove directory dir. Return 1 on success, 0 on failure. */ { int i; string_t *path; path = check_valid_path(sp->u.str, current_object, STR_RMDIR, MY_TRUE); i = !(path == 0 || rmdir(get_txt(path)) == -1); if (path != NULL) free_mstring(path); free_svalue(sp); put_number(sp, i); return sp; } /* f_rmdir() */ /*-------------------------------------------------------------------------*/ svalue_t * f_tail (svalue_t *sp) /* EFUN tail() * * void tail(string file) * * Print out the tail of a file. There is no specific amount of * lines given to the output. Only a maximum of 1000 bytes will * be printed. */ { int rc; string_t *path = NULL; rc = 0; do { char buff[1000]; FILE *f; struct stat st; int offset; path = check_valid_path(sp->u.str, current_object, STR_TAIL, MY_FALSE); if (path == NULL) break; f = fopen(get_txt(path), "r"); if (f == NULL) break; FCOUNT_READ(get_txt(path)); if (fstat(fileno(f), &st) == -1) fatal("Could not stat an open file.\n"); if ( !S_ISREG(st.st_mode) ) { fclose(f); free_mstring(path); break; } offset = st.st_size - 54 * 20; if (offset < 0) offset = 0; if (fseek(f, offset, 0) == -1) fatal("Could not seek.\n"); /* Throw away the first incomplete line. */ if (offset > 0) (void)fgets(buff, sizeof buff, f); while(fgets(buff, sizeof buff, f)) { add_message("%s", buff); } fclose(f); rc = 1; }while(0); if (path) free_mstring(path); free_svalue(sp); put_number(sp, rc); return sp; } /* f_tail() */ /*-------------------------------------------------------------------------*/ svalue_t * f_write_bytes (svalue_t *sp) /* EFUN write_bytes() * * int write_bytes(string file, int start, string str) * * Write string str to file file by overwriting the old bytes at * position start. If start is a negative value then it will be * counted from the end of the file. The file will not be * appended, instead the function will be aborted. Returns 1 for * success 0 for failure during execution. */ { int rc; string_t * file; rc = 0; file = NULL; do { struct stat st; mp_int size, len, start; int f; start = sp[-1].u.number; /* Sanity checks */ file = check_valid_path(sp[-2].u.str, current_object, STR_WRITE_BYTES, MY_TRUE); if (!file) break; len = mstrsize(sp->u.str); if (max_byte_xfer && len > max_byte_xfer) break; f = ixopen(get_txt(file), O_WRONLY); if (f < 0) break; FCOUNT_WRITE(get_txt(file)); if (fstat(f, &st) == -1) fatal("Could not stat an open file.\n"); size = (mp_int)st.st_size; if (start < 0) start = size + start; if (start > size) { close(f); break;; } if ((size = (mp_int)lseek(f,start, 0)) < 0) { close(f); break;; } size = write(f, get_txt(sp->u.str), (size_t)len); close(f); if (size <= 0) { break; } rc = 1; }while(0); if (file) free_mstring(file); free_svalue(sp--); free_svalue(sp--); free_svalue(sp); put_number(sp, rc); return sp; } /* f_write_bytes() */ /*-------------------------------------------------------------------------*/ svalue_t * f_write_file (svalue_t *sp) /* EFUN write_file() * * int write_file(string file, string str, int flags = 0) * * Append the string str to the file . Returns 1 for success * and 0 if any failure occured. * * If is 1, the file is first removed; thus effectively * changing the 'append' into an 'overwrite'. */ { int rc; string_t *file; rc = 0; file = NULL; do { FILE *f; file = check_valid_path(sp[-2].u.str, current_object, STR_WRITE_FILE, MY_TRUE); if (!file) break; if (sp->u.number & 1) if (remove(get_txt(file)) && errno != ENOENT) { perror("write_file (remove)"); errorf("Could not remove %s: errno %d.\n", get_txt(file), errno); } f = fopen(get_txt(file), "a"); if (f == NULL) { if ((errno == EMFILE #ifdef ENFILE || errno == ENFILE ) && current_loc.file #endif ) { /* We are called from within the compiler, probably to write * an error message into a log. * Call lex_close() (-> lexerror() -> yyerror() -> * parse_error() -> apply_master_ob() ) to try to close some * files, the try again. */ push_string(inter_sp, file); lex_close(NULL); inter_sp--; f = fopen(get_txt(file), "a"); } if (f == NULL) { char * emsg, * buf, * buf2; int err = errno; emsg = strerror(errno); buf = alloca(strlen(emsg)+1); buf2 = alloca(mstrsize(file)+1); if (buf && buf2) { strcpy(buf, emsg); extract_cstr(buf2, file, mstrsize(file)+1); free_mstring(file); errorf("Could not open %s for append: (%d) %s.\n" , buf2, err, buf); } else if (buf2) { extract_cstr(buf2, file, mstrsize(file)+1); free_mstring(file); perror("write_file"); errorf("Could not open %s for append: errno %d.\n" , buf2, err); } else { free_mstring(file); perror("write_file"); errorf("Could not open file for append: errno %d.\n" , err); } /* NOTREACHED */ } } FCOUNT_WRITE(get_txt(file)); fwrite(get_txt(sp[-1].u.str), mstrsize(sp[-1].u.str), 1, f); fclose(f); rc = 1; } while(0); if (file) free_mstring(file); free_svalue(sp--); free_svalue(sp--); free_svalue(sp); put_number(sp, rc); return sp; } /* f_write_file() */ /***************************************************************************/