1201 lines
29 KiB
C
1201 lines
29 KiB
C
/*
|
|
* file: file.c
|
|
* description: handle all file based efuns
|
|
*/
|
|
|
|
#include "std.h"
|
|
#include "file.h"
|
|
#include "comm.h"
|
|
#include "lex.h"
|
|
#include "md.h"
|
|
#include "port.h"
|
|
#include "master.h"
|
|
|
|
#ifdef PACKAGE_COMPRESS
|
|
#include <zlib.h>
|
|
#endif
|
|
|
|
int legal_path (const char *);
|
|
|
|
static int match_string (char *, char *);
|
|
static int copy (const char *from, const char *to);
|
|
static int do_move (const char *from, const char *to, int flag);
|
|
static int CDECL pstrcmp (CONST void *, CONST void *);
|
|
static int CDECL parrcmp (CONST void *, CONST void *);
|
|
static void encode_stat (svalue_t *, int, char *, struct stat *);
|
|
|
|
#define MAX_LINES 50
|
|
|
|
/*
|
|
* These are used by qsort in get_dir().
|
|
*/
|
|
static int CDECL pstrcmp (CONST void * p1, CONST void * p2)
|
|
{
|
|
svalue_t *x = (svalue_t *)p1;
|
|
svalue_t *y = (svalue_t *)p2;
|
|
|
|
return strcmp(x->u.string, y->u.string);
|
|
}
|
|
|
|
static int CDECL parrcmp (CONST void * p1, CONST void * p2)
|
|
{
|
|
svalue_t *x = (svalue_t *)p1;
|
|
svalue_t *y = (svalue_t *)p2;
|
|
|
|
return strcmp(x->u.arr->item[0].u.string, y->u.arr->item[0].u.string);
|
|
}
|
|
|
|
static void encode_stat (svalue_t * vp, int flags, char * str, struct stat * st)
|
|
{
|
|
if (flags == -1) {
|
|
array_t *v = allocate_empty_array(3);
|
|
|
|
v->item[0].type = T_STRING;
|
|
v->item[0].subtype = STRING_MALLOC;
|
|
v->item[0].u.string = string_copy(str, "encode_stat");
|
|
v->item[1].type = T_NUMBER;
|
|
v->item[1].u.number =
|
|
((st->st_mode & S_IFDIR) ? -2 : st->st_size);
|
|
v->item[2].type = T_NUMBER;
|
|
v->item[2].u.number = st->st_mtime;
|
|
vp->type = T_ARRAY;
|
|
vp->u.arr = v;
|
|
} else {
|
|
vp->type = T_STRING;
|
|
vp->subtype = STRING_MALLOC;
|
|
vp->u.string = string_copy(str, "encode_stat");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* List files in directory. This function do same as standard list_files did,
|
|
* but instead writing files right away to user this returns an array
|
|
* containing those files. Actually most of code is copied from list_files()
|
|
* function.
|
|
* Differences with list_files:
|
|
*
|
|
* - file_list("/w"); returns ({ "w" })
|
|
*
|
|
* - file_list("/w/"); and file_list("/w/."); return contents of directory
|
|
* "/w"
|
|
*
|
|
* - file_list("/");, file_list("."); and file_list("/."); return contents
|
|
* of directory "/"
|
|
*
|
|
* With second argument equal to non-zero, instead of returning an array
|
|
* of strings, the function will return an array of arrays about files.
|
|
* The information in each array is supplied in the order:
|
|
* name of file,
|
|
* size of file,
|
|
* last update of file.
|
|
*/
|
|
/* WIN32 should be fixed to do this correctly (i.e. no ifdefs for it) */
|
|
#define MAX_FNAME_SIZE 255
|
|
#define MAX_PATH_LEN 1024
|
|
array_t *get_dir (const char * path, int flags)
|
|
{
|
|
array_t *v;
|
|
int i, count = 0;
|
|
#ifndef WIN32
|
|
DIR *dirp;
|
|
#endif
|
|
int namelen, do_match = 0;
|
|
|
|
#ifndef WIN32
|
|
#ifdef USE_STRUCT_DIRENT
|
|
struct dirent *de;
|
|
#else
|
|
struct direct *de;
|
|
#endif
|
|
#endif
|
|
struct stat st;
|
|
char *endtemp;
|
|
char temppath[MAX_FNAME_SIZE + MAX_PATH_LEN + 2];
|
|
char regexppath[MAX_FNAME_SIZE + MAX_PATH_LEN + 2];
|
|
char *p;
|
|
|
|
#ifdef WIN32
|
|
struct _finddata_t FindBuffer;
|
|
long FileHandle, FileCount;
|
|
#endif
|
|
|
|
if (!path)
|
|
return 0;
|
|
|
|
path = check_valid_path(path, current_object, "stat", 0);
|
|
|
|
if (path == 0)
|
|
return 0;
|
|
|
|
if (strlen(path) < 2) {
|
|
temppath[0] = path[0] ? path[0] : '.';
|
|
temppath[1] = '\000';
|
|
p = temppath;
|
|
} else {
|
|
strncpy(temppath, path, MAX_FNAME_SIZE + MAX_PATH_LEN + 1);
|
|
temppath[MAX_FNAME_SIZE + MAX_PATH_LEN + 1] = '\0';
|
|
|
|
/*
|
|
* If path ends with '/' or "/." remove it
|
|
*/
|
|
if ((p = strrchr(temppath, '/')) == 0)
|
|
p = temppath;
|
|
if (p[0] == '/' && ((p[1] == '.' && p[2] == '\0') || p[1] == '\0'))
|
|
*p = '\0';
|
|
}
|
|
|
|
if (stat(temppath, &st) < 0) {
|
|
if (*p == '\0')
|
|
return 0;
|
|
if (p != temppath) {
|
|
strcpy(regexppath, p + 1);
|
|
*p = '\0';
|
|
} else {
|
|
strcpy(regexppath, p);
|
|
strcpy(temppath, ".");
|
|
}
|
|
do_match = 1;
|
|
} else if (*p != '\0' && strcmp(temppath, ".")) {
|
|
if (*p == '/' && *(p + 1) != '\0')
|
|
p++;
|
|
v = allocate_empty_array(1);
|
|
encode_stat(&v->item[0], flags, p, &st);
|
|
return v;
|
|
}
|
|
#ifdef WIN32
|
|
FileHandle = -1;
|
|
FileCount = 1;
|
|
/* strcat(temppath, "\\*"); */
|
|
strcat(temppath, "/*");
|
|
if ((FileHandle = _findfirst(temppath, &FindBuffer)) == -1) return 0;
|
|
#else
|
|
if ((dirp = opendir(temppath)) == 0)
|
|
return 0;
|
|
#endif
|
|
|
|
/*
|
|
* Count files
|
|
*/
|
|
#ifdef WIN32
|
|
do {
|
|
if (!do_match && (!strcmp(FindBuffer.name, ".") ||
|
|
!strcmp(FindBuffer.name, ".."))) {
|
|
continue;
|
|
}
|
|
if (do_match && !match_string(regexppath, FindBuffer.name)) {
|
|
continue;
|
|
}
|
|
count++;
|
|
if (count >= max_array_size) {
|
|
break;
|
|
}
|
|
} while (!_findnext(FileHandle, &FindBuffer));
|
|
_findclose(FileHandle);
|
|
#else
|
|
for (de = readdir(dirp); de; de = readdir(dirp)) {
|
|
#ifdef USE_STRUCT_DIRENT
|
|
namelen = strlen(de->d_name);
|
|
#else
|
|
namelen = de->d_namlen;
|
|
#endif
|
|
if (!do_match && (strcmp(de->d_name, ".") == 0 ||
|
|
strcmp(de->d_name, "..") == 0))
|
|
continue;
|
|
if (do_match && !match_string(regexppath, de->d_name))
|
|
continue;
|
|
count++;
|
|
if (count >= max_array_size)
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Make array and put files on it.
|
|
*/
|
|
v = allocate_empty_array(count);
|
|
if (count == 0) {
|
|
/* This is the easy case :-) */
|
|
#ifndef WIN32
|
|
closedir(dirp);
|
|
#endif
|
|
return v;
|
|
}
|
|
#ifdef WIN32
|
|
FileHandle = -1;
|
|
if ((FileHandle = _findfirst(temppath, &FindBuffer)) == -1) return 0;
|
|
endtemp = temppath + strlen(temppath) - 2;
|
|
*endtemp = 0;
|
|
/* strcat(endtemp++, "\\"); */
|
|
strcat(endtemp++, "/");
|
|
i = 0;
|
|
do {
|
|
if (!do_match && (!strcmp(FindBuffer.name, ".") ||
|
|
!strcmp(FindBuffer.name, ".."))) continue;
|
|
if (do_match && !match_string(regexppath, FindBuffer.name)) continue;
|
|
if (flags == -1) {
|
|
strcpy(endtemp, FindBuffer.name);
|
|
stat(temppath, &st);
|
|
}
|
|
encode_stat(&v->item[i], flags, FindBuffer.name, &st);
|
|
i++;
|
|
} while (!_findnext(FileHandle, &FindBuffer));
|
|
_findclose(FileHandle);
|
|
#else /* WIN32 */
|
|
rewinddir(dirp);
|
|
endtemp = temppath + strlen(temppath);
|
|
|
|
strcat(endtemp++, "/");
|
|
|
|
for (i = 0, de = readdir(dirp); i < count; de = readdir(dirp)) {
|
|
#ifdef USE_STRUCT_DIRENT
|
|
namelen = strlen(de->d_name);
|
|
#else
|
|
namelen = de->d_namlen;
|
|
#endif
|
|
if (!do_match && (strcmp(de->d_name, ".") == 0 ||
|
|
strcmp(de->d_name, "..") == 0))
|
|
continue;
|
|
if (do_match && !match_string(regexppath, de->d_name))
|
|
continue;
|
|
de->d_name[namelen] = '\0';
|
|
if (flags == -1) {
|
|
/*
|
|
* We'll have to .... sigh.... stat() the file to get some add'tl
|
|
* info.
|
|
*/
|
|
strcpy(endtemp, de->d_name);
|
|
stat(temppath, &st);/* We assume it works. */
|
|
}
|
|
encode_stat(&v->item[i], flags, de->d_name, &st);
|
|
i++;
|
|
}
|
|
closedir(dirp);
|
|
#endif /* OS2 */
|
|
|
|
/* Sort the names. */
|
|
qsort((void *) v->item, count, sizeof v->item[0],
|
|
(flags == -1) ? parrcmp : pstrcmp);
|
|
return v;
|
|
}
|
|
|
|
int remove_file (const char * path)
|
|
{
|
|
path = check_valid_path(path, current_object, "remove_file", 1);
|
|
|
|
if (path == 0)
|
|
return 0;
|
|
if (unlink(path) == -1)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Check that it is an legal path. No '..' are allowed.
|
|
*/
|
|
int legal_path (const char * path)
|
|
{
|
|
const char *p;
|
|
|
|
if (path == NULL)
|
|
return 0;
|
|
if (path[0] == '/')
|
|
return 0;
|
|
/*
|
|
* disallowing # seems the easiest way to solve a bug involving loading
|
|
* files containing that character
|
|
*/
|
|
if (strchr(path, '#')) {
|
|
return 0;
|
|
}
|
|
p = path;
|
|
while (p) { /* Zak, 930530 - do better checking */
|
|
if (p[0] == '.') {
|
|
if (p[1] == '\0') /* trailing `.' ok */
|
|
break;
|
|
if (p[1] == '.') /* check for `..' or `../' */
|
|
p++;
|
|
if (p[1] == '/' || p[1] == '\0')
|
|
return 0; /* check for `./', `..', or `../' */
|
|
}
|
|
p = (char *)strstr(p, "/."); /* search next component */
|
|
if (p)
|
|
p++; /* step over `/' */
|
|
}
|
|
#if defined(WIN32)
|
|
/*
|
|
* I don't know what the proper define should be, just leaving an
|
|
* appropriate place for the right stuff to happen here - Wayfarer
|
|
*/
|
|
/* Could be a drive thingy for os2. */
|
|
if (strchr(path, ':'))
|
|
return 0;
|
|
#endif
|
|
return 1;
|
|
} /* legal_path() */
|
|
|
|
/*
|
|
* There is an error in a specific file. Ask the MudOS driver to log the
|
|
* message somewhere.
|
|
*/
|
|
void smart_log (const char * error_file, int line, const char * what, int flag)
|
|
{
|
|
char *buff;
|
|
svalue_t *mret;
|
|
extern int pragmas;
|
|
|
|
buff = (char *)
|
|
DMALLOC(strlen(error_file) + strlen(what) +
|
|
((pragmas & PRAGMA_ERROR_CONTEXT) ? 100 : 40), TAG_TEMPORARY, "smart_log: 1");
|
|
|
|
if (flag)
|
|
sprintf(buff, "/%s line %d: Warning: %s", error_file, line, what);
|
|
else
|
|
sprintf(buff, "/%s line %d: %s", error_file, line, what);
|
|
|
|
if (pragmas & PRAGMA_ERROR_CONTEXT) {
|
|
char *ls = strrchr(buff, '\n');
|
|
unsigned char *tmp;
|
|
if (ls) {
|
|
tmp = (unsigned char *)ls + 1;
|
|
while (*tmp && isspace(*tmp)) tmp++;
|
|
if (!*tmp) *ls = 0;
|
|
}
|
|
strcat(buff, show_error_context());
|
|
} else strcat(buff, "\n");
|
|
|
|
push_malloced_string(add_slash(error_file));
|
|
copy_and_push_string(buff);
|
|
mret = safe_apply_master_ob(APPLY_LOG_ERROR, 2);
|
|
if (!mret || mret == (svalue_t *)-1) {
|
|
debug_message("%s", buff);
|
|
}
|
|
FREE(buff);
|
|
} /* smart_log() */
|
|
|
|
/*
|
|
* Append string to file. Return 0 for failure, otherwise 1.
|
|
*/
|
|
int write_file (const char * file, const char * str, int flags)
|
|
{
|
|
FILE *f;
|
|
#ifdef PACKAGE_COMPRESS
|
|
gzFile gf;
|
|
#endif
|
|
#ifdef WIN32
|
|
char fmode[3];
|
|
#endif
|
|
|
|
file = check_valid_path(file, current_object, "write_file", 1);
|
|
if (!file)
|
|
return 0;
|
|
#ifdef PACKAGE_COMPRESS
|
|
if(flags & 2){
|
|
gf = gzopen(file, (flags & 1)?"w":"a");
|
|
if(!gf)
|
|
error("Wrong permissions for opening file /%s for %s.\n\"%s\"\n",
|
|
file, (flags & 1) ? "overwrite" : "append", port_strerror(errno));
|
|
} else {
|
|
#endif
|
|
#ifdef WIN32
|
|
fmode[0] = (flags & 1) ? 'w' : 'a';
|
|
fmode[1] = 't';
|
|
fmode[2] = '\0';
|
|
f = fopen(file, fmode);
|
|
#else
|
|
f = fopen(file, (flags & 1) ? "w" : "a");
|
|
#endif
|
|
if (f == 0) {
|
|
error("Wrong permissions for opening file /%s for %s.\n\"%s\"\n",
|
|
file, (flags & 1) ? "overwrite" : "append", port_strerror(errno));
|
|
}
|
|
#ifdef PACKAGE_COMPRESS
|
|
}
|
|
if(flags & 2){
|
|
gzwrite(gf, str, strlen(str));
|
|
}else
|
|
#endif
|
|
fwrite(str, strlen(str), 1, f);
|
|
#ifdef PACKAGE_COMPRESS
|
|
if(flags & 2)
|
|
gzclose(gf);
|
|
else
|
|
#endif
|
|
fclose(f);
|
|
return 1;
|
|
}
|
|
|
|
char *read_file(const char * file, int start, int len) {
|
|
struct stat st;
|
|
#ifndef PACKAGE_COMPRESS
|
|
FILE *f;
|
|
#else
|
|
gzFile f;
|
|
#endif
|
|
int chunk;
|
|
char *str, *p, *p2;
|
|
|
|
if (len < 0)
|
|
return 0;
|
|
|
|
file = check_valid_path(file, current_object, "read_file", 0);
|
|
|
|
if (!file)
|
|
return 0;
|
|
|
|
/*
|
|
* file doesn't exist, or is really a directory
|
|
*/
|
|
if (stat(file, &st) == -1 || (st.st_mode & S_IFDIR))
|
|
return 0;
|
|
#ifndef PACKAGE_COMPRESS
|
|
f = fopen(file, FOPEN_READ);
|
|
#else
|
|
f = gzopen(file, "rb");
|
|
#endif
|
|
if (f == 0)
|
|
return 0;
|
|
|
|
if (start < 1)
|
|
start = 1;
|
|
if (len == 0)
|
|
len = 2*READ_FILE_MAX_SIZE;
|
|
|
|
str = new_string(2*READ_FILE_MAX_SIZE, "read_file: str");
|
|
if (st.st_size== 0) {
|
|
/* zero length file */
|
|
str[0] = 0;
|
|
#ifndef PACKAGE_COMPRESS
|
|
fclose(f);
|
|
#else
|
|
gzclose(f);
|
|
#endif
|
|
str = extend_string(str, 0); /* fix string length */
|
|
return str;
|
|
}
|
|
|
|
do {
|
|
#ifndef PACKAGE_COMPRESS
|
|
chunk = fread(str, 1, 2*READ_FILE_MAX_SIZE, f);
|
|
#else
|
|
chunk = gzread(f, str, 2*READ_FILE_MAX_SIZE);
|
|
#endif
|
|
if (chunk < 1)
|
|
goto free_str;
|
|
p = str;
|
|
while (start > 1) {
|
|
/* skip newlines */
|
|
p2 = (char *)memchr(p, '\n', 2*READ_FILE_MAX_SIZE+str-p);
|
|
if (p2) {
|
|
p = p2 + 1;
|
|
start--;
|
|
} else
|
|
break;
|
|
}
|
|
} while (start > 1);
|
|
|
|
p2 = str;
|
|
while (1) {
|
|
char c;
|
|
|
|
c = *p++;
|
|
if (p-str > chunk) {
|
|
if (chunk == 2*READ_FILE_MAX_SIZE) {
|
|
goto free_str; //file too big
|
|
} else
|
|
break; //reached the end
|
|
}
|
|
|
|
if (p2-str > READ_FILE_MAX_SIZE)
|
|
goto free_str; //file too big
|
|
|
|
if (c == '\0') {
|
|
FREE_MSTR(str);
|
|
#ifndef PACKAGE_COMPRESS
|
|
fclose(f);
|
|
#else
|
|
gzclose(f);
|
|
#endif
|
|
error("Attempted to read '\\0' into string!\n");
|
|
}
|
|
if (c != '\r' || *p != '\n') {
|
|
*p2++ = c;
|
|
if (c == '\n' && --len == 0)
|
|
break; /* done */
|
|
}
|
|
}
|
|
|
|
*p2 = 0;
|
|
str = extend_string(str, p2 - str); /* fix string length */
|
|
|
|
#ifndef PACKAGE_COMPRESS
|
|
fclose(f);
|
|
#else
|
|
gzclose(f);
|
|
#endif
|
|
|
|
return str;
|
|
|
|
/* Error path: unwind allocated resources */
|
|
|
|
free_str:
|
|
FREE_MSTR(str);
|
|
#ifndef PACKAGE_COMPRESS
|
|
fclose(f);
|
|
#else
|
|
gzclose(f);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
char *read_bytes (const char * file, int start, int len, int * rlen)
|
|
{
|
|
struct stat st;
|
|
FILE *fptr;
|
|
char *str;
|
|
int size;
|
|
|
|
if (len < 0)
|
|
return 0;
|
|
file = check_valid_path(file, current_object,
|
|
"read_bytes", 0);
|
|
if (!file)
|
|
return 0;
|
|
fptr = fopen(file, "rb");
|
|
if (fptr == NULL)
|
|
return 0;
|
|
if (fstat(fileno(fptr), &st) == -1)
|
|
fatal("Could not stat an open file.\n");
|
|
size = st.st_size;
|
|
if (start < 0)
|
|
start = size + start;
|
|
|
|
if (len == 0)
|
|
len = size;
|
|
if (len > MAX_BYTE_TRANSFER) {
|
|
fclose(fptr);
|
|
error("Transfer exceeded maximum allowed number of bytes.\n");
|
|
return 0;
|
|
}
|
|
if (start >= size) {
|
|
fclose(fptr);
|
|
return 0;
|
|
}
|
|
if ((start + len) > size)
|
|
len = (size - start);
|
|
|
|
if ((size = fseek(fptr, start, 0)) < 0) {
|
|
fclose(fptr);
|
|
return 0;
|
|
}
|
|
|
|
str = new_string(len, "read_bytes: str");
|
|
|
|
size = fread(str, 1, len, fptr);
|
|
|
|
fclose(fptr);
|
|
|
|
if (size <= 0) {
|
|
FREE_MSTR(str);
|
|
return 0;
|
|
}
|
|
/*
|
|
* The string has to end to '\0'!!!
|
|
*/
|
|
str[size] = '\0';
|
|
|
|
*rlen = size;
|
|
return str;
|
|
}
|
|
|
|
int write_bytes (const char * file, int start, const char * str, int theLength)
|
|
{
|
|
struct stat st;
|
|
int size;
|
|
FILE *fptr;
|
|
|
|
file = check_valid_path(file, current_object, "write_bytes", 1);
|
|
|
|
if (!file)
|
|
return 0;
|
|
if (theLength > MAX_BYTE_TRANSFER)
|
|
return 0;
|
|
/* Under system V, it isn't possible change existing data in a file
|
|
* opened for append, so it can't be opened for append.
|
|
* opening for r+ won't create the file if it doesn't exist.
|
|
* opening for w or w+ will truncate it if it does exist. So we
|
|
* have to check if it exists first.
|
|
*/
|
|
if (stat(file, &st) == -1) {
|
|
fptr = fopen(file, "wb");
|
|
} else {
|
|
fptr = fopen(file, "r+b");
|
|
}
|
|
if (fptr == NULL) {
|
|
return 0;
|
|
}
|
|
if (fstat(fileno(fptr), &st) == -1)
|
|
fatal("Could not stat an open file.\n");
|
|
size = st.st_size;
|
|
if (start < 0)
|
|
start = size + start;
|
|
if (start < 0 || start > size) {
|
|
fclose(fptr);
|
|
return 0;
|
|
}
|
|
if ((size = fseek(fptr, start, 0)) < 0) {
|
|
fclose(fptr);
|
|
return 0;
|
|
}
|
|
size = fwrite(str, 1, theLength, fptr);
|
|
|
|
fclose(fptr);
|
|
|
|
if (size <= 0) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int file_size (const char * file)
|
|
{
|
|
struct stat st;
|
|
long ret;
|
|
#ifdef WIN32
|
|
char *t;
|
|
int needs_free = 0, len;
|
|
const char *p;
|
|
#endif
|
|
|
|
file = check_valid_path(file, current_object, "file_size", 0);
|
|
if (!file)
|
|
return -1;
|
|
|
|
#ifdef WIN32
|
|
len = strlen(file);
|
|
p = file + len - 1;
|
|
if (*p == '/') {
|
|
needs_free = 1;
|
|
p = file;
|
|
t = new_string(len - 1, "file_size");
|
|
memcpy(t, p, len - 1);
|
|
t[len-1] = 0;
|
|
file = t;
|
|
}
|
|
#endif
|
|
|
|
if (stat(file, &st) == -1)
|
|
ret = -1;
|
|
else if (S_IFDIR & st.st_mode)
|
|
ret = -2;
|
|
else
|
|
ret = st.st_size;
|
|
|
|
#ifdef WIN32
|
|
if (needs_free) FREE_MSTR(file);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Check that a path to a file is valid for read or write.
|
|
* This is done by functions in the master object.
|
|
* The path is always treated as an absolute path, and is returned without
|
|
* a leading '/'.
|
|
* If the path was '/', then '.' is returned.
|
|
* Otherwise, the returned path is temporarily allocated by apply(), which
|
|
* means it will be deallocated at next apply().
|
|
*/
|
|
const char *check_valid_path (const char * path, object_t * call_object, const char * const call_fun, int writeflg)
|
|
{
|
|
svalue_t *v;
|
|
|
|
if(!master_ob && !call_object){
|
|
//early startup, ignore security
|
|
extern svalue_t apply_ret_value;
|
|
free_svalue(&apply_ret_value, "check_valid_path");
|
|
apply_ret_value.type = T_STRING;
|
|
apply_ret_value.subtype = STRING_MALLOC;
|
|
path = apply_ret_value.u.string = string_copy(path, "check_valid_path");
|
|
return path;
|
|
}
|
|
|
|
if (call_object == 0 || call_object->flags & O_DESTRUCTED)
|
|
return 0;
|
|
|
|
#ifdef WIN32
|
|
{
|
|
char *p;
|
|
|
|
for(p=path; *p; p++) if (*p == '\\') *p='/';
|
|
}
|
|
#endif
|
|
|
|
copy_and_push_string(path);
|
|
push_object(call_object);
|
|
push_constant_string(call_fun);
|
|
if (writeflg)
|
|
v = apply_master_ob(APPLY_VALID_WRITE, 3);
|
|
else
|
|
v = apply_master_ob(APPLY_VALID_READ, 3);
|
|
|
|
if (v == (svalue_t *)-1)
|
|
v = 0;
|
|
|
|
if (v && v->type == T_NUMBER && v->u.number == 0) return 0;
|
|
if (v && v->type == T_STRING) {
|
|
path = v->u.string;
|
|
} else {
|
|
extern svalue_t apply_ret_value;
|
|
|
|
free_svalue(&apply_ret_value, "check_valid_path");
|
|
apply_ret_value.type = T_STRING;
|
|
apply_ret_value.subtype = STRING_MALLOC;
|
|
path = apply_ret_value.u.string = string_copy(path, "check_valid_path");
|
|
}
|
|
|
|
if (path[0] == '/')
|
|
path++;
|
|
if (path[0] == '\0')
|
|
path = ".";
|
|
if (legal_path(path))
|
|
return path;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int match_string (char * match, char * str)
|
|
{
|
|
int i;
|
|
|
|
again:
|
|
if (*str == '\0' && *match == '\0')
|
|
return 1;
|
|
switch (*match) {
|
|
case '?':
|
|
if (*str == '\0')
|
|
return 0;
|
|
str++;
|
|
match++;
|
|
goto again;
|
|
case '*':
|
|
match++;
|
|
if (*match == '\0')
|
|
return 1;
|
|
for (i = 0; str[i] != '\0'; i++)
|
|
if (match_string(match, str + i))
|
|
return 1;
|
|
return 0;
|
|
case '\0':
|
|
return 0;
|
|
case '\\':
|
|
match++;
|
|
if (*match == '\0')
|
|
return 0;
|
|
/* Fall through ! */
|
|
default:
|
|
if (*match == *str) {
|
|
match++;
|
|
str++;
|
|
goto again;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static struct stat to_stats, from_stats;
|
|
|
|
static int copy (const char * from, const char * to)
|
|
{
|
|
int ifd;
|
|
int ofd;
|
|
char buf[1024 * 8];
|
|
int len; /* Number of bytes read into `buf'. */
|
|
|
|
if (!S_ISREG(from_stats.st_mode)) {
|
|
return 1;
|
|
}
|
|
if (unlink(to) && errno != ENOENT) {
|
|
return 1;
|
|
}
|
|
ifd = open(from, OPEN_READ);
|
|
if (ifd < 0) {
|
|
return errno;
|
|
}
|
|
ofd = open(to, OPEN_WRITE | O_CREAT | O_TRUNC, 0666);
|
|
if (ofd < 0) {
|
|
close(ifd);
|
|
return 1;
|
|
}
|
|
#ifdef HAS_FCHMOD
|
|
if (fchmod(ofd, from_stats.st_mode & 0777)) {
|
|
close(ifd);
|
|
close(ofd);
|
|
unlink(to);
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
while ((len = read(ifd, buf, sizeof(buf))) > 0) {
|
|
int wrote = 0;
|
|
char *bp = buf;
|
|
|
|
do {
|
|
wrote = write(ofd, bp, len);
|
|
if (wrote < 0) {
|
|
close(ifd);
|
|
close(ofd);
|
|
unlink(to);
|
|
return 1;
|
|
}
|
|
bp += wrote;
|
|
len -= wrote;
|
|
} while (len > 0);
|
|
}
|
|
if (len < 0) {
|
|
close(ifd);
|
|
close(ofd);
|
|
unlink(to);
|
|
return 1;
|
|
}
|
|
if (close(ifd) < 0) {
|
|
close(ofd);
|
|
return 1;
|
|
}
|
|
if (close(ofd) < 0) {
|
|
return 1;
|
|
}
|
|
#ifdef FCHMOD_MISSING
|
|
if (chmod(to, from_stats.st_mode & 0777)) {
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Move FROM onto TO. Handles cross-filesystem moves.
|
|
If TO is a directory, FROM must be also.
|
|
Return 0 if successful, 1 if an error occurred. */
|
|
|
|
#ifdef F_RENAME
|
|
static int do_move (const char * from, const char * to, int flag)
|
|
{
|
|
if (lstat(from, &from_stats) != 0) {
|
|
error("/%s: lstat failed\n", from);
|
|
return 1;
|
|
}
|
|
if (lstat(to, &to_stats) == 0) {
|
|
#ifdef WIN32
|
|
if (!strcmp(from, to))
|
|
#else
|
|
if (from_stats.st_dev == to_stats.st_dev
|
|
&& from_stats.st_ino == to_stats.st_ino)
|
|
#endif
|
|
{
|
|
error("`/%s' and `/%s' are the same file", from, to);
|
|
return 1;
|
|
}
|
|
if (S_ISDIR(to_stats.st_mode)) {
|
|
error("/%s: cannot overwrite directory", to);
|
|
return 1;
|
|
}
|
|
#ifdef WIN32
|
|
unlink(to);
|
|
#endif
|
|
} else if (errno != ENOENT) {
|
|
error("/%s: unknown error\n", to);
|
|
return 1;
|
|
}
|
|
#ifdef SYSV
|
|
if ((flag == F_RENAME) && file_size(from) == -2) {
|
|
char cmd_buf[100];
|
|
|
|
sprintf(cmd_buf, "/usr/lib/mv_dir %s %s", from, to);
|
|
return system(cmd_buf);
|
|
} else
|
|
#endif /* SYSV */
|
|
if ((flag == F_RENAME) && (rename(from, to) == 0))
|
|
return 0;
|
|
#ifdef F_LINK
|
|
else if (flag == F_LINK) {
|
|
#ifdef WIN32
|
|
error("link() not supported.\n");
|
|
#else
|
|
if (link(from, to) == 0)
|
|
return 0;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
if (errno != EXDEV) {
|
|
if (flag == F_RENAME)
|
|
error("cannot move `/%s' to `/%s'\n", from, to);
|
|
else
|
|
error("cannot link `/%s' to `/%s'\n", from, to);
|
|
return 1;
|
|
}
|
|
/* rename failed on cross-filesystem link. Copy the file instead. */
|
|
|
|
if (flag == F_RENAME) {
|
|
if (copy(from, to))
|
|
return 1;
|
|
if (unlink(from)) {
|
|
error("cannot remove `/%s'", from);
|
|
return 1;
|
|
}
|
|
}
|
|
#ifdef F_LINK
|
|
else if (flag == F_LINK) {
|
|
if (symlink(from, to) == 0) /* symbolic link */
|
|
return 0;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
void debug_perror (const char * what, const char * file) {
|
|
if (file)
|
|
debug_message("System Error: %s:%s:%s\n", what, file, port_strerror(errno));
|
|
else
|
|
debug_message("System Error: %s:%s\n", what, port_strerror(errno));
|
|
}
|
|
|
|
/*
|
|
* do_rename is used by the efun rename. It is basically a combination
|
|
* of the unix system call rename and the unix command mv.
|
|
*/
|
|
|
|
static svalue_t from_sv = { T_NUMBER };
|
|
static svalue_t to_sv = { T_NUMBER };
|
|
|
|
#ifdef DEBUGMALLOC_EXTENSIONS
|
|
void mark_file_sv() {
|
|
mark_svalue(&from_sv);
|
|
mark_svalue(&to_sv);
|
|
}
|
|
#endif
|
|
|
|
#ifdef F_RENAME
|
|
int do_rename (const char * fr, const char * t, int flag)
|
|
{
|
|
const char *from;
|
|
const char *to;
|
|
char newfrom[MAX_FNAME_SIZE + MAX_PATH_LEN + 2];
|
|
int flen;
|
|
extern svalue_t apply_ret_value;
|
|
|
|
/*
|
|
* important that the same write access checks are done for link() as are
|
|
* done for rename(). Otherwise all kinds of security problems would
|
|
* arise (e.g. creating links to files in protected directories and then
|
|
* modifying the protected file by modifying the linked file). The idea
|
|
* is prevent linking to a file unless the person doing the linking has
|
|
* permission to move the file.
|
|
*/
|
|
from = check_valid_path(fr, current_object, "rename", 1);
|
|
if (!from)
|
|
return 1;
|
|
|
|
assign_svalue(&from_sv, &apply_ret_value);
|
|
|
|
to = check_valid_path(t, current_object, "rename", 1);
|
|
if (!to)
|
|
return 1;
|
|
|
|
assign_svalue(&to_sv, &apply_ret_value);
|
|
if (!strlen(to) && !strcmp(t, "/")) {
|
|
to = "./";
|
|
}
|
|
|
|
/* Strip trailing slashes */
|
|
flen = strlen(from);
|
|
if (flen > 1 && from[flen - 1] == '/') {
|
|
const char *p = from + flen - 2;
|
|
int n;
|
|
|
|
while (*p == '/' && (p > from))
|
|
p--;
|
|
n = p - from + 1;
|
|
memcpy(newfrom, from, n);
|
|
newfrom[n] = 0;
|
|
from = newfrom;
|
|
}
|
|
|
|
if (file_size(to) == -2) {
|
|
/* Target is a directory; build full target filename. */
|
|
const char *cp;
|
|
char newto[MAX_FNAME_SIZE + MAX_PATH_LEN + 2];
|
|
|
|
cp = strrchr(from, '/');
|
|
if (cp)
|
|
cp++;
|
|
else
|
|
cp = from;
|
|
|
|
sprintf(newto, "%s/%s", to, cp);
|
|
return do_move(from, newto, flag);
|
|
} else
|
|
return do_move(from, to, flag);
|
|
}
|
|
#endif /* F_RENAME */
|
|
|
|
int copy_file (const char * from, const char * to)
|
|
{
|
|
char buf[128];
|
|
int from_fd, to_fd;
|
|
int num_read, num_written;
|
|
char *write_ptr;
|
|
extern svalue_t apply_ret_value;
|
|
|
|
from = check_valid_path(from, current_object, "move_file", 0);
|
|
assign_svalue(&from_sv, &apply_ret_value);
|
|
|
|
to = check_valid_path(to, current_object, "move_file", 1);
|
|
assign_svalue(&to_sv, &apply_ret_value);
|
|
|
|
if (from == 0)
|
|
return -1;
|
|
if (to == 0)
|
|
return -2;
|
|
|
|
if (lstat(from, &from_stats) != 0) {
|
|
error("/%s: lstat failed\n", from);
|
|
return 1;
|
|
}
|
|
if (lstat(to, &to_stats) == 0) {
|
|
#ifdef WIN32
|
|
if (!strcmp(from, to))
|
|
#else
|
|
if (from_stats.st_dev == to_stats.st_dev
|
|
&& from_stats.st_ino == to_stats.st_ino)
|
|
#endif
|
|
{
|
|
error("`/%s' and `/%s' are the same file", from, to);
|
|
return 1;
|
|
}
|
|
} else if (errno != ENOENT) {
|
|
error("/%s: unknown error\n", to);
|
|
return 1;
|
|
}
|
|
|
|
from_fd = open(from, OPEN_READ);
|
|
if (from_fd < 0)
|
|
return -1;
|
|
|
|
if (file_size(to) == -2) {
|
|
/* Target is a directory; build full target filename. */
|
|
const char *cp;
|
|
char newto[MAX_FNAME_SIZE + MAX_PATH_LEN + 2];
|
|
|
|
cp = strrchr(from, '/');
|
|
if (cp)
|
|
cp++;
|
|
else
|
|
cp = from;
|
|
|
|
sprintf(newto, "%s/%s", to, cp);
|
|
close(from_fd);
|
|
return copy_file(from, newto);
|
|
}
|
|
to_fd = open(to, OPEN_WRITE | O_CREAT | O_TRUNC, 0666);
|
|
if (to_fd < 0) {
|
|
close(from_fd);
|
|
return -2;
|
|
}
|
|
while ((num_read = read(from_fd, buf, 128)) != 0) {
|
|
if (num_read < 0) {
|
|
debug_perror("copy_file: read", from);
|
|
close(from_fd);
|
|
close(to_fd);
|
|
return -3;
|
|
}
|
|
write_ptr = buf;
|
|
while (write_ptr != (buf + num_read)) {
|
|
num_written = write(to_fd, write_ptr, num_read);
|
|
if (num_written < 0) {
|
|
debug_perror("copy_file: write", to);
|
|
close(from_fd);
|
|
close(to_fd);
|
|
return -3;
|
|
}
|
|
write_ptr += num_written;
|
|
}
|
|
}
|
|
close(from_fd);
|
|
close(to_fd);
|
|
return 1;
|
|
}
|
|
|
|
void dump_file_descriptors (outbuffer_t * out)
|
|
{
|
|
int i;
|
|
dev_t dev;
|
|
struct stat stbuf;
|
|
|
|
outbuf_add(out, "Fd Device Number Inode Mode Uid Gid Size\n");
|
|
outbuf_add(out, "-- ------------- ----- ------ ----- ----- ----------\n");
|
|
|
|
for (i = 0; i < FD_SETSIZE; i++) {
|
|
/* bug in NeXT OS 2.1, st_mode == 0 for sockets */
|
|
if (fstat(i, &stbuf) == -1)
|
|
continue;
|
|
|
|
#if !defined(WIN32)
|
|
if (S_ISCHR(stbuf.st_mode) || S_ISBLK(stbuf.st_mode))
|
|
dev = stbuf.st_rdev;
|
|
else
|
|
#endif
|
|
dev = stbuf.st_dev;
|
|
|
|
outbuf_addv(out, "%2d", i);
|
|
outbuf_addv(out, "%13x", dev);
|
|
outbuf_addv(out, "%9d", stbuf.st_ino);
|
|
outbuf_add(out, " ");
|
|
|
|
switch (stbuf.st_mode & S_IFMT) {
|
|
|
|
case S_IFDIR:
|
|
outbuf_add(out, "d");
|
|
break;
|
|
case S_IFCHR:
|
|
outbuf_add(out, "c");
|
|
break;
|
|
#ifdef S_IFBLK
|
|
case S_IFBLK:
|
|
outbuf_add(out, "b");
|
|
break;
|
|
#endif
|
|
case S_IFREG:
|
|
outbuf_add(out, "f");
|
|
break;
|
|
#ifdef S_IFIFO
|
|
case S_IFIFO:
|
|
outbuf_add(out, "p");
|
|
break;
|
|
#endif
|
|
#ifdef S_IFLNK
|
|
case S_IFLNK:
|
|
outbuf_add(out, "l");
|
|
break;
|
|
#endif
|
|
#ifdef S_IFSOCK
|
|
case S_IFSOCK:
|
|
outbuf_add(out, "s");
|
|
break;
|
|
#endif
|
|
default:
|
|
outbuf_add(out, "?");
|
|
break;
|
|
}
|
|
|
|
outbuf_addv(out, "%5o", stbuf.st_mode & ~S_IFMT);
|
|
outbuf_addv(out, "%7d", stbuf.st_uid);
|
|
outbuf_addv(out, "%7d", stbuf.st_gid);
|
|
outbuf_addv(out, "%12d", stbuf.st_size);
|
|
outbuf_add(out, "\n");
|
|
}
|
|
}
|