]> git.sur5r.net Git - bacula/bacula/blob - bacula/patches/testing/faketape.c
962faf86bbb6cb17b81955ab1efb5384858b7ed1
[bacula/bacula] / bacula / patches / testing / faketape.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2008 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation, which is 
11    listed in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28
29 #include "faketape.h"
30 #include <dirent.h>
31 #include <sys/mtio.h>
32 #include <ctype.h>
33
34 static int dbglevel = 10;
35
36 faketape *ftape_list[FTAPE_MAX_DRIVE];
37
38 static faketape *get_tape(int fd)
39 {
40    ASSERT(fd >= 0);
41
42    if (fd >= FTAPE_MAX_DRIVE) {
43       /* error */
44       return NULL;
45    }
46
47    return ftape_list[fd];
48 }
49
50 static bool put_tape(faketape *ftape)
51 {
52    ASSERT(ftape != NULL);
53
54    int fd = ftape->get_fd();
55    if (fd >= FTAPE_MAX_DRIVE) {
56       /* error */
57       return false;
58    }
59    ftape_list[fd] = ftape;
60    return true;
61 }
62
63 /****************************************************************/
64 /* theses function will replace open/read/write/close/ioctl
65  * in bacula core
66  */
67 int faketape_open(const char *pathname, int flags, int mode)
68 {
69    ASSERT(pathname != NULL);
70
71    int fd;
72    faketape *tape = new faketape();
73    fd = tape->open(pathname, flags, mode);
74    if (fd > 0) {
75       put_tape(tape);
76    }
77    return fd;
78 }
79
80 int faketape_read(int fd, void *buffer, unsigned int count)
81 {
82    faketape *tape = get_tape(fd);
83    ASSERT(tape != NULL);
84    return tape->read(buffer, count);
85 }
86
87 int faketape_write(int fd, const void *buffer, unsigned int count)
88 {
89    faketape *tape = get_tape(fd);
90    ASSERT(tape != NULL);
91    return tape->write(buffer, count);
92 }
93
94 int faketape_close(int fd)
95 {
96    faketape *tape = get_tape(fd);
97    ASSERT(tape != NULL);
98    tape->close();
99    delete tape;
100    return 0;
101 }
102
103 int faketape_ioctl(int fd, unsigned long int request, ...)
104 {
105    va_list argp;
106    int result=0;
107
108    faketape *t = get_tape(fd);
109    if (!t) {
110       errno = EBADF;
111       return -1;
112    }
113
114    va_start(argp, request);
115
116    if (request == MTIOCTOP) {
117       result = t->tape_op(va_arg(argp, mtop *));
118    } else if (request == MTIOCGET) {
119       result = t->tape_get(va_arg(argp, mtget *));
120    }
121 //
122 //   case MTIOCPOS:
123 //      result = tape_pos(fd, va_arg(argp, mtpos *));
124 //      break;
125 //
126    else {
127       errno = ENOTTY;
128       result = -1;
129    }
130    va_end(argp);
131
132    return result;
133 }
134
135 /****************************************************************/
136
137 int faketape::tape_op(struct mtop *mt_com)
138 {
139    int result=0;
140    
141    switch (mt_com->mt_op)
142    {
143    case MTRESET:
144    case MTNOP:
145    case MTSETDRVBUFFER:
146       break;
147
148    default:
149    case MTRAS1:
150    case MTRAS2:
151    case MTRAS3:
152    case MTSETDENSITY:
153       errno = ENOTTY;
154       result = -1;
155       break;
156
157    case MTFSF:                  /* Forward space over mt_count filemarks. */
158       result = fsf(mt_com->mt_count);
159       break;
160
161    case MTBSF:                  /* Backward space over mt_count filemarks. */
162       current_file = current_file - mt_com->mt_count;
163       if (current_file < 0) {
164          current_file = 0;
165          errno = EIO;
166          result = -1;
167       }
168       atEOD = false;
169       atEOF = false;
170       atBOT = false;
171       open_file();
172       break;
173
174    case MTFSR:      /* Forward space over mt_count records (tape blocks). */
175 /*
176     file number = 1
177     block number = 0
178    
179     file number = 1
180     block number = 1
181    
182     mt: /dev/lto2: Erreur d'entrée/sortie
183    
184     file number = 2
185     block number = 0
186 */
187       /* tester si on se trouve a la fin du fichier */
188       result = fsr(mt_com->mt_count);
189       break;
190
191    case MTBSR:      /* Backward space over mt_count records (tape blocks). */
192       result = -1;
193       break;
194
195    case MTWEOF:                 /* Write mt_count filemarks. */
196       weof(mt_com->mt_count);
197       break;
198
199    case MTREW:                  /* Rewind. */
200       close_file();
201       atEOF = atEOD = false;
202       atBOT = true;
203       current_file = 0;
204       current_block = 0;
205       break;
206
207    case MTOFFL:                 /* put tape offline */
208       result = offline();
209       break;
210
211    case MTRETEN:                /* Re-tension tape. */
212       result = 0;
213       break;
214
215    case MTBSFM:                 /* not used by bacula */
216       errno = EIO;
217       result = -1;
218       break;
219
220    case MTFSFM:                 /* not used by bacula */
221       errno = EIO;
222       result = -1;
223       break;
224
225    case MTEOM:/* Go to the end of the recorded media (for appending files). */
226 /*
227    file number = 3
228    block number = -1
229 */
230       /* Can be at EOM */
231       atBOT = false;
232       atEOF = true;
233       atEOD = true;
234       close_file();
235       current_file = last_file+1;
236       current_block = -1;
237       /* Ne pas creer le fichier si on est a la fin */
238
239       break;
240
241    case MTERASE:                /* not used by bacula */
242       atEOD = true;
243       atEOF = false;
244       atEOT = false;
245       current_file = 0;
246       current_block = -1;
247       delete_files(current_file);
248       break;
249
250    case MTSETBLK:
251       break;
252
253    case MTSEEK:
254       break;
255
256    case MTTELL:
257       break;
258
259    case MTFSS:
260       break;
261
262    case MTBSS:
263       break;
264
265    case MTWSM:
266       break;
267
268    case MTLOCK:
269       break;
270
271    case MTUNLOCK:
272       break;
273
274    case MTLOAD:
275       break;
276
277    case MTUNLOAD:
278       break;
279
280    case MTCOMPRESSION:
281       break;
282
283    case MTSETPART:
284       break;
285
286    case MTMKPART:
287       break;
288    }
289 //
290 //   switch (result) {
291 //   case NO_ERROR:
292 //   case -1:   /* Error has already been translated into errno */
293 //      break;
294 //
295 //   default:
296 //   case ERROR_FILEMARK_DETECTED:
297 //      errno = EIO;
298 //      break;
299 //
300 //   case ERROR_END_OF_MEDIA:
301 //      errno = EIO;
302 //      break;
303 //
304 //   case ERROR_NO_DATA_DETECTED:
305 //      errno = EIO;
306 //      break;
307 //
308 //   case ERROR_NO_MEDIA_IN_DRIVE:
309 //      errno = ENOMEDIUM;
310 //      break;
311 //
312 //   case ERROR_INVALID_HANDLE:
313 //   case ERROR_ACCESS_DENIED:
314 //   case ERROR_LOCK_VIOLATION:
315 //      errno = EBADF;
316 //      break;
317 //   }
318 //
319    return result == 0 ? 0 : -1;
320 }
321
322 int faketape::tape_get(struct mtget *mt_get)
323 {
324    int density = 1;
325
326    mt_get->mt_type = MT_ISSCSI2;
327    mt_get->mt_blkno = current_block;
328    mt_get->mt_fileno = current_file;
329
330    mt_get->mt_resid = -1;
331 //   pos_info.PartitionBlockValid ? pos_info.Partition : (ULONG)-1;
332
333    /* TODO */
334    mt_get->mt_dsreg = 
335       ((density << MT_ST_DENSITY_SHIFT) & MT_ST_DENSITY_MASK) |
336       ((tape_info.block_size << MT_ST_BLKSIZE_SHIFT) & MT_ST_BLKSIZE_MASK);
337
338
339    mt_get->mt_gstat = 0x00010000;  /* Immediate report mode.*/
340
341    if (atEOF) {
342       mt_get->mt_gstat |= 0x80000000;     // GMT_EOF
343    }
344
345    if (atBOT) {
346       mt_get->mt_gstat |= 0x40000000;     // GMT_BOT
347    }
348    if (atEOT) {
349       mt_get->mt_gstat |= 0x20000000;     // GMT_EOT
350    }
351
352    if (atEOD) {
353       mt_get->mt_gstat |= 0x08000000;     // GMT_EOD
354    }
355
356    if (0) { //WriteProtected) {
357       mt_get->mt_gstat |= 0x04000000;     // GMT_WR_PROT
358    }
359
360    if (online) {
361       mt_get->mt_gstat |= 0x01000000;     // GMT_ONLINE
362    } else {
363       mt_get->mt_gstat |= 0x00040000;  // GMT_DR_OPEN
364    }
365    mt_get->mt_erreg = 0;
366
367    return 0;
368 }
369
370 int faketape::tape_pos(struct mtpos *mt_pos)
371 {
372    return 0;
373 }
374
375 /*
376  * This function try to emulate the append only behavior
377  * of a tape. When you wrote something, data after the
378  * current position are discarded.
379  */
380 int faketape::delete_files(int startfile)
381 {  
382    int cur,max=0;
383    char *p;
384    POOL_MEM tmp;
385    DIR *fp_dir;
386    struct dirent *dir;
387    struct stat statp;
388
389    Dmsg1(dbglevel, "delete_files %i\n", startfile);
390
391    fp_dir = opendir(this->volume);
392    if (!fp_dir) {
393       this->last_file=0;
394       this->size = 0;
395       return -1;
396    }
397
398    this->size = 0;
399
400    /* search for all digit files 
401     * and we remove all ones that are greater than
402     * startfile
403     */
404    while ((dir = readdir (fp_dir)) != NULL)
405    {
406       Mmsg(tmp, "%s/%s", this->volume, dir->d_name);
407       cur = 0;
408       /* check if d_name contains only digits */
409       for(p = dir->d_name; *p && isdigit(*p); p++)
410       {
411          cur *= 10;
412          cur += *p - '0';
413       }
414       if (!*p && cur > 0) {
415          if (cur >= startfile) { /* remove it */
416             unlink(tmp.c_str());
417          } else {
418             if (lstat(tmp.c_str(), &statp) == 0) {
419                this->size += statp.st_size;
420             }
421             max = (max > cur)?max:cur;
422          }
423       }
424    }
425
426    closedir(fp_dir);
427    this->last_file = max;
428    return max;
429 }
430
431 faketape::faketape()
432 {
433    fd = -1;
434    cur_fd = -1;
435
436    atEOF = false;
437    atBOT = false;
438    atEOT = false;
439    atEOD = false;
440    online = false;
441    
442    size = 0;
443    last_file = 0;
444    current_file = 0;
445    current_block = -1;
446
447    volume = get_pool_memory(PM_NAME);
448    cur_file = get_pool_memory(PM_NAME);
449    cur_info = get_pool_memory(PM_NAME);
450 }
451
452 faketape::~faketape()
453 {
454    free_pool_memory(volume);
455    free_pool_memory(cur_file);
456    free_pool_memory(cur_info);
457 }
458
459 int faketape::get_fd()
460 {
461    return this->fd;
462 }
463
464 int faketape::write(const void *buffer, unsigned int count)
465 {
466    unsigned int nb;
467    Dmsg2(dbglevel, "write len=%i blocks=%i\n", count, current_block);
468    check_file();
469
470    if (atEOT) {
471       Dmsg0(dbglevel, "write nothing, EOT !\n");
472       return 0;
473    }
474
475    if (current_block != -1) {
476       current_block++;
477    }
478
479    atBOT = false;
480    atEOD = true;
481
482    /* TODO: remove all files > current_file and 
483     * remove blocks > current_block 
484     */
485    if (count + size > tape_info.max_size) {
486       Dmsg2(dbglevel, 
487             "EOT writing only %i of %i requested\n", 
488             tape_info.max_size - size, count);
489       count = tape_info.max_size - size;
490       atEOT = true;
491    }
492
493    ::write(cur_fd, &count, sizeof(count));
494    nb = ::write(cur_fd, buffer, count);
495    
496    if (nb != count) {
497       atEOT = true;
498       Dmsg2(dbglevel, 
499             "Not enough space writing only %i of %i requested\n", 
500             nb, count);
501    }
502
503    return nb;
504 }
505
506 int faketape::weof(int count)
507 {
508    Dmsg2(dbglevel, "Writing EOF %i:%i\n", current_file, current_block);
509    if (atEOT) {
510       current_block = -1;
511       return -1;
512    }
513
514    count--;                     /* end this file */
515    ftruncate(cur_fd, lseek(cur_fd, 0, SEEK_CUR));
516    current_file++;
517    open_file();
518
519    /* we erase all previous information */
520    if (last_file > current_file) {
521       delete_files(current_file);
522    }
523    atEOF = true;
524    atEOD = false;
525
526    if (count > 0) {
527       current_block = -1;
528       return -1;
529    } else {
530       current_block = 0;
531    }
532
533    return 0;
534 }
535
536 int faketape::fsf(int count)
537 {
538
539 /*
540  * 1 0 -> fsf -> 2 0 -> fsf -> 2 -1
541  */
542    if (atEOT) {
543       current_block = -1;
544       return -1;
545    }
546
547    close_file();
548
549    atEOF=1;
550    Dmsg3(dbglevel+1, "fsf %i+%i <= %i\n", current_file, count, last_file);
551
552    if ((current_file + count) <= last_file) {
553       current_file += count;
554       current_block = 0;
555       return 0;
556    } else {
557       Dmsg0(dbglevel, "Try to FSF after EOT\n");
558       current_file = last_file ;
559       current_block = -1;
560       atEOD=true;
561       return -1;
562    }
563 }
564
565 int faketape::fsr(int count)
566 {
567    int i,nb;
568    off_t where=0, size;
569    Dmsg2(dbglevel, "fsr current_block=%i count=%i\n", current_block, count);
570
571    if (atEOT) {
572       current_block = -1;
573       return -1;
574    }
575
576    check_file();
577
578    /* check all block record */
579    for(i=0; (i < count) && (where != -1) ; i++) {
580       nb = ::read(cur_fd, &size, sizeof(size)); /* get size of block */
581       if (nb == sizeof(size)) {
582          current_block++;
583          where = lseek(cur_fd, size, SEEK_CUR); /* seek after this block */
584          if (where == -1) {
585             errno = EIO;
586             return -1;
587          }
588       } else {
589          errno = EIO;
590          return -1;
591       }
592    }
593    return 0;
594 }
595
596 int faketape::bsf(int count)
597 {
598    close_file();
599    atEOT = atEOD = false;
600
601    if (current_file - count < 0) {
602       current_file = 0;
603       current_block = 0;
604       atBOT = true;
605       return -1;
606    }
607
608    current_file = current_file - count;
609    current_block = 0;
610    if (!current_file) {
611       atBOT = true;
612    }
613
614    return 1;
615 }
616
617 /* 
618  * Put faketape in offline mode
619  */
620 int faketape::offline()
621 {
622    ASSERT(cur_fd > 0);
623
624    close_file();
625
626    cur_fd = -1;
627    atEOF = false;
628    atEOT = false;
629    atEOD = false;
630    atBOT = false;
631
632    current_file = -1;
633    current_block = -1;
634    last_file = 0;
635    return 0;
636 }
637
638 int faketape::close_file()
639 {
640    Dmsg0(dbglevel, "close_file\n");
641    if (cur_fd > 0) {
642       ::close(cur_fd);
643       cur_fd = -1;
644    }
645    return 0;
646 }
647
648 int faketape::close()
649 {
650    close_file();
651    ::close(fd);
652
653    return 0;
654 }
655 /*
656  **rb
657  **status
658  * EOF Bacula status: file=2 block=0
659  * Device status: EOF ONLINE IM_REP_EN file=2 block=0
660  **rb
661  **status
662  * EOD EOF Bacula status: file=2 block=0
663  * Device status: EOD ONLINE IM_REP_EN file=2 block=-1
664  *
665  */
666
667 int faketape::read(void *buffer, unsigned int count)
668 {
669    unsigned int nb, size;
670    check_file();
671
672    if (atEOT || atEOD) {
673       return 0;
674    }
675
676    atBOT = false;
677    current_block++;
678
679    nb = ::read(cur_fd, &size, sizeof(size));
680    if (size > count) {
681       lseek(cur_fd, size, SEEK_CUR);
682       errno = ENOMEM;
683       return -1;
684    }
685    nb = ::read(cur_fd, buffer, size);
686    if (size != nb) {
687       atEOF = true;
688       if (current_file == last_file) {
689          atEOD = true;
690          current_block = -1;
691       }
692       Dmsg0(dbglevel, "EOF during reading\n");
693    }
694    return nb;
695 }
696
697 int faketape::read_volinfo()
698 {
699    struct stat statp;
700    memset(&tape_info, 0, sizeof(FTAPE_FORMAT));
701
702    Dmsg2(dbglevel, "read_volinfo %p %p\n", cur_info, volume);
703    Mmsg(cur_info, "%s/info", volume);
704    fd = ::open(cur_info, O_CREAT | O_RDWR | O_BINARY, 0640);
705    
706    if (fd < 0) {
707       return -1;
708    }
709    
710    fstat(fd, &statp);
711    
712    /* read volume info */
713    int nb = ::read(fd, &tape_info, sizeof(FTAPE_FORMAT));
714    if (nb != sizeof(FTAPE_FORMAT)) { /* new tape ? */
715       Dmsg1(dbglevel, "Initialize %s\n", volume);
716       tape_info.version = 1;
717       tape_info.block_max = 2000000;
718       tape_info.block_size = statp.st_blksize;
719       tape_info.max_size = 10000000;
720
721       lseek(fd, SEEK_SET, 0);
722       nb = ::write(fd, &tape_info, sizeof(FTAPE_FORMAT));
723
724       if (nb != sizeof(FTAPE_FORMAT)) {
725          ::close(fd);
726          return -1;
727       }
728    }
729
730    Dmsg0(dbglevel, "read_volinfo OK\n");
731    find_maxfile();
732
733    return fd;
734 }
735
736 int faketape::open(const char *pathname, int uflags, int umode)
737 {
738    Dmsg3(dbglevel, "faketape::open(%s, %i, %i)\n", pathname, uflags, umode);
739    pm_strcpy(volume, pathname);
740
741    struct stat statp;   
742    if (stat(volume, &statp) != 0) {
743       Dmsg1(dbglevel, "Can't stat on %s\n", volume);
744       return -1;
745    }
746
747    if (!S_ISDIR(statp.st_mode)) {
748       Dmsg1(dbglevel, "%s is not a directory\n", volume);
749       errno = EACCES;
750       return -1;
751    }
752
753    /* open volume descriptor and get this->fd */
754    if (read_volinfo() < 0) {
755       return -1;
756    }
757
758    current_block=-1;
759
760    return fd;
761 }
762
763 /*
764  * read volume directory to get the last file number
765  */
766 int faketape::find_maxfile()
767 {
768    int max=0;
769    int cur;
770    char *p;
771    POOL_MEM tmp;
772    DIR *fp_dir;
773    struct dirent *dir;
774    struct stat statp;
775
776    fp_dir = opendir(this->volume);
777    if (!fp_dir) {
778       last_file=0;
779       return -1;
780    }
781
782    this->size = 0;
783
784    /* search for all digit file */
785    while ((dir = readdir (fp_dir)) != NULL)
786    {
787       Mmsg(tmp, "%s/%s", this->volume, dir->d_name);
788       if (lstat(tmp.c_str(), &statp) == 0) {
789          this->size += statp.st_size;
790       } else {
791          Dmsg1(dbglevel, "Can't stat %s\n", tmp.c_str());
792       }
793       cur = 0;
794       /* TODO: compute size */
795       for(p = dir->d_name; *p && isdigit(*p); p++)
796       {
797          cur *= 10;
798          cur += *p;
799       }
800       if (!*p && cur) {
801          max = (max > cur)?max:cur;
802       }
803    }
804
805    closedir(fp_dir);
806    this->last_file = max;
807    return max;
808 }
809
810 int faketape::open_file() 
811 {
812    ASSERT(current_file >= 0);
813    close_file();
814
815    Mmsg(cur_file, "%s/%i", volume, current_file);
816    cur_fd = ::open(cur_file, O_CREAT | O_RDWR | O_BINARY, 0640);
817    if (cur_fd < 0) {
818       return -1;
819    }
820    current_block = 0;
821    last_file = (last_file > current_file)?last_file:current_file;
822
823    Dmsg1(dbglevel, "open_file %s\n", cur_file);
824
825    return cur_fd;
826 }
827
828 void faketape::dump()
829 {
830    Dmsg0(dbglevel+1, "===================\n");
831    Dmsg2(dbglevel, "file:block = %i:%i\n", current_file, current_block);
832    Dmsg1(dbglevel+1, "last_file=%i\n", last_file);
833    Dmsg1(dbglevel+1, "volume=%s\n", volume);
834    Dmsg1(dbglevel+1, "cur_file=%s\n", cur_file);  
835    Dmsg1(dbglevel+1, "size=%i\n", size);  
836    Dmsg1(dbglevel+1, "EOF=%i\n", atEOF);  
837    Dmsg1(dbglevel+1, "EOT=%i\n", atEOT);  
838    Dmsg1(dbglevel+1, "EOD=%i\n", atEOD);  
839 }
840
841 /****************************************************************
842
843 #define GMT_EOF(x)              ((x) & 0x80000000)
844 #define GMT_BOT(x)              ((x) & 0x40000000)
845 #define GMT_EOT(x)              ((x) & 0x20000000)
846 #define GMT_SM(x)               ((x) & 0x10000000)
847 #define GMT_EOD(x)              ((x) & 0x08000000)
848
849
850  GMT_EOF(x) : La bande est positionnée juste après une filemark (toujours faux
851      après une opération MTSEEK).
852
853  GMT_BOT(x) : La bande est positionnée juste au début du premier fichier
854      (toujours faux après une opération MTSEEK).
855  
856  GMT_EOT(x) : Une opération a atteint la fin physique de la bande (End Of
857  Tape).
858
859  GMT_SM(x) : La bande est positionnée sur une setmark (toujours faux après une
860  opération MTSEEK).
861
862  GMT_EOD(x) : La bande est positionnée à la fin des données enregistrées.
863
864
865 blkno = -1 (after MTBSF MTBSS or MTSEEK)
866 fileno = -1 (after MTBSS or MTSEEK)
867
868 *** mtx load
869 drive type = Generic SCSI-2 tape
870 drive status = 0
871 sense key error = 0
872 residue count = 0
873 file number = 0
874 block number = 0
875 Tape block size 0 bytes. Density code 0x0 (default).
876 Soft error count since last status=0
877 General status bits on (41010000):
878  BOT ONLINE IM_REP_EN
879
880 *** read empty block
881 dd if=/dev/lto2 of=/tmp/toto count=1
882 dd: lecture de `/dev/lto2': Ne peut allouer de la mémoire
883 0+0 enregistrements lus
884 0+0 enregistrements écrits
885 1 octet (1B) copié, 4,82219 seconde, 0,0 kB/s
886
887 file number = 0
888 block number = 1
889
890 *** read file mark
891 dd if=/dev/lto2 of=/tmp/toto count=1
892 0+0 enregistrements lus
893 0+0 enregistrements écrits
894 1 octet (1B) copié, 0,167274 seconde, 0,0 kB/s
895
896 file number = 1
897 block number = 0
898
899  *** write 2 blocks after rewind
900 dd if=/dev/zero of=/dev/lto2 count=2
901 2+0 enregistrements lus
902 2+0 enregistrements écrits
903 1024 octets (1,0 kB) copiés, 6,57402 seconde, 0,2 kB/s
904
905 file number = 1
906 block number = 0
907
908 *** write 2 blocks
909 file number = 2
910 block number = 0
911
912 *** rewind and fsr
913 file number = 0
914 block number = 1
915
916 *** rewind and 2x fsr (we have just 2 blocks)
917 file number = 0
918 block number = 2
919
920 *** fsr
921 mt: /dev/lto2: Erreur
922 file number = 1
923 block number = 0
924
925
926  ****************************************************************/
927
928
929 #ifdef TEST
930
931 int main()
932 {
933    int fd;
934    char buf[500];
935    printf("Starting FakeTape\n");
936
937    mkdir("/tmp/fake", 0700);
938
939
940
941
942    return 0;
943 }
944
945 #endif