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