|
|
1.1 ! root 1: static char Sccsid[] = "ay.c @(#)ay.c 1.1 10/1/82 Berkeley "; ! 2: # ! 3: ! 4: /* ! 5: * APL Buffered I/O routines ! 6: * ! 7: * ! 8: * The following routines perform all of the I/O required by APL. ! 9: * They are designed to maximize buffer utilization while maintaining ! 10: * reliable operation. For a machine such as the PDP-11, the number ! 11: * of buffers, defined in apl.h, should be around 4 for best results. ! 12: * For a machine such as the VAX 11/780, a larger number may be desirable ! 13: * for better results. ! 14: * ! 15: */ ! 16: ! 17: #include "apl.h" /* Header definitions */ ! 18: #ifdef NBUF /* Don't do any of this unless buffering ! 19: * was requested by defining NBUF ! 20: */ ! 21: ! 22: #define realfd(x) files[x].fd_dup ! 23: ! 24: openf(fname, mode) ! 25: char *fname; ! 26: { ! 27: ! 28: int fd; ! 29: ! 30: ! 31: /* The first order of business is to open the ! 32: * file. If this fails, return -1. ! 33: */ ! 34: ! 35: if ((fd=open(fname,mode)) < 0) return(fd); ! 36: ! 37: ! 38: /* Now, perform all of the work necessary for setting ! 39: * up the file tables. This code is shared by "openf" ! 40: * and "creatf" since both open files. ! 41: */ ! 42: ! 43: openup(fd); ! 44: return(fd); ! 45: } ! 46: ! 47: creatf(fname, mode) ! 48: char *fname; ! 49: { ! 50: ! 51: int fd; ! 52: ! 53: ! 54: /* The first order of business is to create the ! 55: * file. If this fails, return -1. ! 56: */ ! 57: ! 58: if ((fd=creat(fname,mode)) < 0) return(fd); ! 59: ! 60: ! 61: /* Now, perform all of the work necessary for setting ! 62: * up the file tables. This code is shared by "openf" ! 63: * and "creatf" since both open files. ! 64: */ ! 65: ! 66: openup(fd); ! 67: return(fd); ! 68: } ! 69: ! 70: openup(fd) ! 71: { ! 72: struct stat stat_buf; ! 73: register struct fds *p, *q; ! 74: register j; ! 75: ! 76: ! 77: /* Try to perform an "fstat" call on the file. This ! 78: * information is required for all file descriptors ! 79: * to prevent reading from a file while data destined ! 80: * for that file is still buffered for output. If for ! 81: * some reason the "fstat" fails, arbitrarily set ! 82: * the major/minor number and inode number to zero. ! 83: */ ! 84: ! 85: if (fstat(fd, &stat_buf) < 0){ ! 86: write(2, "Can't \"stat\" new fd\n", 20); ! 87: stat_buf.st_dev = 0; ! 88: stat_buf.st_ino = 0; ! 89: } ! 90: ! 91: ! 92: /* Copy the information into the "files" structure, which ! 93: * maintains such data on all open files. ! 94: */ ! 95: ! 96: p = &files[fd]; ! 97: p->fd_dev = stat_buf.st_dev; /* Major/minor device numbers */ ! 98: p->fd_ind = stat_buf.st_ino; /* Inode number */ ! 99: p->fd_open = 1; /* File is open */ ! 100: p->fd_buf = -1; /* No buffer is currently assigned */ ! 101: p->fd_dup = fd; /* All it duplicates is itself */ ! 102: p->fd_lastop = 0; /* Lastop was read (default) */ ! 103: p->fd_pipe = !!lseek(fd, 0L, 1); /* Seek will fail if a pipe */ ! 104: ! 105: ! 106: /* Determine if the new fd is unique or not. If not, ! 107: * make sure any matching fd's know that they no longer ! 108: * are either. ! 109: */ ! 110: ! 111: p->fd_uniq = 1; /* Assume uniqueness */ ! 112: for(j=0; j<NFDS; j++){ ! 113: if (!files[j].fd_open) continue; /* File not open */ ! 114: ! 115: q = &files[realfd(j)]; ! 116: if (p->fd_ind == q->fd_ind && p->fd_dev == q->fd_dev) ! 117: p->fd_uniq = q->fd_uniq = 0; ! 118: } ! 119: check(); /*DEBUG*/ ! 120: } ! 121: ! 122: dupf(fd) ! 123: { ! 124: ! 125: int fd2; ! 126: register struct fds *p, *q; ! 127: ! 128: ! 129: /* Duplicating a file descriptor does not require an "fstat" ! 130: * on the new file descriptor, because its device and inode ! 131: * are already recorded. However, duplicate file descriptors ! 132: * complicate I/O because they MUST be linked to the same ! 133: * buffer. ! 134: */ ! 135: ! 136: if ((fd2=dup(fd)) < 0) return(fd2); /* Get new fd */ ! 137: ! 138: /* Copy all of the information into the "files" for the new ! 139: * file descriptor. The uniqueness of the original fd is copied ! 140: * to the new fd -- they both represent the same thing. ! 141: */ ! 142: ! 143: p = &files[fd]; /* Point to appropriate places */ ! 144: q = &files[fd2]; ! 145: ! 146: q->fd_dev = p->fd_dev; /* Major/minor device number */ ! 147: q->fd_ind = p->fd_ind; /* Inode number */ ! 148: q->fd_buf = -1; /* Buffer is assigned to princ. fd */ ! 149: q->fd_uniq = p->fd_uniq; /* Uniqueness of dev/inode */ ! 150: q->fd_dup = p->fd_dup; /* Point new entry to old one */ ! 151: q->fd_open = 1; /* File is now open */ ! 152: q->fd_lastop = p->fd_lastop; /* Last operation is the same */ ! 153: ! 154: return(fd2); /* Return new fd */ ! 155: } ! 156: ! 157: closef(fd) ! 158: { ! 159: ! 160: register j; ! 161: register struct fds *p, *q; ! 162: int fd2, count; ! 163: ! 164: /* Closing a file is easier than opening it, but some precautions ! 165: * are necessary. For instance, if a "dup" was performed to ! 166: * obtain some new file descriptor and the original file is ! 167: * now being closed, the duplicated file must retain all ! 168: * pertinent information. Thus, the first order of business ! 169: * is to scan the list of file descriptors for duplicates. ! 170: */ ! 171: ! 172: p = &files[fd]; /* Entry for dying fd */ ! 173: ! 174: count = 0; /* Number of remaining dups */ ! 175: fd2 = -1; /* New fd reference for dups */ ! 176: ! 177: if (p->fd_dup == fd) for (j=0; j<NFDS; j++){ ! 178: if (j == fd) continue; /* Don't look at fd */ ! 179: q = &files[j]; ! 180: if (!q->fd_open) continue; /* Not open */ ! 181: if (q->fd_dup != fd) continue; ! 182: if (fd2 == -1){ ! 183: fd2 = j; /* New reference */ ! 184: q->fd_buf = p->fd_buf; /* Vital data */ ! 185: q->fd_open = 1; ! 186: q->fd_lastop = p->fd_lastop; ! 187: } ! 188: q->fd_dup = fd2; ! 189: count++; ! 190: } ! 191: ! 192: ! 193: /* Flush and release the buffer associated with this fd. */ ! 194: ! 195: newbuf(files[realfd(fd)].fd_buf, -1); ! 196: ! 197: ! 198: /* Mark the entry in the file descriptor table as "closed" ! 199: * and check the uniqueness of any remaining fd's pointing to ! 200: * the same area. Be sure the buffer is released. ! 201: */ ! 202: ! 203: p->fd_open = 0; ! 204: p->fd_dup = fd; ! 205: p->fd_buf = -1; ! 206: ! 207: for(j=0; j<NFDS; j++){ ! 208: if (j == fd) continue; /* Skip the same fd */ ! 209: q = &files[fd]; ! 210: if (!q->fd_open) continue; /* Skip closed files */ ! 211: if (p->fd_dev == q->fd_dev && p->fd_ind == q->fd_ind) break; ! 212: } ! 213: ! 214: if (j<NFDS) q->fd_uniq = 1; /* Assume new fd is unique */ ! 215: while(++j < NFDS){ /* Now check to be sure */ ! 216: p = &files[j]; ! 217: if (p->fd_dev == q->fd_dev && p->fd_ind == q->fd_ind) ! 218: p->fd_uniq = q->fd_uniq = 0; ! 219: } ! 220: ! 221: ! 222: /* Actually perform the close of the fd, and return the status ! 223: * of that system call. ! 224: */ ! 225: ! 226: return(close(fd)); ! 227: } ! 228: ! 229: ! 230: initbuf() ! 231: { ! 232: ! 233: /* Initialize buffer structure, then use "openup" to set up ! 234: * file descriptors 0, and 1, which should already be ! 235: * open from parent process. If 0 and 1 have the same inode ! 236: * and major/minor number they will be considered to be ! 237: * duplicates. Fils descriptor 2 will be closed and set to ! 238: * duplicate file descriptor 1. ! 239: */ ! 240: ! 241: register j; ! 242: register struct fds *p, *q; ! 243: ! 244: ! 245: /* Initialize file descriptor table */ ! 246: ! 247: for(j=0; j<NFDS; j++){ ! 248: files[j].fd_open = 0; /* Closed */ ! 249: files[j].fd_dup = j; /* Dup = fd */ ! 250: files[j].fd_buf = -1; /* No buffer */ ! 251: files[j].fd_pipe = 0; /* Not pipe */ ! 252: } ! 253: ! 254: ! 255: /* Initialize buffer table */ ! 256: ! 257: for(j=0; j<NBUF; j++) iobuf[j].b_fd = -1; /* Available */ ! 258: ! 259: ! 260: /* "Open" devices 0, and 1 */ ! 261: ! 262: openup(0); ! 263: openup(1); ! 264: p = &files[0]; ! 265: q = &files[1]; ! 266: if (p->fd_ind == q->fd_ind && p->fd_dev == q->fd_dev){ ! 267: q->fd_dup = 0; ! 268: p->fd_uniq = q->fd_uniq = 1; ! 269: } ! 270: ! 271: ! 272: /* File descriptor 2 duplicates fd 1 */ ! 273: ! 274: close(2); ! 275: dupf(1); ! 276: } ! 277: ! 278: newbuf(bufnum, fd) ! 279: { ! 280: ! 281: register struct iobuf *bp; ! 282: register struct fds *fp; ! 283: register j; ! 284: static int bufcnt = 0; ! 285: ! 286: ! 287: /* The two arguments for this routine specify a buffer to ! 288: * be operated upon and a file descriptor. There are three ! 289: * legal cases: ! 290: * ! 291: * bufnum < 0, fd >= 0 -- assign new buffer to file descriptor fd ! 292: * bufnum >= 0, fd >= 0 -- assign buffer "bufnum" to fd ! 293: * bufnum >= 0, fd < 0 -- de-assign buffer "bufnum" ! 294: */ ! 295: ! 296: if ((bufnum < 0 && fd < 0) || bufnum >= NBUF || fd >= NFDS) ! 297: return(-1); /* Invalid args */ ! 298: ! 299: fd = (fd < 0) ? fd : realfd(fd); /* Handle dups */ ! 300: ! 301: ! 302: /* Step one -- if a buffer was specified, flush it. If the ! 303: * last operation was a write, then write out the rest of ! 304: * the buffer. If the last operation was a read, then perform ! 305: * a seek backwards on the corresponding fd to reposition the ! 306: * next read. ! 307: */ ! 308: ! 309: if (bufnum >= 0){ ! 310: bp = &iobuf[bufnum]; ! 311: if (bp->b_len && (bp->b_fd >= 0)) ! 312: if (files[bp->b_fd].fd_lastop) ! 313: write(bp->b_fd, bp->b_buf, bp->b_len); ! 314: else ! 315: lseek(bp->b_fd, (long)-bp->b_len, 1); ! 316: ! 317: bp->b_len = 0; /* Reset length */ ! 318: bp->b_next = 0; /* Reset next position */ ! 319: ! 320: ! 321: /* Step one point five -- if a file descriptor was ! 322: * specified, check to insure that it is open. Then, ! 323: * reassign the buffer to that fd. ! 324: */ ! 325: ! 326: if(bp->b_fd >= 0) /* Drop old assignment */ ! 327: files[bp->b_fd].fd_buf = -1; ! 328: ! 329: bp->b_fd = fd; /* Give buffer new fd */ ! 330: if (fd < 0) return(0); /* If fd < 0, done */ ! 331: ! 332: fp = &files[fd]; /* Point to new structure */ ! 333: ! 334: if (!fp->fd_open){ /* New file is not open */ ! 335: bp->b_fd = -1; /* Drop assignment */ ! 336: return(-1); ! 337: } ! 338: ! 339: fp->fd_buf = bufnum; /* New assignment */ ! 340: return(0); ! 341: ! 342: } ! 343: ! 344: ! 345: /* Step two -- if no buffer was specified, but a file descriptor ! 346: * was, then some existing buffer must be allocated to that fd. ! 347: */ ! 348: ! 349: fp = &files[fd]; ! 350: ! 351: ! 352: /* First, check to see if a buffer is already assigned */ ! 353: ! 354: for (j=0; j<NBUF; j++) ! 355: if (iobuf[j].b_fd == fd){ ! 356: bufcnt = j; ! 357: goto recursive; ! 358: } ! 359: ! 360: ! 361: /* Next, scan the table of buffers looking for one ! 362: * which is not assigned. ! 363: */ ! 364: ! 365: for (j=0; j<NBUF; j++) if (iobuf[j].b_fd < 0){ ! 366: bufcnt = j; ! 367: goto recursive; ! 368: } ! 369: ! 370: ! 371: /* If no vacant buffer was found, then a buffer must ! 372: * be allocated arbitrarily. The static local variable ! 373: * "bufcnt" is used for this purpose. It contains the ! 374: * number of the last assigned buffer. Buffers are ! 375: * switched in a sort-of "round robin" approach. ! 376: */ ! 377: ! 378: j = bufcnt; ! 379: do { ! 380: bp = &iobuf[j]; ! 381: fp = &files[bp->b_fd]; ! 382: if (fp->fd_pipe && (!fp->fd_lastop) && bp->b_len > 0) ! 383: continue; ! 384: bufcnt = j; ! 385: goto recursive; ! 386: } while((j = (j+1)%NBUF) != bufcnt); ! 387: return(-1); ! 388: ! 389: ! 390: /* Now, with a recursive call to this routine, ! 391: * switch to the new buffer. ! 392: */ ! 393: ! 394: recursive: return(newbuf(bufcnt, fd)); ! 395: ! 396: } ! 397: ! 398: #ifndef realfd ! 399: realfd(fd) ! 400: { ! 401: ! 402: register struct fds *fp; ! 403: ! 404: ! 405: /* Process duplicate file descriptors. If the main fd is ! 406: * nonnegative, use the value of the dup number for the true fd. ! 407: * No change is made if the file is closed. ! 408: */ ! 409: ! 410: fp = &files[fd]; ! 411: if (!fp->fd_open) return(fd); ! 412: return(fp->fd_dup < 0 ? fd : fp->fd_dup); ! 413: } ! 414: #endif ! 415: ! 416: ! 417: readf(fd, buffer, length) ! 418: char *buffer; ! 419: { ! 420: ! 421: register struct fds *fp; ! 422: register struct iobuf *bp; ! 423: register j; ! 424: int change, trlog, again; ! 425: ! 426: ! 427: /* Handle duplicate files first. If this file descriptor is ! 428: * a duplicate of another one, then the other's buffer and ! 429: * other information must be used. ! 430: */ ! 431: ! 432: fd = realfd(fd); ! 433: ! 434: ! 435: /* If a read is to be performed on a file, all output to that ! 436: * file must first be flushed. If the file descriptor's last ! 437: * function was output, use "newbuf" to flush the output, ! 438: * retaining the buffer, and then set the mode to input. This ! 439: * must be done for all entries in "files" which have the same ! 440: * major/minor number as the current file. ! 441: */ ! 442: ! 443: ! 444: ! 445: fp = &files[fd]; ! 446: if (!fp->fd_open) return(-1); /* File not open */ ! 447: if (fp->fd_uniq){ ! 448: if (fp->fd_lastop && fp->fd_buf >= 0) ! 449: newbuf(fp->fd_buf, fd); ! 450: } else for(j=0; j<NFDS; j++){ ! 451: fp = &files[realfd(j)]; ! 452: if (fp->fd_dev == files[fd].fd_dev ! 453: && fp->fd_ind == files[fd].fd_ind ! 454: && fp->fd_open ! 455: && fp->fd_lastop ! 456: && fp->fd_buf >= 0){ ! 457: newbuf(fp->fd_buf, j); ! 458: } ! 459: } ! 460: ! 461: ! 462: /* The above code nicely took care of any buffer associated ! 463: * with the current fd. Now, set the last operation on this fd ! 464: * to read, and if no buffer is currently assigned, get one. ! 465: */ ! 466: ! 467: fp = &files[fd]; ! 468: fp->fd_lastop = 0; ! 469: ! 470: if (fp->fd_buf < 0) ! 471: if (newbuf(-1, fd) < 0) return(read(fd, buffer, length)); ! 472: ! 473: ! 474: /* The flag "again" determines whether or not the read buffer ! 475: * should be refilled when the end of it is reached. Basically, ! 476: * "again" is set to 0 when a read of less than one buffer length ! 477: * occurs. This way, terminal reads can stop at the carriage ! 478: * return, but disc reads can still buffer in BLEN-byte chunks ! 479: */ ! 480: ! 481: again = 1; ! 482: ! 483: ! 484: /* The variable "trlog" keeps track of how many bytes have been ! 485: * transferred into the buffer supplied by the routine which ! 486: * called "readf". Initially, this number is -1 (EOF). ! 487: */ ! 488: ! 489: trlog = -1; ! 490: ! 491: ! 492: /* The main loop is rather simple -- the buffer is continually ! 493: * emptied and refilled until all of the requested data has been ! 494: * transferred. ! 495: */ ! 496: ! 497: bp = &iobuf[fp->fd_buf]; ! 498: while(1){ ! 499: if (length <= 0) return(trlog); ! 500: if (bp->b_len == 0 && again){ ! 501: again = (bp->b_len=read(fd,bp->b_buf,BLEN)) == BLEN; ! 502: bp->b_next = 0; ! 503: } ! 504: if (bp->b_len <= 0) return(trlog); ! 505: if (trlog < 0) trlog++; ! 506: change = (bp->b_len < length) ? bp->b_len : length; ! 507: for (j=0; j<change; j++) ! 508: buffer[trlog+j] = bp->b_buf[bp->b_next+j]; ! 509: trlog += change; ! 510: bp->b_len -= change; ! 511: bp->b_next += change; ! 512: length -= change; ! 513: } ! 514: } ! 515: ! 516: ! 517: writef(fd, buffer, length) ! 518: char *buffer; ! 519: { ! 520: ! 521: register struct fds *fp; ! 522: register struct iobuf *bp; ! 523: register j; ! 524: int change, trlog, again; ! 525: ! 526: ! 527: /* Handle duplicate files first. If this file descriptor is ! 528: * a duplicate of another one, then the other's buffer and ! 529: * other information must be used. ! 530: */ ! 531: ! 532: fd = realfd(fd); ! 533: ! 534: ! 535: /* If a write is to be performed on a file, all input from that ! 536: * file must first be flushed. If the file descriptor's last ! 537: * function was input, use "newbuf" to flush the input, ! 538: * retaining the buffer, and then set the mode to output. This ! 539: * must be done for all entries in "files" which have the same ! 540: * major/minor number as the current file. ! 541: */ ! 542: ! 543: fp = &files[fd]; ! 544: if (!fp->fd_open) return(-1); /* File not open */ ! 545: if (fp->fd_uniq){ ! 546: if ((!fp->fd_lastop) && fp->fd_buf >= 0) ! 547: newbuf(fp->fd_buf, fd); ! 548: ! 549: } else for(j=0; j<NFDS; j++){ ! 550: fp = &files[realfd(j)]; ! 551: if (fp->fd_dev == files[fd].fd_dev ! 552: && fp->fd_ind == files[fd].fd_ind ! 553: && fp->fd_open ! 554: && (!fp->fd_lastop) ! 555: && fp->fd_buf >= 0){ ! 556: newbuf(fp->fd_buf, j); ! 557: } ! 558: } ! 559: ! 560: ! 561: /* The above code nicely took care of any buffer associated ! 562: * with the current fd. Now, set the last operation on this fd ! 563: * to write, and if no buffer is currently assigned, get one. ! 564: */ ! 565: ! 566: fp = &files[fd]; ! 567: fp->fd_lastop = 1; ! 568: ! 569: if (fp->fd_buf < 0) ! 570: if (newbuf(-1, fd) < 0) return(write(fd,buffer,length)); ! 571: ! 572: ! 573: /* The main loop of output, like the main loop for input, is ! 574: * rather simple. In fact, output is easier. It is only ! 575: * necessary to check when the buffer is full and to flush it ! 576: * as necessary to the file. ! 577: */ ! 578: ! 579: bp = &iobuf[fp->fd_buf]; ! 580: trlog = 0; ! 581: while(1){ ! 582: if (!length) return(trlog); ! 583: if (bp->b_len >= BLEN){ ! 584: write(fd, bp->b_buf, bp->b_len); ! 585: bp->b_next = bp->b_len = 0; ! 586: } ! 587: ! 588: change = ((BLEN - bp->b_len) < length) ? ! 589: (BLEN - bp->b_len) : length; ! 590: ! 591: for (j=0; j<change; j++) ! 592: bp->b_buf[j+bp->b_next] = buffer[j+trlog]; ! 593: ! 594: trlog += change; ! 595: bp->b_next = bp->b_len += change; ! 596: length -= change; ! 597: } ! 598: } ! 599: ! 600: long lseekf(fd, p1, p2) ! 601: int fd, p2; ! 602: long p1; ! 603: { ! 604: ! 605: register struct fds *fp; ! 606: register j; ! 607: long lseek(); ! 608: ! 609: ! 610: /* A seek on a file requires a flush of the buffer for that ! 611: * file and for any other file descriptors which are pointing ! 612: * to the same file. ! 613: */ ! 614: ! 615: fd = realfd(fd); /* Take care of dups */ ! 616: fp = &files[fd]; ! 617: ! 618: if (!fp->fd_open) return(-1); /* not open */ ! 619: ! 620: if (fp->fd_uniq){ ! 621: if (fp->fd_buf >= 0) newbuf(fp->fd_buf, fd); ! 622: } else for(j=0; j<NFDS; j++){ ! 623: fp = &files[j]; ! 624: if (fp->fd_dev == files[fd].fd_dev ! 625: && fp->fd_ind == files[fd].fd_ind ! 626: && fp->fd_open ! 627: && fp->fd_buf >= 0) ! 628: newbuf(fp->fd_buf, j); ! 629: } ! 630: ! 631: ! 632: /* Now that all of the preliminaries are over, the actual ! 633: * seek can be performed. ! 634: */ ! 635: ! 636: return(lseek(fd, p1, p2)); ! 637: } ! 638: ! 639: fstatf(fd, stat_buf) ! 640: struct stat *stat_buf; ! 641: { ! 642: ! 643: register fd2; ! 644: ! 645: fd2 = realfd(fd); ! 646: newbuf(files[fd2].fd_buf, fd2); ! 647: return(fstat(fd, stat_buf)); ! 648: ! 649: } ! 650: ! 651: bflush(){ ! 652: ! 653: register j; ! 654: ! 655: /* Flush all buffers */ ! 656: ! 657: for(j=0; j<NBUF; j++) newbuf(j, -1); ! 658: } ! 659: ! 660: check() ! 661: { ! 662: register j, k; ! 663: ! 664: for (j=0; j<NFDS; j++) ! 665: if ((k=files[j].fd_buf) >= 0) ! 666: if (iobuf[k].b_fd != j){ ! 667: write(2, "CONSISTENCY CHECK!\n", 19); ! 668: if (files[j].fd_dup != j) ! 669: write(2, "BAD DUP ENTRY\n", 14); ! 670: } ! 671: ! 672: ! 673: for (j=0; j<NBUF; j++) ! 674: if ((k=iobuf[j].b_fd) >= 0) ! 675: if (files[k].fd_buf != j) ! 676: write(2, "CONSISTENCY CHECK2!\n", 20); ! 677: ! 678: } ! 679: ! 680: #endif ! 681: ! 682: empty(fd){ ! 683: ! 684: struct stat sbuf; ! 685: register struct fds *fp; ! 686: ! 687: /* Simulate the Rand Corp.'s "empty" system call on a ! 688: * V7 system by seeing if the given fd is a pipe, and if ! 689: * so, whether or not it is empty. ! 690: */ ! 691: ! 692: #ifdef NBUF ! 693: fp = &files[realfd(fd)]; ! 694: ! 695: if (!fp->fd_pipe || !fp->fd_open) ! 696: return(-1); /* Not a pipe */ ! 697: ! 698: if (fp->fd_buf >= 0 && iobuf[fp->fd_buf].b_len > 0) ! 699: return(0); /* Data pending in buffer */ ! 700: #endif ! 701: ! 702: if (fstat(fd, &sbuf) < 0) ! 703: return(-1); /* Can't "stat" it */ ! 704: ! 705: return(sbuf.st_size == 0); ! 706: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.