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
90char Wlog_Error_String[2048];
91
92#if __STDC__
93static int wlog_rec_pack(struct wlog_rec *wrec, char *buf, int flag);
94static int wlog_rec_unpack(struct wlog_rec *wrec, char *buf);
95#else
96static int wlog_rec_pack();
97static 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
114int 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		snprintf(Wlog_Error_String, sizeof(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		snprintf(Wlog_Error_String, sizeof(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
160int 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
193int 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			snprintf(Wlog_Error_String, sizeof(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				snprintf(Wlog_Error_String, sizeof(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			snprintf(Wlog_Error_String, sizeof(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				snprintf(Wlog_Error_String, sizeof(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
262int 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		snprintf(Wlog_Error_String, sizeof(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		snprintf(Wlog_Error_String, sizeof(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			snprintf(Wlog_Error_String, sizeof(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			snprintf(Wlog_Error_String, sizeof(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
405static 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
452static 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