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