diff --git a/ChangeLog-fileio.txt b/ChangeLog-fileio.txt new file mode 100644 index 0000000..9596740 --- /dev/null +++ b/ChangeLog-fileio.txt @@ -0,0 +1,29 @@ +Version 1.5p3 - Jul 11 +---------------------- + - Added return value check on calls to binary_to_raw_bytes (via out_filter) + - Replaced use of fgets to better handle files with null bytes + - No longer accept the tab character as valid clean input data + +Version 1.5p2 - Jun 11 +---------------------- + - Fixed mismatch mymalloc/free + - Removed restriction that the final line in text mode must be terminated by a newline + - Reset errno before each call + - Removed vestigial file_send + - Replaced "E_FILE" error with true E_FILE + +Version 1.5p1 - Dec 97 +---------------------- + - Fixed bug where tabs were not included in the input stream + - Added CHANGELOG to the distribution + - Added README + +Version 1.5 +----------- + - First version maintained by Andy Bakun. + - Fixed bugs where file_eof and file_tell didn't return meaningful results + didn't raise errors on invalid file descriptors. + +Versions < 1.5 +-------------- +Maintained by Ken Fox. Really, the initial public version. diff --git a/FileioDocs.txt b/FileioDocs.txt new file mode 100644 index 0000000..4f02a24 --- /dev/null +++ b/FileioDocs.txt @@ -0,0 +1,432 @@ + File I/O + Ken Fox, Andy Bakun, Todd Sundsted + + This is the documentation for the File I/O (FIO) patch for the Lamb- + daMOO server. FIO adds several administrator-only builtins for manip- + ulating files from inside the MOO. Security is enforced by making + these builtins executable with wizard permissions only as well as only + allowing access to a directory under the current directory (the one + the server is running in). + + 1. Introduction + + 1.1. Purpose + + This patch to the LambdaMOO server adds several new builtins that + allow the manipulation of files from MOO code. The new builtins are + structured similarly to the stdio library for C. This allows MOO-code + to perform stream-oriented I/O to files. + + Granting MOO code direct access to files opens a hole in the otherwise + fairly good wall that the LambdaMOO server puts up between the OS and + the database. The patch contains the risk as much as possible by + restricting where files can be opened and allowing the new builtins to + be called by wizard permissions only. It is still possible execute + various forms denial of service attacks, but the MOO server allows + this form of attack as well. + + There is a related package available that contains a db front end for + this package as well as a help database with help for all the builtin + functions and for the front end. It is not recommended that you use + these functions directly. + + 1.2. Copyright, License, and Disclaimer + + Copyright 1996, 1997 by Ken Fox. Copyright 1997 by Andy Bakun. + Copyright 2011 by Todd Sundsted. + + All Rights Reserved + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both that copyright notice and this permission notice appear in + supporting documentation. + + KEN FOX AND ANDY BAKUN AND TODD SUNDSTED DISCLAIM ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL KEN FOX OR ANDY BAKUN + OR TODD SUNDSTED BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + 2. Installation + + Most of the new code for this patch are in the files extension- + fileio.c and extension-fileio.h. These files contain the header and + implementation for all of the new builtin functions. One line has to + be added to bf_register.h and a related line must be added + functions.c. + + The distribution archive should contain the text version of this + documentation (fileio-README), extension-fileio.c, extension-fileio.h, + and fileio.patch. + + If you are going to use patch to install it, just copy fileio.patch + into the server directory and apply the patch: + + patch < fileio.patch + + It is recommended that you use patch to install it, but you can also + manually make the changes to the server source. First, copy the + extension-fileio.c and .h files into a directory with the freshly + untarred server source. Add extension-fileio.c to the CSRCS line of + Makefile.in, add + + extern void register_fileio(void); + + to bf_register.h and add + + register_fileio + + to the list of functions in functions.c. + + Finally, make the files directory in the directory the MOO server is + run in. Only files in this directory will be accessible using this + patch. + + mkdir files + + Pathnames passed to these functions are restricted to prevent access + outside this directory. + + 3. Functions + + The functions in this patch are grouped into a few categories. There + is a function to open a file and a function for closing a file, a set + of functions for doing stream-oriented I/O from files, and a set of + housekeeping functions. + + Function documentation includes a prototype, information about the + function, and a list of exceptions the function might raise (in + addition to the ones outlined in "Error handling". + + WARNING: All of the actual I/O functions in this package are + implemented using the stdio portion of libc. Your system's + documentation may have applicable warnings for these functions. When + appropriate, the function documentation will say which libc function + is used. + + 3.1. Error handling + + Errors are always handled by raising some kind of exception. The + following exceptions are defined: + + E_FILE + This is raised when a stdio call returned an error value. + CODE is set to E_FILE, MSG is set to the return of strerror() + (which may vary from system to system), and VALUE depends on + which function raised the error. When a function fails + because the stdio function returned EOF, VALUE is set to + "EOF". + + E_INVARG + This is raised for a number of reasons. The common reasons are + an invalid FHANDLE being passed to a function and an invalid + pathname specification. In each of these cases MSG will be set + to the cause and VALUE will be the offending value. + + E_PERM + This is raised when any of these functions are called with non- + wizardly permissions. + + 3.2. Version + + Function: STR file_version() + + Returns the package shortname/version number of this package e.g. + + ;file_version() + => "FIO/1.5p3" + + 3.3. Opening and closing of files and related functions + + File streams are associated with FHANDLES. FHANDLES are similar to + the FILE* using stdio. You get an FHANDLE from file_open. You should + not depend on the actual type of FHANDLEs (currently TYPE_INT). + FHANDLEs are not persistent across server restarts. That is, files + open when the server is shut down are closed when it comes back up and + no information about open files is saved in the DB. + + 3.3.1. file_open + + Function: FHANDLE file_open(STR pathname, STR mode) + + Raises: E_INVARG if mode is not a valid mode, E_QUOTA if too many + files open + This opens a file specified by pathname and returns an FHANDLE for it. + It ensures pathname is legal. mode is a string of characters + indicating what mode the file is opened in. The mode string is four + characters. + + The first character must be (r)ead, (w)rite, or (a)ppend. The second + must be '+' or '-'. This modifies the previous argument. + + o r- opens the file for reading and fails if the file does not exist. + + o r+ opens the file for reading and writing and fails if the file + does not exist. + + o w- opens the file for writing, truncating if it exists and creating + if not. + + o w+ opens the file for reading and writing, truncating if it exists + and creating if not. + + o a- opens a file for writing, creates it if it does not exist and + positions the stream at the end of the file. + + o a+ opens the file for reading and writing, creates it if does not + exist and positions the stream at the end of the file. + + The third character is either (t)ext or (b)inary. In text mode, + data is written as-is from the MOO and data read in by the MOO is + stripped of unprintable characters. In binary mode, data is + written filtered through the binary-string->raw-bytes conversion + and data is read filtered through the raw-bytes->binary-string + conversion. For example, in text mode writing " 1B" means three + bytes are written: ' ' Similarly, in text mode reading " 1B" means + the characters ' ' '1' 'B' were present in the file. In binary + mode reading " 1B" means an ASCII ESC was in the file. In text + mode, reading an ESC from a file results in the ESC getting + stripped. + + It is not recommended that files containing unprintable ASCII data be + read in text mode, for obvious reasons. + + The final character is either 'n' or 'f'. If this character is 'f', + whenever data is written to the file, the MOO will force it to finish + writing to the physical disk before returning. If it is 'n' then + this won't happen. + + This is implemented using fopen(). + + 3.3.2. file_close + + Function: void file_close(FHANDLE fh) + + Closes the file associated with fh. + + This is implemented using fclose(). + + 3.3.3. file_name + + Function: STR file_name(FHANDLE fh) + + Returns the pathname originally associated with fh by file_open(). + This is not necessarily the file's current name if it was renamed or + unlinked after the fh was opened. + + 3.3.4. file_openmode + + Function: STR file_openmode(FHANDLE fh) + + Returns the mode the file associated with fh was opened in. + + 3.4. Input and Ouput operations + + 3.4.1. file_readline + + Function: STR file_readline(FHANDLE fh) + + Reads the next line in the file and returns it (without the newline). + + Not recommended for use on files in binary mode. + + This is implemented using fgetc(). + + 3.4.2. file_readlines + + Function: LIST file_readlines(FHANDLE fh, INT start, INT end) + + Rewinds the file and then reads the specified lines from the file, + returning them as a list of strings. After this operation, the stream + is positioned right after the last line read. + + Not recommended for use on files in binary mode. + + This is implemented using fgetc(). + + 3.4.3. file_writeline + + Function: void file_writeline(FHANDLE fh, STR line) + + Writes the specified line to the file (adding a newline). + + Not recommended for use on files in binary mode. + + This is implemented using fputs(). + + 3.4.4. file_read + + Function: STR file_read(FHANDLE fh, INT bytes) + + Reads up to the specified number of bytes from the file and returns + them. + + Not recommended for use on files in text mode. + + This is implemented using fread(). + + 3.4.5. file_write + + Function: INT file_write(FHANDLE fh, STR data) + + Writes the specified data to the file. Returns number of bytes + written. + + Not recommended for use on files in text mode. + + This is implemented using fwrite(). + + 3.4.6. Getting and setting stream position + + 3.4.7. file_tell + + Function: INT file_tell(FHANDLE fh) + + Returns position in file. + + This is implemented using ftell(). + + 3.4.8. file_seek + + Function: void file_seek(FHANDLE fh, INT loc, STR whence) + + Seeks to a particular location in a file. whence is one of the + strings: + + o "SEEK_SET" - seek to location relative to beginning + + o "SEEK_CUR" - seek to location relative to current + + o "SEEK_END" - seek to location relative to end + + This is implemented using fseek(). + + 3.4.9. file_eof + + Function: INT file_eof(FHANDLE fh) + + Returns true if and only if fh's stream is positioned at EOF. + + This is implemented using feof(). + + 3.5. Housekeeping operations + + 3.5.1. file_size, file_mode, file_last_access, file_last_modify, + file_last_change + + Function: INT file_size(STR pathname) + Function: STR file_mode(STR pathname) + Function: INT file_last_access(STR pathname) + Function: INT file_last_modify(STR pathname) + Function: INT file_last_change(STR pathname) + Function: INT file_size(FHANDLE fh) + Function: STR file_mode(FHANDLE fh) + Function: INT file_last_access(FHANDLE fh) + Function: INT file_last_modify(FHANDLE fh) + Function: INT file_last_change(FHANDLE fh) + + Returns the size, mode, last access time, last modify time, or last + change time of the specified file. All of these functions also take + FHANDLE arguments and then operate on the open file. + + These are all implemented using fstat() (for open FHANDLEs) or stat() + (for pathnames). + + 3.5.2. file_stat + + Function: void file_stat(STR pathname) + Function: void file_stat(FHANDLE fh) + + Returns the result of stat() (or fstat()) on the given file. + Specifically a list as follows: + + {file size in bytes, file type, file access mode, owner, group, + last access, last modify, and last change} + + owner and group are always the empty string. + + It is recommended that the specific information functions file_size, + file_type, file_mode, file_last_access, file_last_modify, and + file_last_change be used instead. In most cases only one of these + elements is desired and in those cases there's no reason to make and + free a list. + + 3.5.3. file_rename + + Function: void file_rename(STR oldpath, STR newpath) + + Attempts to rename the oldpath to newpath. + + This is implemented using rename(). + + 3.5.4. file_remove + + Function: void file_remove(STR pathname) + + Attempts to remove the given file. + + This is implemented using remove(). + + 3.5.5. file_mkdir + + Function: void file_mkdir(STR pathname) + + Attempts to create the given directory. + + This is implemented using mkdir(). + + 3.5.6. file_rmdir + + Function: void file_rmdir(STR pathname) + + Attempts to remove the given directory. + + This is implemented using rmdir(). + + 3.5.7. file_list + + Function: LIST file_list(STR pathname, [ANY detailed]) + + Attempts to list the contents of the given directory. Returns a list + of files in the directory. If the detailed argument is provided and + true, then the list contains detailed entries, otherwise it contains a + simple list of names. + + detailed entry: + {STR filename, STR file type, STR file mode, INT file size} + normal entry: + STR filename + + This is implemented using scandir(). + + 3.5.8. file_type + + Function: STR file_type(STR pathname) + + Returns the type of the given pathname, one of "reg", "dir", "dev", + "fifo", or "socket". + + This is implemented using stat(). + + 3.5.9. file_mode + + Function: STR file_mode(STR filename) + + Returns octal mode for a file (e.g. "644"). + + This is implemented using stat(). + + 3.5.10. file_chmod + + Function: void file_chmod(STR filename, STR mode) + + Attempts to set mode of a file using mode as an octal string of + exactly three characters. + + This is implemented using chmod(). + diff --git a/Makefile.in b/Makefile.in index 5e0e9de..f069839 100644 --- a/Makefile.in +++ b/Makefile.in @@ -35,7 +35,8 @@ CSRCS = ast.c code_gen.c db_file.c db_io.c db_objects.c db_properties.c \ log.c malloc.c match.c md5.c name_lookup.c network.c net_mplex.c \ net_proto.c numbers.c objects.c parse_cmd.c pattern.c program.c \ property.c quota.c ref_count.c regexpr.c server.c storage.c streams.c str_intern.c \ - sym_table.c tasks.c timers.c unparse.c utils.c verbs.c version.c + sym_table.c tasks.c timers.c unparse.c utils.c verbs.c version.c \ + extension-fileio.c OPT_NET_SRCS = net_single.c net_multi.c \ net_mp_selct.c net_mp_poll.c net_mp_fake.c \ @@ -54,6 +55,7 @@ HDRS = ast.h bf_register.h code_gen.h db.h db_io.h db_private.h decompile.h \ options.h parse_cmd.h parser.h pattern.h program.h quota.h random.h \ ref_count.h regexpr.h server.h storage.h streams.h structures.h str_intern.h \ sym_table.h tasks.h timers.h tokens.h unparse.h utils.h verbs.h \ + extension-fileio.h \ version.h SYSHDRS = my-ctype.h my-fcntl.h my-in.h my-inet.h my-ioctl.h my-math.h \ @@ -290,7 +292,7 @@ depend: ${ALL_CSRCS} # Revision 1.3 1992/07/27 18:30:21 pjames # Update what vector.o and vector.po depend on. ############################################################################### - + # Have to do this one manually, since 'make depend' can't hack yacc files. parser.o: my-ctype.h my-math.h my-stdlib.h my-string.h \ ast.h code_gen.h config.h functions.h \ diff --git a/README.fileio b/README.fileio new file mode 100644 index 0000000..a694905 --- /dev/null +++ b/README.fileio @@ -0,0 +1,18 @@ + +Welcome to FileIO. + +FileIO is a patch to the LambdaMOO server to allow stdio-style access to +files. Please see the file fileio-docs.txt for complete information. + +The current version of FileIO, along with documentation in HTML format, +is on-line at + + http://www.scinc.com/~abakun/fileio/ + +The maintainer of FileIO, Andy Bakun, can be reached via e-mail at + + abakun@scinc.com + +Bug reports and fixes, patches, extensions, what you've done with +FileIO, and general complaints are welcome. + diff --git a/bf_register.h b/bf_register.h index 1adb10b..9ca4e9b 100644 --- a/bf_register.h +++ b/bf_register.h @@ -27,6 +27,7 @@ extern void register_property(void); extern void register_server(void); extern void register_tasks(void); extern void register_verbs(void); +extern void register_fileio(void); /* * $Log: bf_register.h,v $ diff --git a/extension-fileio.c b/extension-fileio.c new file mode 100644 index 0000000..59c2656 --- /dev/null +++ b/extension-fileio.c @@ -0,0 +1,1557 @@ +/* + * file i/o server modification + */ + +#define FILE_IO 1 + +#include + +#include "my-stat.h" + +#include + +/* some things are not defined in stdio on all systems -- AAB 06/03/97 */ +#include +#include + +#include "my-unistd.h" + +#include "my-ctype.h" +#include "my-string.h" +#include "structures.h" +#include "exceptions.h" +#include "bf_register.h" +#include "functions.h" +#include "list.h" +#include "storage.h" +#include "utils.h" +#include "streams.h" +#include "server.h" +#include "network.h" + + +#include "tasks.h" +#include "log.h" + +#include "extension-fileio.h" + +/* apparently, not defined on some SysVish systems -- AAB 06/03/97 */ +typedef unsigned short umode_t; +/* your system may define o_mode_t instead -- AAB 06/03/97 */ +/* typedef o_mode_t umode_t; */ + +/***************************************************** + * Utility functions + *****************************************************/ + +const char *raw_bytes_to_clean(const char *buffer, int buflen) { + static Stream *s = 0; + int i; + + if(!s) + s = new_stream(100); + + for (i = 0; i < buflen; i++) { + unsigned char c = buffer[i]; + + if (isgraph(c) || c == ' ') + stream_add_char(s, c); + /* else drop it on the floor */ + } + + return reset_stream(s); +} + +const char *clean_to_raw_bytes(const char *buffer, int *buflen) { + *buflen = strlen(buffer); + return buffer; +} + + +/****************************************************** + * Module-internal data structures + *****************************************************/ + +/* + * File types are either TEXT or BINARY + */ + +typedef struct file_type *file_type; + +struct file_type { + + const char* (*in_filter)(const char *data, int buflen); + + const char* (*out_filter)(const char *data, int *buflen); + +}; + +file_type file_type_binary = NULL; +file_type file_type_text = NULL; + + + +#define FILE_O_READ 1 +#define FILE_O_WRITE 2 +#define FILE_O_FLUSH 4 + +typedef unsigned char file_mode; + +typedef struct file_handle file_handle; + +struct file_handle { + char valid; /* Is this a valid entry? */ + char *name; /* pathname of the file */ + file_type type; /* text or binary, sir? */ + file_mode mode; /* readin', writin' or both */ + + FILE *file; /* the actual file handle */ +}; + +typedef struct line_buffer line_buffer; + +struct line_buffer { + char *line; + struct line_buffer *next; +}; + +/*************************************************************** + * Version and package informaion + ***************************************************************/ + +char file_package_name[] = "FIO"; +char file_package_version[] = "1.5p3"; + + +/*************************************************************** + * File <-> FHANDLE descriptor table interface + ***************************************************************/ + + +file_handle file_table[FILE_IO_MAX_FILES]; + +char file_handle_valid(Var fhandle) { + int32 i = fhandle.v.num; + if(fhandle.type != TYPE_INT) + return 0; + if((i < 0) || (i >= FILE_IO_MAX_FILES)) + return 0; + return file_table[i].valid; +} + + +FILE *file_handle_file(Var fhandle) { + int32 i = fhandle.v.num; + return file_table[i].file; +} + +const char *file_handle_name(Var fhandle) { + int32 i = fhandle.v.num; + return file_table[i].name; +} + +file_type file_handle_type(Var fhandle) { + int32 i = fhandle.v.num; + return file_table[i].type; +} + +file_mode file_handle_mode(Var fhandle) { + int32 i = fhandle.v.num; + return file_table[i].mode; +} + + +void file_handle_destroy(Var fhandle) { + int32 i = fhandle.v.num; + file_table[i].file = NULL; + file_table[i].valid = 0; + free_str(file_table[i].name); +} + + +int32 file_allocate_next_handle(void) { + static int32 current_handle = 0; + int32 wrapped = current_handle; + + if(current_handle > FILE_IO_MAX_FILES) + wrapped = current_handle = 0; + + while(current_handle < FILE_IO_MAX_FILES) { + if(!file_table[current_handle].valid) + break; + + current_handle++; + if(current_handle > FILE_IO_MAX_FILES) + current_handle = 0; + if(current_handle == wrapped) + current_handle = FILE_IO_MAX_FILES; + } + if(current_handle == FILE_IO_MAX_FILES) { + current_handle = 0; + return -1; + } + return current_handle; +} + + +Var file_handle_new(const char *name, file_type type, file_mode mode) { + Var r; + int32 handle = file_allocate_next_handle(); + + r.type = TYPE_INT; + r.v.num = handle; + + if(handle >= 0) { + file_table[handle].valid = 1; + file_table[handle].name = str_dup(name); + file_table[handle].type = type; + file_table[handle].mode = mode; + } + + return r; +} + +void file_handle_set_file(Var fhandle, FILE *f) { + int32 i = fhandle.v.num; + file_table[i].file = f; +} + +/*************************************************************** + * Interface for modestrings + ***************************************************************/ + +/* + * Convert modestring to settings for type and mode. + * Returns pointer to stdio modestring if successfull and + * NULL if not. + */ + +const char *file_modestr_to_mode(const char *s, file_type *type, file_mode *mode) { + static char buffer[4] = {0, 0, 0, 0}; + int p = 0; + file_type t; + file_mode m = 0; + + if(!file_type_binary) { + file_type_binary = mymalloc(sizeof(struct file_type), M_STRING); + file_type_text = mymalloc(sizeof(struct file_type), M_STRING); + file_type_binary->in_filter = raw_bytes_to_binary; + file_type_binary->out_filter = binary_to_raw_bytes; + file_type_text->in_filter = raw_bytes_to_clean; + file_type_text->out_filter = clean_to_raw_bytes; + } + + + if(strlen(s) != 4) + return 0; + + if(s[0] == 'r') m |= FILE_O_READ; + else if(s[0] == 'w') m |= FILE_O_WRITE; + else if(s[0] == 'a') m |= FILE_O_WRITE; + else + return NULL; + + + buffer[p++] = s[0]; + + if(s[1] == '+') { + m |= (s[0] == 'r') ? FILE_O_WRITE : FILE_O_READ; + buffer[p++] = '+'; + } else if (s[1] != '-') { + return NULL; + } + + if(s[2] == 't') t = file_type_text; + else if (s[2] == 'b') { + t = file_type_binary; + buffer[p++] = 'b'; + } else + return NULL; + + if(s[3] == 'f') m |= FILE_O_FLUSH; + else if (s[3] != 'n') + return NULL; + + *type = t; *mode = m; + buffer[p] = 0; + return buffer; +} + + +/*************************************************************** + * Various error handlers + ***************************************************************/ + +package +file_make_error(const char *errtype, const char *msg) { + package p; + Var value; + + value.type = TYPE_STR; + value.v.str = str_dup(errtype); + + p.kind = BI_RAISE; + p.u.raise.code.type = TYPE_ERR; + p.u.raise.code.v.err = E_FILE; + p.u.raise.msg = str_dup(msg); + p.u.raise.value = value; + + return p; +} + +package file_raise_errno(const char *value_str) { + char *strerr; + + if(errno) { + strerr = strerror(errno); + return file_make_error(value_str, strerr); + } else { + return file_make_error("End of file", "End of file"); + } + +} + +package file_raise_notokcall(const char *funcid, Objid progr) { + return make_error_pack(E_PERM); +} + +package file_raise_notokfilename(const char *funcid, const char *pathname) { + Var p; + + p.type = TYPE_STR; + p.v.str = str_dup(pathname); + return make_raise_pack(E_INVARG, "Invalid pathname", p); +} + +/*************************************************************** + * Security verification + ***************************************************************/ + +int file_verify_caller(Objid progr) { + return is_wizard(progr); +} + +int file_verify_path(const char *pathname) { + /* + * A pathname is OK does not contain a + * any of instances the substring "/." + */ + + if(pathname[0] == '\0') + return 1; + + if((strlen(pathname) > 1) && (pathname[0] == '.') && (pathname[1] == '.')) + return 0; + + if(strindex(pathname, "/.", 0)) + return 0; + + return 1; +} + +/*************************************************************** + * Common code for FHANDLE-using functions + **************************************************************/ + +FILE *file_handle_file_safe(Var handle) { + if(!file_handle_valid(handle)) + return NULL; + else + return file_handle_file(handle); +} + +const char *file_handle_name_safe(Var handle) { + if(!file_handle_valid(handle)) + return NULL; + else + return file_handle_name(handle); +} + +/*************************************************************** + * Common code for file opening functions + ***************************************************************/ + +const char *file_resolve_path(const char *pathname) { + static Stream *s = 0; + + if(!s) + s = new_stream(strlen(pathname) + strlen(FILE_SUBDIR) + 1); + + if(!file_verify_path(pathname)) + return NULL; + + stream_add_string(s, FILE_SUBDIR); + if(pathname[0] == '/') + stream_add_string(s, pathname + 1); + else + stream_add_string(s, pathname); + + return reset_stream(s); + +} + +/*************************************************************** + * Built in functions + * file_version + ***************************************************************/ + +static package +bf_file_version(Var arglist, Byte next, void *vdata, Objid progr) +{ + char tmpbuffer[50]; + Var rv; + + sprintf(tmpbuffer, "%s/%s", file_package_name, file_package_version); + + rv.type = TYPE_STR; + rv.v.str = str_dup(tmpbuffer); + + return make_var_pack(rv); + +} + + +/*************************************************************** + * File open and close. + ***************************************************************/ + + +/* + * FHANDLE file_open(STR name, STR mode) + */ + +static package +bf_file_open(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle; + const char *real_filename; + const char *filename = arglist.v.list[1].v.str; + const char *mode = arglist.v.list[2].v.str; + const char *fmode; + file_mode rmode; + file_type type; + FILE *f; + + errno = 0; + + if(!file_verify_caller(progr)) + r = file_raise_notokcall("file_open", progr); + else if ((real_filename = file_resolve_path(filename)) == NULL) + r = file_raise_notokfilename("file_open", filename); + else if ((fmode = file_modestr_to_mode(mode, &type, &rmode)) == NULL) + r = make_raise_pack(E_INVARG, "Invalid mode string", var_ref(arglist.v.list[2])); + else if ((fhandle = file_handle_new(filename, type, rmode)).v.num < 0) + r = make_raise_pack(E_QUOTA, "Too many files open", zero); + else if ((f = fopen(real_filename, fmode)) == NULL) { + file_handle_destroy(fhandle); + r = file_raise_errno("file_open"); + } else { + /* phew, we actually got a successfull open */ + file_handle_set_file(fhandle, f); + r = make_var_pack(fhandle); + } + free_var(arglist); + return r; +} + +/* + * void file_close(FHANDLE handle); + */ + +static package +bf_file_close(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle = arglist.v.list[1]; + FILE *f; + + errno = 0; + + if(!file_verify_caller(progr)) + r = file_raise_notokcall("file_close", progr); + else if ((f = file_handle_file_safe(fhandle)) == NULL) + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", fhandle); + else { + fclose(f); + file_handle_destroy(fhandle); + r = no_var_pack(); + } + free_var(arglist); + return r; +} + +/* + * STR file_name(FHANDLE handle) + */ + +static package +bf_file_name(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle = arglist.v.list[1]; + const char *name; + Var rv; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_name", progr); + } else if ((name = file_handle_name_safe(fhandle)) == NULL) { + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", fhandle); + } else { + rv.type = TYPE_STR; + rv.v.str = str_dup(name); + r = make_var_pack(rv); + } + free_var(arglist); + return r; +} + +static package +bf_file_openmode(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle = arglist.v.list[1]; + char buffer[5] = {0, 0, 0, 0, 0}; + file_mode mode; + file_type type; + Var rv; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_name", progr); + } else if (!file_handle_valid(fhandle)) { + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", fhandle); + } else { + type = file_handle_type(fhandle); + mode = file_handle_mode(fhandle); + if(mode & FILE_O_READ) { + buffer[0] = 'r'; + } else if(mode & FILE_O_WRITE) { + buffer[0] = 'w'; + } + if(mode & (FILE_O_READ | FILE_O_WRITE)) + buffer[1] = '+'; + else + buffer[1] = '-'; + + if(type == file_type_binary) + buffer[2] = 'b'; + else + buffer[2] = 't'; + + if(mode & FILE_O_FLUSH) + buffer[3] = 'f'; + else + buffer[3] = 'n'; + + + rv.type = TYPE_STR; + rv.v.str = str_dup(buffer); + r = make_var_pack(rv); + } + free_var(arglist); + return r; +} + + + +/********************************************************** + * string (line-based) i/o + **********************************************************/ + +/* + * common functionality of file_readline and file_readlines + */ + +static const char *file_read_line(Var fhandle, int *count) { + static Stream *str = 0; + FILE *f; + int c; + + f = file_handle_file(fhandle); + + if(str == 0) + str = new_stream(FILE_IO_BUFFER_LENGTH); + + while((c = fgetc(f)) != EOF && c != '\n') + stream_add_char(str, c); + + if(c == EOF && stream_length(str) == 0) { + reset_stream(str); + *count = 0; + return NULL; + } + + *count = stream_length(str); + return reset_stream(str); +} + + +/* + * STR file_readline(FHANDLE handle) + */ + +static package +bf_file_readline(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle = arglist.v.list[1]; + Var rv; + int len; + file_mode mode; + file_type type; + const char *line; + + errno = 0; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_readline", progr); + } else if (!file_handle_valid(fhandle)) { + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", fhandle); + } else if (!(mode = file_handle_mode(fhandle)) & FILE_O_READ) + r = make_raise_pack(E_INVARG, "File is open write-only", fhandle); + else { + type = file_handle_type(fhandle); + if((line = file_read_line(fhandle, &len)) == NULL) + r = file_raise_errno("readline"); + else { + rv.type = TYPE_STR; + rv.v.str = str_dup((type->in_filter)(line, len)); + r = make_var_pack(rv); + } + } + free_var(arglist); + return r; +} + +/* + * STR file_readlines(FHANDLE handle, INT start, INT end) + */ + +void free_line_buffer(line_buffer *head, int strings_too) { + line_buffer *next; + if(head) { + next = head->next; + myfree(head, M_STRUCT); + head = next; + while(head != NULL) { + next = head->next; + if(strings_too) + free_str(head->line); + myfree(head, M_STRUCT); + head = next; + } + } +} + +line_buffer *new_line_buffer(char *line) { + line_buffer *p = mymalloc(sizeof(line_buffer), M_STRUCT); + p->line = line; + p->next = NULL; + return p; +} + +static package +bf_file_readlines(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle = arglist.v.list[1]; + int32 begin = arglist.v.list[2].v.num; + int32 end = arglist.v.list[3].v.num; + int32 begin_loc = 0, linecount = 0; + file_type type; + file_mode mode; + Var rv; + int current_line = 0, len = 0, i = 0; + const char *line = NULL; + FILE *f; + line_buffer *linebuf_head = NULL, *linebuf_cur = NULL; + + errno = 0; + + if((begin < 1) || (begin > end)) + return make_error_pack(E_INVARG); + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_readlines", progr); + } else if ((f = file_handle_file_safe(fhandle)) == NULL) { + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", fhandle); + } else if (!(mode = file_handle_mode(fhandle)) & FILE_O_READ) + r = make_raise_pack(E_INVARG, "File is open write-only", fhandle); + else { + + /* Back to the beginning ... */ + rewind(f); + + /* "seek" to that line */ + begin--; + while((current_line != begin) + && ((line = file_read_line(fhandle, &len)) != NULL)) + current_line++; + + if(((begin != 0) && (line == NULL)) || ((begin_loc = ftell(f)) == -1)) + r = file_raise_errno("read_line"); + else { + type = file_handle_type(fhandle); + + /* + * now that we have where to begin, it's time to slurp lines + * and seek to EOF or to the end_line, whichever comes first + */ + + linebuf_head = linebuf_cur = new_line_buffer(NULL); + + while((current_line != end) + && ((line = file_read_line(fhandle, &len)) != NULL)) { + linebuf_cur->next = new_line_buffer(str_dup((type->in_filter)(line, len))); + linebuf_cur = linebuf_cur->next; + + current_line++; + } + linecount = current_line - begin; + + linebuf_cur = linebuf_head->next; + + if(fseek(f, begin_loc, SEEK_SET) == -1) { + free_line_buffer(linebuf_head, 1); + r = file_raise_errno("seeking"); + } else { + rv = new_list(linecount); + i = 1; + while(linebuf_cur != NULL) { + rv.v.list[i].type = TYPE_STR; + rv.v.list[i].v.str = linebuf_cur->line; + linebuf_cur = linebuf_cur->next; + i++; + } + free_line_buffer(linebuf_head, 0); + r = make_var_pack(rv); + } + } + } + + free_var(arglist); + return r; +} + +/* + * void file_writeline(FHANDLE handle, STR line) + */ + +static package +bf_file_writeline(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle = arglist.v.list[1]; + const char *buffer = arglist.v.list[2].v.str; + const char *rawbuffer; + file_mode mode; + file_type type; + int len; + FILE *f; + + errno = 0; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_writeline", progr); + } else if ((f = file_handle_file_safe(fhandle)) == NULL) { + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", fhandle); + } else if (!(mode = file_handle_mode(fhandle)) & FILE_O_WRITE) + r = make_raise_pack(E_INVARG, "File is open read-only", fhandle); + else { + type = file_handle_type(fhandle); + if((rawbuffer = (type->out_filter)(buffer, &len)) == NULL) + r = make_raise_pack(E_INVARG, "Invalid binary string", fhandle); + else if((fputs(rawbuffer, f) == EOF) || (fputc('\n', f) != '\n')) + r = file_raise_errno(file_handle_name(fhandle)); + else { + if(mode & FILE_O_FLUSH) { + fflush(f); + } + r = no_var_pack(); + } + } + free_var(arglist); + return r; +} + +/******************************************************** + * binary i/o + ********************************************************/ + +/* + * STR file_read(FHANDLE handle, INT record_length) + */ + +static package +bf_file_read(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + + Var fhandle = arglist.v.list[1]; + file_mode mode; + file_type type; + int32 record_length = arglist.v.list[2].v.num; + int32 read_length; + + char buffer[FILE_IO_BUFFER_LENGTH]; + + Var rv; + + static Stream *str = 0; + int len = 0, read = 0; + + FILE *f; + + errno = 0; + + read_length = (record_length > sizeof(buffer)) ? sizeof(buffer) : record_length; + + if(str == 0) + str = new_stream(FILE_IO_BUFFER_LENGTH); + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_read", progr); + } else if ((f = file_handle_file_safe(fhandle)) == NULL) { + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", fhandle); + } else if (!(mode = file_handle_mode(fhandle)) & FILE_O_READ) + r = make_raise_pack(E_INVARG, "File is open write-only", fhandle); + else { + type = file_handle_type(fhandle); + + try_again: + read = fread(buffer, sizeof(char), read_length, f); + if(!read && !len) { + /* + * No more to read. This is only an error if nothing + * has been read so far. + * + */ + r = file_raise_errno(file_handle_name(fhandle)); + } else if (read && ((len += read) < record_length)){ + /* + * We got something this time, but it isn't enough. + */ + stream_add_string(str, (type->in_filter)(buffer, read)); + read = 0; + goto try_again; + } else { + /* + * We didn't get anything last time, but we have something already + * OR + * We got everything we need. + */ + + stream_add_string(str, (type->in_filter)(buffer, read)); + + rv.type = TYPE_STR; + rv.v.str = str_dup(reset_stream(str)); + + r = make_var_pack(rv); + } + } + free_var(arglist); + return r; +} + +/* + * void file_flush(FHANDLE handle) + */ + +static package +bf_file_flush(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle = arglist.v.list[1]; + FILE *f; + + errno = 0; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_flush", progr); + } else if ((f = file_handle_file_safe(fhandle)) == NULL) { + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", fhandle); + } else { + if(fflush(f)) + r = file_raise_errno("flushing"); + else + r = no_var_pack(); + } + free_var(arglist); + return r; +} + + +/* + * INT file_write(FHANDLE fh, STR data) + */ + +static package +bf_file_write(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle = arglist.v.list[1], rv; + const char *buffer = arglist.v.list[2].v.str; + const char *rawbuffer; + file_mode mode; + file_type type; + int len; + int written; + FILE *f; + + errno = 0; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_write", progr); + } else if ((f = file_handle_file_safe(fhandle)) == NULL) { + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", fhandle); + } else if (!(mode = file_handle_mode(fhandle)) & FILE_O_WRITE) + r = make_raise_pack(E_INVARG, "File is open read-only", fhandle); + else { + type = file_handle_type(fhandle); + if((rawbuffer = (type->out_filter)(buffer, &len)) == NULL) + r = make_raise_pack(E_INVARG, "Invalid binary string", fhandle); + else if(!(written = fwrite(rawbuffer, sizeof(char), len, f))) + r = file_raise_errno(file_handle_name(fhandle)); + else { + if(mode & FILE_O_FLUSH) + fflush(f); + rv.type = TYPE_INT; + rv.v.num = written; + r = make_var_pack(rv); + } + } + free_var(arglist); + return r; +} + + +/************************************************ + * navigating the file + ************************************************/ + +/* + * void file_seek(FHANDLE handle, FLOC location, STR whence) + * whence in {"SEEK_SET", "SEEK_CUR", "SEEK_END"} + */ + +static package +bf_file_seek(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle = arglist.v.list[1]; + int32 seek_to = arglist.v.list[2].v.num; + const char *whence = arglist.v.list[3].v.str; + int whnce = 0, whence_ok = 1; + FILE *f; + + errno = 0; + + if(!mystrcasecmp(whence, "SEEK_SET")) + whnce = SEEK_SET; + else if (!mystrcasecmp(whence, "SEEK_CUR")) + whnce = SEEK_CUR; + else if (!mystrcasecmp(whence, "SEEK_END")) + whnce = SEEK_END; + else + whence_ok = 0; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_seek", progr); + } else if ((f = file_handle_file_safe(fhandle)) == NULL) { + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", var_ref(fhandle)); + } else if (!whence_ok) { + r = make_raise_pack(E_INVARG, "Invalid whence", zero); + } else { + if(fseek(f, seek_to, whnce)) + r = file_raise_errno(file_handle_name(fhandle)); + else + r = no_var_pack(); + } + free_var(arglist); + return r; +} + +/* + * FLOC file_tell(FHANDLE handle) + */ + +static package +bf_file_tell(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle = arglist.v.list[1]; + Var rv; + FILE *f; + + errno = 0; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_tell", progr); + } else if ((f = file_handle_file_safe(fhandle)) == NULL) { + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", var_ref(fhandle)); + } else { + rv.type = TYPE_INT; + if((rv.v.num = ftell(f)) < 0) + r = file_raise_errno(file_handle_name(fhandle)); + else + r = make_var_pack(rv); + } + free_var(arglist); + return r; +} + +/* + * INT file_eof(FHANDLE handle) + */ + +static package +bf_file_eof(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var fhandle = arglist.v.list[1]; + Var rv; + FILE *f; + + errno = 0; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_eof", progr); + } else if ((f = file_handle_file_safe(fhandle)) == NULL) { + r = make_raise_pack(E_INVARG, "Invalid FHANDLE", var_ref(fhandle)); + } else { + rv.type = TYPE_INT; + rv.v.num = feof(f); + r = make_var_pack(rv); + } + free_var(arglist); + return r; +} + +/***************************************************************** + * Functions that stat() + *****************************************************************/ + +/* + * (internal) int(statok) file_stat(Var filespec, package *r, struct stat *buf) + */ + +int file_stat(Objid progr, Var filespec, package *r, struct stat *buf) { + int statok = 0; + + if(!file_verify_caller(progr)) { + *r = file_raise_notokcall("file_stat", progr); + } else if (filespec.type == TYPE_STR) { + const char *filename = filespec.v.str; + const char *real_filename; + + if((real_filename = file_resolve_path(filename)) == NULL) { + *r = file_raise_notokfilename("file_stat", filename); + } else { + if(stat(real_filename, buf) != 0) + *r = file_raise_errno(filename); + else { + statok = 1; + } + } + } else { + FILE *f; + if((f = file_handle_file_safe(filespec)) == NULL) + *r = make_raise_pack(E_INVARG, "Invalid FHANDLE", filespec); + else { + if(fstat(fileno(f), buf) != 0) + *r = file_raise_errno(file_handle_name(filespec)); + else { + statok = 1; + } + } + } + return statok; +} + +const char *file_type_string(umode_t st_mode) { + if(S_ISREG(st_mode)) + return "reg"; + else if (S_ISDIR(st_mode)) + return "dir"; + else if (S_ISFIFO(st_mode)) + return "fifo"; + else if (S_ISBLK(st_mode)) + return "block"; + else if (S_ISSOCK(st_mode)) + return "socket"; + else + return "unknown"; +} + +const char *file_mode_string(umode_t st_mode) { + static Stream *s = 0; + if(!s) + s = new_stream(4); + stream_printf(s, "%03o", st_mode & 0777); + return reset_stream(s); +} + +/* + * INT file_size(STR filename) + * INT file_size(FHANDLE fh) + */ + +static package +bf_file_size(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var rv; + Var filespec = arglist.v.list[1]; + struct stat buf; + + if (file_stat(progr, filespec, &r, &buf)) { + rv.type = TYPE_INT; + rv.v.num = buf.st_size; + r = make_var_pack(rv); + } + free_var(arglist); + return r; +} + +/* + * STR file_mode(STR filename) + * STR file_mode(FHANDLE fh) + */ + +static package +bf_file_mode(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var rv; + Var filespec = arglist.v.list[1]; + struct stat buf; + + if (file_stat(progr, filespec, &r, &buf)) { + rv.type = TYPE_STR; + rv.v.str = str_dup(file_mode_string(buf.st_mode)); + r = make_var_pack(rv); + } + free_var(arglist); + return r; +} + +/* + * STR file_type(STR filename) + * STR file_type(FHANDLE fh) + */ + +static package +bf_file_type(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var rv; + Var filespec = arglist.v.list[1]; + struct stat buf; + + if (file_stat(progr, filespec, &r, &buf)) { + rv.type = TYPE_STR; + rv.v.str = str_dup(file_type_string(buf.st_mode)); + r = make_var_pack(rv); + } + free_var(arglist); + return r; +} + +/* + * INT file_last_access(STR filename) + * INT file_last_access(FHANDLE fh) + */ + +static package +bf_file_last_access(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var rv; + Var filespec = arglist.v.list[1]; + struct stat buf; + + if (file_stat(progr, filespec, &r, &buf)) { + rv.type = TYPE_INT; + rv.v.num = buf.st_atime; + r = make_var_pack(rv); + } + free_var(arglist); + return r; +} + +/* + * INT file_last_modify(STR filename) + * INT file_last_modify(FHANDLE fh) + */ + +static package +bf_file_last_modify(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var rv; + Var filespec = arglist.v.list[1]; + struct stat buf; + + if (file_stat(progr, filespec, &r, &buf)) { + rv.type = TYPE_INT; + rv.v.num = buf.st_mtime; + r = make_var_pack(rv); + } + free_var(arglist); + return r; +} + +/* + * INT file_last_change(STR filename) + * INT file_last_change(FHANDLE fh) + */ + +static package +bf_file_last_change(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var rv; + Var filespec = arglist.v.list[1]; + struct stat buf; + + if (file_stat(progr, filespec, &r, &buf)) { + rv.type = TYPE_INT; + rv.v.num = buf.st_ctime; + r = make_var_pack(rv); + } + free_var(arglist); + return r; +} + +/* + * INT file_stat(STR filename) + * INT file_stat(FHANDLE fh) + */ + +static package +bf_file_stat(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + Var rv; + Var filespec = arglist.v.list[1]; + struct stat buf; + + if (file_stat(progr, filespec, &r, &buf)) { + rv = new_list(8); + rv.v.list[1].type = TYPE_INT; + rv.v.list[1].v.num = buf.st_size; + rv.v.list[2].type = TYPE_STR; + rv.v.list[2].v.str = str_dup(file_type_string(buf.st_mode)); + rv.v.list[3].type = TYPE_STR; + rv.v.list[3].v.str = str_dup(file_mode_string(buf.st_mode)); + rv.v.list[4].type = TYPE_STR; + rv.v.list[4].v.str = str_dup(""); + rv.v.list[5].type = TYPE_STR; + rv.v.list[5].v.str = str_dup(""); + rv.v.list[6].type = TYPE_INT; + rv.v.list[6].v.num = buf.st_atime; + rv.v.list[7].type = TYPE_INT; + rv.v.list[7].v.num = buf.st_mtime; + rv.v.list[8].type = TYPE_INT; + rv.v.list[8].v.num = buf.st_ctime; + r = make_var_pack(rv); + } + free_var(arglist); + return r; +} + +/***************************************************************** + * Housekeeping functions + *****************************************************************/ + +/* + * LIST file_list(STR pathname, [ANY detailed]) + */ + +int file_list_select(const struct dirent *d) { + const char *name = d->d_name; + int l = strlen(name); + if((l == 1) && (name[0] == '.')) + return 0; + else if ((l == 2) && (name[0] == '.') && (name[1] == '.')) + return 0; + else + return 1; +} + +static package +bf_file_list(Var arglist, Byte next, void *vdata, Objid progr) +{ + /* modified to use opendir/readdir which is slightly more "standard" + than the original scandir method. -- AAB 06/03/97 + */ + package r; + const char *pathspec = arglist.v.list[1].v.str; + const char *real_pathname; + int detailed = (arglist.v.list[0].v.num > 1 + ? is_true(arglist.v.list[2]) + : 0); + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_list", progr); + } else if((real_pathname = file_resolve_path(pathspec)) == NULL) { + r = file_raise_notokfilename("file_list", pathspec); + } else { + DIR *curdir; + Stream *s = new_stream(64); + int failed = 0; + struct stat buf; + Var rv, detail; + struct dirent *curfile; + + if (!(curdir = opendir (real_pathname))) + r = file_raise_errno(pathspec); + else { + rv = new_list(0); + while ( (curfile = readdir(curdir)) != 0 ) { + if (strncmp(curfile->d_name, ".", 2) != 0 && strncmp(curfile->d_name, "..", 3) != 0) { + if (detailed) { + stream_add_string(s, real_pathname); + stream_add_char(s, '/'); + stream_add_string(s, curfile->d_name); + if (stat(reset_stream(s), &buf) != 0) { + failed = 1; + break; + } else { + detail = new_list(4); + detail.v.list[1].type = TYPE_STR; + detail.v.list[1].v.str = str_dup(curfile->d_name); + detail.v.list[2].type = TYPE_STR; + detail.v.list[2].v.str = str_dup(file_type_string(buf.st_mode)); + detail.v.list[3].type = TYPE_STR; + detail.v.list[3].v.str = str_dup(file_mode_string(buf.st_mode)); + detail.v.list[4].type = TYPE_INT; + detail.v.list[4].v.num = buf.st_size; + } + } else { + detail.type = TYPE_STR; + detail.v.str = str_dup(curfile->d_name); + } + rv = listappend(rv, detail); + } + } + if(failed) { + free_var(rv); + r = file_raise_errno(pathspec); + } else + r = make_var_pack(rv); + closedir(curdir); + } + free_stream(s); + } + free_var(arglist); + return r; +} + + +/* + * void file_mkdir(STR pathname) + */ + +static package +bf_file_mkdir(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + const char *pathspec = arglist.v.list[1].v.str; + const char *real_pathname; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_mkdir", progr); + } else if((real_pathname = file_resolve_path(pathspec)) == NULL) { + r = file_raise_notokfilename("file_mkdir", pathspec); + } else { + if(mkdir(real_pathname, 0777) != 0) + r = file_raise_errno(pathspec); + else + r = no_var_pack(); + + } + free_var(arglist); + return r; +} + +/* + * void file_rmdir(STR pathname) + */ + +static package +bf_file_rmdir(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + const char *pathspec = arglist.v.list[1].v.str; + const char *real_pathname; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_rmdir", progr); + } else if((real_pathname = file_resolve_path(pathspec)) == NULL) { + r = file_raise_notokfilename("file_rmdir", pathspec); + } else { + if(rmdir(real_pathname) != 0) + r = file_raise_errno(pathspec); + else + r = no_var_pack(); + + } + free_var(arglist); + return r; +} + +/* + * void file_remove(STR pathname) + */ + +static package +bf_file_remove(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + const char *pathspec = arglist.v.list[1].v.str; + const char *real_pathname; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_remove", progr); + } else if((real_pathname = file_resolve_path(pathspec)) == NULL) { + r = file_raise_notokfilename("file_remove", pathspec); + } else { + if(remove(real_pathname) != 0) + r = file_raise_errno(pathspec); + else + r = no_var_pack(); + } + free_var(arglist); + return r; +} + +/* + * void file_rename(STR pathname) + */ + +static package +bf_file_rename(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + const char *fromspec = arglist.v.list[1].v.str; + const char *tospec = arglist.v.list[2].v.str; + char *real_fromspec = NULL; + const char *real_tospec; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_rename", progr); + } else if((real_fromspec = str_dup(file_resolve_path(fromspec))) == NULL) { + r = file_raise_notokfilename("file_rename", fromspec); + } else if((real_tospec = file_resolve_path(tospec)) == NULL) { + r = file_raise_notokfilename("file_rename", tospec); + } else { + if(rename(real_fromspec, real_tospec) != 0) + r = file_raise_errno("rename"); + else + r = no_var_pack(); + } + if(real_fromspec) + free_str(real_fromspec); + free_var(arglist); + return r; +} + + + +/* + * void file_chmod(STR pathname, STR mode) + */ + + +int file_chmodstr_to_mode(const char *modespec, mode_t *newmode) { + mode_t m = 0; + int i = 0, fct = 1; + + if(strlen(modespec) != 3) + return 0; + else { + for(i = 2; i >= 0; i--) { + char c = modespec[i]; + if(!((c >= '0') && (c <= '7'))) + return 0; + else { + m += fct * (c - '0'); + } + fct *= 8; + } + } + *newmode = m; + return 1; +} + +static package +bf_file_chmod(Var arglist, Byte next, void *vdata, Objid progr) +{ + package r; + const char *pathspec = arglist.v.list[1].v.str; + const char *modespec = arglist.v.list[2].v.str; + mode_t newmode; + const char *real_filename; + + if(!file_verify_caller(progr)) { + r = file_raise_notokcall("file_chmod", progr); + } else if(!file_chmodstr_to_mode(modespec, &newmode)) { + r = make_raise_pack(E_INVARG, "Invalid mode string", zero); + } else if((real_filename = file_resolve_path(pathspec)) == NULL) { + r = file_raise_notokfilename("file_chmod", pathspec); + } else { + if(chmod(real_filename, newmode) != 0) + r = file_raise_errno("chmod"); + else + r = no_var_pack(); + } + free_var(arglist); + return r; +} + +/************************************************************************/ + +void +register_fileio(void) +{ +#if FILE_IO + + register_function("file_version", 0, 0, bf_file_version); + + register_function("file_open", 2, 2, bf_file_open, TYPE_STR, TYPE_STR); + register_function("file_close", 1, 1, bf_file_close, TYPE_INT); + register_function("file_name", 1, 1, bf_file_name, TYPE_INT); + register_function("file_openmode", 1, 1, bf_file_openmode, TYPE_INT); + + + register_function("file_readline", 1, 1, bf_file_readline, TYPE_INT); + register_function("file_readlines", 3, 3, bf_file_readlines, TYPE_INT, TYPE_INT, TYPE_INT); + register_function("file_writeline", 2, 2, bf_file_writeline, TYPE_INT, TYPE_STR); + + register_function("file_read", 2, 2, bf_file_read, TYPE_INT, TYPE_INT); + register_function("file_write", 2, 2, bf_file_write, TYPE_INT, TYPE_STR); + register_function("file_flush", 1, 1, bf_file_flush, TYPE_INT); + + + register_function("file_seek", 3, 3, bf_file_seek, TYPE_INT, TYPE_INT, TYPE_STR); + register_function("file_tell", 1, 1, bf_file_tell, TYPE_INT); + + register_function("file_eof", 1, 1, bf_file_eof, TYPE_INT); + + register_function("file_list", 1, 2, bf_file_list, TYPE_STR, TYPE_ANY); + register_function("file_mkdir", 1, 1, bf_file_mkdir, TYPE_STR); + register_function("file_rmdir", 1, 1, bf_file_rmdir, TYPE_STR); + register_function("file_remove", 1, 1, bf_file_remove, TYPE_STR); + register_function("file_rename", 2, 2, bf_file_rename, TYPE_STR, TYPE_STR); + register_function("file_chmod", 2, 2, bf_file_chmod, TYPE_STR, TYPE_STR); + + register_function("file_size", 1, 1, bf_file_size, TYPE_ANY); + register_function("file_mode", 1, 1, bf_file_mode, TYPE_ANY); + register_function("file_type", 1, 1, bf_file_type, TYPE_ANY); + register_function("file_last_access", 1, 1, bf_file_last_access, TYPE_ANY); + register_function("file_last_modify", 1, 1, bf_file_last_modify, TYPE_ANY); + register_function("file_last_change", 1, 1, bf_file_last_change, TYPE_ANY); + register_function("file_stat", 1, 1, bf_file_stat, TYPE_ANY); + +#endif +} diff --git a/extension-fileio.h b/extension-fileio.h new file mode 100644 index 0000000..466c698 --- /dev/null +++ b/extension-fileio.h @@ -0,0 +1,15 @@ +/* + * extension-fileio.h + * + */ + +#ifndef EXT_FILE_IO_H + +#define EXT_FILE_IO_H 1 + +#define FILE_IO_MAX_FILES 256 +#define FILE_SUBDIR "files/" + +#define FILE_IO_BUFFER_LENGTH 4096 + +#endif diff --git a/functions.c b/functions.c index 5c786f9..d8176e1 100644 --- a/functions.c +++ b/functions.c @@ -54,7 +54,8 @@ static registry bi_function_registries[] = register_property, register_server, register_tasks, - register_verbs + register_verbs, + register_fileio }; void diff --git a/keywords.c b/keywords.c index 0a64bd5..e98a5a1 100644 --- a/keywords.c +++ b/keywords.c @@ -1,5 +1,5 @@ /* C code produced by gperf version 2.1p1 (K&R C version, modified by Pavel) */ -/* Command-line: pgperf -aCIptT -k1,3,$ keywords.gperf */ +/* Command-line: pgperf/pgperf -aCIptT -k1,3,$ keywords.gperf */ #include "my-ctype.h" @@ -35,179 +35,133 @@ #define MIN_HASH_VALUE 3 #define MAX_HASH_VALUE 106 /* - 35 keywords - 104 is the maximum key range - */ + 36 keywords + 104 is the maximum key range +*/ static int -hash(register const char *str, register int len) +hash (register const char *str, register int len) { - static const unsigned char hash_table[] = + static const unsigned char hash_table[] = { - 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, - 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, - 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, - 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, - 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, - 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, - 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, - 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, - 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, - 106, 106, 106, 106, 106, 106, 106, 10, 0, 45, - 0, 0, 0, 10, 106, 45, 106, 10, 106, 35, - 5, 106, 5, 10, 0, 25, 55, 106, 35, 5, - 106, 10, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 10, 0, 45, + 20, 0, 0, 10, 106, 45, 106, 10, 106, 35, + 5, 106, 5, 10, 0, 25, 55, 106, 35, 5, + 106, 10, 106, 106, 106, 106, 106, 106, }; - register int hval = len; - - switch (hval) { - default: - case 3: - hval += hash_table[tolower((unsigned char) str[2])]; - case 2: - case 1: - hval += hash_table[tolower((unsigned char) str[0])]; + register int hval = len; + + switch (hval) + { + default: + case 3: + hval += hash_table[tolower((unsigned char) str[2])]; + case 2: + case 1: + hval += hash_table[tolower((unsigned char) str[0])]; } - return hval + hash_table[tolower((unsigned char) str[len - 1])]; + return hval + hash_table[tolower((unsigned char) str[len - 1])]; } static int -case_strcmp(register const char *str, register const char *key) +case_strcmp (register const char *str, register const char *key) { - int ans = 0; + int ans = 0; - while (!(ans = tolower(*str) - (int) *key) && *str) - str++, key++; + while (!(ans = tolower(*str) - (int) *key) && *str) + str++, key++; - return ans; + return ans; } const struct keyword * -in_word_set(register const char *str, register int len) +in_word_set (register const char *str, register int len) { - static const struct keyword wordlist[] = + static const struct keyword wordlist[] = { - {"",}, - {"",}, - {"",}, - {"for", DBV_Prehistory, tFOR}, - {"",}, - {"endif", DBV_Prehistory, tENDIF}, - {"endfor", DBV_Prehistory, tENDFOR}, - {"e_range", DBV_Prehistory, tERROR, E_RANGE}, - {"endwhile", DBV_Prehistory, tENDWHILE}, - {"e_recmove", DBV_Prehistory, tERROR, E_RECMOVE}, - {"",}, - {"e_none", DBV_Prehistory, tERROR, E_NONE}, - {"",}, - {"e_propnf", DBV_Prehistory, tERROR, E_PROPNF}, - {"fork", DBV_Prehistory, tFORK}, - {"break", DBV_BreakCont, tBREAK}, - {"endtry", DBV_Exceptions, tENDTRY}, - {"endfork", DBV_Prehistory, tENDFORK}, - {"",}, - {"",}, - {"",}, - {"",}, - {"finally", DBV_Exceptions, tFINALLY}, - {"",}, - {"",}, - {"",}, - {"",}, - {"e_quota", DBV_Prehistory, tERROR, E_QUOTA}, - {"",}, - {"else", DBV_Prehistory, tELSE}, - {"",}, - {"elseif", DBV_Prehistory, tELSEIF}, - {"",}, - {"any", DBV_Exceptions, tANY}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"e_div", DBV_Prehistory, tERROR, E_DIV}, - {"e_args", DBV_Prehistory, tERROR, E_ARGS}, - {"e_varnf", DBV_Prehistory, tERROR, E_VARNF}, - {"e_verbnf", DBV_Prehistory, tERROR, E_VERBNF}, - {"",}, - {"",}, - {"e_perm", DBV_Prehistory, tERROR, E_PERM}, - {"if", DBV_Prehistory, tIF}, - {"",}, - {"",}, - {"",}, - {"",}, - {"in", DBV_Prehistory, tIN}, - {"e_invind", DBV_Prehistory, tERROR, E_INVIND}, - {"",}, - {"while", DBV_Prehistory, tWHILE}, - {"e_nacc", DBV_Prehistory, tERROR, E_NACC}, - {"",}, - {"continue", DBV_BreakCont, tCONTINUE}, - {"",}, - {"",}, - {"e_type", DBV_Prehistory, tERROR, E_TYPE}, - {"e_float", DBV_Float, tERROR, E_FLOAT}, - {"e_invarg", DBV_Prehistory, tERROR, E_INVARG}, - {"",}, - {"",}, - {"return", DBV_Prehistory, tRETURN}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"try", DBV_Exceptions, tTRY}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"e_maxrec", DBV_Prehistory, tERROR, E_MAXREC}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"",}, - {"except", DBV_Exceptions, tEXCEPT}, + {"",}, {"",}, {"",}, + {"for", DBV_Prehistory, tFOR}, + {"",}, {"",}, + {"e_file", DBV_FileIO, tERROR, E_FILE}, + {"e_range", DBV_Prehistory, tERROR, E_RANGE}, + {"",}, + {"e_recmove", DBV_Prehistory, tERROR, E_RECMOVE}, + {"",}, + {"e_none", DBV_Prehistory, tERROR, E_NONE}, + {"",}, + {"e_propnf", DBV_Prehistory, tERROR, E_PROPNF}, + {"fork", DBV_Prehistory, tFORK}, + {"break", DBV_BreakCont, tBREAK}, + {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, + {"finally", DBV_Exceptions, tFINALLY}, + {"",}, {"",}, + {"endif", DBV_Prehistory, tENDIF}, + {"endfor", DBV_Prehistory, tENDFOR}, + {"e_quota", DBV_Prehistory, tERROR, E_QUOTA}, + {"endwhile", DBV_Prehistory, tENDWHILE}, + {"else", DBV_Prehistory, tELSE}, + {"",}, + {"elseif", DBV_Prehistory, tELSEIF}, + {"",}, + {"any", DBV_Exceptions, tANY}, + {"",}, {"",}, + {"endtry", DBV_Exceptions, tENDTRY}, + {"endfork", DBV_Prehistory, tENDFORK}, + {"",}, {"",}, {"",}, + {"e_args", DBV_Prehistory, tERROR, E_ARGS}, + {"e_varnf", DBV_Prehistory, tERROR, E_VARNF}, + {"e_verbnf", DBV_Prehistory, tERROR, E_VERBNF}, + {"",}, {"",}, + {"e_perm", DBV_Prehistory, tERROR, E_PERM}, + {"if", DBV_Prehistory, tIF}, + {"",}, {"",}, {"",}, {"",}, + {"in", DBV_Prehistory, tIN}, + {"",}, {"",}, + {"while", DBV_Prehistory, tWHILE}, + {"e_nacc", DBV_Prehistory, tERROR, E_NACC}, + {"",}, + {"continue", DBV_BreakCont, tCONTINUE}, + {"",}, + {"e_div", DBV_Prehistory, tERROR, E_DIV}, + {"e_type", DBV_Prehistory, tERROR, E_TYPE}, + {"e_float", DBV_Float, tERROR, E_FLOAT}, + {"e_invarg", DBV_Prehistory, tERROR, E_INVARG}, + {"",}, {"",}, + {"return", DBV_Prehistory, tRETURN}, + {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, + {"e_invind", DBV_Prehistory, tERROR, E_INVIND}, + {"",}, {"",}, {"",}, {"",}, + {"try", DBV_Exceptions, tTRY}, + {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, + {"e_maxrec", DBV_Prehistory, tERROR, E_MAXREC}, + {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, + {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, {"",}, + {"except", DBV_Exceptions, tEXCEPT}, }; - if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) { - register int key = hash(str, len); + if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) + { + register int key = hash (str, len); - if (key <= MAX_HASH_VALUE && key >= MIN_HASH_VALUE) { - register const char *s = wordlist[key].name; + if (key <= MAX_HASH_VALUE && key >= MIN_HASH_VALUE) + { + register const char *s = wordlist[key].name; - if (*s == tolower(*str) && !case_strcmp(str + 1, s + 1)) - return &wordlist[key]; - } + if (*s == tolower(*str) && !case_strcmp (str + 1, s + 1)) + return &wordlist[key]; + } } - return 0; + return 0; } const struct keyword * @@ -216,19 +170,12 @@ find_keyword(const char *word) return in_word_set(word, strlen(word)); } -char rcsid_keywords[] = "$Id: keywords.c,v 1.3 1998/12/14 13:17:55 nop Exp $"; +char rcsid_keywords[] = "$Id: keywords.gperf,v 1.1 1997/03/03 03:45:02 nop Exp $"; -/* - * $Log: keywords.c,v $ - * Revision 1.3 1998/12/14 13:17:55 nop - * Merge UNSAFE_OPTS (ref fixups); fix Log tag placement to fit CVS whims - * - * Revision 1.2 1997/03/03 04:18:45 nop - * GNU Indent normalization - * - * Revision 1.1.1.1 1997/03/03 03:45:00 nop - * LambdaMOO 1.8.0p5 - * +/* $Log: keywords.gperf,v $ +/* Revision 1.1 1997/03/03 03:45:02 nop +/* Initial revision +/* * Revision 2.2 1996/02/08 06:33:21 pavel * Added `break', `continue', and E_FLOAT. Updated copyright notice for 1996. * Release 1.8.0beta1. diff --git a/keywords.gperf b/keywords.gperf index 6bf53ab..60e2d21 100644 --- a/keywords.gperf +++ b/keywords.gperf @@ -62,6 +62,7 @@ E_NACC, DBV_Prehistory, tERROR, E_NACC E_INVARG, DBV_Prehistory, tERROR, E_INVARG E_QUOTA, DBV_Prehistory, tERROR, E_QUOTA E_FLOAT, DBV_Float, tERROR, E_FLOAT +E_FILE, DBV_FileIO, tERROR, E_FILE %% const struct keyword * diff --git a/storage.h b/storage.h index c071823..6e34c3c 100644 --- a/storage.h +++ b/storage.h @@ -35,6 +35,9 @@ typedef enum Memory_Type { M_REF_ENTRY, M_REF_TABLE, M_VC_ENTRY, M_VC_TABLE, M_STRING_PTRS, M_INTERN_POINTER, M_INTERN_ENTRY, M_INTERN_HUNK, + /* where no more specific type applies */ + M_STRUCT, + Sizeof_Memory_Type } Memory_Type; diff --git a/structures.h b/structures.h index f0cff94..66015d4 100644 --- a/structures.h +++ b/structures.h @@ -39,7 +39,8 @@ typedef int32 Objid; */ enum error { E_NONE, E_TYPE, E_DIV, E_PERM, E_PROPNF, E_VERBNF, E_VARNF, E_INVIND, - E_RECMOVE, E_MAXREC, E_RANGE, E_ARGS, E_NACC, E_INVARG, E_QUOTA, E_FLOAT + E_RECMOVE, E_MAXREC, E_RANGE, E_ARGS, E_NACC, E_INVARG, E_QUOTA, E_FLOAT, + E_FILE }; /* Do not reorder or otherwise modify this list, except to add new elements at diff --git a/unparse.c b/unparse.c index a58156a..ba3d4eb 100644 --- a/unparse.c +++ b/unparse.c @@ -71,6 +71,8 @@ unparse_error(enum error e) return "Resource limit exceeded"; case E_FLOAT: return "Floating-point arithmetic error"; + case E_FILE: + return "File error"; } return "Unknown Error"; @@ -112,6 +114,8 @@ error_name(enum error e) return "E_QUOTA"; case E_FLOAT: return "E_FLOAT"; + case E_FILE: + return "E_FILE"; } return "E_?"; diff --git a/version.h b/version.h index 3dde0c8..d3da6be 100644 --- a/version.h +++ b/version.h @@ -45,6 +45,8 @@ typedef enum { * change exists solely to turn off special * bug handling in read_bi_func_data(). */ + DBV_FileIO, /* Includes addition of the 'E_FILE' keyword. + */ Num_DB_Versions /* Special: the current version is this - 1. */ } DB_Version;