bf.c revision 98121
1/* 2 * Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers. 3 * All rights reserved. 4 * 5 * By using this file, you agree to the terms and conditions set 6 * forth in the LICENSE file which can be found at the top level of 7 * the sendmail distribution. 8 * 9 * Contributed by Exactis.com, Inc. 10 * 11 */ 12 13/* 14** This is in transition. Changed from the original bf_torek.c code 15** to use sm_io function calls directly rather than through stdio 16** translation layer. Will be made a built-in file type of libsm 17** next (once safeopen() linkable from libsm). 18*/ 19 20#include <sm/gen.h> 21SM_RCSID("@(#)$Id: bf.c,v 8.54 2002/04/20 18:03:42 gshapiro Exp $") 22 23#include <sys/types.h> 24#include <sys/stat.h> 25#include <sys/uio.h> 26#include <fcntl.h> 27#include <unistd.h> 28#include <stdlib.h> 29#include <string.h> 30#include <errno.h> 31#include "sendmail.h" 32#include "bf.h" 33 34#include <syslog.h> 35 36/* bf io functions */ 37static ssize_t sm_bfread __P((SM_FILE_T *, char *, size_t)); 38static ssize_t sm_bfwrite __P((SM_FILE_T *, const char *, size_t)); 39static off_t sm_bfseek __P((SM_FILE_T *, off_t, int)); 40static int sm_bfclose __P((SM_FILE_T *)); 41 42static int sm_bfopen __P((SM_FILE_T *, const void *, int, const void *)); 43static int sm_bfsetinfo __P((SM_FILE_T *, int , void *)); 44static int sm_bfgetinfo __P((SM_FILE_T *, int , void *)); 45 46/* 47** Data structure for storing information about each buffered file 48** (Originally in sendmail/bf_torek.h for the curious.) 49*/ 50 51struct bf 52{ 53 bool bf_committed; /* Has this buffered file been committed? */ 54 bool bf_ondisk; /* On disk: committed or buffer overflow */ 55 long bf_flags; 56 int bf_disk_fd; /* If on disk, associated file descriptor */ 57 char *bf_buf; /* Memory buffer */ 58 int bf_bufsize; /* Length of above buffer */ 59 int bf_buffilled; /* Bytes of buffer actually filled */ 60 char *bf_filename; /* Name of buffered file, if ever committed */ 61 MODE_T bf_filemode; /* Mode of buffered file, if ever committed */ 62 off_t bf_offset; /* Currect file offset */ 63 int bf_size; /* Total current size of file */ 64}; 65 66#ifdef BF_STANDALONE 67# define OPEN(fn, omode, cmode, sff) open(fn, omode, cmode) 68#else /* BF_STANDALONE */ 69# define OPEN(fn, omode, cmode, sff) safeopen(fn, omode, cmode, sff) 70#endif /* BF_STANDALONE */ 71 72struct bf_info 73{ 74 char *bi_filename; 75 MODE_T bi_fmode; 76 size_t bi_bsize; 77 long bi_flags; 78}; 79 80/* 81** SM_BFOPEN -- the "base" open function called by sm_io_open() for the 82** internal, file-type-specific info setup. 83** 84** Parameters: 85** fp -- file pointer being filled-in for file being open'd 86** info -- information about file being opened 87** flags -- ignored 88** rpool -- ignored (currently) 89** 90** Returns: 91** Failure: -1 and sets errno 92** Success: 0 (zero) 93*/ 94 95static int 96sm_bfopen(fp, info, flags, rpool) 97 SM_FILE_T *fp; 98 const void *info; 99 int flags; 100 const void *rpool; 101{ 102 char *filename; 103 MODE_T fmode; 104 size_t bsize; 105 long sflags; 106 struct bf *bfp; 107 int l; 108 struct stat st; 109 110 filename = ((struct bf_info *) info)->bi_filename; 111 fmode = ((struct bf_info *) info)->bi_fmode; 112 bsize = ((struct bf_info *) info)->bi_bsize; 113 sflags = ((struct bf_info *) info)->bi_flags; 114 115 /* Sanity checks */ 116 if (*filename == '\0') 117 { 118 /* Empty filename string */ 119 errno = ENOENT; 120 return -1; 121 } 122 if (stat(filename, &st) == 0) 123 { 124 /* File already exists on disk */ 125 errno = EEXIST; 126 return -1; 127 } 128 129 /* Allocate memory */ 130 bfp = (struct bf *) sm_malloc(sizeof(struct bf)); 131 if (bfp == NULL) 132 { 133 errno = ENOMEM; 134 return -1; 135 } 136 137 /* Assign data buffer */ 138 /* A zero bsize is valid, just don't allocate memory */ 139 if (bsize > 0) 140 { 141 bfp->bf_buf = (char *) sm_malloc(bsize); 142 if (bfp->bf_buf == NULL) 143 { 144 bfp->bf_bufsize = 0; 145 sm_free(bfp); 146 errno = ENOMEM; 147 return -1; 148 } 149 } 150 else 151 bfp->bf_buf = NULL; 152 153 /* Nearly home free, just set all the parameters now */ 154 bfp->bf_committed = false; 155 bfp->bf_ondisk = false; 156 bfp->bf_flags = sflags; 157 bfp->bf_bufsize = bsize; 158 bfp->bf_buffilled = 0; 159 l = strlen(filename) + 1; 160 bfp->bf_filename = (char *) sm_malloc(l); 161 if (bfp->bf_filename == NULL) 162 { 163 if (bfp->bf_buf != NULL) 164 sm_free(bfp->bf_buf); 165 sm_free(bfp); 166 errno = ENOMEM; 167 return -1; 168 } 169 (void) sm_strlcpy(bfp->bf_filename, filename, l); 170 bfp->bf_filemode = fmode; 171 bfp->bf_offset = 0; 172 bfp->bf_size = 0; 173 bfp->bf_disk_fd = -1; 174 fp->f_cookie = bfp; 175 176 if (tTd(58, 8)) 177 sm_dprintf("sm_bfopen(%s)\n", filename); 178 179 return 0; 180} 181 182/* 183** BFOPEN -- create a new buffered file 184** 185** Parameters: 186** filename -- the file's name 187** fmode -- what mode the file should be created as 188** bsize -- amount of buffer space to allocate (may be 0) 189** flags -- if running under sendmail, passed directly to safeopen 190** 191** Returns: 192** a SM_FILE_T * which may then be used with stdio functions, 193** or NULL on failure. SM_FILE_T * is opened for writing 194** "SM_IO_WHAT_VECTORS"). 195** 196** Side Effects: 197** none. 198** 199** Sets errno: 200** any value of errno specified by sm_io_setinfo_type() 201** any value of errno specified by sm_io_open() 202** any value of errno specified by sm_io_setinfo() 203*/ 204 205#ifdef __STDC__ 206/* 207** XXX This is a temporary hack since MODE_T on HP-UX 10.x is short. 208** If we use K&R here, the compiler will complain about 209** Inconsistent parameter list declaration 210** due to the change from short to int. 211*/ 212 213SM_FILE_T * 214bfopen(char *filename, MODE_T fmode, size_t bsize, long flags) 215#else /* __STDC__ */ 216SM_FILE_T * 217bfopen(filename, fmode, bsize, flags) 218 char *filename; 219 MODE_T fmode; 220 size_t bsize; 221 long flags; 222#endif /* __STDC__ */ 223{ 224 MODE_T omask; 225 SM_FILE_T SM_IO_SET_TYPE(vector, BF_FILE_TYPE, sm_bfopen, sm_bfclose, 226 sm_bfread, sm_bfwrite, sm_bfseek, sm_bfgetinfo, sm_bfsetinfo, 227 SM_TIME_FOREVER); 228 struct bf_info info; 229 230 /* 231 ** Apply current umask to fmode as it may change by the time 232 ** the file is actually created. fmode becomes the true 233 ** permissions of the file, which OPEN() must obey. 234 */ 235 236 omask = umask(0); 237 fmode &= ~omask; 238 (void) umask(omask); 239 240 SM_IO_INIT_TYPE(vector, BF_FILE_TYPE, sm_bfopen, sm_bfclose, 241 sm_bfread, sm_bfwrite, sm_bfseek, sm_bfgetinfo, sm_bfsetinfo, 242 SM_TIME_FOREVER); 243 info.bi_filename = filename; 244 info.bi_fmode = fmode; 245 info.bi_bsize = bsize; 246 info.bi_flags = flags; 247 248 return sm_io_open(&vector, SM_TIME_DEFAULT, &info, SM_IO_RDWR, NULL); 249} 250 251/* 252** SM_BFGETINFO -- returns info about an open file pointer 253** 254** Parameters: 255** fp -- file pointer to get info about 256** what -- type of info to obtain 257** valp -- thing to return the info in 258*/ 259 260static int 261sm_bfgetinfo(fp, what, valp) 262 SM_FILE_T *fp; 263 int what; 264 void *valp; 265{ 266 struct bf *bfp; 267 268 bfp = (struct bf *) fp->f_cookie; 269 switch (what) 270 { 271 case SM_IO_WHAT_FD: 272 return bfp->bf_disk_fd; 273 case SM_IO_WHAT_SIZE: 274 return bfp->bf_size; 275 default: 276 return -1; 277 } 278} 279 280/* 281** SM_BFCLOSE -- close a buffered file 282** 283** Parameters: 284** fp -- cookie of file to close 285** 286** Returns: 287** 0 to indicate success 288** 289** Side Effects: 290** deletes backing file, sm_frees memory. 291** 292** Sets errno: 293** never. 294*/ 295 296static int 297sm_bfclose(fp) 298 SM_FILE_T *fp; 299{ 300 struct bf *bfp; 301 302 /* Cast cookie back to correct type */ 303 bfp = (struct bf *) fp->f_cookie; 304 305 /* Need to clean up the file */ 306 if (bfp->bf_ondisk && !bfp->bf_committed) 307 unlink(bfp->bf_filename); 308 sm_free(bfp->bf_filename); 309 310 if (bfp->bf_disk_fd != -1) 311 close(bfp->bf_disk_fd); 312 313 /* Need to sm_free the buffer */ 314 if (bfp->bf_bufsize > 0) 315 sm_free(bfp->bf_buf); 316 317 /* Finally, sm_free the structure */ 318 sm_free(bfp); 319 return 0; 320} 321 322/* 323** SM_BFREAD -- read a buffered file 324** 325** Parameters: 326** cookie -- cookie of file to read 327** buf -- buffer to fill 328** nbytes -- how many bytes to read 329** 330** Returns: 331** number of bytes read or -1 indicate failure 332** 333** Side Effects: 334** none. 335** 336*/ 337 338static ssize_t 339sm_bfread(fp, buf, nbytes) 340 SM_FILE_T *fp; 341 char *buf; 342 size_t nbytes; 343{ 344 struct bf *bfp; 345 ssize_t count = 0; /* Number of bytes put in buf so far */ 346 int retval; 347 348 /* Cast cookie back to correct type */ 349 bfp = (struct bf *) fp->f_cookie; 350 351 if (bfp->bf_offset < bfp->bf_buffilled) 352 { 353 /* Need to grab some from buffer */ 354 count = nbytes; 355 if ((bfp->bf_offset + count) > bfp->bf_buffilled) 356 count = bfp->bf_buffilled - bfp->bf_offset; 357 358 memcpy(buf, bfp->bf_buf + bfp->bf_offset, count); 359 } 360 361 if ((bfp->bf_offset + nbytes) > bfp->bf_buffilled) 362 { 363 /* Need to grab some from file */ 364 if (!bfp->bf_ondisk) 365 { 366 /* Oops, the file doesn't exist. EOF. */ 367 if (tTd(58, 8)) 368 sm_dprintf("sm_bfread(%s): to disk\n", 369 bfp->bf_filename); 370 goto finished; 371 } 372 373 /* Catch a read() on an earlier failed write to disk */ 374 if (bfp->bf_disk_fd < 0) 375 { 376 errno = EIO; 377 return -1; 378 } 379 380 if (lseek(bfp->bf_disk_fd, 381 bfp->bf_offset + count, SEEK_SET) < 0) 382 { 383 if ((errno == EINVAL) || (errno == ESPIPE)) 384 { 385 /* 386 ** stdio won't be expecting these 387 ** errnos from read()! Change them 388 ** into something it can understand. 389 */ 390 391 errno = EIO; 392 } 393 return -1; 394 } 395 396 while (count < nbytes) 397 { 398 retval = read(bfp->bf_disk_fd, 399 buf + count, 400 nbytes - count); 401 if (retval < 0) 402 { 403 /* errno is set implicitly by read() */ 404 return -1; 405 } 406 else if (retval == 0) 407 goto finished; 408 else 409 count += retval; 410 } 411 } 412 413finished: 414 bfp->bf_offset += count; 415 return count; 416} 417 418/* 419** SM_BFSEEK -- seek to a position in a buffered file 420** 421** Parameters: 422** fp -- fp of file to seek 423** offset -- position to seek to 424** whence -- how to seek 425** 426** Returns: 427** new file offset or -1 indicate failure 428** 429** Side Effects: 430** none. 431** 432*/ 433 434static off_t 435sm_bfseek(fp, offset, whence) 436 SM_FILE_T *fp; 437 off_t offset; 438 int whence; 439 440{ 441 struct bf *bfp; 442 443 /* Cast cookie back to correct type */ 444 bfp = (struct bf *) fp->f_cookie; 445 446 switch (whence) 447 { 448 case SEEK_SET: 449 bfp->bf_offset = offset; 450 break; 451 452 case SEEK_CUR: 453 bfp->bf_offset += offset; 454 break; 455 456 case SEEK_END: 457 bfp->bf_offset = bfp->bf_size + offset; 458 break; 459 460 default: 461 errno = EINVAL; 462 return -1; 463 } 464 return bfp->bf_offset; 465} 466 467/* 468** SM_BFWRITE -- write to a buffered file 469** 470** Parameters: 471** fp -- fp of file to write 472** buf -- data buffer 473** nbytes -- how many bytes to write 474** 475** Returns: 476** number of bytes written or -1 indicate failure 477** 478** Side Effects: 479** may create backing file if over memory limit for file. 480** 481*/ 482 483static ssize_t 484sm_bfwrite(fp, buf, nbytes) 485 SM_FILE_T *fp; 486 const char *buf; 487 size_t nbytes; 488{ 489 struct bf *bfp; 490 ssize_t count = 0; /* Number of bytes written so far */ 491 int retval; 492 493 /* Cast cookie back to correct type */ 494 bfp = (struct bf *) fp->f_cookie; 495 496 /* If committed, go straight to disk */ 497 if (bfp->bf_committed) 498 { 499 if (lseek(bfp->bf_disk_fd, bfp->bf_offset, SEEK_SET) < 0) 500 { 501 if ((errno == EINVAL) || (errno == ESPIPE)) 502 { 503 /* 504 ** stdio won't be expecting these 505 ** errnos from write()! Change them 506 ** into something it can understand. 507 */ 508 509 errno = EIO; 510 } 511 return -1; 512 } 513 514 count = write(bfp->bf_disk_fd, buf, nbytes); 515 if (count < 0) 516 { 517 /* errno is set implicitly by write() */ 518 return -1; 519 } 520 goto finished; 521 } 522 523 if (bfp->bf_offset < bfp->bf_bufsize) 524 { 525 /* Need to put some in buffer */ 526 count = nbytes; 527 if ((bfp->bf_offset + count) > bfp->bf_bufsize) 528 count = bfp->bf_bufsize - bfp->bf_offset; 529 530 memcpy(bfp->bf_buf + bfp->bf_offset, buf, count); 531 if ((bfp->bf_offset + count) > bfp->bf_buffilled) 532 bfp->bf_buffilled = bfp->bf_offset + count; 533 } 534 535 if ((bfp->bf_offset + nbytes) > bfp->bf_bufsize) 536 { 537 /* Need to put some in file */ 538 if (!bfp->bf_ondisk) 539 { 540 MODE_T omask; 541 542 /* Clear umask as bf_filemode are the true perms */ 543 omask = umask(0); 544 retval = OPEN(bfp->bf_filename, 545 O_RDWR | O_CREAT | O_TRUNC, 546 bfp->bf_filemode, bfp->bf_flags); 547 (void) umask(omask); 548 549 /* Couldn't create file: failure */ 550 if (retval < 0) 551 { 552 /* 553 ** stdio may not be expecting these 554 ** errnos from write()! Change to 555 ** something which it can understand. 556 ** Note that ENOSPC and EDQUOT are saved 557 ** because they are actually valid for 558 ** write(). 559 */ 560 561 if (!(errno == ENOSPC 562#ifdef EDQUOT 563 || errno == EDQUOT 564#endif /* EDQUOT */ 565 )) 566 errno = EIO; 567 568 return -1; 569 } 570 bfp->bf_disk_fd = retval; 571 bfp->bf_ondisk = true; 572 } 573 574 /* Catch a write() on an earlier failed write to disk */ 575 if (bfp->bf_ondisk && bfp->bf_disk_fd < 0) 576 { 577 errno = EIO; 578 return -1; 579 } 580 581 if (lseek(bfp->bf_disk_fd, 582 bfp->bf_offset + count, SEEK_SET) < 0) 583 { 584 if ((errno == EINVAL) || (errno == ESPIPE)) 585 { 586 /* 587 ** stdio won't be expecting these 588 ** errnos from write()! Change them into 589 ** something which it can understand. 590 */ 591 592 errno = EIO; 593 } 594 return -1; 595 } 596 597 while (count < nbytes) 598 { 599 retval = write(bfp->bf_disk_fd, buf + count, 600 nbytes - count); 601 if (retval < 0) 602 { 603 /* errno is set implicitly by write() */ 604 return -1; 605 } 606 else 607 count += retval; 608 } 609 } 610 611finished: 612 bfp->bf_offset += count; 613 if (bfp->bf_offset > bfp->bf_size) 614 bfp->bf_size = bfp->bf_offset; 615 return count; 616} 617 618/* 619** BFREWIND -- rewinds the SM_FILE_T * 620** 621** Parameters: 622** fp -- SM_FILE_T * to rewind 623** 624** Returns: 625** 0 on success, -1 on error 626** 627** Side Effects: 628** rewinds the SM_FILE_T * and puts it into read mode. Normally 629** one would bfopen() a file, write to it, then bfrewind() and 630** fread(). If fp is not a buffered file, this is equivalent to 631** rewind(). 632** 633** Sets errno: 634** any value of errno specified by sm_io_rewind() 635*/ 636 637int 638bfrewind(fp) 639 SM_FILE_T *fp; 640{ 641 (void) sm_io_flush(fp, SM_TIME_DEFAULT); 642 sm_io_clearerr(fp); /* quicker just to do it */ 643 return sm_io_seek(fp, SM_TIME_DEFAULT, 0, SM_IO_SEEK_SET); 644} 645 646/* 647** SM_BFCOMMIT -- "commits" the buffered file 648** 649** Parameters: 650** fp -- SM_FILE_T * to commit to disk 651** 652** Returns: 653** 0 on success, -1 on error 654** 655** Side Effects: 656** Forces the given SM_FILE_T * to be written to disk if it is not 657** already, and ensures that it will be kept after closing. If 658** fp is not a buffered file, this is a no-op. 659** 660** Sets errno: 661** any value of errno specified by open() 662** any value of errno specified by write() 663** any value of errno specified by lseek() 664*/ 665 666static int 667sm_bfcommit(fp) 668 SM_FILE_T *fp; 669{ 670 struct bf *bfp; 671 int retval; 672 int byteswritten; 673 674 /* Get associated bf structure */ 675 bfp = (struct bf *) fp->f_cookie; 676 677 /* If already committed, noop */ 678 if (bfp->bf_committed) 679 return 0; 680 681 /* Do we need to open a file? */ 682 if (!bfp->bf_ondisk) 683 { 684 MODE_T omask; 685 struct stat st; 686 687 if (tTd(58, 8)) 688 { 689 sm_dprintf("bfcommit(%s): to disk\n", bfp->bf_filename); 690 if (tTd(58, 32)) 691 sm_dprintf("bfcommit(): filemode %o flags %ld\n", 692 bfp->bf_filemode, bfp->bf_flags); 693 } 694 695 if (stat(bfp->bf_filename, &st) == 0) 696 { 697 errno = EEXIST; 698 return -1; 699 } 700 701 /* Clear umask as bf_filemode are the true perms */ 702 omask = umask(0); 703 retval = OPEN(bfp->bf_filename, O_RDWR | O_CREAT | O_TRUNC, 704 bfp->bf_filemode, bfp->bf_flags); 705 (void) umask(omask); 706 707 /* Couldn't create file: failure */ 708 if (retval < 0) 709 { 710 /* errno is set implicitly by open() */ 711 return -1; 712 } 713 714 bfp->bf_disk_fd = retval; 715 bfp->bf_ondisk = true; 716 } 717 718 /* Write out the contents of our buffer, if we have any */ 719 if (bfp->bf_buffilled > 0) 720 { 721 byteswritten = 0; 722 723 if (lseek(bfp->bf_disk_fd, 0, SEEK_SET) < 0) 724 { 725 /* errno is set implicitly by lseek() */ 726 return -1; 727 } 728 729 while (byteswritten < bfp->bf_buffilled) 730 { 731 retval = write(bfp->bf_disk_fd, 732 bfp->bf_buf + byteswritten, 733 bfp->bf_buffilled - byteswritten); 734 if (retval < 0) 735 { 736 /* errno is set implicitly by write() */ 737 return -1; 738 } 739 else 740 byteswritten += retval; 741 } 742 } 743 bfp->bf_committed = true; 744 745 /* Invalidate buf; all goes to file now */ 746 bfp->bf_buffilled = 0; 747 if (bfp->bf_bufsize > 0) 748 { 749 /* Don't need buffer anymore; free it */ 750 bfp->bf_bufsize = 0; 751 sm_free(bfp->bf_buf); 752 } 753 return 0; 754} 755 756/* 757** SM_BFTRUNCATE -- rewinds and truncates the SM_FILE_T * 758** 759** Parameters: 760** fp -- SM_FILE_T * to truncate 761** 762** Returns: 763** 0 on success, -1 on error 764** 765** Side Effects: 766** rewinds the SM_FILE_T *, truncates it to zero length, and puts 767** it into write mode. 768** 769** Sets errno: 770** any value of errno specified by fseek() 771** any value of errno specified by ftruncate() 772*/ 773 774static int 775sm_bftruncate(fp) 776 SM_FILE_T *fp; 777{ 778 struct bf *bfp; 779 780 if (bfrewind(fp) < 0) 781 return -1; 782 783 /* Get bf structure */ 784 bfp = (struct bf *) fp->f_cookie; 785 bfp->bf_buffilled = 0; 786 bfp->bf_size = 0; 787 788 /* Need to zero the buffer */ 789 if (bfp->bf_bufsize > 0) 790 memset(bfp->bf_buf, '\0', bfp->bf_bufsize); 791 if (bfp->bf_ondisk) 792 { 793#if NOFTRUNCATE 794 /* XXX: Not much we can do except rewind it */ 795 errno = EINVAL; 796 return -1; 797#else /* NOFTRUNCATE */ 798 return ftruncate(bfp->bf_disk_fd, 0); 799#endif /* NOFTRUNCATE */ 800 } 801 return 0; 802} 803 804/* 805** SM_BFSETINFO -- set/change info for an open file pointer 806** 807** Parameters: 808** fp -- file pointer to get info about 809** what -- type of info to set/change 810** valp -- thing to set/change the info to 811** 812*/ 813 814static int 815sm_bfsetinfo(fp, what, valp) 816 SM_FILE_T *fp; 817 int what; 818 void *valp; 819{ 820 struct bf *bfp; 821 int bsize; 822 823 /* Get bf structure */ 824 bfp = (struct bf *) fp->f_cookie; 825 switch (what) 826 { 827 case SM_BF_SETBUFSIZE: 828 bsize = *((int *) valp); 829 bfp->bf_bufsize = bsize; 830 831 /* A zero bsize is valid, just don't allocate memory */ 832 if (bsize > 0) 833 { 834 bfp->bf_buf = (char *) sm_malloc(bsize); 835 if (bfp->bf_buf == NULL) 836 { 837 bfp->bf_bufsize = 0; 838 errno = ENOMEM; 839 return -1; 840 } 841 } 842 else 843 bfp->bf_buf = NULL; 844 return 0; 845 case SM_BF_COMMIT: 846 return sm_bfcommit(fp); 847 case SM_BF_TRUNCATE: 848 return sm_bftruncate(fp); 849 case SM_BF_TEST: 850 return 1; /* always */ 851 default: 852 errno = EINVAL; 853 return -1; 854 } 855} 856