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