write_log(3) a set of routines for logging writes to a history file

SYNOPSIS

#include "write_log.h"
int wlog_open(struct wlog_file *wfile, int trunc, int mode);
int wlog_close(struct wlog_file *wfile);
int wlog_record_write(struct wlog_file *wfile, struct wlog_rec *wrec, long offset);
int wlog_scan_backward(struct wlog_file *wfile, int nrecs, int (*func)(struct wlog_rec *rec), long data);
char *Wlog_Error_String;

DESCRIPTION

The write_log package is a set of routines for creating a history file of write operations done to a set of files.

It is assumed that the actual pattern written to the file will be repeated occurrences of a string whose length is no greater than WLOG_MAX_PATTERN. See the pattern(3) man page for routines to conveniently generate buffers of this kind.

wlog_open() initializes the history file contained in wfile->w_file, and fills in the wfile structure. If trunc is non-zero, and existing history file by the same name will be truncated. If no history file exists, it will be created with the specified mode.

wlog_close() releases any resources associated with the given wfile. Use of wfile after this point is undefined until it is initialized again with wlog_open().

wlog_record_write() is the main routine for putting a wlog_rec into the history file. The caller is responsible for supplying a fully initialized wlog_rec structure. If offset is < 0, the record will be appended to the end of the history file. If offset is >= 0, the record will be written at the indicated offset. This, along with the w_done field in the wlog_rec structure, provide a mechanism for 'pre-logging' a write, doing the write operation, and then overlaying the original record with the w_done flag set to 1. This is useful for async writes which may not complete in a timely manner. It is also useful for seeing which write operations were pending at the time of a system crash - the ones whose w_done flag is 0 have not yet been verified as complete. The return value from wlog_record_write() is the offset in the history file at which the record was written.

wlog_scan_backward() can be used to conveniently scan a write history file. The routine scans the file in reverse order (ie. first record written is scanned last). For every record found, the user supplied function is called with 2 parameters: the read record, and an arbitrary word passed in by the user. This word may be interpreted however the user desires. If nrecs is greater than 0, up to nrecs will be scanned. The user supplied function should return 1 of the following: WLOG_STOP_SCAN, or WLOG_CONTINUE_SCAN. WLOG_STOP_SCAN provides a way for the user supplied function to prematurely abort the scanning process. WLOG_CONTINUE_SCAN instructs wlog_scan_backward() to continue scanning the next record.

In order for the history file to be effective, some basic rules must be followed by the programs using the history mechanism:

The area of the data file being written must be locked from before the write operation, until after the wlog_record_write() is complete. This is necessary to 'synchronize' simultaneous writes to the same area of a file. Note that the entire file does not need to be locked, only the portion being written to. If the calling program can guarantee that there will never be more than 1 process writing to the same area of a file at the same time, locking is not necessary. (Note: UNICOS Shared File Systems do not support record locking. The whole file is silently locked.)

Pathnames in the history file (w_path field) should be full pathnames if possible. This allows validation tools to be able to find the test files without having to take working directory considerations into account.

/*
 * write log file data type.  wlog_open() initializes this structure
 * which is then passed around to the various wlog_xxx routines.
 */
struct wlog_file {
   int  w_afd;    /* append fd */
   int  w_rfd;    /* random-access fd */
   char w_file[1024];/* name of the write_log */
};

/*
 * User view of a history file record.  Note that this is not
 * necessarily how the data is formatted on disk (significant
 * compression occurs), so don't expect to od(1) the history file and
 * see things formatted this way.  See the file write_log.h for comments
 * on how the data is actually written to disk.
 */
struct wlog_rec {
   int  w_pid;              /* pid doing the write */
   int  w_offset;           /* file offset */
   int  w_nbytes;           /* # bytes written */
   int  w_oflags;           /* low-order open() flags */
   int  w_done;             /* 1 if io confirmed done */
   int  w_async;            /* 1 if async write (writea) */
   char w_host[WLOG_MAX_HOST+1];/* host doing write */
   int  w_hostlen;          /* host name length */
   char w_path[WLOG_MAX_PATH+1];/* file written to */
   int  w_pathlen;          /* file name length */
   char w_pattern[WLOG_MAX_PATTERN+1];/* pattern written */
   int  w_patternlen;       /* pattern length */
};

Note: The history files can become very large very quickly if a lot of processes are logging writes. This is especially apt to happen if long pathnames or patterns are used. This is because the w_host, w_path, and w_pattern fields are variable length fields when stored on disk. Thus, use short pathnames and patterns to minimize the size of the history file. If any of the w_path, w_pattern, or w_host fields are not important to you, set the respective length field to 0 in the wlog_rec structure.

EXAMPLES

This is a simple example of how to initialize a history file, and record a write to it.

#include "write_log.h"
main()
{
        struct wlog_rec wrec;
        struct wlog_file wfile;
        ...
        strcpy(wfile.w_file, hisfile);
        if (wlog_open(&wfile, 1, 0666) < 0) {
                fprintf("wlog_open failed);
                exit(2);
        }
        ...
        wrec.w_pid = getpid();
        wrec.w_offset = write_offset;
        wrec.w_nbytes = nbytes;
        wrec.w_oflags = open_flags;
        wrec.w_done = 0;
        wrec.w_async = 0;
        wrec.w_host = 0;             /* don't care about host */
        wrec.w_pathlen = sprintf(wrec.w_path, "%s", path);
        wrec.w_patternlen = sprintf(wrec.w_pattern, "%s", pattern);
        pattern_fill(buf, nbytes, pattern, strlen(pattern), 0);
        ... lock fd here ...
        log_offset = wlog_record_write(&wfile, &wrec, -1);
        write(fd, buf, nbytes);
        wrec.w_done = 1;
        wlog_record_write(&wfile, &wrec, log_offset);
        ... unlock fd here ...
        ...
        /*
         * Scan the logfile printing records for the file in 'path'.
         */
        wlog_scan_backward(&wfile, 0, print_log_record, (long)path);
}
int
print_log_record(record, data)
struct wlog_rec *record;
long            data;
{
        char    *path;
        path = (char *)data;
        if (strcmp(record->w_path, path) == 0) {
                printf("write() of %d bytes to %s at offset %d by pid %d,
                        record->w_nbytes, record->path, record->w_offset, record->w_pid);
        }
        return WLOG_CONTINUE_SCAN;
}

DIAGNOSTICS

All routines return a value < 0 on failure, and >= 0 on success. Error messages can be accessed through Wlog_Error_String.

BUGS

None known.