1014 lines
29 KiB
C
1014 lines
29 KiB
C
#ifndef lint
|
|
#ifdef sccs
|
|
static char sccsid[] = "@(#)es_file.c 1.1 94/10/31";
|
|
#endif
|
|
#endif
|
|
|
|
/*
|
|
* Copyright (c) 1986, 1987 by Sun Microsystems, Inc.
|
|
*/
|
|
|
|
/*
|
|
* Entity stream implementation for disk files.
|
|
*
|
|
* A file is acting as one of three kinds of streams:
|
|
* 1) a read-only source of characters (an "original" stream for
|
|
* ps_impl.c) or,
|
|
* 2) as a write_only sink of characters (a backup or checkpoint), or
|
|
* 3) as a read-write edit history (a "scratch" stream).
|
|
* A scratch stream can get VERY large, and could choose to "wrap around"
|
|
* and re-use the bytes of the underlying file to conserve file space.
|
|
* However, since ps_impl.c must support large scratch streams for memory
|
|
* streams as well, it implements the wrap-around, thereby changing the
|
|
* expected access pattern to the stream implemented in this module.
|
|
*
|
|
* Based on the above, the implementation uses two buffers:
|
|
* 1) a read buffer, that contains the current insertion point and some
|
|
* portion of the characters before and after it, and
|
|
* 2) a write buffer, that contains the last point at which more than
|
|
* 4 bytes were written at once, plus some of the characters around it.
|
|
* This peculiar requirement for more than 4 bytes is because ps_impl.c keeps
|
|
* updating the count of the number of characters in the last contiguous
|
|
* insertion sequence.
|
|
* The read buffer always matchs an existing portion of the file as it
|
|
* exists on disk, but the write buffer can be "off the end", containing
|
|
* characters that have not yet been sent to disk. Thus, there can be valid
|
|
* indices in [length_on_disk..length) that are not valid positions for the
|
|
* underlying file!
|
|
* For an original stream, the write buffer is NULL, and
|
|
* length == length_on_disk.
|
|
*
|
|
* --- Misc. notes that may result in changes ... ---
|
|
* An empty original stream need not be open except to prevent another
|
|
* process or some piece of client code ripping it out from under this module.
|
|
* However, delayed opening moves where certain error conditions have to be
|
|
* handled by clients.
|
|
* A original stream pointed at a non-existent file could treat it as
|
|
* auto-creation of an empty stream, and then act as appropriate to an empty
|
|
* stream.
|
|
*
|
|
*
|
|
* Considerations for file consistency:
|
|
* Sun Unix 3.X (and BSD 4.X):
|
|
* For a local file system, write(2) does not report success unless there is
|
|
* space available on the disk for the data and write(2) claims that space.
|
|
* For a NFS file system, write(2) returns as soon as it has transferred all
|
|
* of the user data into kernel buffers. There need not be enough space for
|
|
* that data on the remote disk, and only a successful fsync(2) guarantees
|
|
* that the data is on the remote disk. If the fsync(2) fails, there is no
|
|
* indication of which data did not make it to the disk!
|
|
* Sun Unix 4.X (the VM-rewrite):
|
|
* With mapped files, the local file system may have the same problem as the
|
|
* NFS file system.
|
|
* NFS replacing ND:
|
|
* For diskless clients, the local file system has the same problem as the
|
|
* NFS file system unless it is using the (optional) RAM-disk /tmp.
|
|
* AT&T System V R ?
|
|
* There is a notion of "synchronous" files, where the write(2) does not
|
|
* report success until the data is on the disk. SunOS 4.X provides this
|
|
* feature, but "synchronous" files completely bypass the kernel disk cache,
|
|
* and thus significantly slow read(2) as well as write(2).
|
|
* stdio
|
|
* fwrite(3) is not guarantee to write(2) unless the stream is unbuffered.
|
|
* fflush(3) forces the write(2), but does not provide enough information to
|
|
* caller for it to figure out what did not get written. Worse yet, fseek(3)
|
|
* can call fflush(3) as a side-effect, and does not even report an error
|
|
* if the fflush(3) fails!
|
|
*
|
|
* --- And now for some history ... ---
|
|
* Prior to "-r 10.12", es_file used stdio and only needed to work for 3.X
|
|
* versions of the SunOS. The following strategy was employed.
|
|
* To get around the delayed nature of the calls to write(2), we force
|
|
* WRITE_BUF_LEN > BUFSIZE, forcing all full-buffer fwrite(3) calls to call
|
|
* write(2) immediately. (We don't just want to make the stream unbuffered
|
|
* because we need the buffering for reading). Since the only remaining
|
|
* partial calls to fwrite(3) should be in es_commit and es_destroy, this
|
|
* reduces the number of places that have to be very careful about disk
|
|
* consistency to entity_stream shutdown and es_replace callers.
|
|
*
|
|
* Other problems with using stdio:
|
|
* Since stdio may be looking at stdin (or some other file that is
|
|
* being asynchronously extended), stdio does a lot of lseek(2) calls to see
|
|
* if the file has been so extended. For the textsw, this is unnecessary and
|
|
* costly functionality.
|
|
* Stdio interacts poorly with ps_impl.c, because fseek fflush's the
|
|
* writable scratch file a lot.
|
|
*/
|
|
|
|
#include <strings.h>
|
|
#include <varargs.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/dir.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/vfs.h>
|
|
#include <sys/file.h>
|
|
#include <stdio.h>
|
|
#include <suntool/primal.h>
|
|
#include <suntool/entity_stream.h>
|
|
|
|
extern int errno, sys_nerr;
|
|
extern char *sys_errlist[];
|
|
extern char *malloc(), *sprintf();
|
|
extern long lseek();
|
|
|
|
static Es_status es_file_commit();
|
|
static Es_handle es_file_destroy();
|
|
static caddr_t es_file_get();
|
|
static Es_index es_file_get_length();
|
|
static Es_index es_file_get_position();
|
|
static Es_index es_file_set_position();
|
|
static Es_index es_file_read();
|
|
static Es_index es_file_replace();
|
|
static int es_file_set();
|
|
|
|
static struct es_ops es_file_ops = {
|
|
es_file_commit,
|
|
es_file_destroy,
|
|
es_file_get,
|
|
es_file_get_length,
|
|
es_file_get_position,
|
|
es_file_set_position,
|
|
es_file_read,
|
|
es_file_replace,
|
|
es_file_set
|
|
};
|
|
|
|
typedef struct _es_file_buf {
|
|
Es_index start; /* Disk position, valid iff used > 0 */
|
|
unsigned used; /* # valid chars in buf */
|
|
char *chars;
|
|
} es_file_buf;
|
|
typedef es_file_buf *Es_file_buf;
|
|
#define BUF_INVALIDATE(_buf) (_buf)->used = 0
|
|
#define BUF_LAST_PLUS_ONE(_buf) ((_buf)->start + (_buf)->used)
|
|
#define BUF_CONTAINS_POS(_buf, _pos) \
|
|
((_buf)->used > 0 && \
|
|
(_buf)->start <= (_pos) && (_pos) < BUF_LAST_PLUS_ONE(_buf))
|
|
|
|
struct private_data {
|
|
Es_status status;
|
|
char *name;
|
|
#ifndef BACKUP_AT_HEAD_OF_LINK
|
|
char *true_name; /* Non-null iff name was sym link */
|
|
#endif
|
|
unsigned flags, options;
|
|
caddr_t client_data;
|
|
Es_index length, length_on_disk, pos;
|
|
int fd;
|
|
#ifdef obsolete
|
|
FILE *file;
|
|
#endif
|
|
es_file_buf read_buf; /* cache for read's */
|
|
es_file_buf write_buf; /* cache for replace's */
|
|
};
|
|
typedef struct private_data *Es_file_data;
|
|
#define READ_BUF_LEN 8096
|
|
#define WRITE_BUF_LEN 8096
|
|
/* Bits for flags */
|
|
#define COMMIT_DONE 0x00000001
|
|
|
|
#define ABS_TO_REP(esh) (Es_file_data)LINT_CAST(esh->data)
|
|
|
|
/*
|
|
* Some invariants for read_buf and write_buf:
|
|
* 1) The buffers are allowed to overlap, but read's must retrieve
|
|
* from the write_buf first, to ensure that the client reads what it wrote.
|
|
*/
|
|
static char *file_name_only_msgs[] = {
|
|
/* 0 */ "cannot read file '%s'",
|
|
/* 1 */ "'%s' does not exist",
|
|
/* 2 */ "not permitted to access '%s'",
|
|
/* 3 */ "'%s' is not a file of ASCII text",
|
|
/* 4 */ "too many symbolic links from '%s'",
|
|
/* 5 */ "out of space for file '%s'"
|
|
};
|
|
|
|
extern int
|
|
es_file_append_error(error_buf, file_name, status)
|
|
char *error_buf, *file_name;
|
|
Es_status status;
|
|
/* Messages appended to error_buf have no trailing newline */
|
|
{
|
|
register char *first_free_in_buf;
|
|
register int msg_index = 0;
|
|
|
|
if (error_buf == 0)
|
|
return; /* Caller is fouled up. */
|
|
first_free_in_buf = error_buf+strlen(error_buf);
|
|
if (status & ES_CLIENT_STATUS(0)) {
|
|
(void) sprintf(first_free_in_buf,
|
|
"INTERNAL error for file '%s', status is %ld",
|
|
file_name, status);
|
|
return;
|
|
}
|
|
switch (ES_BASE_STATUS(status)) {
|
|
case ES_SUCCESS:
|
|
break; /* Caller is REALLY lazy! */
|
|
case ES_CHECK_ERRNO:
|
|
switch (errno) {
|
|
case ENOENT:
|
|
msg_index = 1;
|
|
goto Default;
|
|
case EACCES:
|
|
msg_index = 2;
|
|
goto Default;
|
|
case EISDIR:
|
|
msg_index = 3;
|
|
goto Default;
|
|
case ELOOP:
|
|
msg_index = 4;
|
|
goto Default;
|
|
case ENOMEM:
|
|
(void) strcat(error_buf, "alloc failure");
|
|
break;
|
|
default:
|
|
if (errno <= 0 || errno >= sys_nerr)
|
|
goto Default;
|
|
(void) sprintf(first_free_in_buf, "file '%s': %s",
|
|
file_name, sys_errlist[errno]);
|
|
break;
|
|
}
|
|
break;
|
|
case ES_INVALID_HANDLE:
|
|
(void) strcat(error_buf, "invalid es_handle");
|
|
break;
|
|
case ES_SEEK_FAILED:
|
|
(void) strcat(error_buf, "seek failed");
|
|
break;
|
|
case ES_FLUSH_FAILED:
|
|
case ES_FSYNC_FAILED:
|
|
case ES_SHORT_WRITE:
|
|
msg_index = 5;
|
|
goto Default;
|
|
default:
|
|
Default:
|
|
(void) sprintf(first_free_in_buf, file_name_only_msgs[msg_index],
|
|
file_name);
|
|
}
|
|
}
|
|
|
|
Es_handle
|
|
es_file_create(name, options, status)
|
|
char *name;
|
|
int options;
|
|
Es_status *status;
|
|
{
|
|
extern char *calloc(), *malloc();
|
|
extern fstat();
|
|
Es_handle esh = NEW(Es_object);
|
|
register Es_file_data private;
|
|
int open_option;
|
|
struct stat buf;
|
|
Es_status dummy_status;
|
|
#ifndef BACKUP_AT_HEAD_OF_LINK
|
|
char *temp_name, true_name[MAXNAMLEN];
|
|
int link_count, true_name_len;
|
|
#endif
|
|
|
|
if (status == 0)
|
|
status = &dummy_status;
|
|
*status = ES_CHECK_ERRNO;
|
|
errno = 0;
|
|
|
|
/* (1) Try to allocate all necessary memory */
|
|
if (esh == NULL)
|
|
goto AllocFailed;
|
|
if ((private = NEW(struct private_data)) == NULL)
|
|
goto AllocFailed;
|
|
private->fd = -1; /* In case of later AllocFailed */
|
|
BUF_INVALIDATE(&private->read_buf);
|
|
if ((private->read_buf.chars = malloc(READ_BUF_LEN)) == NULL)
|
|
goto AllocFailed;
|
|
BUF_INVALIDATE(&private->write_buf);
|
|
if (options & ES_OPT_APPEND) {
|
|
if ((private->write_buf.chars = malloc(WRITE_BUF_LEN)) == NULL)
|
|
goto AllocFailed;
|
|
} else {
|
|
private->write_buf.chars = NULL;
|
|
}
|
|
if ((private->name = strdup(name)) == NULL)
|
|
goto AllocFailed;
|
|
|
|
#ifndef BACKUP_AT_HEAD_OF_LINK
|
|
/* (2) Chase the symbolic link if 'name' is one. */
|
|
for (temp_name = name, link_count = 0;
|
|
(link_count < MAXSYMLINKS) &&
|
|
(-1 != (true_name_len =
|
|
readlink(temp_name, true_name, sizeof(true_name)) ));
|
|
temp_name = true_name, link_count++) {
|
|
true_name[true_name_len] = '\0';
|
|
}
|
|
if (link_count == MAXSYMLINKS) {
|
|
errno = ELOOP;
|
|
goto Error_Return;
|
|
}
|
|
if (temp_name == name) {
|
|
private->true_name = NULL;
|
|
} else
|
|
private->true_name = strdup(true_name);
|
|
#endif
|
|
|
|
/* (3) Open up the file and check to see it is not directory. */
|
|
open_option = (options & ES_OPT_APPEND)
|
|
? (O_RDWR | O_TRUNC | O_CREAT)
|
|
: (O_RDONLY);
|
|
private->fd = open(name, open_option, 0666);
|
|
if (private->fd < 0) {
|
|
goto Error_Return;
|
|
}
|
|
private->flags = 0;
|
|
private->options = options;
|
|
if ((private->options & ES_OPT_APPEND) == 0) {
|
|
if (fstat(private->fd, &buf) == -1)
|
|
goto Error_Return;
|
|
if ((buf.st_mode & S_IFMT) != S_IFREG) {
|
|
errno = EISDIR;
|
|
goto Error_Return;
|
|
}
|
|
private->length = buf.st_size;
|
|
}
|
|
|
|
/* (4) Final fix ups. */
|
|
private->length_on_disk = private->length;
|
|
esh->ops = &es_file_ops;
|
|
esh->data = (caddr_t)private;
|
|
*status = private->status = ES_SUCCESS;
|
|
return(esh);
|
|
|
|
AllocFailed:
|
|
errno = ENOMEM;
|
|
Error_Return:
|
|
if (esh) {
|
|
free((char *)esh); esh = ES_NULL;
|
|
}
|
|
if (private) {
|
|
if (private->read_buf.chars)
|
|
free(private->read_buf.chars);
|
|
if (private->write_buf.chars)
|
|
free(private->write_buf.chars);
|
|
if (private->fd >= 0)
|
|
(void) close(private->fd);
|
|
free((char *)private); private = (Es_file_data)0;
|
|
}
|
|
return(esh);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static caddr_t
|
|
es_file_get(esh, attribute, va_alist)
|
|
Es_handle esh;
|
|
Es_attribute attribute;
|
|
va_dcl
|
|
{
|
|
register Es_file_data private = ABS_TO_REP(esh);
|
|
#ifndef lint
|
|
va_list args;
|
|
#endif
|
|
switch (attribute) {
|
|
case ES_CLIENT_DATA:
|
|
return((caddr_t)(private->client_data));
|
|
case ES_NAME:
|
|
return((caddr_t)(private->name));
|
|
case ES_STATUS:
|
|
return((caddr_t)(private->status));
|
|
case ES_SIZE_OF_ENTITY:
|
|
return((caddr_t)sizeof(char));
|
|
case ES_TYPE:
|
|
return((caddr_t)ES_TYPE_FILE);
|
|
default:
|
|
return(0);
|
|
}
|
|
}
|
|
|
|
static int
|
|
es_file_set(esh, attrs)
|
|
Es_handle esh;
|
|
Attr_avlist attrs;
|
|
{
|
|
register Es_file_data private = ABS_TO_REP(esh);
|
|
Es_status status_dummy = ES_SUCCESS;
|
|
register Es_status *status = &status_dummy;
|
|
|
|
for (; *attrs && (*status == ES_SUCCESS); attrs = attr_next(attrs)) {
|
|
switch ((Es_attribute)*attrs) {
|
|
case ES_CLIENT_DATA:
|
|
private->client_data = attrs[1];
|
|
break;
|
|
case ES_FILE_MODE:
|
|
if (fchmod(private->fd, (int)attrs[1]) == -1)
|
|
*status = private->status = ES_CHECK_ERRNO;
|
|
break;
|
|
case ES_STATUS:
|
|
private->status = (Es_status)attrs[1];
|
|
break;
|
|
case ES_STATUS_PTR:
|
|
status = (Es_status *)LINT_CAST(attrs[1]);
|
|
*status = status_dummy;
|
|
break;
|
|
default:
|
|
*status = ES_INVALID_ATTRIBUTE;
|
|
break;
|
|
}
|
|
}
|
|
return((*status == ES_SUCCESS));
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
es_file_seek(private, pos, caller)
|
|
register Es_file_data private;
|
|
Es_index pos;
|
|
char *caller;
|
|
{
|
|
#ifdef DEBUG
|
|
if (private->length_on_disk < pos) {
|
|
private->status = ES_SEEK_FAILED;
|
|
(void) fprintf(stderr,
|
|
"%s: lseek to position %d > length_on_disk %d!!\n",
|
|
caller, pos, private->length_on_disk);
|
|
return(1);
|
|
}
|
|
#endif
|
|
if (lseek(private->fd, pos, L_SET) == -1) {
|
|
private->status = ES_SEEK_FAILED;
|
|
#ifdef DEBUG
|
|
(void) fprintf(stderr, "Bad lseek in %s to position %d\n",
|
|
caller, pos);
|
|
#endif
|
|
return(1);
|
|
} else {
|
|
return(0);
|
|
}
|
|
}
|
|
|
|
static int
|
|
es_file_fill_buf(private, buf, first, last_plus_one)
|
|
register Es_file_data private;
|
|
register Es_file_buf buf;
|
|
register Es_index first, last_plus_one;
|
|
{
|
|
register int read_in;
|
|
|
|
if (first < last_plus_one) {
|
|
if (es_file_seek(private, first, "es_file_fill_buf")) {
|
|
read_in = -1;
|
|
goto Return;
|
|
}
|
|
read_in = read(private->fd, buf->chars, last_plus_one-first);
|
|
if (read_in == -1 ||
|
|
read_in != last_plus_one-first /* paranoia */) {
|
|
private->status = ES_CHECK_ERRNO;
|
|
read_in = -2;
|
|
#ifdef DEBUG
|
|
(void) fprintf(stderr,
|
|
"Failed read in %s of %d chars\n",
|
|
"es_file_fill_buf", last_plus_one-first);
|
|
#endif
|
|
goto Return;
|
|
}
|
|
} else {
|
|
read_in = 0;
|
|
#ifdef DEBUG
|
|
if (first != private->length)
|
|
(void) fprintf(stderr,
|
|
"Null read in %s at %d with length %d\n",
|
|
"es_file_fill_buf", first, private->length);
|
|
#endif
|
|
}
|
|
buf->start = first;
|
|
buf->used = read_in;
|
|
Return:
|
|
return(read_in);
|
|
}
|
|
|
|
static int
|
|
es_file_flush_write_buf(private, buf)
|
|
register Es_file_data private;
|
|
register Es_file_buf buf;
|
|
/* This routine detects errors in attempted write, etc. but does not allow
|
|
* for successful retry in all cases (e.g., short writes or failed fsynch).
|
|
*/
|
|
{
|
|
register int written;
|
|
|
|
if (buf->used == 0) {
|
|
written = 0;
|
|
goto Return;
|
|
}
|
|
if (es_file_seek(private, buf->start, "es_file_flush_write_buf")) {
|
|
written = -1;
|
|
goto Return;
|
|
}
|
|
written = write(private->fd, buf->chars, buf->used);
|
|
if (written == -1 ||
|
|
written != buf->used /* paranoia */) {
|
|
private->status = ES_SHORT_WRITE; /* ES_FLUSH_FAILED instead? */
|
|
written = -2;
|
|
#ifdef DEBUG
|
|
(void) fprintf(stderr,
|
|
"Failed write in %s of %d chars\n",
|
|
"es_file_flush_write_buf", buf->used);
|
|
#endif
|
|
goto Return;
|
|
}
|
|
if (buf->start+written > private->length_on_disk) {
|
|
/* Extending file on disk, so make sure bytes make it. */
|
|
if (fsync(private->fd) == -1) {
|
|
private->status = ES_FSYNC_FAILED;
|
|
#ifdef DEBUG
|
|
(void) fprintf(stderr,
|
|
"Failed fsynch in es_file_flush_write_buf\n");
|
|
#endif
|
|
/* BUG ALERT! Who knows what state the file is in? */
|
|
written = -3;
|
|
goto Return;
|
|
}
|
|
private->length_on_disk = buf->start+written;
|
|
}
|
|
BUF_INVALIDATE(buf);
|
|
Return:
|
|
return(written);
|
|
}
|
|
|
|
static int
|
|
es_file_move_write_buf(private, include, also_include, include_offset)
|
|
register Es_file_data private;
|
|
register Es_index include, also_include;
|
|
char **include_offset;
|
|
/*
|
|
* Caller ensures:
|
|
* include <= also_include < include+WRITE_BUF_LEN < ES_INFINITY,
|
|
* include <= private->length
|
|
* Return values:
|
|
* < 0 indicate various errors,
|
|
* = 0 implies write_buf already correctly positioned,
|
|
* > 0 implies write_buf written then moved.
|
|
* Assumptions:
|
|
* if you have to write any bytes, you might as well write them all.
|
|
* if you have to read any bytes, you might as well read them all.
|
|
*/
|
|
{
|
|
register Es_file_buf buf = &private->write_buf;
|
|
register Es_file_buf read_buf;
|
|
register int written = 0;
|
|
Es_index buf_last_plus_one;
|
|
|
|
if (buf->used == 0)
|
|
goto Fill_Buffer;
|
|
buf_last_plus_one = BUF_LAST_PLUS_ONE(buf);
|
|
#ifdef DEBUG
|
|
if (buf_last_plus_one > private->length)
|
|
take_breakpoint();
|
|
#endif
|
|
/* Buffer must contain include and also_include.
|
|
* There are two possible problems, even if include is contained:
|
|
* 1) include is too far into the buffer for also_include to also be
|
|
* contained, or
|
|
* 2) there is space available in the buffer for also_include, but
|
|
* the buffer needs to fill in bytes from the disk between
|
|
* its current end and also_include.
|
|
* If include is at buffer end, or also_include is at or beyond
|
|
* buffer end, it may be possible to simply extend the buffer iff
|
|
* buffer is at the end of the stream and it has room left.
|
|
*/
|
|
if (include < buf->start || include > buf_last_plus_one ||
|
|
(include == buf_last_plus_one &&
|
|
include >= buf->start+WRITE_BUF_LEN) ||
|
|
(also_include >= buf_last_plus_one &&
|
|
(buf_last_plus_one < private->length ||
|
|
also_include >= buf->start+WRITE_BUF_LEN))
|
|
) {
|
|
written = es_file_flush_write_buf(private, &private->write_buf);
|
|
if (written < 0)
|
|
goto Return;
|
|
/* Possible future optimizations:
|
|
* 1) Deal with buf_last_plus_one <= also_include
|
|
* < buf->start+WRITE_BUF_LEN
|
|
* 2) Slide buffer around to avoid full read when possible
|
|
* 3) Copy from read_buf to avoid full read when possible
|
|
*/
|
|
Fill_Buffer:
|
|
if (es_file_fill_buf(private, buf, include,
|
|
include+WRITE_BUF_LEN > private->length_on_disk
|
|
? private->length_on_disk : include+WRITE_BUF_LEN) < 0) {
|
|
written = -4;
|
|
goto Return;
|
|
}
|
|
}
|
|
*include_offset = buf->chars + (include - buf->start);
|
|
Return:
|
|
return(written);
|
|
}
|
|
|
|
static void
|
|
es_file_maybe_truncate_buf(buf, new_last_plus_one)
|
|
register Es_file_buf buf;
|
|
register Es_index new_last_plus_one;
|
|
{
|
|
if (buf->used > 0 && new_last_plus_one < BUF_LAST_PLUS_ONE(buf)) {
|
|
buf->used = (new_last_plus_one < buf->start)
|
|
? 0 : new_last_plus_one - buf->start;
|
|
}
|
|
}
|
|
|
|
static Es_status
|
|
es_file_commit(esh)
|
|
Es_handle esh;
|
|
{
|
|
register Es_file_data private = ABS_TO_REP(esh);
|
|
|
|
if (es_file_flush_write_buf(private, &private->write_buf) < 0) {
|
|
return(private->status);
|
|
}
|
|
private->flags |= COMMIT_DONE;
|
|
return(ES_SUCCESS);
|
|
}
|
|
|
|
static Es_handle
|
|
es_file_destroy(esh)
|
|
Es_handle esh;
|
|
{
|
|
register Es_file_data private = ABS_TO_REP(esh);
|
|
|
|
if (private->write_buf.chars) {
|
|
#ifdef DEBUG
|
|
if ((private->write_buf.used > 0) &&
|
|
(private->flags & COMMIT_DONE)) {
|
|
/* Caller should have called es_commit in order to guarantee
|
|
* appropriate recovery in case of errors.
|
|
*/
|
|
take_breakpoint();
|
|
}
|
|
#endif
|
|
free(private->write_buf.chars);
|
|
}
|
|
(void) close(private->fd); private->fd = -1;
|
|
if ((private->options & ES_OPT_APPEND) &&
|
|
(private->flags & COMMIT_DONE) == 0) {
|
|
(void) unlink(private->name);
|
|
}
|
|
free((char *)esh);
|
|
free(private->read_buf.chars);
|
|
#ifndef BACKUP_AT_HEAD_OF_LINK
|
|
free(private->true_name);
|
|
#endif
|
|
free(private->name);
|
|
free((char *)private);
|
|
return(NULL);
|
|
}
|
|
|
|
static Es_index
|
|
es_file_get_length(esh)
|
|
Es_handle esh;
|
|
{
|
|
register Es_file_data private = ABS_TO_REP(esh);
|
|
return(private->length);
|
|
}
|
|
|
|
static Es_index
|
|
es_file_get_position(esh)
|
|
Es_handle esh;
|
|
{
|
|
register Es_file_data private = ABS_TO_REP(esh);
|
|
return(private->pos);
|
|
}
|
|
|
|
static Es_index
|
|
es_file_set_position(esh, pos)
|
|
Es_handle esh;
|
|
register Es_index pos;
|
|
{
|
|
register Es_file_data private = ABS_TO_REP(esh);
|
|
if (pos > private->length)
|
|
pos = private->length;
|
|
private->pos = pos;
|
|
return(private->pos);
|
|
}
|
|
|
|
static Es_index
|
|
es_file_read(esh, count, buf, count_read)
|
|
Es_handle esh;
|
|
int count;
|
|
register int *count_read;
|
|
caddr_t buf;
|
|
/*
|
|
* Needed characters may be in the read_buf, the write_buf, or on disk, or
|
|
* some combination of all three.
|
|
*/
|
|
{
|
|
register Es_file_data private = ABS_TO_REP(esh);
|
|
register Es_index pos = private->pos, lpo;
|
|
register int to_read, still_needed;
|
|
es_file_buf dummy_read_buf;
|
|
char *offset;
|
|
|
|
/* Client may request more bytes than are available, so count cannot
|
|
* be trusted in the following code.
|
|
*/
|
|
*count_read = (count > private->length - pos)
|
|
? (private->length - pos) : count;
|
|
for (still_needed = *count_read;
|
|
still_needed > 0;
|
|
still_needed -= to_read, pos += to_read) {
|
|
/* Figure out where the next set of characters is coming from.
|
|
* The write_buf has precedence over the read_buf in the tests
|
|
* so that overlap range reads from the write_buf!
|
|
*/
|
|
if (BUF_CONTAINS_POS(&private->write_buf, pos)) {
|
|
to_read = BUF_LAST_PLUS_ONE(&private->write_buf) - pos;
|
|
if (to_read > still_needed)
|
|
to_read = still_needed;
|
|
bcopy(private->write_buf.chars+(pos-private->write_buf.start),
|
|
buf + (*count_read-still_needed), to_read);
|
|
} else if (BUF_CONTAINS_POS(&private->read_buf, pos)) {
|
|
to_read = BUF_LAST_PLUS_ONE(&private->read_buf) - pos;
|
|
if (to_read > still_needed)
|
|
to_read = still_needed;
|
|
bcopy(private->read_buf.chars+(pos-private->read_buf.start),
|
|
buf + (*count_read-still_needed), to_read);
|
|
} else if (still_needed <= READ_BUF_LEN) {
|
|
/* Since we have to read from disk, might as well get as
|
|
* many characters as possible.
|
|
*/
|
|
lpo = pos + READ_BUF_LEN;
|
|
if (lpo > private->length_on_disk)
|
|
lpo = private->length_on_disk;
|
|
/* Overlap with write_buf is avoided for good hygiene. */
|
|
if (private->write_buf.used > 0 &&
|
|
pos < private->write_buf.start &&
|
|
lpo > private->write_buf.start)
|
|
lpo = private->write_buf.start;
|
|
if (es_file_fill_buf(private, &private->read_buf, pos, lpo)
|
|
< 0) {
|
|
*count_read = 0;
|
|
pos = private->pos;
|
|
goto Return;
|
|
}
|
|
/* Go around again and characters will get copied
|
|
* from read_buf in earlier part of loop.
|
|
*/
|
|
to_read = 0;
|
|
} else {
|
|
/* Read directly from the disk */
|
|
dummy_read_buf.chars = buf + (*count_read-still_needed);
|
|
lpo = pos + still_needed;
|
|
if (lpo > private->length_on_disk)
|
|
lpo = private->length_on_disk;
|
|
/* Overlap with write_buf is forbidden. */
|
|
if (private->write_buf.used > 0 &&
|
|
lpo > private->write_buf.start)
|
|
lpo = private->write_buf.start;
|
|
if (es_file_fill_buf(private, &dummy_read_buf, pos, lpo)
|
|
< 0) {
|
|
*count_read = 0;
|
|
pos = private->pos;
|
|
goto Return;
|
|
}
|
|
to_read = dummy_read_buf.used;
|
|
}
|
|
}
|
|
Return:
|
|
private->pos = pos;
|
|
return(pos);
|
|
}
|
|
|
|
/* Following enumeration details the three possible types of replace. */
|
|
typedef enum {esfr_truncate, esfr_overwrite, esfr_insert} Esfr_mode;
|
|
|
|
static Es_index
|
|
es_file_replace(esh, last_plus_one, count, buf, count_used)
|
|
Es_handle esh;
|
|
register int count;
|
|
int *count_used, last_plus_one;
|
|
caddr_t buf;
|
|
{
|
|
register Es_file_data private = ABS_TO_REP(esh);
|
|
register Esfr_mode mode;
|
|
char *offset;
|
|
es_file_buf dummy_write_buf;
|
|
|
|
/* Ensure that the operation is consistent with the options. */
|
|
if ((private->options & ES_OPT_APPEND) == 0) {
|
|
private->status = ES_INCONSISTENT_POS;
|
|
#ifdef DEBUG
|
|
(void) fprintf(stderr, "es_file_replace: read-only stream\n");
|
|
#endif
|
|
goto Error_Return;
|
|
}
|
|
if (private->pos < private->length) {
|
|
if (last_plus_one <= private->length) {
|
|
mode = esfr_overwrite;
|
|
} else {
|
|
mode = esfr_truncate;
|
|
if (count != 0) {
|
|
private->status = ES_INVALID_ARGUMENTS;
|
|
#ifdef DEBUG
|
|
(void) fprintf(stderr,
|
|
"%s: non-zero (%d) count in truncate\n",
|
|
"es_file_replace", count);
|
|
#endif
|
|
goto Error_Return;
|
|
}
|
|
if (private->pos < private->length_on_disk) {
|
|
private->status = ES_INVALID_ARGUMENTS;
|
|
#ifdef DEBUG
|
|
(void) fprintf(stderr,
|
|
"%s: truncate @ %d when length_on_disk %d\n",
|
|
"es_file_replace", private->pos,
|
|
private->length_on_disk);
|
|
#endif
|
|
goto Error_Return;
|
|
}
|
|
}
|
|
if ((private->options & ES_OPT_OVERWRITE) == 0 ||
|
|
((mode == esfr_overwrite) &&
|
|
(count != last_plus_one-private->pos))) {
|
|
private->status = ES_INVALID_ARGUMENTS;
|
|
#ifdef DEBUG
|
|
(void) fprintf(stderr, "%s last_plus_one is %d, len is %d\n",
|
|
"es_file_replace position error:",
|
|
last_plus_one, private->length);
|
|
#endif
|
|
goto Error_Return;
|
|
}
|
|
} else {
|
|
mode = esfr_insert;
|
|
}
|
|
|
|
/* Do the replace */
|
|
if (mode == esfr_truncate) {
|
|
es_file_maybe_truncate_buf(&private->read_buf);
|
|
es_file_maybe_truncate_buf(&private->write_buf);
|
|
*count_used = 0;
|
|
} else if (mode == esfr_overwrite) {
|
|
/* If new bytes will fit in the write_buf, position the write_buf
|
|
* to accomodate them (unless there are 4 or fewer bytes) and
|
|
* overwrite. Otherwise, flush out the write_buf and write
|
|
* the new bytes directly.
|
|
*/
|
|
if (count <= WRITE_BUF_LEN) {
|
|
if (count < 5 &&
|
|
(last_plus_one < private->write_buf.start ||
|
|
private->pos >= BUF_LAST_PLUS_ONE(&private->write_buf))) {
|
|
goto Write_Direct;
|
|
}
|
|
if (es_file_move_write_buf(private, private->pos,
|
|
last_plus_one, &offset) < 0) {
|
|
goto Error_Return;
|
|
}
|
|
bcopy(buf, offset, count);
|
|
*count_used = count;
|
|
} else {
|
|
goto Flush_And_Write_Direct;
|
|
}
|
|
} else /* mode == es_insert */ {
|
|
/* Insert only happens at end-of-stream, thus it always increases
|
|
* private->write_buf.used by the size of the insertion.
|
|
* If new bytes will fit in the write_buf, position the write_buf
|
|
* to accomodate them and add them. Otherwise, flush out the
|
|
* write_buf and write the new bytes directly.
|
|
*/
|
|
if (count <= WRITE_BUF_LEN) {
|
|
if (es_file_move_write_buf(private, private->pos,
|
|
private->pos+count, &offset) < 0) {
|
|
goto Error_Return;
|
|
}
|
|
bcopy(buf, offset, count);
|
|
private->write_buf.used += count;
|
|
} else {
|
|
Flush_And_Write_Direct:
|
|
if (es_file_flush_write_buf(private,
|
|
&private->write_buf) < 0) {
|
|
goto Error_Return;
|
|
}
|
|
Write_Direct:
|
|
/* Fake up a write buffer */
|
|
dummy_write_buf.used = count;
|
|
dummy_write_buf.start = private->pos;
|
|
dummy_write_buf.chars = buf;
|
|
if (es_file_flush_write_buf(private,
|
|
&dummy_write_buf) <= 0) {
|
|
goto Error_Return;
|
|
}
|
|
/* Correct overlap with the read_buf that is not masked
|
|
* by overlap with the write_buf.
|
|
*/
|
|
if (private->read_buf.used > 0 &&
|
|
private->pos < BUF_LAST_PLUS_ONE(&private->read_buf) &&
|
|
private->read_buf.start < private->pos+count) {
|
|
/* There is overlap: in future, might be better to update
|
|
* read_buf, but for now, just discard it.
|
|
*/
|
|
BUF_INVALIDATE(&private->read_buf);
|
|
}
|
|
}
|
|
*count_used = count;
|
|
}
|
|
|
|
Return:
|
|
private->pos += *count_used;
|
|
if (mode != esfr_overwrite) private->length = private->pos;
|
|
return(private->pos);
|
|
|
|
Error_Return:
|
|
return(ES_CANNOT_SET);
|
|
}
|
|
|
|
extern int
|
|
es_file_copy_status(esh, to)
|
|
Es_handle esh;
|
|
char *to;
|
|
{
|
|
Es_file_data private = ABS_TO_REP(esh);
|
|
int dummy;
|
|
|
|
return(es_copy_status(to, private->fd, &dummy));
|
|
}
|
|
|
|
extern Es_handle
|
|
es_file_make_backup(esh, backup_pattern, status)
|
|
register Es_handle esh;
|
|
char *backup_pattern;
|
|
Es_status *status;
|
|
/* Currently backup_pattern must be of the form "%s<suffix>" */
|
|
{
|
|
register Es_file_data private;
|
|
char backup_name[MAXNAMLEN];
|
|
int fd, len, retrying = FALSE;
|
|
Es_status dummy_status;
|
|
Es_handle result;
|
|
struct stat stto, stfrom;
|
|
struct statfs fs;
|
|
|
|
if (status == 0)
|
|
status = &dummy_status;
|
|
if ((esh == NULL) || (esh->ops != &es_file_ops)) {
|
|
*status = ES_INVALID_HANDLE;
|
|
return(NULL);
|
|
}
|
|
*status = ES_CHECK_ERRNO;
|
|
errno = 0;
|
|
private = ABS_TO_REP(esh);
|
|
#ifdef BACKUP_AT_HEAD_OF_LINK
|
|
(void) sprintf(backup_name, backup_pattern, private->name);
|
|
#else
|
|
(void) sprintf(backup_name, backup_pattern,
|
|
(private->true_name) ? private->true_name
|
|
: private->name);
|
|
#endif
|
|
fd = private->fd;
|
|
len = lseek(fd, 0L, 1);
|
|
if (lseek(fd, 0L, 0) != 0)
|
|
goto Lseek_Failed;
|
|
|
|
if (stat(private->name, &stfrom) < 0) {
|
|
*status = ES_CHECK_ERRNO;
|
|
return (ES_NULL);
|
|
}
|
|
if (stat(backup_name, &stto) < 0 && errno != ENOENT) {
|
|
*status = ES_CHECK_ERRNO;
|
|
return (ES_NULL);
|
|
}
|
|
else if (errno == ENOENT) stto.st_size = 0;
|
|
if (statfs(private->name, &fs) < 0) {
|
|
*status = ES_CHECK_ERRNO;
|
|
return (ES_NULL);
|
|
}
|
|
|
|
if (stfrom.st_size - stto.st_size > fs.f_bavail * DEV_BSIZE) {
|
|
*status = ES_CHECK_ERRNO;
|
|
errno = ENOSPC;
|
|
return (ES_NULL);
|
|
}
|
|
Retry:
|
|
if (es_copy_fd(private->name, backup_name, fd) != 0) {
|
|
if ((!retrying) && (errno == EACCES)) {
|
|
/* It may be that the backup_name is already taken by a file
|
|
* that cannot be overwritten, so try to remove it first.
|
|
*/
|
|
if (unlink(backup_name) == 0) {
|
|
retrying = TRUE;
|
|
goto Retry;
|
|
}
|
|
if (errno == ENOENT)
|
|
/* backup_name does not exist, so problem with es_copy_fd
|
|
* really is unfixable access error, which that needs to
|
|
* be reported to caller, so set errno back!
|
|
*/
|
|
errno = EACCES;
|
|
}
|
|
return(NULL);
|
|
}
|
|
if (lseek(fd, (long)len, 0) != len)
|
|
goto Lseek_Failed;
|
|
result = es_file_create(backup_name, 0, status);
|
|
*status = ES_SUCCESS;
|
|
return(result);
|
|
|
|
Lseek_Failed:
|
|
*status = ES_SEEK_FAILED;
|
|
return(NULL);
|
|
}
|