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 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 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 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 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 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 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