Home | History | Annotate | Download | only in doio
      1 /*
      2  * Copyright (c) 2000 Silicon Graphics, Inc.  All Rights Reserved.
      3  *
      4  * This program is free software; you can redistribute it and/or modify it
      5  * under the terms of version 2 of the GNU General Public License as
      6  * published by the Free Software Foundation.
      7  *
      8  * This program is distributed in the hope that it would be useful, but
      9  * WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     11  *
     12  * Further, this software is distributed without any warranty that it is
     13  * free of the rightful claim of any third person regarding infringement
     14  * or the like.  Any license provided herein, whether implied or
     15  * otherwise, applies only to this software file.  Patent licenses, if
     16  * any, provided herein do not apply to combinations of this program with
     17  * other software, or any other product whatsoever.
     18  *
     19  * You should have received a copy of the GNU General Public License along
     20  * with this program; if not, write the Free Software Foundation, Inc.,
     21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     22  *
     23  * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
     24  * Mountain View, CA  94043, or:
     25  *
     26  * http://www.sgi.com
     27  *
     28  * For further information regarding this notice, see:
     29  *
     30  * http://oss.sgi.com/projects/GenInfo/NoticeExplan/
     31  */
     32 /*
     33  * This module contains code for logging writes to files, and for
     34  * perusing the resultant logfile.  The main intent of all this is
     35  * to provide a 'write history' of a file which can be examined to
     36  * judge the state of a file (ie. whether it is corrupted or not) based
     37  * on the write activity.
     38  *
     39  * The main abstractions available to the user are the wlog_file, and
     40  * the wlog_rec.  A wlog_file is a handle encapsulating a write logfile.
     41  * It is initialized with the wlog_open() function.  This handle is
     42  * then passed to the various wlog_xxx() functions to provide transparent
     43  * access to the write logfile.
     44  *
     45  * The wlog_rec datatype is a structure which contains all the information
     46  * about a file write.  Examples include the file name, offset, length,
     47  * pattern, etc.  In addition there is a bit which is cleared/set based
     48  * on whether or not the write has been confirmed as complete.  This
     49  * allows the write logfile to contain information on writes which have
     50  * been initiated, but not yet completed (as in async io).
     51  *
     52  * There is also a function to scan a write logfile in reverse order.
     53  *
     54  * NOTE:	For target file analysis based on a write logfile, the
     55  * 		assumption is made that the file being written to is
     56  * 		locked from simultaneous access, so that the order of
     57  * 		write completion is predictable.  This is an issue when
     58  * 		more than 1 process is trying to write data to the same
     59  *		target file simultaneously.
     60  *
     61  * The history file created is a collection of variable length records
     62  * described by scruct wlog_rec_disk in write_log.h.  See that module for
     63  * the layout of the data on disk.
     64  */
     65 
     66 #include <stdio.h>
     67 #include <unistd.h>
     68 #include <fcntl.h>
     69 #include <errno.h>
     70 #include <string.h>
     71 #include <sys/param.h>
     72 #include <sys/stat.h>
     73 #include <sys/types.h>
     74 #include "write_log.h"
     75 
     76 #ifndef BSIZE
     77 #ifdef DEV_BSIZE
     78 #define BSIZE DEV_BSIZE
     79 #else
     80 #warning DEV_BSIZE is not defined, defaulting to 512
     81 #define BSIZE 512
     82 #endif
     83 #endif
     84 
     85 #ifndef PATH_MAX
     86 #define PATH_MAX          255
     87 /*#define PATH_MAX pathconf("/", _PC_PATH_MAX)*/
     88 #endif
     89 
     90 char Wlog_Error_String[256];
     91 
     92 #if __STDC__
     93 static int wlog_rec_pack(struct wlog_rec *wrec, char *buf, int flag);
     94 static int wlog_rec_unpack(struct wlog_rec *wrec, char *buf);
     95 #else
     96 static int wlog_rec_pack();
     97 static int wlog_rec_unpack();
     98 #endif
     99 
    100 /*
    101  * Initialize a write logfile.  wfile is a wlog_file structure that has
    102  * the w_file field filled in.  The rest of the information in the
    103  * structure is initialized by the routine.
    104  *
    105  * The trunc flag is used to indicate whether or not the logfile should
    106  * be truncated if it currently exists.  If it is non-zero, the file will
    107  * be truncated, otherwise it will be appended to.
    108  *
    109  * The mode argument is the [absolute] mode which the file will be
    110  * given if it does not exist.  This mode is not affected by your process
    111  * umask.
    112  */
    113 
    114 int wlog_open(struct wlog_file *wfile, int trunc, int mode)
    115 {
    116 	int omask, oflags;
    117 
    118 	if (trunc)
    119 		trunc = O_TRUNC;
    120 
    121 	omask = umask(0);
    122 
    123 	/*
    124 	 * Open 1 file descriptor as O_APPEND
    125 	 */
    126 
    127 	oflags = O_WRONLY | O_APPEND | O_CREAT | trunc;
    128 	wfile->w_afd = open(wfile->w_file, oflags, mode);
    129 	umask(omask);
    130 
    131 	if (wfile->w_afd == -1) {
    132 		sprintf(Wlog_Error_String,
    133 			"Could not open write_log - open(%s, %#o, %#o) failed:  %s\n",
    134 			wfile->w_file, oflags, mode, strerror(errno));
    135 		return -1;
    136 	}
    137 
    138 	/*
    139 	 * Open the next fd as a random access descriptor
    140 	 */
    141 
    142 	oflags = O_RDWR;
    143 	if ((wfile->w_rfd = open(wfile->w_file, oflags)) == -1) {
    144 		sprintf(Wlog_Error_String,
    145 			"Could not open write log - open(%s, %#o) failed:  %s\n",
    146 			wfile->w_file, oflags, strerror(errno));
    147 		close(wfile->w_afd);
    148 		wfile->w_afd = -1;
    149 		return -1;
    150 	}
    151 
    152 	return 0;
    153 }
    154 
    155 /*
    156  * Release all resources associated with a wlog_file structure allocated
    157  * with the wlog_open() call.
    158  */
    159 
    160 int wlog_close(struct wlog_file *wfile)
    161 {
    162 	close(wfile->w_afd);
    163 	close(wfile->w_rfd);
    164 	return 0;
    165 }
    166 
    167 /*
    168  * Write a wlog_rec structure to a write logfile.  Offset is used to
    169  * control where the record will be written.  If offset is < 0, the
    170  * record will be appended to the end of the logfile.  Otherwise, the
    171  * record which exists at the indicated offset will be overlayed.  This
    172  * is so that we can record writes which are outstanding (with the w_done
    173  * bit in wrec cleared), but not completed, and then later update the
    174  * logfile when the write request completes (as with async io).  When
    175  * offset is >= 0, only the fixed length portion of the record is
    176  * rewritten.  See text in write_log.h for details on the format of an
    177  * on-disk record.
    178  *
    179  * The return value of the function is the byte offset in the logfile
    180  * where the record begins.
    181  *
    182  * Note:  It is the callers responsibility to make sure that the offset
    183  * parameter 'points' to a valid record location when a record is to be
    184  * overlayed.  This is guarenteed by saving the return value of a previous
    185  * call to wlog_record_write() which wrote the record to be overlayed.
    186  *
    187  * Note2:  The on-disk version of the wlog_rec is MUCH different than
    188  * the user version.  Don't expect to od the logfile and see data formatted
    189  * as it is in the wlog_rec structure.  Considerable data packing takes
    190  * place before the record is written.
    191  */
    192 
    193 int wlog_record_write(struct wlog_file *wfile, struct wlog_rec *wrec,
    194 			long offset)
    195 {
    196 	int reclen;
    197 	char wbuf[WLOG_REC_MAX_SIZE + 2];
    198 
    199 	/*
    200 	 * If offset is -1, we append the record at the end of file
    201 	 *
    202 	 * Otherwise, we overlay wrec at the file offset indicated and assume
    203 	 * that the caller passed us the correct offset.  We do not record the
    204 	 * fname in this case.
    205 	 */
    206 
    207 	reclen = wlog_rec_pack(wrec, wbuf, (offset < 0));
    208 
    209 	if (offset < 0) {
    210 		/*
    211 		 * Since we're writing a complete new record, we must also tack
    212 		 * its length onto the end so that wlog_scan_backward() will work.
    213 		 * Length is asumed to fit into 2 bytes.
    214 		 */
    215 
    216 		wbuf[reclen] = reclen / 256;
    217 		wbuf[reclen + 1] = reclen % 256;
    218 		reclen += 2;
    219 
    220 		if (write(wfile->w_afd, wbuf, reclen) == -1) {
    221 			sprintf(Wlog_Error_String,
    222 				"Could not write log - write(%s, %s, %d) failed:  %s\n",
    223 				wfile->w_file, wbuf, reclen, strerror(errno));
    224 			return -1;
    225 		} else {
    226 			offset = lseek(wfile->w_afd, 0, SEEK_CUR) - reclen;
    227 			if (offset == -1) {
    228 				sprintf(Wlog_Error_String,
    229 					"Could not reposition file pointer - lseek(%s, 0, SEEK_CUR) failed:  %s\n",
    230 					wfile->w_file, strerror(errno));
    231 				return -1;
    232 			}
    233 		}
    234 	} else {
    235 		if ((lseek(wfile->w_rfd, offset, SEEK_SET)) == -1) {
    236 			sprintf(Wlog_Error_String,
    237 				"Could not reposition file pointer - lseek(%s, %ld, SEEK_SET) failed:  %s\n",
    238 				wfile->w_file, offset, strerror(errno));
    239 			return -1;
    240 		} else {
    241 			if ((write(wfile->w_rfd, wbuf, reclen)) == -1) {
    242 				sprintf(Wlog_Error_String,
    243 					"Could not write log - write(%s, %s, %d) failed:  %s\n",
    244 					wfile->w_file, wbuf, reclen,
    245 					strerror(errno));
    246 				return -1;
    247 			}
    248 		}
    249 	}
    250 
    251 	return offset;
    252 }
    253 
    254 /*
    255  * Function to scan a logfile in reverse order.  Wfile is a valid
    256  * wlog_file structure initialized by wlog_open().  nrecs is the number
    257  * of records to scan (all records are scanned if nrecs is 0).  func is
    258  * a user-supplied function to call for each record found.  The function
    259  * will be passed a single parameter - a wlog_rec structure .
    260  */
    261 
    262 int wlog_scan_backward(struct wlog_file *wfile, int nrecs,
    263 			int (*func)(), long data)
    264 {
    265 	int fd, leftover, nbytes, offset, recnum, reclen, rval;
    266 	char buf[BSIZE * 32], *bufend, *cp, *bufstart;
    267 	char albuf[WLOG_REC_MAX_SIZE];
    268 	struct wlog_rec wrec;
    269 
    270 	fd = wfile->w_rfd;
    271 
    272 	/*
    273 	 * Move to EOF.  offset will always hold the current file offset
    274 	 */
    275 
    276 	if ((lseek(fd, 0, SEEK_END)) == -1) {
    277 		sprintf(Wlog_Error_String,
    278 			"Could not reposition file pointer - lseek(%s, 0, SEEK_END) failed:  %s\n",
    279 			wfile->w_file, strerror(errno));
    280 		return -1;
    281 	}
    282 	offset = lseek(fd, 0, SEEK_CUR);
    283 	if ((offset == -1)) {
    284 		sprintf(Wlog_Error_String,
    285 			"Could not reposition file pointer - lseek(%s, 0, SEEK_CUR) failed:  %s\n",
    286 			wfile->w_file, strerror(errno));
    287 		return -1;
    288 	}
    289 
    290 	bufend = buf + sizeof(buf);
    291 	bufstart = buf;
    292 
    293 	recnum = 0;
    294 	leftover = 0;
    295 	while ((!nrecs || recnum < nrecs) && offset > 0) {
    296 		/*
    297 		 * Check for beginning of file - if there aren't enough bytes
    298 		 * remaining to fill buf, adjust bufstart.
    299 		 */
    300 
    301 		if ((unsigned int)offset + leftover < sizeof(buf)) {
    302 			bufstart = bufend - (offset + leftover);
    303 			offset = 0;
    304 		} else {
    305 			offset -= sizeof(buf) - leftover;
    306 		}
    307 
    308 		/*
    309 		 * Move to the proper file offset, and read into buf
    310 		 */
    311 		if ((lseek(fd, offset, SEEK_SET)) == -1) {
    312 			sprintf(Wlog_Error_String,
    313 				"Could not reposition file pointer - lseek(%s, %d, SEEK_SET) failed:  %s\n",
    314 				wfile->w_file, offset, strerror(errno));
    315 			return -1;
    316 		}
    317 
    318 		nbytes = read(fd, bufstart, bufend - bufstart - leftover);
    319 
    320 		if (nbytes == -1) {
    321 			sprintf(Wlog_Error_String,
    322 				"Could not read history file at offset %d - read(%d, %p, %d) failed:  %s\n",
    323 				offset, fd, bufstart,
    324 				(int)(bufend - bufstart - leftover),
    325 				strerror(errno));
    326 			return -1;
    327 		}
    328 
    329 		cp = bufend;
    330 		leftover = 0;
    331 
    332 		while (cp >= bufstart) {
    333 
    334 			/*
    335 			 * If cp-bufstart is not large enough to hold a piece
    336 			 * of record length information, copy remainder to end
    337 			 * of buf and continue reading the file.
    338 			 */
    339 
    340 			if (cp - bufstart < 2) {
    341 				leftover = cp - bufstart;
    342 				memcpy(bufend - leftover, bufstart, leftover);
    343 				break;
    344 			}
    345 
    346 			/*
    347 			 * Extract the record length.  We must do it this way
    348 			 * instead of casting cp to an int because cp might
    349 			 * not be word aligned.
    350 			 */
    351 
    352 			reclen = (*(cp - 2) * 256) + *(cp - 1);
    353 
    354 			/*
    355 			 * If cp-bufstart isn't large enough to hold a
    356 			 * complete record, plus the length information, copy
    357 			 * the leftover bytes to the end of buf and continue
    358 			 * reading.
    359 			 */
    360 
    361 			if (cp - bufstart < reclen + 2) {
    362 				leftover = cp - bufstart;
    363 				memcpy(bufend - leftover, bufstart, leftover);
    364 				break;
    365 			}
    366 
    367 			/*
    368 			 * Adjust cp to point at the start of the record.
    369 			 * Copy the record into wbuf so that it is word
    370 			 * aligned and pass the record to the user supplied
    371 			 * function.
    372 			 */
    373 
    374 			cp -= reclen + 2;
    375 			memcpy(albuf, cp, reclen);
    376 
    377 			wlog_rec_unpack(&wrec, albuf);
    378 
    379 			/*
    380 			 * Call the user supplied function -
    381 			 * stop if instructed to.
    382 			 */
    383 
    384 			if ((rval = (*func) (&wrec, data)) == WLOG_STOP_SCAN) {
    385 				break;
    386 			}
    387 
    388 			recnum++;
    389 
    390 			if (nrecs && recnum >= nrecs)
    391 				break;
    392 		}
    393 	}
    394 
    395 	return 0;
    396 }
    397 
    398 /*
    399  * The following 2 routines are used to pack and unpack the user
    400  * visible wlog_rec structure to/from a character buffer which is
    401  * stored or read from the write logfile.  Any changes to either of
    402  * these routines must be reflected in the other.
    403  */
    404 
    405 static int wlog_rec_pack(struct wlog_rec *wrec, char *buf, int flag)
    406 {
    407 	char *file, *host, *pattern;
    408 	struct wlog_rec_disk *wrecd;
    409 
    410 	wrecd = (struct wlog_rec_disk *)buf;
    411 
    412 	wrecd->w_pid = (uint) wrec->w_pid;
    413 	wrecd->w_offset = (uint) wrec->w_offset;
    414 	wrecd->w_nbytes = (uint) wrec->w_nbytes;
    415 	wrecd->w_oflags = (uint) wrec->w_oflags;
    416 	wrecd->w_done = (uint) wrec->w_done;
    417 	wrecd->w_async = (uint) wrec->w_async;
    418 
    419 	wrecd->w_pathlen = (wrec->w_pathlen > 0) ? (uint) wrec->w_pathlen : 0;
    420 	wrecd->w_hostlen = (wrec->w_hostlen > 0) ? (uint) wrec->w_hostlen : 0;
    421 	wrecd->w_patternlen =
    422 	    (wrec->w_patternlen > 0) ? (uint) wrec->w_patternlen : 0;
    423 
    424 	/*
    425 	 * If flag is true, we should also pack the variable length parts
    426 	 * of the wlog_rec.  By default, we only pack the fixed length
    427 	 * parts.
    428 	 */
    429 
    430 	if (flag) {
    431 		file = buf + sizeof(struct wlog_rec_disk);
    432 		host = file + wrecd->w_pathlen;
    433 		pattern = host + wrecd->w_hostlen;
    434 
    435 		if (wrecd->w_pathlen > 0)
    436 			memcpy(file, wrec->w_path, wrecd->w_pathlen);
    437 
    438 		if (wrecd->w_hostlen > 0)
    439 			memcpy(host, wrec->w_host, wrecd->w_hostlen);
    440 
    441 		if (wrecd->w_patternlen > 0)
    442 			memcpy(pattern, wrec->w_pattern, wrecd->w_patternlen);
    443 
    444 		return (sizeof(struct wlog_rec_disk) +
    445 			wrecd->w_pathlen + wrecd->w_hostlen +
    446 			wrecd->w_patternlen);
    447 	} else {
    448 		return sizeof(struct wlog_rec_disk);
    449 	}
    450 }
    451 
    452 static int wlog_rec_unpack(struct wlog_rec *wrec, char *buf)
    453 {
    454 	char *file, *host, *pattern;
    455 	struct wlog_rec_disk *wrecd;
    456 
    457 	memset((char *)wrec, 0x00, sizeof(struct wlog_rec));
    458 	wrecd = (struct wlog_rec_disk *)buf;
    459 
    460 	wrec->w_pid = wrecd->w_pid;
    461 	wrec->w_offset = wrecd->w_offset;
    462 	wrec->w_nbytes = wrecd->w_nbytes;
    463 	wrec->w_oflags = wrecd->w_oflags;
    464 	wrec->w_hostlen = wrecd->w_hostlen;
    465 	wrec->w_pathlen = wrecd->w_pathlen;
    466 	wrec->w_patternlen = wrecd->w_patternlen;
    467 	wrec->w_done = wrecd->w_done;
    468 	wrec->w_async = wrecd->w_async;
    469 
    470 	if (wrec->w_pathlen > 0) {
    471 		file = buf + sizeof(struct wlog_rec_disk);
    472 		memcpy(wrec->w_path, file, wrec->w_pathlen);
    473 	}
    474 
    475 	if (wrec->w_hostlen > 0) {
    476 		host = buf + sizeof(struct wlog_rec_disk) + wrec->w_pathlen;
    477 		memcpy(wrec->w_host, host, wrec->w_hostlen);
    478 	}
    479 
    480 	if (wrec->w_patternlen > 0) {
    481 		pattern = buf + sizeof(struct wlog_rec_disk) +
    482 		    wrec->w_pathlen + wrec->w_hostlen;
    483 		memcpy(wrec->w_pattern, pattern, wrec->w_patternlen);
    484 	}
    485 
    486 	return 0;
    487 }
    488