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