]> git.sur5r.net Git - bacula/bacula/blob - bacula/patches/testing/faketape.c
ebl update fake tape driver
[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;
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    for(i=0; (i < count) && (where != -1) ; i++) {
579       Dmsg3(dbglevel,"  fsr ok count=%i i=%i where=%i\n", count, i, where);
580       nb = ::read(cur_fd, &size, sizeof(size));
581       if (nb == sizeof(size)) {
582          where = lseek(cur_fd, size, SEEK_CUR);
583          if (where == -1) {
584             errno = EIO;
585             return -1;
586          }
587          current_block++;
588       } else {
589          errno = EIO;
590          return -1;
591       }
592    }
593    Dmsg2(dbglevel,"  fsr ok i=%i where=%i\n", i, where);
594    return 0;
595 }
596
597 int faketape::bsf(int count)
598 {
599    close_file();
600    atEOT = atEOD = false;
601
602    if (current_file - count < 0) {
603       current_file = 0;
604       current_block = 0;
605       atBOT = true;
606       return -1;
607    }
608
609    current_file = current_file - count;
610    current_block = 0;
611    if (!current_file) {
612       atBOT = true;
613    }
614
615    return 1;
616 }
617
618 /* 
619  * Put faketape in offline mode
620  */
621 int faketape::offline()
622 {
623    ASSERT(cur_fd > 0);
624
625    close_file();
626
627    cur_fd = -1;
628    atEOF = false;
629    atEOT = false;
630    atEOD = false;
631    atBOT = false;
632
633    current_file = -1;
634    current_block = -1;
635    last_file = 0;
636    return 0;
637 }
638
639 int faketape::close_file()
640 {
641    Dmsg0(dbglevel, "close_file\n");
642    if (cur_fd > 0) {
643       ::close(cur_fd);
644       cur_fd = -1;
645    }
646    return 0;
647 }
648
649 int faketape::close()
650 {
651    close_file();
652    ::close(fd);
653
654    return 0;
655 }
656 /*
657  **rb
658  **status
659  * EOF Bacula status: file=2 block=0
660  * Device status: EOF ONLINE IM_REP_EN file=2 block=0
661  **rb
662  **status
663  * EOD EOF Bacula status: file=2 block=0
664  * Device status: EOD ONLINE IM_REP_EN file=2 block=-1
665  *
666  */
667
668 int faketape::read(void *buffer, unsigned int count)
669 {
670    unsigned int nb, size;
671    check_file();
672
673    if (atEOT || atEOD) {
674       return 0;
675    }
676
677    atBOT = false;
678    current_block++;
679
680    nb = ::read(cur_fd, &size, sizeof(size));
681    if (size > count) {
682       lseek(cur_fd, size, SEEK_CUR);
683       errno = ENOMEM;
684       return -1;
685    }
686    nb = ::read(cur_fd, buffer, size);
687    if (size != nb) {
688       atEOF = true;
689       if (current_file == last_file) {
690          atEOD = true;
691          current_block = -1;
692       }
693       Dmsg0(dbglevel, "EOF during reading\n");
694    }
695    return nb;
696 }
697
698 int faketape::read_volinfo()
699 {
700    struct stat statp;
701    memset(&tape_info, 0, sizeof(FTAPE_FORMAT));
702
703    Dmsg2(dbglevel, "read_volinfo %p %p\n", cur_info, volume);
704    Mmsg(cur_info, "%s/info", volume);
705    fd = ::open(cur_info, O_CREAT | O_RDWR | O_BINARY, 0640);
706    
707    if (fd < 0) {
708       return -1;
709    }
710    
711    fstat(fd, &statp);
712    
713    /* read volume info */
714    int nb = ::read(fd, &tape_info, sizeof(FTAPE_FORMAT));
715    if (nb != sizeof(FTAPE_FORMAT)) { /* new tape ? */
716       Dmsg1(dbglevel, "Initialize %s\n", volume);
717       tape_info.version = 1;
718       tape_info.block_max = 2000000;
719       tape_info.block_size = statp.st_blksize;
720       tape_info.max_size = 10000000;
721
722       lseek(fd, SEEK_SET, 0);
723       nb = ::write(fd, &tape_info, sizeof(FTAPE_FORMAT));
724
725       if (nb != sizeof(FTAPE_FORMAT)) {
726          ::close(fd);
727          return -1;
728       }
729    }
730
731    Dmsg0(dbglevel, "read_volinfo OK\n");
732    find_maxfile();
733
734    return fd;
735 }
736
737 int faketape::open(const char *pathname, int uflags, int umode)
738 {
739    Dmsg3(dbglevel, "faketape::open(%s, %i, %i)\n", pathname, uflags, umode);
740    pm_strcpy(volume, pathname);
741
742    struct stat statp;   
743    if (stat(volume, &statp) != 0) {
744       Dmsg1(dbglevel, "Can't stat on %s\n", volume);
745       return -1;
746    }
747
748    if (!S_ISDIR(statp.st_mode)) {
749       Dmsg1(dbglevel, "%s is not a directory\n", volume);
750       errno = EACCES;
751       return -1;
752    }
753
754    /* open volume descriptor and get this->fd */
755    if (read_volinfo() < 0) {
756       return -1;
757    }
758
759    current_block=-1;
760
761    return fd;
762 }
763
764 /*
765  * read volume directory to get the last file number
766  */
767 int faketape::find_maxfile()
768 {
769    int max=0;
770    int cur;
771    char *p;
772    POOL_MEM tmp;
773    DIR *fp_dir;
774    struct dirent *dir;
775    struct stat statp;
776
777    fp_dir = opendir(this->volume);
778    if (!fp_dir) {
779       last_file=0;
780       return -1;
781    }
782
783    this->size = 0;
784
785    /* search for all digit file */
786    while ((dir = readdir (fp_dir)) != NULL)
787    {
788       Mmsg(tmp, "%s/%s", this->volume, dir->d_name);
789       if (lstat(tmp.c_str(), &statp) == 0) {
790          this->size += statp.st_size;
791       } else {
792          Dmsg1(dbglevel, "Can't stat %s\n", tmp.c_str());
793       }
794       cur = 0;
795       /* TODO: compute size */
796       for(p = dir->d_name; *p && isdigit(*p); p++)
797       {
798          cur *= 10;
799          cur += *p;
800       }
801       if (!*p && cur) {
802          max = (max > cur)?max:cur;
803       }
804    }
805
806    closedir(fp_dir);
807    this->last_file = max;
808    return max;
809 }
810
811 int faketape::open_file() 
812 {
813    ASSERT(current_file >= 0);
814    close_file();
815
816    Mmsg(cur_file, "%s/%i", volume, current_file);
817    cur_fd = ::open(cur_file, O_CREAT | O_RDWR | O_BINARY, 0640);
818    if (cur_fd < 0) {
819       return -1;
820    }
821    current_block = 0;
822    last_file = (last_file > current_file)?last_file:current_file;
823
824    Dmsg1(dbglevel, "open_file %s\n", cur_file);
825
826    return cur_fd;
827 }
828
829 void faketape::dump()
830 {
831    Dmsg0(dbglevel+1, "===================\n");
832    Dmsg2(dbglevel, "file:block = %i:%i\n", current_file, current_block);
833    Dmsg1(dbglevel+1, "last_file=%i\n", last_file);
834    Dmsg1(dbglevel+1, "volume=%s\n", volume);
835    Dmsg1(dbglevel+1, "cur_file=%s\n", cur_file);  
836    Dmsg1(dbglevel+1, "size=%i\n", size);  
837    Dmsg1(dbglevel+1, "EOF=%i\n", atEOF);  
838    Dmsg1(dbglevel+1, "EOT=%i\n", atEOT);  
839    Dmsg1(dbglevel+1, "EOD=%i\n", atEOD);  
840 }
841
842 /****************************************************************
843
844 #define GMT_EOF(x)              ((x) & 0x80000000)
845 #define GMT_BOT(x)              ((x) & 0x40000000)
846 #define GMT_EOT(x)              ((x) & 0x20000000)
847 #define GMT_SM(x)               ((x) & 0x10000000)
848 #define GMT_EOD(x)              ((x) & 0x08000000)
849
850
851  GMT_EOF(x) : La bande est positionnée juste après une filemark (toujours faux
852      après une opération MTSEEK).
853
854  GMT_BOT(x) : La bande est positionnée juste au début du premier fichier
855      (toujours faux après une opération MTSEEK).
856  
857  GMT_EOT(x) : Une opération a atteint la fin physique de la bande (End Of
858  Tape).
859
860  GMT_SM(x) : La bande est positionnée sur une setmark (toujours faux après une
861  opération MTSEEK).
862
863  GMT_EOD(x) : La bande est positionnée à la fin des données enregistrées.
864
865
866 blkno = -1 (after MTBSF MTBSS or MTSEEK)
867 fileno = -1 (after MTBSS or MTSEEK)
868
869 *** mtx load
870 drive type = Generic SCSI-2 tape
871 drive status = 0
872 sense key error = 0
873 residue count = 0
874 file number = 0
875 block number = 0
876 Tape block size 0 bytes. Density code 0x0 (default).
877 Soft error count since last status=0
878 General status bits on (41010000):
879  BOT ONLINE IM_REP_EN
880
881 *** read empty block
882 dd if=/dev/lto2 of=/tmp/toto count=1
883 dd: lecture de `/dev/lto2': Ne peut allouer de la mémoire
884 0+0 enregistrements lus
885 0+0 enregistrements écrits
886 1 octet (1B) copié, 4,82219 seconde, 0,0 kB/s
887
888 file number = 0
889 block number = 1
890
891 *** read file mark
892 dd if=/dev/lto2 of=/tmp/toto count=1
893 0+0 enregistrements lus
894 0+0 enregistrements écrits
895 1 octet (1B) copié, 0,167274 seconde, 0,0 kB/s
896
897 file number = 1
898 block number = 0
899
900  *** write 2 blocks after rewind
901 dd if=/dev/zero of=/dev/lto2 count=2
902 2+0 enregistrements lus
903 2+0 enregistrements écrits
904 1024 octets (1,0 kB) copiés, 6,57402 seconde, 0,2 kB/s
905
906 file number = 1
907 block number = 0
908
909 *** write 2 blocks
910 file number = 2
911 block number = 0
912
913 *** rewind and fsr
914 file number = 0
915 block number = 1
916
917 *** rewind and 2x fsr (we have just 2 blocks)
918 file number = 0
919 block number = 2
920
921 *** fsr
922 mt: /dev/lto2: Erreur
923 file number = 1
924 block number = 0
925
926
927  ****************************************************************/
928
929
930 #ifdef TEST
931
932 int main()
933 {
934    int fd;
935    char buf[500];
936    printf("Starting FakeTape\n");
937
938    mkdir("/tmp/fake", 0700);
939
940
941
942
943    return 0;
944 }
945
946 #endif