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 #define BSIZE BBSIZE
81 #endif
82 #endif
83 
84 #ifndef PATH_MAX
85 #define PATH_MAX          255
86 /*#define PATH_MAX pathconf("/", _PC_PATH_MAX)*/
87 #endif
88 
89 char Wlog_Error_String[256];
90 
91 #if __STDC__
92 static int wlog_rec_pack(struct wlog_rec *wrec, char *buf, int flag);
93 static int wlog_rec_unpack(struct wlog_rec *wrec, char *buf);
94 #else
95 static int wlog_rec_pack();
96 static int wlog_rec_unpack();
97 #endif
98 
99 /*
100  * Initialize a write logfile.  wfile is a wlog_file structure that has
101  * the w_file field filled in.  The rest of the information in the
102  * structure is initialized by the routine.
103  *
104  * The trunc flag is used to indicate whether or not the logfile should
105  * be truncated if it currently exists.  If it is non-zero, the file will
106  * be truncated, otherwise it will be appended to.
107  *
108  * The mode argument is the [absolute] mode which the file will be
109  * given if it does not exist.  This mode is not affected by your process
110  * umask.
111  */
112 
wlog_open(struct wlog_file * wfile,int trunc,int mode)113 int wlog_open(struct wlog_file *wfile, int trunc, int mode)
114 {
115 	int omask, oflags;
116 
117 	if (trunc)
118 		trunc = O_TRUNC;
119 
120 	omask = umask(0);
121 
122 	/*
123 	 * Open 1 file descriptor as O_APPEND
124 	 */
125 
126 	oflags = O_WRONLY | O_APPEND | O_CREAT | trunc;
127 	wfile->w_afd = open(wfile->w_file, oflags, mode);
128 	umask(omask);
129 
130 	if (wfile->w_afd == -1) {
131 		sprintf(Wlog_Error_String,
132 			"Could not open write_log - open(%s, %#o, %#o) failed:  %s\n",
133 			wfile->w_file, oflags, mode, strerror(errno));
134 		return -1;
135 	}
136 
137 	/*
138 	 * Open the next fd as a random access descriptor
139 	 */
140 
141 	oflags = O_RDWR;
142 	if ((wfile->w_rfd = open(wfile->w_file, oflags)) == -1) {
143 		sprintf(Wlog_Error_String,
144 			"Could not open write log - open(%s, %#o) failed:  %s\n",
145 			wfile->w_file, oflags, strerror(errno));
146 		close(wfile->w_afd);
147 		wfile->w_afd = -1;
148 		return -1;
149 	}
150 
151 	return 0;
152 }
153 
154 /*
155  * Release all resources associated with a wlog_file structure allocated
156  * with the wlog_open() call.
157  */
158 
wlog_close(struct wlog_file * wfile)159 int wlog_close(struct wlog_file *wfile)
160 {
161 	close(wfile->w_afd);
162 	close(wfile->w_rfd);
163 	return 0;
164 }
165 
166 /*
167  * Write a wlog_rec structure to a write logfile.  Offset is used to
168  * control where the record will be written.  If offset is < 0, the
169  * record will be appended to the end of the logfile.  Otherwise, the
170  * record which exists at the indicated offset will be overlayed.  This
171  * is so that we can record writes which are outstanding (with the w_done
172  * bit in wrec cleared), but not completed, and then later update the
173  * logfile when the write request completes (as with async io).  When
174  * offset is >= 0, only the fixed length portion of the record is
175  * rewritten.  See text in write_log.h for details on the format of an
176  * on-disk record.
177  *
178  * The return value of the function is the byte offset in the logfile
179  * where the record begins.
180  *
181  * Note:  It is the callers responsibility to make sure that the offset
182  * parameter 'points' to a valid record location when a record is to be
183  * overlayed.  This is guarenteed by saving the return value of a previous
184  * call to wlog_record_write() which wrote the record to be overlayed.
185  *
186  * Note2:  The on-disk version of the wlog_rec is MUCH different than
187  * the user version.  Don't expect to od the logfile and see data formatted
188  * as it is in the wlog_rec structure.  Considerable data packing takes
189  * place before the record is written.
190  */
191 
wlog_record_write(struct wlog_file * wfile,struct wlog_rec * wrec,long offset)192 int wlog_record_write(struct wlog_file *wfile, struct wlog_rec *wrec,
193 			long offset)
194 {
195 	int reclen;
196 	char wbuf[WLOG_REC_MAX_SIZE + 2];
197 
198 	/*
199 	 * If offset is -1, we append the record at the end of file
200 	 *
201 	 * Otherwise, we overlay wrec at the file offset indicated and assume
202 	 * that the caller passed us the correct offset.  We do not record the
203 	 * fname in this case.
204 	 */
205 
206 	reclen = wlog_rec_pack(wrec, wbuf, (offset < 0));
207 
208 	if (offset < 0) {
209 		/*
210 		 * Since we're writing a complete new record, we must also tack
211 		 * its length onto the end so that wlog_scan_backward() will work.
212 		 * Length is asumed to fit into 2 bytes.
213 		 */
214 
215 		wbuf[reclen] = reclen / 256;
216 		wbuf[reclen + 1] = reclen % 256;
217 		reclen += 2;
218 
219 		if (write(wfile->w_afd, wbuf, reclen) == -1) {
220 			sprintf(Wlog_Error_String,
221 				"Could not write log - write(%s, %s, %d) failed:  %s\n",
222 				wfile->w_file, wbuf, reclen, strerror(errno));
223 			return -1;
224 		} else {
225 			offset = lseek(wfile->w_afd, 0, SEEK_CUR) - reclen;
226 			if (offset == -1) {
227 				sprintf(Wlog_Error_String,
228 					"Could not reposition file pointer - lseek(%s, 0, SEEK_CUR) failed:  %s\n",
229 					wfile->w_file, strerror(errno));
230 				return -1;
231 			}
232 		}
233 	} else {
234 		if ((lseek(wfile->w_rfd, offset, SEEK_SET)) == -1) {
235 			sprintf(Wlog_Error_String,
236 				"Could not reposition file pointer - lseek(%s, %ld, SEEK_SET) failed:  %s\n",
237 				wfile->w_file, offset, strerror(errno));
238 			return -1;
239 		} else {
240 			if ((write(wfile->w_rfd, wbuf, reclen)) == -1) {
241 				sprintf(Wlog_Error_String,
242 					"Could not write log - write(%s, %s, %d) failed:  %s\n",
243 					wfile->w_file, wbuf, reclen,
244 					strerror(errno));
245 				return -1;
246 			}
247 		}
248 	}
249 
250 	return offset;
251 }
252 
253 /*
254  * Function to scan a logfile in reverse order.  Wfile is a valid
255  * wlog_file structure initialized by wlog_open().  nrecs is the number
256  * of records to scan (all records are scanned if nrecs is 0).  func is
257  * a user-supplied function to call for each record found.  The function
258  * will be passed a single parameter - a wlog_rec structure .
259  */
260 
wlog_scan_backward(struct wlog_file * wfile,int nrecs,int (* func)(),long data)261 int wlog_scan_backward(struct wlog_file *wfile, int nrecs,
262 			int (*func)(), long data)
263 {
264 	int fd, leftover, nbytes, offset, recnum, reclen, rval;
265 	char buf[BSIZE * 32], *bufend, *cp, *bufstart;
266 	char albuf[WLOG_REC_MAX_SIZE];
267 	struct wlog_rec wrec;
268 
269 	fd = wfile->w_rfd;
270 
271 	/*
272 	 * Move to EOF.  offset will always hold the current file offset
273 	 */
274 
275 	if ((lseek(fd, 0, SEEK_END)) == -1) {
276 		sprintf(Wlog_Error_String,
277 			"Could not reposition file pointer - lseek(%s, 0, SEEK_END) failed:  %s\n",
278 			wfile->w_file, strerror(errno));
279 		return -1;
280 	}
281 	offset = lseek(fd, 0, SEEK_CUR);
282 	if ((offset == -1)) {
283 		sprintf(Wlog_Error_String,
284 			"Could not reposition file pointer - lseek(%s, 0, SEEK_CUR) failed:  %s\n",
285 			wfile->w_file, strerror(errno));
286 		return -1;
287 	}
288 
289 	bufend = buf + sizeof(buf);
290 	bufstart = buf;
291 
292 	recnum = 0;
293 	leftover = 0;
294 	while ((!nrecs || recnum < nrecs) && offset > 0) {
295 		/*
296 		 * Check for beginning of file - if there aren't enough bytes
297 		 * remaining to fill buf, adjust bufstart.
298 		 */
299 
300 		if ((unsigned int)offset + leftover < sizeof(buf)) {
301 			bufstart = bufend - (offset + leftover);
302 			offset = 0;
303 		} else {
304 			offset -= sizeof(buf) - leftover;
305 		}
306 
307 		/*
308 		 * Move to the proper file offset, and read into buf
309 		 */
310 		if ((lseek(fd, offset, SEEK_SET)) == -1) {
311 			sprintf(Wlog_Error_String,
312 				"Could not reposition file pointer - lseek(%s, %d, SEEK_SET) failed:  %s\n",
313 				wfile->w_file, offset, strerror(errno));
314 			return -1;
315 		}
316 
317 		nbytes = read(fd, bufstart, bufend - bufstart - leftover);
318 
319 		if (nbytes == -1) {
320 			sprintf(Wlog_Error_String,
321 				"Could not read history file at offset %d - read(%d, %p, %d) failed:  %s\n",
322 				offset, fd, bufstart,
323 				(int)(bufend - bufstart - leftover),
324 				strerror(errno));
325 			return -1;
326 		}
327 
328 		cp = bufend;
329 		leftover = 0;
330 
331 		while (cp >= bufstart) {
332 
333 			/*
334 			 * If cp-bufstart is not large enough to hold a piece
335 			 * of record length information, copy remainder to end
336 			 * of buf and continue reading the file.
337 			 */
338 
339 			if (cp - bufstart < 2) {
340 				leftover = cp - bufstart;
341 				memcpy(bufend - leftover, bufstart, leftover);
342 				break;
343 			}
344 
345 			/*
346 			 * Extract the record length.  We must do it this way
347 			 * instead of casting cp to an int because cp might
348 			 * not be word aligned.
349 			 */
350 
351 			reclen = (*(cp - 2) * 256) + *(cp - 1);
352 
353 			/*
354 			 * If cp-bufstart isn't large enough to hold a
355 			 * complete record, plus the length information, copy
356 			 * the leftover bytes to the end of buf and continue
357 			 * reading.
358 			 */
359 
360 			if (cp - bufstart < reclen + 2) {
361 				leftover = cp - bufstart;
362 				memcpy(bufend - leftover, bufstart, leftover);
363 				break;
364 			}
365 
366 			/*
367 			 * Adjust cp to point at the start of the record.
368 			 * Copy the record into wbuf so that it is word
369 			 * aligned and pass the record to the user supplied
370 			 * function.
371 			 */
372 
373 			cp -= reclen + 2;
374 			memcpy(albuf, cp, reclen);
375 
376 			wlog_rec_unpack(&wrec, albuf);
377 
378 			/*
379 			 * Call the user supplied function -
380 			 * stop if instructed to.
381 			 */
382 
383 			if ((rval = (*func) (&wrec, data)) == WLOG_STOP_SCAN) {
384 				break;
385 			}
386 
387 			recnum++;
388 
389 			if (nrecs && recnum >= nrecs)
390 				break;
391 		}
392 	}
393 
394 	return 0;
395 }
396 
397 /*
398  * The following 2 routines are used to pack and unpack the user
399  * visible wlog_rec structure to/from a character buffer which is
400  * stored or read from the write logfile.  Any changes to either of
401  * these routines must be reflected in the other.
402  */
403 
wlog_rec_pack(struct wlog_rec * wrec,char * buf,int flag)404 static int wlog_rec_pack(struct wlog_rec *wrec, char *buf, int flag)
405 {
406 	char *file, *host, *pattern;
407 	struct wlog_rec_disk *wrecd;
408 
409 	wrecd = (struct wlog_rec_disk *)buf;
410 
411 	wrecd->w_pid = (uint) wrec->w_pid;
412 	wrecd->w_offset = (uint) wrec->w_offset;
413 	wrecd->w_nbytes = (uint) wrec->w_nbytes;
414 	wrecd->w_oflags = (uint) wrec->w_oflags;
415 	wrecd->w_done = (uint) wrec->w_done;
416 	wrecd->w_async = (uint) wrec->w_async;
417 
418 	wrecd->w_pathlen = (wrec->w_pathlen > 0) ? (uint) wrec->w_pathlen : 0;
419 	wrecd->w_hostlen = (wrec->w_hostlen > 0) ? (uint) wrec->w_hostlen : 0;
420 	wrecd->w_patternlen =
421 	    (wrec->w_patternlen > 0) ? (uint) wrec->w_patternlen : 0;
422 
423 	/*
424 	 * If flag is true, we should also pack the variable length parts
425 	 * of the wlog_rec.  By default, we only pack the fixed length
426 	 * parts.
427 	 */
428 
429 	if (flag) {
430 		file = buf + sizeof(struct wlog_rec_disk);
431 		host = file + wrecd->w_pathlen;
432 		pattern = host + wrecd->w_hostlen;
433 
434 		if (wrecd->w_pathlen > 0)
435 			memcpy(file, wrec->w_path, wrecd->w_pathlen);
436 
437 		if (wrecd->w_hostlen > 0)
438 			memcpy(host, wrec->w_host, wrecd->w_hostlen);
439 
440 		if (wrecd->w_patternlen > 0)
441 			memcpy(pattern, wrec->w_pattern, wrecd->w_patternlen);
442 
443 		return (sizeof(struct wlog_rec_disk) +
444 			wrecd->w_pathlen + wrecd->w_hostlen +
445 			wrecd->w_patternlen);
446 	} else {
447 		return sizeof(struct wlog_rec_disk);
448 	}
449 }
450 
wlog_rec_unpack(struct wlog_rec * wrec,char * buf)451 static int wlog_rec_unpack(struct wlog_rec *wrec, char *buf)
452 {
453 	char *file, *host, *pattern;
454 	struct wlog_rec_disk *wrecd;
455 
456 	memset((char *)wrec, 0x00, sizeof(struct wlog_rec));
457 	wrecd = (struct wlog_rec_disk *)buf;
458 
459 	wrec->w_pid = wrecd->w_pid;
460 	wrec->w_offset = wrecd->w_offset;
461 	wrec->w_nbytes = wrecd->w_nbytes;
462 	wrec->w_oflags = wrecd->w_oflags;
463 	wrec->w_hostlen = wrecd->w_hostlen;
464 	wrec->w_pathlen = wrecd->w_pathlen;
465 	wrec->w_patternlen = wrecd->w_patternlen;
466 	wrec->w_done = wrecd->w_done;
467 	wrec->w_async = wrecd->w_async;
468 
469 	if (wrec->w_pathlen > 0) {
470 		file = buf + sizeof(struct wlog_rec_disk);
471 		memcpy(wrec->w_path, file, wrec->w_pathlen);
472 	}
473 
474 	if (wrec->w_hostlen > 0) {
475 		host = buf + sizeof(struct wlog_rec_disk) + wrec->w_pathlen;
476 		memcpy(wrec->w_host, host, wrec->w_hostlen);
477 	}
478 
479 	if (wrec->w_patternlen > 0) {
480 		pattern = buf + sizeof(struct wlog_rec_disk) +
481 		    wrec->w_pathlen + wrec->w_hostlen;
482 		memcpy(wrec->w_pattern, pattern, wrec->w_patternlen);
483 	}
484 
485 	return 0;
486 }
487