📄 es_file.c
字号:
#ifndef lint#ifdef sccsstatic char sccsid[] = "@(#)es_file.c 1.1 92/07/30";#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 intes_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_handlees_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_tes_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 intes_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 intes_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 intes_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 intes_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? */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -