--- /dev/null
+Index: src/stored/faketape.c
+===================================================================
+--- src/stored/faketape.c (révision 7092)
++++ src/stored/faketape.c (copie de travail)
+@@ -170,6 +170,7 @@
+ int faketape::tape_op(struct mtop *mt_com)
+ {
+ int result=0;
++ int count = mt_com->mt_count;
+
+ if (!online) {
+ errno = ENOMEDIUM;
+@@ -193,11 +194,15 @@
+ break;
+
+ case MTFSF: /* Forward space over mt_count filemarks. */
+- result = fsf(mt_com->mt_count);
++ do {
++ result = fsf();
++ } while (--count > 0 && result == 0);
+ break;
+
+ case MTBSF: /* Backward space over mt_count filemarks. */
+- result = bsf(mt_com->mt_count);
++ do {
++ result = bsf();
++ } while (--count > 0 && result == 0);
+ break;
+
+ case MTFSR: /* Forward space over mt_count records (tape blocks). */
+@@ -222,16 +227,20 @@
+ break;
+
+ case MTWEOF: /* Write mt_count filemarks. */
+- result = weof(mt_com->mt_count);
++ do {
++ result = weof();
++ } while (result == 0 && --count > 0);
+ break;
+
+ case MTREW: /* Rewind. */
+ Dmsg0(dbglevel, "rewind faketape\n");
++ check_eof();
+ atEOF = atEOD = false;
+ atBOT = true;
+ current_file = 0;
+ current_block = 0;
+- seek_file();
++ lseek(fd, 0, SEEK_SET);
++ read_next_fm(true);
+ break;
+
+ case MTOFFL: /* put tape offline */
+@@ -253,19 +262,28 @@
+ break;
+
+ case MTEOM:/* Go to the end of the recorded media (for appending files). */
++ while (next_FM) {
++ lseek(fd, next_FM, SEEK_SET);
++ read_next_fm(true);
++ }
++ off_t l;
++ while (::read(fd, &l, sizeof(l)) > 0) {
++ if (l) {
++ lseek(fd, l, SEEK_CUR);
++ } else {
++ ASSERT(0);
++ }
++ Dmsg0(dbglevel, "skip 1 block\n");
++ }
++ current_block = -1;
++ atEOF = false;
++ atEOD = true;
++
+ /*
+ file number = 3
+ block number = -1
+ */
+ /* Can be at EOM */
+- atBOT = false;
+- atEOF = false;
+- atEOD = true;
+- atEOT = false;
+-
+- current_file = last_file;
+- current_block = -1;
+- seek_file();
+ break;
+
+ case MTERASE: /* not used by bacula */
+@@ -275,7 +293,8 @@
+
+ current_file = 0;
+ current_block = -1;
+- seek_file();
++ lseek(fd, 0, SEEK_SET);
++ read_next_fm(true);
+ truncate_file();
+ break;
+
+@@ -405,8 +424,8 @@
+ atEOT = false;
+ atEOD = false;
+ online = false;
+- inplace = false;
+ needEOF = false;
++ eot_count = 0;
+
+ file_block = 0;
+ last_file = 0;
+@@ -444,8 +463,6 @@
+ return -1;
+ }
+
+- check_inplace();
+-
+ if (!atEOD) { /* if not at the end of the data */
+ truncate_file();
+ }
+@@ -460,14 +477,6 @@
+
+ needEOF = true; /* next operation need EOF mark */
+
+-// if ((count + file_size) > max_size) {
+-// Dmsg2(dbglevel,
+-// "EOT writing only %i of %i requested\n",
+-// max_size - file_size, count);
+-// count = max_size - file_size;
+-// atEOT = true;
+-// }
+-
+ uint32_t size = count;
+ ::write(fd, &size, sizeof(uint32_t));
+ nb = ::write(fd, buffer, count);
+@@ -484,43 +493,68 @@
+ return nb;
+ }
+
+-int faketape::weof(int count)
++/*
++ * +---+---------+---+------------------+---+-------------------+
++ * |00N| DATA |0LN| DATA |0LC| DATA |
++ * +---+---------+---+------------------+---+-------------------+
++ *
++ * 0 : zero
++ * L : Last FileMark offset
++ * N : Next FileMark offset
++ * C : Current FileMark Offset
++ */
++int faketape::weof()
+ {
+ ASSERT(online);
+ ASSERT(current_file >= 0);
+ Dmsg3(dbglevel, "Writing EOF %i:%i last=%i\n",
+ current_file, current_block,last_file);
++
++ off_t cur_FM;
++
+ if (atEOT) {
+ errno = ENOSPC;
+ current_block = -1;
+ return -1;
+ }
+- needEOF = false;
+
+- check_inplace();
+- truncate_file(); /* nothing after this point */
++ if (!atEOD) {
++ truncate_file(); /* nothing after this point */
++ }
++
++ cur_FM = lseek(fd, 0, SEEK_CUR); // current position
+
++ /* update previous next_FM */
++ lseek(fd, last_FM + sizeof(uint32_t)+sizeof(off_t), SEEK_SET);
++ ::write(fd, &cur_FM, sizeof(off_t));
++ lseek(fd, cur_FM, SEEK_SET);
++
++ prev_FM = last_FM;
++ last_FM = cur_FM;
++ next_FM = 0;
++
+ uint32_t c=0;
+- ::write(fd, &c, sizeof(uint32_t));
++ ::write(fd, &c, sizeof(uint32_t)); // EOF
++ ::write(fd, &last_FM, sizeof(last_FM)); // F-1
++ ::write(fd, &next_FM, sizeof(next_FM)); // F (will be updated next time)
+
+- current_file += count;
++ current_file++;
+ current_block = 0;
+- seek_file();
+
+- c=0;
+- ::write(fd, &c, sizeof(uint32_t));
+- lseek(fd, lseek(fd, 0, SEEK_CUR) - sizeof(uint32_t), SEEK_SET);
+-
++ needEOF = false;
+ atEOD = false;
+ atBOT = false;
+ atEOF = true;
+
+- update_pos();
++ last_file = MAX(current_file, last_file);
+
+ return 0;
+ }
+
+-int faketape::fsf(int count)
++/*
++ * Go to next FM
++ */
++int faketape::fsf()
+ {
+ ASSERT(online);
+ ASSERT(current_file >= 0);
+@@ -528,24 +562,33 @@
+ /*
+ * 1 0 -> fsf -> 2 0 -> fsf -> 2 -1
+ */
+- check_inplace();
+- check_eof();
+
+- int ret;
++ int ret=0;
+ if (atEOT || atEOD) {
+ errno = EIO;
+ current_block = -1;
+ return -1;
+ }
+
+- atBOT = atEOF = false;
+- Dmsg3(dbglevel+1, "fsf %i+%i <= %i\n", current_file, count, last_file);
++ atBOT = false;
++ Dmsg2(dbglevel+1, "fsf %i <= %i\n", current_file, last_file);
+
+- if ((current_file + count) <= last_file) {
+- current_file += count;
+- current_block = 0;
++ if (next_FM > last_FM) { /* not the last file */
++ lseek(fd, next_FM, SEEK_SET);
++ read_next_fm(true);
++ current_file++;
++ atEOF = true;
+ ret = 0;
+- } else {
++
++ } else if (atEOF) { /* last file mark */
++ current_block=-1;
++ errno = EIO;
++ atEOF = false;
++ atEOD = true;
++
++ } else { /* last file, but no at the end */
++ fsr(100000);
++
+ Dmsg0(dbglevel, "Try to FSF after EOT\n");
+ errno = EIO;
+ current_file = last_file ;
+@@ -553,10 +596,44 @@
+ atEOD=true;
+ ret = -1;
+ }
+- seek_file();
+ return ret;
+ }
+
++/* /------------\ /---------------\
++ * +---+------+---+---------------+-+
++ * |OLN| |0LN| | |
++ * +---+------+---+---------------+-+
++ */
++bool faketape::read_next_fm(bool read_all /* read the 0 byte */)
++{
++ prev_FM = last_FM;
++ return read_fm(read_all);
++}
++
++bool faketape::read_fm(bool read_all /* read the 0 byte */)
++{
++ int ret;
++ uint32_t c;
++ if (read_all) {
++ ::read(fd, &c, sizeof(c));
++ if (c != 0) {
++ lseek(fd, lseek(fd, 0, SEEK_CUR) - sizeof(c), SEEK_SET);
++ return false;
++ }
++ }
++ ::read(fd, &last_FM, sizeof(last_FM));
++ ret = ::read(fd, &next_FM, sizeof(next_FM));
++
++ current_block=0;
++
++ Dmsg1(dbglevel, "Read FM next=%lli\n", next_FM);
++
++ return (ret == sizeof(next_FM));
++}
++
++/*
++ * TODO: Check fsr with EOF
++ */
+ int faketape::fsr(int count)
+ {
+ ASSERT(online);
+@@ -568,7 +645,6 @@
+ uint32_t s;
+ Dmsg3(dbglevel, "fsr %i:%i count=%i\n", current_file,current_block, count);
+
+- check_inplace();
+ check_eof();
+
+ if (atEOT) {
+@@ -595,20 +671,23 @@
+ current_file, current_block, nb,s);
+ errno = EIO;
+ ret = -1;
+- if (current_file < last_file) {
++ if (next_FM) {
+ current_block = 0;
+ current_file++;
+- seek_file();
+- }
++ read_next_fm(false);
++ }
+ atEOF = true; /* stop the loop */
+ }
+ }
+
+- find_maxfile(); /* refresh stats */
+-
+ return ret;
+ }
+
++/*
++ * BSR + EOF => begin of EOF + EIO
++ * BSR + BSR + EOF => last block
++ * current_block = -1
++ */
+ int faketape::bsr(int count)
+ {
+ Dmsg2(dbglevel, "bsr current_block=%i count=%i\n",
+@@ -619,7 +698,6 @@
+ ASSERT(count == 1);
+ ASSERT(fd >= 0);
+
+- check_inplace();
+ check_eof();
+
+ if (!count) {
+@@ -641,22 +719,21 @@
+ return -1;
+ }
+
++ /* at EOF 0:-1 BOT=0 EOD=0 EOF=0 ERR: Input/output error */
+ if (atEOF) {
+- if (!current_block) {
+- if (current_file > 0) {
+- current_file--;
+- }
+- current_block=-1;
+- errno = EIO;
+- return -1;
+-
+- } else {
+- atEOF=false;
+- }
++ lseek(fd, last_FM, SEEK_CUR);
++ atEOF = false;
++ current_block=-1;
++ errno = EIO;
++ return -1;
+ }
+
+- current_block=0;
+- seek_file();
++ /*
++ * First, go to last_FM and read all blocks to find the good one
++ */
++
++ lseek(fd, last_FM, SEEK_CUR);
++ read_fm(true);
+
+ do {
+ if (!atEOF) {
+@@ -700,31 +777,35 @@
+ return 0;
+ }
+
+-int faketape::bsf(int count)
++/* BSF => just before last EOF
++ * EOF + BSF => just before EOF
++ * file 0 + BSF => BOT + errno
++ */
++int faketape::bsf()
+ {
+ ASSERT(online);
+ ASSERT(current_file >= 0);
+- Dmsg3(dbglevel, "bsf %i:%i count=%i\n", current_file, current_block, count);
++ Dmsg2(dbglevel, "bsf %i:%i count=%i\n", current_file, current_block);
+ int ret = 0;
+
+- check_inplace();
+ check_eof();
+
+ atBOT = atEOF = atEOT = atEOD = false;
+
+- if (current_file - count < 0) {
++ if (current_file == 0) {/* BOT + errno */
++ lseek(fd, 0, SEEK_SET);
++ read_fm(true);
++ prev_FM = 0;
+ current_file = 0;
+ current_block = 0;
+ atBOT = true;
+ errno = EIO;
+ ret = -1;
+ } else {
+- current_file = current_file - count + 1;
+- current_block = -1;
+- seek_file();
++ Dmsg1(dbglevel, "bfs last=%lli\n", last_FM);
++ lseek(fd, last_FM, SEEK_SET);
+ current_file--;
+- /* go just before last EOF */
+- lseek(fd, lseek(fd, 0, SEEK_CUR) - sizeof(uint32_t), SEEK_SET);
++ current_block=-1;
+ }
+ return ret;
+ }
+@@ -749,6 +830,9 @@
+ return 0;
+ }
+
++/* A filemark is automatically written to tape if the last tape operation
++ * before close was a write.
++ */
+ int faketape::close()
+ {
+ check_eof();
+@@ -756,18 +840,15 @@
+ fd = -1;
+ return 0;
+ }
++
+ /*
+- **rb
+- **status
+- * EOF Bacula status: file=2 block=0
+- * Device status: EOF ONLINE IM_REP_EN file=2 block=0
+- **rb
+- **status
+- * EOD EOF Bacula status: file=2 block=0
+- * Device status: EOD ONLINE IM_REP_EN file=2 block=-1
+- *
++ * When a filemark is encountered while reading, the following happens. If
++ * there are data remaining in the buffer when the filemark is found, the
++ * buffered data is returned. The next read returns zero bytes. The following
++ * read returns data from the next file. The end of recorded data is sig‐
++ * naled by returning zero bytes for two consecutive read calls. The third
++ * read returns an error.
+ */
+-
+ int faketape::read(void *buffer, unsigned int count)
+ {
+ ASSERT(online);
+@@ -778,6 +859,10 @@
+ Dmsg2(dbglevel, "read %i:%i\n", current_file, current_block);
+
+ if (atEOT || atEOD) {
++ if (eot_count < 2) { // first two reads return 0, after EIO
++ eot_count++;
++ return 0;
++ }
+ errno = EIO;
+ return -1;
+ }
+@@ -792,10 +877,10 @@
+ atEOF=false;
+ }
+
+- check_inplace();
+ check_eof();
+
+ atEOD = atBOT = false;
++ eot_count = 0;
+
+ /* reading size of data */
+ nb = ::read(fd, &s, sizeof(uint32_t));
+@@ -813,11 +898,10 @@
+
+ if (!s) { /* EOF */
+ atEOF = true;
+- if (current_file < last_file) { /* move to next file if possible */
+- current_file++;
+- current_block = 0;
+- inplace=false;
++ if (read_next_fm(false)) {
++ current_file++;
+ }
++
+ return 0;
+ }
+
+@@ -825,7 +909,7 @@
+ nb = ::read(fd, buffer, s);
+ if (s != nb) { /* read error */
+ errno=EIO;
+- atEOT = true;
++ set_eot();
+ current_block = -1;
+ Dmsg0(dbglevel, "EOT during reading\n");
+ return -1;
+@@ -860,36 +944,25 @@
+ return -1;
+ }
+
+- /* open volume descriptor and get this->fd */
+- find_maxfile();
+-
+ file_block = 0;
+ current_block = 0;
+ current_file = 0;
++ prev_FM = next_FM = last_FM = 0;
++ eot_count = 0;
+ needEOF = false;
+- inplace = true;
+ atBOT = true;
+ atEOT = atEOD = false;
+
+- return fd;
+-}
+-
+-/*
+- * read volume to get the last file number
+- */
+-int faketape::find_maxfile()
+-{
+- struct stat statp;
+- if (fstat(fd, &statp) != 0) {
+- return 0;
++
++ if (!read_fm(true)) {
++ weof();
++ last_file = current_file=0;
+ }
+- last_file = statp.st_size>>FILE_OFFSET;
+-
+- Dmsg1(dbglevel+1, "last_file=%i\n", last_file);
+
+- return last_file;
++ return fd;
+ }
+
++/* use this to track file usage */
+ void faketape::update_pos()
+ {
+ ASSERT(online);
+@@ -901,32 +974,12 @@
+ Dmsg1(dbglevel+1, "update_pos=%i\n", file_block);
+
+ if (file_block > max_block) {
+- atEOT = true;
++ set_eot();
+ } else {
+ atEOT = false;
+ }
+ }
+
+-int faketape::seek_file()
+-{
+- ASSERT(online);
+- ASSERT(current_file >= 0);
+- Dmsg2(dbglevel, "seek_file %i:%i\n", current_file, current_block);
+- inplace = true;
+-
+- off_t pos = ((off_t)current_file)<<FILE_OFFSET;
+- if(lseek(fd, pos, SEEK_SET) == -1) {
+- return -1;
+- }
+-
+- last_file = MAX(last_file, current_file);
+- if (current_block > 0) {
+- fsr(current_block);
+- }
+-
+- return 0;
+-}
+-
+ void faketape::dump()
+ {
+ Dmsg0(dbglevel+1, "===================\n");
+Index: src/stored/faketape.h
+===================================================================
+--- src/stored/faketape.h (révision 7084)
++++ src/stored/faketape.h (copie de travail)
+@@ -56,35 +56,39 @@
+ private:
+ int fd; /* Our file descriptor */
+
+- off_t file_block; /* size */
++ off_t file_block; /* size */
+ off_t max_block;
+
++ off_t prev_FM; /* previous file mark */
++ off_t last_FM; /* last file mark (current file) */
++ off_t next_FM;
++
+ bool atEOF; /* End of file */
+ bool atEOT; /* End of media */
+ bool atEOD; /* End of data */
+ bool atBOT; /* Begin of tape */
+ bool online; /* volume online */
+- bool inplace; /* have to seek before writing ? */
+ bool needEOF; /* check if last operation need eof */
+
+ int32_t last_file; /* last file of the volume */
+ int32_t current_file; /* current position */
+ int32_t current_block; /* current position */
++ int eot_count; /* count eot reads */
+
+ void destroy();
+- int find_maxfile();
+ int offline();
+ int truncate_file();
+- int seek_file();
+- void check_eof() { if(needEOF) weof(1);};
+- void check_inplace() { if (!inplace) seek_file();};
++ void check_eof() { if(needEOF) weof();};
+ void update_pos();
++ bool read_fm(bool readfirst);
++ bool read_next_fm(bool readfirst);
++ void set_eot() { eot_count=0; atEOT=true;};
+
+ public:
+- int fsf(int count);
++ int fsf();
+ int fsr(int count);
+- int weof(int count);
+- int bsf(int count);
++ int weof();
++ int bsf();
+ int bsr(int count);
+
+ faketape();