00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078 #include "postgres.h"
00079
00080 #include "storage/buffile.h"
00081 #include "utils/logtape.h"
00082
00083
00084
00085
00086
00087 #define BLOCKS_PER_INDIR_BLOCK ((int) (BLCKSZ / sizeof(long)))
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097 typedef struct IndirectBlock
00098 {
00099 int nextSlot;
00100 struct IndirectBlock *nextup;
00101
00102 long ptrs[BLOCKS_PER_INDIR_BLOCK];
00103 } IndirectBlock;
00104
00105
00106
00107
00108
00109
00110
00111 typedef struct LogicalTape
00112 {
00113 IndirectBlock *indirect;
00114 bool writing;
00115 bool frozen;
00116 bool dirty;
00117
00118
00119
00120
00121
00122
00123 long numFullBlocks;
00124 int lastBlockBytes;
00125
00126
00127
00128
00129
00130
00131
00132
00133 char *buffer;
00134 long curBlockNumber;
00135 int pos;
00136 int nbytes;
00137 } LogicalTape;
00138
00139
00140
00141
00142
00143
00144
00145 struct LogicalTapeSet
00146 {
00147 BufFile *pfile;
00148 long nFileBlocks;
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162
00163 bool forgetFreeSpace;
00164 bool blocksSorted;
00165 long *freeBlocks;
00166 int nFreeBlocks;
00167 int freeBlocksLen;
00168
00169
00170
00171
00172
00173 int nTapes;
00174 LogicalTape tapes[1];
00175 };
00176
00177 static void ltsWriteBlock(LogicalTapeSet *lts, long blocknum, void *buffer);
00178 static void ltsReadBlock(LogicalTapeSet *lts, long blocknum, void *buffer);
00179 static long ltsGetFreeBlock(LogicalTapeSet *lts);
00180 static void ltsReleaseBlock(LogicalTapeSet *lts, long blocknum);
00181 static void ltsRecordBlockNum(LogicalTapeSet *lts, IndirectBlock *indirect,
00182 long blocknum);
00183 static long ltsRewindIndirectBlock(LogicalTapeSet *lts,
00184 IndirectBlock *indirect,
00185 bool freezing);
00186 static long ltsRewindFrozenIndirectBlock(LogicalTapeSet *lts,
00187 IndirectBlock *indirect);
00188 static long ltsRecallNextBlockNum(LogicalTapeSet *lts,
00189 IndirectBlock *indirect,
00190 bool frozen);
00191 static long ltsRecallPrevBlockNum(LogicalTapeSet *lts,
00192 IndirectBlock *indirect);
00193 static void ltsDumpBuffer(LogicalTapeSet *lts, LogicalTape *lt);
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205 static void
00206 ltsWriteBlock(LogicalTapeSet *lts, long blocknum, void *buffer)
00207 {
00208 if (BufFileSeekBlock(lts->pfile, blocknum) != 0 ||
00209 BufFileWrite(lts->pfile, buffer, BLCKSZ) != BLCKSZ)
00210 ereport(ERROR,
00211
00212 (errcode_for_file_access(),
00213 errmsg("could not write block %ld of temporary file: %m",
00214 blocknum),
00215 errhint("Perhaps out of disk space?")));
00216 }
00217
00218
00219
00220
00221
00222
00223
00224 static void
00225 ltsReadBlock(LogicalTapeSet *lts, long blocknum, void *buffer)
00226 {
00227 if (BufFileSeekBlock(lts->pfile, blocknum) != 0 ||
00228 BufFileRead(lts->pfile, buffer, BLCKSZ) != BLCKSZ)
00229 ereport(ERROR,
00230
00231 (errcode_for_file_access(),
00232 errmsg("could not read block %ld of temporary file: %m",
00233 blocknum)));
00234 }
00235
00236
00237
00238
00239 static int
00240 freeBlocks_cmp(const void *a, const void *b)
00241 {
00242 long ablk = *((const long *) a);
00243 long bblk = *((const long *) b);
00244
00245
00246 if (ablk < bblk)
00247 return 1;
00248 if (ablk > bblk)
00249 return -1;
00250 return 0;
00251 }
00252
00253
00254
00255
00256
00257
00258
00259 static long
00260 ltsGetFreeBlock(LogicalTapeSet *lts)
00261 {
00262
00263
00264
00265
00266
00267 if (lts->nFreeBlocks > 0)
00268 {
00269 if (!lts->blocksSorted)
00270 {
00271 qsort((void *) lts->freeBlocks, lts->nFreeBlocks,
00272 sizeof(long), freeBlocks_cmp);
00273 lts->blocksSorted = true;
00274 }
00275 return lts->freeBlocks[--lts->nFreeBlocks];
00276 }
00277 else
00278 return lts->nFileBlocks++;
00279 }
00280
00281
00282
00283
00284 static void
00285 ltsReleaseBlock(LogicalTapeSet *lts, long blocknum)
00286 {
00287 int ndx;
00288
00289
00290
00291
00292 if (lts->forgetFreeSpace)
00293 return;
00294
00295
00296
00297
00298 if (lts->nFreeBlocks >= lts->freeBlocksLen)
00299 {
00300 lts->freeBlocksLen *= 2;
00301 lts->freeBlocks = (long *) repalloc(lts->freeBlocks,
00302 lts->freeBlocksLen * sizeof(long));
00303 }
00304
00305
00306
00307
00308
00309 ndx = lts->nFreeBlocks++;
00310 lts->freeBlocks[ndx] = blocknum;
00311 if (ndx > 0 && lts->freeBlocks[ndx - 1] < blocknum)
00312 lts->blocksSorted = false;
00313 }
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324 static void
00325 ltsRecordBlockNum(LogicalTapeSet *lts, IndirectBlock *indirect,
00326 long blocknum)
00327 {
00328 if (indirect->nextSlot >= BLOCKS_PER_INDIR_BLOCK)
00329 {
00330
00331
00332
00333
00334
00335 long indirblock = ltsGetFreeBlock(lts);
00336
00337 ltsWriteBlock(lts, indirblock, (void *) indirect->ptrs);
00338 if (indirect->nextup == NULL)
00339 {
00340 indirect->nextup = (IndirectBlock *) palloc(sizeof(IndirectBlock));
00341 indirect->nextup->nextSlot = 0;
00342 indirect->nextup->nextup = NULL;
00343 }
00344 ltsRecordBlockNum(lts, indirect->nextup, indirblock);
00345
00346
00347
00348
00349 indirect->nextSlot = 0;
00350 }
00351 indirect->ptrs[indirect->nextSlot++] = blocknum;
00352 }
00353
00354
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364 static long
00365 ltsRewindIndirectBlock(LogicalTapeSet *lts,
00366 IndirectBlock *indirect,
00367 bool freezing)
00368 {
00369
00370 if (indirect == NULL)
00371 return -1L;
00372
00373
00374 if (indirect->nextSlot < BLOCKS_PER_INDIR_BLOCK)
00375 indirect->ptrs[indirect->nextSlot] = -1L;
00376
00377
00378
00379
00380
00381 if (indirect->nextup != NULL)
00382 {
00383 long indirblock = ltsGetFreeBlock(lts);
00384
00385 ltsWriteBlock(lts, indirblock, (void *) indirect->ptrs);
00386 ltsRecordBlockNum(lts, indirect->nextup, indirblock);
00387 indirblock = ltsRewindIndirectBlock(lts, indirect->nextup, freezing);
00388 Assert(indirblock != -1L);
00389 ltsReadBlock(lts, indirblock, (void *) indirect->ptrs);
00390 if (!freezing)
00391 ltsReleaseBlock(lts, indirblock);
00392 }
00393
00394
00395
00396
00397 indirect->nextSlot = 0;
00398 if (indirect->ptrs[0] == -1L)
00399 return -1L;
00400 return indirect->ptrs[indirect->nextSlot++];
00401 }
00402
00403
00404
00405
00406
00407
00408 static long
00409 ltsRewindFrozenIndirectBlock(LogicalTapeSet *lts,
00410 IndirectBlock *indirect)
00411 {
00412
00413 if (indirect == NULL)
00414 return -1L;
00415
00416
00417
00418
00419
00420 if (indirect->nextup != NULL)
00421 {
00422 long indirblock;
00423
00424 indirblock = ltsRewindFrozenIndirectBlock(lts, indirect->nextup);
00425 Assert(indirblock != -1L);
00426 ltsReadBlock(lts, indirblock, (void *) indirect->ptrs);
00427 }
00428
00429
00430
00431
00432 indirect->nextSlot = 0;
00433 if (indirect->ptrs[0] == -1L)
00434 return -1L;
00435 return indirect->ptrs[indirect->nextSlot++];
00436 }
00437
00438
00439
00440
00441
00442
00443
00444 static long
00445 ltsRecallNextBlockNum(LogicalTapeSet *lts,
00446 IndirectBlock *indirect,
00447 bool frozen)
00448 {
00449
00450 if (indirect == NULL)
00451 return -1L;
00452
00453 if (indirect->nextSlot >= BLOCKS_PER_INDIR_BLOCK ||
00454 indirect->ptrs[indirect->nextSlot] == -1L)
00455 {
00456 long indirblock;
00457
00458 if (indirect->nextup == NULL)
00459 return -1L;
00460 indirblock = ltsRecallNextBlockNum(lts, indirect->nextup, frozen);
00461 if (indirblock == -1L)
00462 return -1L;
00463 ltsReadBlock(lts, indirblock, (void *) indirect->ptrs);
00464 if (!frozen)
00465 ltsReleaseBlock(lts, indirblock);
00466 indirect->nextSlot = 0;
00467 }
00468 if (indirect->ptrs[indirect->nextSlot] == -1L)
00469 return -1L;
00470 return indirect->ptrs[indirect->nextSlot++];
00471 }
00472
00473
00474
00475
00476
00477
00478
00479
00480
00481
00482 static long
00483 ltsRecallPrevBlockNum(LogicalTapeSet *lts,
00484 IndirectBlock *indirect)
00485 {
00486
00487 if (indirect == NULL)
00488 return -1L;
00489
00490 if (indirect->nextSlot <= 1)
00491 {
00492 long indirblock;
00493
00494 if (indirect->nextup == NULL)
00495 return -1L;
00496 indirblock = ltsRecallPrevBlockNum(lts, indirect->nextup);
00497 if (indirblock == -1L)
00498 return -1L;
00499 ltsReadBlock(lts, indirblock, (void *) indirect->ptrs);
00500
00501
00502
00503
00504
00505 indirect->nextSlot = BLOCKS_PER_INDIR_BLOCK + 1;
00506 }
00507 indirect->nextSlot--;
00508 return indirect->ptrs[indirect->nextSlot - 1];
00509 }
00510
00511
00512
00513
00514
00515
00516
00517 LogicalTapeSet *
00518 LogicalTapeSetCreate(int ntapes)
00519 {
00520 LogicalTapeSet *lts;
00521 LogicalTape *lt;
00522 int i;
00523
00524
00525
00526
00527
00528 Assert(ntapes > 0);
00529 lts = (LogicalTapeSet *) palloc(sizeof(LogicalTapeSet) +
00530 (ntapes - 1) *sizeof(LogicalTape));
00531 lts->pfile = BufFileCreateTemp(false);
00532 lts->nFileBlocks = 0L;
00533 lts->forgetFreeSpace = false;
00534 lts->blocksSorted = true;
00535 lts->freeBlocksLen = 32;
00536 lts->freeBlocks = (long *) palloc(lts->freeBlocksLen * sizeof(long));
00537 lts->nFreeBlocks = 0;
00538 lts->nTapes = ntapes;
00539
00540
00541
00542
00543
00544
00545
00546 for (i = 0; i < ntapes; i++)
00547 {
00548 lt = <s->tapes[i];
00549 lt->indirect = NULL;
00550 lt->writing = true;
00551 lt->frozen = false;
00552 lt->dirty = false;
00553 lt->numFullBlocks = 0L;
00554 lt->lastBlockBytes = 0;
00555 lt->buffer = NULL;
00556 lt->curBlockNumber = 0L;
00557 lt->pos = 0;
00558 lt->nbytes = 0;
00559 }
00560 return lts;
00561 }
00562
00563
00564
00565
00566 void
00567 LogicalTapeSetClose(LogicalTapeSet *lts)
00568 {
00569 LogicalTape *lt;
00570 IndirectBlock *ib,
00571 *nextib;
00572 int i;
00573
00574 BufFileClose(lts->pfile);
00575 for (i = 0; i < lts->nTapes; i++)
00576 {
00577 lt = <s->tapes[i];
00578 for (ib = lt->indirect; ib != NULL; ib = nextib)
00579 {
00580 nextib = ib->nextup;
00581 pfree(ib);
00582 }
00583 if (lt->buffer)
00584 pfree(lt->buffer);
00585 }
00586 pfree(lts->freeBlocks);
00587 pfree(lts);
00588 }
00589
00590
00591
00592
00593
00594
00595
00596
00597
00598
00599 void
00600 LogicalTapeSetForgetFreeSpace(LogicalTapeSet *lts)
00601 {
00602 lts->forgetFreeSpace = true;
00603 }
00604
00605
00606
00607
00608 static void
00609 ltsDumpBuffer(LogicalTapeSet *lts, LogicalTape *lt)
00610 {
00611 long datablock = ltsGetFreeBlock(lts);
00612
00613 Assert(lt->dirty);
00614 ltsWriteBlock(lts, datablock, (void *) lt->buffer);
00615 ltsRecordBlockNum(lts, lt->indirect, datablock);
00616 lt->dirty = false;
00617
00618 }
00619
00620
00621
00622
00623
00624
00625 void
00626 LogicalTapeWrite(LogicalTapeSet *lts, int tapenum,
00627 void *ptr, size_t size)
00628 {
00629 LogicalTape *lt;
00630 size_t nthistime;
00631
00632 Assert(tapenum >= 0 && tapenum < lts->nTapes);
00633 lt = <s->tapes[tapenum];
00634 Assert(lt->writing);
00635
00636
00637 if (lt->buffer == NULL)
00638 lt->buffer = (char *) palloc(BLCKSZ);
00639 if (lt->indirect == NULL)
00640 {
00641 lt->indirect = (IndirectBlock *) palloc(sizeof(IndirectBlock));
00642 lt->indirect->nextSlot = 0;
00643 lt->indirect->nextup = NULL;
00644 }
00645
00646 while (size > 0)
00647 {
00648 if (lt->pos >= BLCKSZ)
00649 {
00650
00651 if (lt->dirty)
00652 ltsDumpBuffer(lts, lt);
00653 else
00654 {
00655
00656 elog(ERROR, "invalid logtape state: should be dirty");
00657 }
00658 lt->numFullBlocks++;
00659 lt->curBlockNumber++;
00660 lt->pos = 0;
00661 lt->nbytes = 0;
00662 }
00663
00664 nthistime = BLCKSZ - lt->pos;
00665 if (nthistime > size)
00666 nthistime = size;
00667 Assert(nthistime > 0);
00668
00669 memcpy(lt->buffer + lt->pos, ptr, nthistime);
00670
00671 lt->dirty = true;
00672 lt->pos += nthistime;
00673 if (lt->nbytes < lt->pos)
00674 lt->nbytes = lt->pos;
00675 ptr = (void *) ((char *) ptr + nthistime);
00676 size -= nthistime;
00677 }
00678 }
00679
00680
00681
00682
00683
00684
00685
00686 void
00687 LogicalTapeRewind(LogicalTapeSet *lts, int tapenum, bool forWrite)
00688 {
00689 LogicalTape *lt;
00690 long datablocknum;
00691
00692 Assert(tapenum >= 0 && tapenum < lts->nTapes);
00693 lt = <s->tapes[tapenum];
00694
00695 if (!forWrite)
00696 {
00697 if (lt->writing)
00698 {
00699
00700
00701
00702
00703
00704 if (lt->dirty)
00705 ltsDumpBuffer(lts, lt);
00706 lt->lastBlockBytes = lt->nbytes;
00707 lt->writing = false;
00708 datablocknum = ltsRewindIndirectBlock(lts, lt->indirect, false);
00709 }
00710 else
00711 {
00712
00713
00714
00715
00716 Assert(lt->frozen);
00717 datablocknum = ltsRewindFrozenIndirectBlock(lts, lt->indirect);
00718 }
00719
00720 lt->curBlockNumber = 0L;
00721 lt->pos = 0;
00722 lt->nbytes = 0;
00723 if (datablocknum != -1L)
00724 {
00725 ltsReadBlock(lts, datablocknum, (void *) lt->buffer);
00726 if (!lt->frozen)
00727 ltsReleaseBlock(lts, datablocknum);
00728 lt->nbytes = (lt->curBlockNumber < lt->numFullBlocks) ?
00729 BLCKSZ : lt->lastBlockBytes;
00730 }
00731 }
00732 else
00733 {
00734
00735
00736
00737
00738
00739
00740
00741
00742 IndirectBlock *ib,
00743 *nextib;
00744
00745 Assert(!lt->writing && !lt->frozen);
00746
00747 if (lt->indirect)
00748 {
00749 for (ib = lt->indirect->nextup; ib != NULL; ib = nextib)
00750 {
00751 nextib = ib->nextup;
00752 pfree(ib);
00753 }
00754 lt->indirect->nextSlot = 0;
00755 lt->indirect->nextup = NULL;
00756 }
00757 lt->writing = true;
00758 lt->dirty = false;
00759 lt->numFullBlocks = 0L;
00760 lt->lastBlockBytes = 0;
00761 lt->curBlockNumber = 0L;
00762 lt->pos = 0;
00763 lt->nbytes = 0;
00764 }
00765 }
00766
00767
00768
00769
00770
00771
00772 size_t
00773 LogicalTapeRead(LogicalTapeSet *lts, int tapenum,
00774 void *ptr, size_t size)
00775 {
00776 LogicalTape *lt;
00777 size_t nread = 0;
00778 size_t nthistime;
00779
00780 Assert(tapenum >= 0 && tapenum < lts->nTapes);
00781 lt = <s->tapes[tapenum];
00782 Assert(!lt->writing);
00783
00784 while (size > 0)
00785 {
00786 if (lt->pos >= lt->nbytes)
00787 {
00788
00789 long datablocknum = ltsRecallNextBlockNum(lts, lt->indirect,
00790 lt->frozen);
00791
00792 if (datablocknum == -1L)
00793 break;
00794 lt->curBlockNumber++;
00795 lt->pos = 0;
00796 ltsReadBlock(lts, datablocknum, (void *) lt->buffer);
00797 if (!lt->frozen)
00798 ltsReleaseBlock(lts, datablocknum);
00799 lt->nbytes = (lt->curBlockNumber < lt->numFullBlocks) ?
00800 BLCKSZ : lt->lastBlockBytes;
00801 if (lt->nbytes <= 0)
00802 break;
00803 }
00804
00805 nthistime = lt->nbytes - lt->pos;
00806 if (nthistime > size)
00807 nthistime = size;
00808 Assert(nthistime > 0);
00809
00810 memcpy(ptr, lt->buffer + lt->pos, nthistime);
00811
00812 lt->pos += nthistime;
00813 ptr = (void *) ((char *) ptr + nthistime);
00814 size -= nthistime;
00815 nread += nthistime;
00816 }
00817
00818 return nread;
00819 }
00820
00821
00822
00823
00824
00825
00826
00827
00828
00829
00830
00831
00832 void
00833 LogicalTapeFreeze(LogicalTapeSet *lts, int tapenum)
00834 {
00835 LogicalTape *lt;
00836 long datablocknum;
00837
00838 Assert(tapenum >= 0 && tapenum < lts->nTapes);
00839 lt = <s->tapes[tapenum];
00840 Assert(lt->writing);
00841
00842
00843
00844
00845
00846 if (lt->dirty)
00847 ltsDumpBuffer(lts, lt);
00848 lt->lastBlockBytes = lt->nbytes;
00849 lt->writing = false;
00850 lt->frozen = true;
00851 datablocknum = ltsRewindIndirectBlock(lts, lt->indirect, true);
00852
00853 lt->curBlockNumber = 0L;
00854 lt->pos = 0;
00855 lt->nbytes = 0;
00856 if (datablocknum != -1L)
00857 {
00858 ltsReadBlock(lts, datablocknum, (void *) lt->buffer);
00859 lt->nbytes = (lt->curBlockNumber < lt->numFullBlocks) ?
00860 BLCKSZ : lt->lastBlockBytes;
00861 }
00862 }
00863
00864
00865
00866
00867
00868
00869
00870
00871
00872
00873
00874
00875 bool
00876 LogicalTapeBackspace(LogicalTapeSet *lts, int tapenum, size_t size)
00877 {
00878 LogicalTape *lt;
00879 long nblocks;
00880 int newpos;
00881
00882 Assert(tapenum >= 0 && tapenum < lts->nTapes);
00883 lt = <s->tapes[tapenum];
00884 Assert(lt->frozen);
00885
00886
00887
00888
00889 if (size <= (size_t) lt->pos)
00890 {
00891 lt->pos -= (int) size;
00892 return true;
00893 }
00894
00895
00896
00897
00898 size -= (size_t) lt->pos;
00899 nblocks = size / BLCKSZ;
00900 size = size % BLCKSZ;
00901 if (size)
00902 {
00903 nblocks++;
00904 newpos = (int) (BLCKSZ - size);
00905 }
00906 else
00907 newpos = 0;
00908 if (nblocks > lt->curBlockNumber)
00909 return false;
00910
00911
00912
00913
00914
00915
00916 while (nblocks-- > 0)
00917 {
00918 long datablocknum = ltsRecallPrevBlockNum(lts, lt->indirect);
00919
00920 if (datablocknum == -1L)
00921 elog(ERROR, "unexpected end of tape");
00922 lt->curBlockNumber--;
00923 if (nblocks == 0)
00924 {
00925 ltsReadBlock(lts, datablocknum, (void *) lt->buffer);
00926 lt->nbytes = BLCKSZ;
00927 }
00928 }
00929 lt->pos = newpos;
00930 return true;
00931 }
00932
00933
00934
00935
00936
00937
00938
00939
00940
00941 bool
00942 LogicalTapeSeek(LogicalTapeSet *lts, int tapenum,
00943 long blocknum, int offset)
00944 {
00945 LogicalTape *lt;
00946
00947 Assert(tapenum >= 0 && tapenum < lts->nTapes);
00948 lt = <s->tapes[tapenum];
00949 Assert(lt->frozen);
00950 Assert(offset >= 0 && offset <= BLCKSZ);
00951
00952
00953
00954
00955 if (blocknum == lt->curBlockNumber && offset <= lt->nbytes)
00956 {
00957 lt->pos = offset;
00958 return true;
00959 }
00960
00961
00962
00963
00964 if (blocknum < 0 || blocknum > lt->numFullBlocks ||
00965 (blocknum == lt->numFullBlocks && offset > lt->lastBlockBytes))
00966 return false;
00967
00968
00969
00970
00971
00972
00973 while (lt->curBlockNumber > blocknum)
00974 {
00975 long datablocknum = ltsRecallPrevBlockNum(lts, lt->indirect);
00976
00977 if (datablocknum == -1L)
00978 elog(ERROR, "unexpected end of tape");
00979 if (--lt->curBlockNumber == blocknum)
00980 ltsReadBlock(lts, datablocknum, (void *) lt->buffer);
00981 }
00982 while (lt->curBlockNumber < blocknum)
00983 {
00984 long datablocknum = ltsRecallNextBlockNum(lts, lt->indirect,
00985 lt->frozen);
00986
00987 if (datablocknum == -1L)
00988 elog(ERROR, "unexpected end of tape");
00989 if (++lt->curBlockNumber == blocknum)
00990 ltsReadBlock(lts, datablocknum, (void *) lt->buffer);
00991 }
00992 lt->nbytes = (lt->curBlockNumber < lt->numFullBlocks) ?
00993 BLCKSZ : lt->lastBlockBytes;
00994 lt->pos = offset;
00995 return true;
00996 }
00997
00998
00999
01000
01001
01002
01003
01004 void
01005 LogicalTapeTell(LogicalTapeSet *lts, int tapenum,
01006 long *blocknum, int *offset)
01007 {
01008 LogicalTape *lt;
01009
01010 Assert(tapenum >= 0 && tapenum < lts->nTapes);
01011 lt = <s->tapes[tapenum];
01012 *blocknum = lt->curBlockNumber;
01013 *offset = lt->pos;
01014 }
01015
01016
01017
01018
01019 long
01020 LogicalTapeSetBlocks(LogicalTapeSet *lts)
01021 {
01022 return lts->nFileBlocks;
01023 }