/*
// Full copyright information is available in the file ../doc/CREDITS
//
// File routines.
//
// Some of these routines are used in io.c, primarily when updating i/o
// io.c modifies and uses 'files'.
*/
#define _file_
#include "defs.h"
#include <ctype.h>
#include <string.h>
#include "file.h"
#include "cdc_pcode.h"
#include "cache.h"
#include "util.h"
#define THROWN(_args_) { \
cthrow _args_ ; \
return NULL; \
}
/*
// --------------------------------------------------------------------
// The first routines deal with file controllers, and should be system
// inspecific.
// --------------------------------------------------------------------
*/
filec_t * files = NULL;
void flush_files(void) {
filec_t * file;
for (file = files; file; file = file->next)
flush_file(file);
}
void close_files(void) {
filec_t * file,
* old;
file = files;
while (file) {
close_file(file);
old = file;
file = file->next;
file_discard(old, NULL);
}
}
/*
// --------------------------------------------------------------------
//
// NOTE: If you send the object along, it is assumed it is the CORRECT
// object bound to this function, sending the wrong object can cause
// problems.
//
*/
void file_discard(filec_t * file, Obj * obj) {
filec_t ** fp, * f;
/* clear the object's file variable */
if (obj == NULL) {
if (file->objnum != INV_OBJNUM) {
Obj * obj = cache_retrieve(file->objnum);
if (obj != NULL) {
obj->file = NULL;
cache_discard(obj);
}
}
} else {
obj->file = NULL;
}
/* pull it out of the 'files' list */
fp = &files;
while (*fp) {
f = *fp;
if (f->objnum == file->objnum) {
if (!f->f.closed)
close_file(f);
*fp = f->next;
break;
} else {
fp = &f->next;
}
}
/* toss the file proper */
string_discard(file->path);
efree(file);
}
filec_t * file_new(void) {
filec_t * fnew = EMALLOC(filec_t, 1);
fnew->fp = NULL;
fnew->objnum = INV_OBJNUM;
fnew->f.readable = 0;
fnew->f.writable = 0;
fnew->f.closed = 0;
fnew->f.binary = 0;
fnew->path = NULL;
fnew->next = NULL;
return fnew;
}
void file_add(filec_t * file) {
file->next = files;
files = file;
}
filec_t * find_file_controller(Obj * obj) {
/* obj->file is only for faster lookups,
and will go away when written to disk. */
if (obj->file == NULL) {
filec_t * file;
/* lets try and find the file */
for (file = files; file; file = file->next) {
if (file->objnum == obj->objnum && !file->f.closed) {
obj->file = file;
break;
}
}
}
/* it may still be NULL */
return obj->file;
}
Int close_file(filec_t * file) {
file->f.closed = 1;
if (fclose(file->fp) == EOF)
return GETERR();
return 0;
}
Int flush_file(filec_t * file) {
if (file->f.writable) {
if (fflush(file->fp) == EOF)
return NO;
return YES;
}
return -1;
}
cBuf * read_binary_file(filec_t * file, Int block) {
cBuf * buf = buffer_new(block);
if (feof(file->fp))
THROWN((eof_id, "End of file."))
buf->len = fread(buf->s, sizeof(unsigned char), block, file->fp);
return buf;
}
/* slower, but we get clean output */
cStr * read_file(filec_t * file) {
register char * p, * s;
register int len;
cStr * str;
if (feof(file->fp))
THROWN((eof_id, "End of file."))
str = fgetstring(file->fp);
if (!str)
THROWN((eof_id, "End of file."))
/* ok, munch meta-characters */
p = s = string_chars(str);
len = string_length(str);
while (len-- && *s) {
if (ISPRINT(*s)) {
*p = *s;
p++;
} else if (*s == '\t') {
*p = ' ';
p++;
}
s++;
}
*p = (char) NULL;
str->len = p - string_chars(str);
return str;
}
Int abort_file(filec_t * file) {
if (file != NULL) {
close_file(file);
file_discard(file, NULL);
return F_SUCCESS;
}
return F_FAILURE;
}
Int stat_file(filec_t * file, struct stat * sbuf) {
if (file != NULL) {
stat(file->path->s, sbuf);
return F_SUCCESS;
}
return F_FAILURE;
}
/*
// --------------------------------------------------------------------
// These routines are file utilities, and will probably not work too
// well on anything but unix.
// --------------------------------------------------------------------
*/
cStr * build_path(char * fname, struct stat * sbuf, Int nodir) {
Int len = strlen(fname);
cStr * str = NULL;
if (len == 0)
THROWN((file_id, "No file specified."))
#ifdef RESTRICTIVE_FILES
if (strstr(fname, "../") || strstr(fname, "/..") || !strcmp(fname, ".."))
THROWN((perm_id, "Filename \"%s\" is not legal.", fname))
str = string_from_chars(c_dir_root, strlen(c_dir_root));
str = string_addc(str, '/');
str = string_add_chars(str, fname, len);
#else
if (*fname != '/') {
str = string_from_chars(c_dir_root, strlen(c_dir_root));
str = string_addc(str, '/');
str = string_add_chars(str, fname, len);
} else {
str = string_from_chars(fname, len);
}
#endif
if (sbuf != NULL) {
if (stat(str->s, sbuf) < 0) {
cthrow(file_id, "Cannot find file \"%s\".", str->s);
string_discard(str);
return NULL;
}
if (nodir) {
if (S_ISDIR(sbuf->st_mode)) {
cthrow(directory_id, "\"%s\" is a directory.", str->s);
string_discard(str);
return NULL;
}
}
}
return str;
}
cList * statbuf_to_list(struct stat * sbuf) {
cList * list;
cData * d;
char buf[LINE];
register Int x;
list = list_new(5);
d = list_empty_spaces(list, 5);
for (x=1; x < 5; x++)
d[x].type = INTEGER;
sprintf(buf, "%o", sbuf->st_mode);
d[0].type = STRING;
d[0].u.str = string_from_chars(buf, strlen(buf));
d[1].u.val = (Int) sbuf->st_size;
d[2].u.val = (Int) sbuf->st_atime;
d[3].u.val = (Int) sbuf->st_mtime;
d[4].u.val = (Int) sbuf->st_ctime;
return list;
}
cList * open_file(cStr * name, cStr * smode, Obj * obj) {
char mode[4];
Int rw = 0;
char * s = NULL;
filec_t * fnew = file_new();
struct stat sbuf;
/* parse the mode first, if the string pointer is NULL, set it readable */
if (smode != NULL) {
s = smode->s;
if (*s == '+') {
rw = 1;
fnew->f.readable = fnew->f.writable = 1;
s++;
}
if (*s == '>') {
s++;
if (*s == '>') {
s++;
mode[0] = 'a';
} else {
mode[0] = 'w';
}
fnew->f.writable = 1;
} else {
if (*s == '<' )
s++;
mode[0] = 'r';
fnew->f.readable = 1;
}
/* here is where we branch from perl, '-' is used to specify it as
a 'binary' file (i.e. use buffers not cold strings) */
if (*s == '-') {
s++;
fnew->f.binary = 1;
}
} else {
mode[0] = 'r';
fnew->f.readable = 1;
}
/* most systems ignore this, some need it */
mode[1] = 'b';
if (rw) {
mode[2] = '+';
mode[3] = (char) NULL;
} else {
mode[2] = (char) NULL;
}
fnew->path = build_path(name->s, NULL, DISALLOW_DIR);
if (fnew->path == NULL)
return NULL;
/* redundant, as build_path could have done this, but we
have a special case which we need to handle differently */
if (stat(fnew->path->s, &sbuf) == F_SUCCESS) {
if (S_ISDIR(sbuf.st_mode))
THROWN((directory_id, "\"%s\" is a directory.", fnew->path->s))
}
fnew->fp = fopen(fnew->path->s, mode);
if (fnew->fp == NULL) {
if (GETERR() == ERR_NOMEM)
panic("open_file(): %s", strerror(GETERR()));
cthrow(file_id, "%s (%s)", strerror(GETERR()), name->s);
file_discard(fnew, NULL);
return NULL;
}
file_add(fnew);
obj->file = fnew;
fnew->objnum = obj->objnum;
return statbuf_to_list(&sbuf);
}
#if DISABLED
/*
// --------------------------------------------------------------------
// called by fread()
*/
cBuf * read_from_file(Obj * obj) {
cBuf * buf;
filec_t * file = find_file_controller(obj);
if (file == NULL)
return NULL;
if (!file->rbuf) {
if (feof(file->fp))
return NULL;
file_read(file);
}
buf = file->rbuf;
file->rbuf = NULL;
return buf;
}
/*
// --------------------------------------------------------------------
// called by fwrite()
*/
Int write_to_file(Obj * obj, cBuf * buf) {
filec_t * file = find_file_controller(obj);
if (file == NULL || !file->f.writable)
return 0;
if (file->wbuf)
file->wbuf = buffer_append(file->wbuf, buf);
else
file->wbuf = buffer_dup(buf);
return 1;
}
/*
// --------------------------------------------------------------------
// called by feof()
*/
Int file_end(Obj * obj) {
filec_t * file = find_file_controller(obj);
if (file == NULL)
return F_FAILURE;
return (feof(file->fp));
}
#endif
#undef _file_