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