/* // Full copyright information is available in the file ../doc/CREDITS // // Function operators // // This file contains functions inherent to the OS, which are actually // operators, but nobody needs to know. // // Many of these functions require information from the current frame, // which is why they are not modularized (such as object functions) or // they are inherent to the functionality of ColdC // // The need to split these into seperate files is not too great, as they // will not be changing often. */ #include "defs.h" #ifdef __Win32__ #define STDIN_FILENO (fileno(stdin)) #define STDOUT_FILENO (fileno(stdout)) #define STDERR_FILENO (fileno(stderr)) #endif #include <string.h> #include <limits.h> #include <sys/types.h> #ifdef __UNIX__ #include <sys/wait.h> #endif #include <sys/stat.h> #include <dirent.h> /* func_files() */ #include <fcntl.h> #include "execute.h" #include "cache.h" #include "util.h" /* some file functions */ #include "file.h" #include "token.h" /* is_valid_ident() */ #define GET_FILE_CONTROLLER(__f) { \ __f = find_file_controller(cur_frame->object); \ if (__f == NULL) { \ cthrow(file_id, "No file is bound to this object."); \ return; \ } \ } \ /* // ----------------------------------------------------------------- */ COLDC_FUNC(fopen) { cList * stat; filec_t * file; INIT_1_OR_2_ARGS(STRING, STRING); file = find_file_controller(cur_frame->object); /* only one file at a time on an object */ if (file != NULL) { cthrow(file_id, "A file (%s) is already open on this object.", file->path->s); return; } /* open the file, it will automagically be set on the current object, if we are sucessfull, otherwise our stat list is NULL */ stat = open_file(STR1, (argc == 2 ? STR2 : NULL), cur_frame->object); /* if its null, open_file() threw an error */ if (stat == NULL) return; pop(argc); push_list(stat); list_discard(stat); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(file) { filec_t * file; cList * info; cData * list; INIT_NO_ARGS(); GET_FILE_CONTROLLER(file) info = list_new(5); list = list_empty_spaces(info, 5); list[0].type = INTEGER; list[0].u.val = (Long) (file->f.readable ? 1 : 0); list[1].type = INTEGER; list[1].u.val = (Long) (file->f.writable ? 1 : 0); list[2].type = INTEGER; list[2].u.val = (Long) (file->f.closed ? 1 : 0); list[3].type = INTEGER; list[3].u.val = (Long) (file->f.binary ? 1 : 0); list[4].type = STRING; list[4].u.str = string_dup(file->path); push_list(info); list_discard(info); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(files) { cStr * path, * name; cList * out; cData d; struct dirent * dent; DIR * dp; struct stat sbuf; INIT_1_ARG(STRING); path = build_path(STR1->s, NULL, -1); if (!path) return; if (stat(path->s, &sbuf) == F_FAILURE) { cthrow(directory_id, "Unable to find directory \"%s\".", path->s); string_discard(path); return; } if (!S_ISDIR(sbuf.st_mode)) { cthrow(directory_id, "File \"%s\" is not a directory.", path->s); string_discard(path); return; } dp = opendir(path->s); out = list_new(0); d.type = STRING; while ((dent = readdir(dp)) != NULL) { if (strncmp(dent->d_name, ".", 1) == F_SUCCESS || strncmp(dent->d_name, "..", 2) == F_SUCCESS) continue; #ifdef HAVE_D_NAMLEN name = string_from_chars(dent->d_name, dent->d_namlen); #else name = string_from_chars(dent->d_name, strlen(dent->d_name)); #endif d.u.str = name; out = list_add(out, &d); string_discard(name); } closedir(dp); pop(1); push_list(out); list_discard(out); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(fclose) { filec_t * file; Int err; INIT_NO_ARGS(); file = find_file_controller(cur_frame->object); if (file == NULL) { cthrow(file_id, "A file is not open on this object."); return; } err = close_file(file); file_discard(file, cur_frame->object); if (err) { cthrow(file_id, strerror(GETERR())); return; } push_int(1); } /* // ----------------------------------------------------------------- // // NOTE: args are inverted from the stdio chmod() function call, // This makes it easier defaulting to this() file. // */ COLDC_FUNC(fchmod) { filec_t * file; cStr * path; Int failed; Long mode; char * p, * ep; INIT_1_OR_2_ARGS(STRING, STRING); /* frob the string to a mode_t */ p = STR1->s; /* strtol sets an error if an overflow/underflow occurs */ SETERR(0); mode = strtol(p, &ep, 8); if (*p < '0' || *p > '7' || mode > INT_MAX || mode < 0) SETERR(ERR_RANGE); if (GETERR()) { cthrow(file_id, "invalid file mode \"%s\": %s", p, strerror(GETERR())); return; } #ifdef RESTRICTIVE_FILES /* don't allow SUID mods, incase somebody is being stupid and running the server as root; so it could actually happen */ if (mode & S_ISUID || mode & S_ISGID || mode & S_ISVTX) { cthrow(file_id, "You cannot set sticky bits this way, sorry."); return; } #endif if (argc == 1) { GET_FILE_CONTROLLER(file) path = string_dup(file->path); } else { struct stat sbuf; path = build_path(STR2->s, &sbuf, ALLOW_DIR); if (path == NULL) return; } failed = chmod(path->s, mode); string_discard(path); if (failed) { cthrow(file_id, strerror(GETERR())); return; } pop(argc); push_int(1); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(frmdir) { cStr * path; Int err; struct stat sbuf; INIT_1_ARG(STRING); if (!(path = build_path(STR1->s, &sbuf, ALLOW_DIR))) return; err = rmdir(path->s); string_discard(path); if (err != F_SUCCESS) { cthrow(file_id, strerror(GETERR())); return; } pop(1); push_int(1); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(fmkdir) { cStr * path; Int err; struct stat sbuf; INIT_1_ARG(STRING); if (!(path = build_path(args[0].u.str->s, NULL, -1))) return; if (stat(path->s, &sbuf) == F_SUCCESS) { cthrow(file_id,"A file or directory already exists as \"%s\".",path->s); string_discard(path); return; } /* default the mode to 0700, they can chmod it later */ err = mkdir(path->s, 0700); string_discard(path); if (err != F_SUCCESS) { cthrow(file_id, strerror(GETERR())); return; } pop(1); push_int(1); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(fremove) { cStr * path; Int err; struct stat sbuf; INIT_1_ARG(STRING); path = build_path(STR1->s, &sbuf, DISALLOW_DIR); if (!path) return; err = unlink(path->s); string_discard(path); if (err != F_SUCCESS) THROW((file_id, strerror(GETERR()))) pop(1); push_int(1); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(fseek) { filec_t * file; Int whence; INIT_2_ARGS(INTEGER, SYMBOL); GET_FILE_CONTROLLER(file) if (!file->f.readable || !file->f.writable) THROW((file_id, "File \"%s\" is not both readable and writable.", file->path->s)) if (SYM2 == SEEK_SET_id) whence = SEEK_SET; else if (SYM2 == SEEK_CUR_id) whence = SEEK_CUR; else if (SYM2 == SEEK_END_id) whence = SEEK_END; else THROW((type_id,"Whence is not one of 'SEEK_SET 'SEEK_CUR or 'SEEK_END")) if (fseek(file->fp, (long) INT1, whence) != F_SUCCESS) THROW((file_id, strerror(GETERR()))) pop(2); push_int(1); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(frename) { cStr * from, * to; struct stat sbuf; Int err; filec_t * file = NULL; INIT_2_ARGS(ANY_TYPE, STRING); if (args[0].type != STRING || !string_length(STR1)) { GET_FILE_CONTROLLER(file) from = string_dup(file->path); } else if (!(from = build_path(args[0].u.str->s, &sbuf, ALLOW_DIR))) return; /* stat it seperately so that we can give a better error */ to = build_path(STR2->s, NULL, ALLOW_DIR); if (stat(to->s, &sbuf) == 0) { cthrow(file_id, "Destination \"%s\" already exists.", to->s); string_discard(to); string_discard(from); return; } err = rename(from->s, to->s); string_discard(from); string_discard(to); if (err == F_SUCCESS) { if (file) { string_discard(file->path); file->path = string_dup(to); } } else { cthrow(file_id, strerror(GETERR())); return; } pop(2); push_int(1); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(fflush) { filec_t * file; INIT_NO_ARGS(); GET_FILE_CONTROLLER(file) if (fflush(file->fp) == NO) { cthrow(file_id, strerror(GETERR())); return; } push_int(1); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(feof) { filec_t * file; INIT_NO_ARGS(); GET_FILE_CONTROLLER(file); if (feof(file->fp)) push_int(1); else push_int(0); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(fread) { filec_t * file; INIT_0_OR_1_ARGS(INTEGER); GET_FILE_CONTROLLER(file) if (!file->f.readable) { cthrow(file_id, "File is not readable."); return; } if (file->f.binary) { cBuf * buf = NULL; Int block = DEF_BLOCKSIZE; if (argc) { block = INT1; pop(1); } buf = read_binary_file(file, block); if (!buf) return; push_buffer(buf); buffer_discard(buf); } else { cStr * str = read_file(file); /* if its null, read_file() threw an error .. */ if (!str) return; if (argc) pop(1); push_string(str); string_discard(str); } } /* // ----------------------------------------------------------------- */ COLDC_FUNC(fwrite) { Int count; filec_t * file; INIT_1_ARG(ANY_TYPE); GET_FILE_CONTROLLER(file) if (!file->f.writable) { cthrow(perm_id, "File is not writable."); return; } if (file->f.binary) { if (args[0].type != BUFFER) { cthrow(type_id,"File type is binary, you may only fwrite buffers."); return; } count = fwrite(args[0].u.buffer->s, sizeof(unsigned char), args[0].u.buffer->len, file->fp); count -= args[0].u.buffer->len; } else { if (args[0].type != STRING) { cthrow(type_id, "File type is text, you may only fwrite strings."); return; } count = fwrite(args[0].u.str->s, sizeof(unsigned char), args[0].u.str->len, file->fp); count -= args[0].u.str->len; /* if we successfully wrote everything, drop a newline on it */ if (!count) fputc((char) 10, file->fp); } pop(1); push_int(count); } /* // ----------------------------------------------------------------- */ COLDC_FUNC(fstat) { struct stat sbuf; cList * stat; filec_t * file; INIT_0_OR_1_ARGS(STRING); if (!argc) { GET_FILE_CONTROLLER(file) stat_file(file, &sbuf); } else { cStr * path = build_path(STR1->s, &sbuf, ALLOW_DIR); /* if path == NULL build_path() threw an error */ if (!path) return; string_discard(path); } stat = statbuf_to_list(&sbuf); /* don't call pop unless we need to */ if (argc) pop(1); push_list(stat); list_discard(stat); } /* // ----------------------------------------------------------------- // // run an executable from the filesystem // */ COLDC_FUNC(execute) { cData *args, *d; cList *script_args; Int num_args, argc, len, i, fd, dlen; int status; pid_t pid; char *fname, **argv; /* Accept a name of a script to run, a list of arguments to give it, and * an optional flag signifying that we should not wait for completion. */ if (!func_init_2_or_3(&args, &num_args, STRING, LIST, INTEGER)) return; script_args = args[1].u.list; /* Verify that all items in argument list are strings. */ for (d = list_first(script_args), i=0; d; d = list_next(script_args, d), i++) { if (d->type != STRING) { cthrow(type_id, "Execute argument %d (%D) is not a string.", i+1, d); return; } } /* Don't allow walking back up the directory tree. */ if (strstr(string_chars(args[0].u.str), "../")) { cthrow(perm_id, "Filename %D is not legal.", &args[0]); return; } /* Construct the name of the script. */ len = string_length(args[0].u.str); dlen = strlen(c_dir_bin); /* +2 is 1 for '/' and 1 for NULL */ fname = TMALLOC(char, len + dlen + 2); memcpy(fname, c_dir_bin, dlen); fname[dlen] = '/'; memcpy(fname + dlen + 1, string_chars(args[0].u.str), len); fname[len + dlen + 1] = (char) NULL; /* Build an argument list. */ argc = list_length(script_args) + 1; argv = TMALLOC(char *, argc + 1); argv[0] = tstrdup(fname); for (d = list_first(script_args), i = 0; d; d = list_next(script_args, d), i++) argv[i + 1] = tstrdup(string_chars(d->u.str)); argv[argc] = NULL; pop(num_args); #ifdef __Win32__ #ifndef CREATE_PROCESS write_err("EXEC: No forking in Win32 (yet)"); /* for now this will not work - JAL */ status = -1; #else fSuccess = CreateProcess((LPTSTR)fname, (LPTSTR)argv, (LPSECURITY_ATTRIBUTES)NULL, (LPSECURITY_ATTRIBUTES)NULL, (BOOL)TRUE,(DWORD)0, NULL, NULL, (LPSTARTUPINFO)&SI, (LPPROCESS_INFORMATION)&pi); /* parent waits for child */ if (fSuccess) { hProcess = pi.hProcess; hThread = pi.hThread; dw = WaitForSingleObject(hProcess, INFINITE); /* if we saw success ... */ if (dw != 0xFFFFFFFF) { /* pick up an exit code for the process */ fExit = GetExitCodeProcess(hProcess, &dwExitCode); } /* close the child process and thread object handles */ CloseHandle(hThread); CloseHandle(hProcess); printf("COMPLETED!\n"); } else printf("Failed to CreateProcess\n"); #endif #else /* Fork off a process. */ pid = FORK_PROCESS(); if (pid == 0) { /* Pipe stdin and stdout to /dev/null, keep stderr. */ fd = open("/dev/null", O_RDWR); if (fd == -1) { write_err("EXEC: Failed to open /dev/null: %s.",strerror(GETERR())); _exit(-1); } dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); execv(fname, argv); write_err("EXEC: Failed to exec \"%s\": %s.", fname,strerror(GETERR())); _exit(-1); } else if (pid > 0) { if (num_args == 3 && args[2].u.val) { if (waitpid(pid, &status, WNOHANG) == 0) status = 0; } else { waitpid(pid, &status, 0); } } else { write_err("EXEC: Failed to fork: %s.", strerror(GETERR())); status = -1; } #endif /* Free the argument list. */ for (i = 0; i < argc; i++) tfree_chars(argv[i]); TFREE(argv, argc + 1); push_int(status); }