kern/56274: pthreads does not return correct value at EOT

Dan Langille dan at langille.org
Mon Sep 1 08:10:12 PDT 2003


>Number:         56274
>Category:       kern
>Synopsis:       pthreads does not return correct value at EOT
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Mon Sep 01 08:10:08 PDT 2003
>Closed-Date:
>Last-Modified:
>Originator:     Dan Langille
>Release:        FreeBSD 4.8-STABLE i386
>Organization:
>Environment:
System: FreeBSD xeon.example.org 4.8-STABLE FreeBSD 4.8-STABLE #0: Fri Aug 15 16:11:30 EDT 2003 dan at polo.example.org:/usr/obj/usr/src/sys/XEON i386

Also present on FreeBSD undef.example.org 5.1-RELEASE FreeBSD 5.1-RELEASE #0: Thu Jun  5 02:55:42 GMT 2003     root at wv1u.btc.adaptec.com:/usr/obj/usr/src/sys/GENERIC  i386


	
>Description:
	

As taken from http://docs.freebsd.org/cgi/mid.cgi?db=irt&id=3F4CBD13.545.1FF6190E@localhost

A return status of 0 from write is not interpreted as an End-Of-Tape.
The threads library isn't smart enough to know that the file
is a tape device and that a 0 status should break it out of the
loop.  Thus, it continues writing.

See also:

http://docs.freebsd.org/cgi/mid.cgi?db=irt&id=bilgkb$nk3$1@sea.gmane.org

>How-To-Repeat:
	

If you run this code with pthreads, you'll get a different number of records
written than if you run it without pthreads.  Tested under both 4.8 and 5.1.

/*
 *
 * Program to test loss of data at EOM on
 *  FreeBSD systems.
 *
 *	 Kern Sibbald, August 2003
 *
 *  Build this program with:
 *
 *  c++ -g -O2 -Wall -c tapetest.c
 *  c++ -g -O2 -Wall tapetest.o -o tapetest
 *
 *  Procedure for testing tape
 *  ./tapetest /dev/your-tape-device
 *  rewind
 *  rawwrite
 *  rewind
 *  scan
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <string.h>
#include <strings.h>
#include <sys/mtio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>

#define FALSE 0
#define TRUE  1

#define dev_state(dev, state) ((dev)->state & (state))

/* Device state bits */
#define ST_OPENED	   (1<<0)     /* set when device opened */
#define ST_TAPE 	   (1<<1)     /* is a tape device */  
#define ST_FILE 	   (1<<2)     /* is a file device */
#define ST_FIFO 	   (1<<3)     /* is a fifo device */
#define ST_PROG 	   (1<<4)     /* is a program device */
#define ST_LABEL	   (1<<5)     /* label found */
#define ST_MALLOC          (1<<6)     /* dev packet malloc'ed in init_dev() */
#define ST_APPEND	   (1<<7)     /* ready for Bacula append */
#define ST_READ 	   (1<<8)     /* ready for Bacula read */
#define ST_EOT		   (1<<9)     /* at end of tape */
#define ST_WEOT 	   (1<<10)    /* Got EOT on write */
#define ST_EOF		   (1<<11)    /* Read EOF i.e. zero bytes */
#define ST_NEXTVOL	   (1<<12)    /* Start writing on next volume */
#define ST_SHORT	   (1<<13)    /* Short block read */

#define BLOCK_SIZE (512 * 126)


/* Exported variables */
int quit = 0;
char buf[100000];
int verbose = 0;
int debug_level = 0;
int fd = 0;

struct DEVICE {
   int fd;
   int dev_errno;
   int file;
   int block_num;
   int state;
   char *buf;
   int buf_len;
   char *dev_name;
   int file_addr;
};

DEVICE *dev;

#define uint32_t unsigned long
#define uint64_t unsigned long long
	    
/* Forward referenced subroutines */
static void do_tape_cmds();
static void helpcmd();
static void scancmd();
static void rewindcmd();
static void rawfill_cmd();


/* Static variables */

static char cmd[1000];

static void usage();
int get_cmd(char *prompt);


/*********************************************************************
 *
 *	   Main Bacula Pool Creation Program
 *
 */
int main(int argc, char *argv[])
{
   int ch;

   while ((ch = getopt(argc, argv, "d:v?")) != -1) {
      switch (ch) {
         case 'd':                    /* set debug level */
	    debug_level = atoi(optarg);
	    if (debug_level <= 0) {
	       debug_level = 1; 
	    }
	    break;

         case 'v':
	    verbose++;
	    break;

         case '?':
	 default:
	    helpcmd();
	    exit(0);

      }  
   }
   argc -= optind;
   argv += optind;


   /* See if we can open a device */
   if (argc == 0) {
      printf("No archive name specified.\n");
      usage();
      exit(1);
   } else if (argc != 1) {
      printf("Improper number of arguments specified.\n");
      usage();
      exit(1);
   }

   fd = open(argv[0], O_RDWR);	 
   if (fd < 0) {
      printf("Error opening %s ERR=%s\n", argv[0], strerror(errno));
      exit(1);
   }
   dev = (DEVICE *)malloc(sizeof(DEVICE));
   memset(dev, 0, sizeof(DEVICE));
   dev->fd = fd;
   dev->dev_name = strdup(argv[0]);
   dev->buf_len = BLOCK_SIZE;
   dev->buf = (char *)malloc(BLOCK_SIZE);

   do_tape_cmds();
   return 0;
}


int rewind_dev(DEVICE *dev)
{
   struct mtop mt_com;

   if (dev->fd < 0) {
      dev->dev_errno = EBADF;
      printf("Bad call to rewind_dev. Device %s not open\n",
	    dev->dev_name);
      return 0;
   }
   dev->state &= ~(ST_APPEND|ST_READ|ST_EOT|ST_EOF|ST_WEOT);  /* remove EOF/EOT flags */
   dev->block_num = dev->file = 0;
   mt_com.mt_op = MTREW;
   mt_com.mt_count = 1;
   if (ioctl(dev->fd, MTIOCTOP, (char *)&mt_com) < 0) {
      dev->dev_errno = errno;
      printf("Rewind error on %s. ERR=%s.\n",
	       dev->dev_name, strerror(dev->dev_errno));
      return 0;
   }
   return 1;
}

/*
 * Write an end of file on the device
 *   Returns: 0 on success
 *	      non-zero on failure
 */
int 
weof_dev(DEVICE *dev, int num)
{ 
   struct mtop mt_com;
   int stat;

   if (dev->fd < 0) {
      dev->dev_errno = EBADF;
      printf("Bad call to fsf_dev. Archive not open\n");
      return -1;
   }

   dev->state &= ~(ST_EOT | ST_EOF);  /* remove EOF/EOT flags */
   dev->block_num = 0;
   printf("weof_dev\n");
   mt_com.mt_op = MTWEOF;
   mt_com.mt_count = num;
   stat = ioctl(dev->fd, MTIOCTOP, (char *)&mt_com);
   if (stat == 0) {
      dev->file++;
      dev->file_addr = 0;
   } else {
      dev->dev_errno = errno;
      printf("ioctl MTWEOF error on %s. ERR=%s.\n",
	 dev->dev_name, strerror(dev->dev_errno));
   }
   return stat;
}





void quitcmd()
{
   quit = 1;
}


/*
 * Rewind the tape.   
 */
static void rewindcmd()
{
   if (!rewind_dev(dev)) {
      printf("Bad status from rewind. ERR=%s\n", strerror(dev->dev_errno));
   } else {
      printf("Rewound %s\n", dev->dev_name);
   }
}

/*
 * Write and end of file on the tape   
 */
static void weofcmd()
{
   int stat;

   if ((stat = weof_dev(dev, 1)) < 0) {
      printf("Bad status from weof %d. ERR=%s\n", stat, strerror(dev->dev_errno));
      return;
   } else {
      printf("Wrote EOF to %s\n", dev->dev_name);
   }
}


/* 
 * Read a record from the tape
 */
static void rrcmd()
{
   char *buf;
   int stat, len;

   if (!get_cmd("Enter length to read: ")) {
      return;
   }
   len = atoi(cmd);
   if (len < 0 || len > 1000000) {
      printf("Bad length entered, using default of 1024 bytes.\n");
      len = 1024;
   }
   buf = (char *)malloc(len);
   stat = read(fd, buf, len);
   if (stat > 0 && stat <= len) {
      errno = 0;
   }
   printf("Read of %d bytes gives stat=%d. ERR=%s\n",
      len, stat, strerror(errno));
   free(buf);
}

/* 
 * Write a record to the tape
 */
static void wrcmd()
{
   int stat;
   int rfd;

   rfd = open("/dev/urandom", O_RDONLY);
   if (rfd) {
      read(rfd, dev->buf, dev->buf_len);
   } else {
      printf("Cannot open /dev/urandom.\n");
      return;
   }
   printf("Write one block of %u bytes.\n", dev->buf_len);
   stat = write(dev->fd, dev->buf, dev->buf_len);
   if (stat != (int)dev->buf_len) {
      if (stat == -1) {
         printf("Bad status from write. ERR=%s\n", strerror(errno));
      } else {
         printf("Expected to write %d bytes but wrote only %d.\n",
	    dev->buf_len, stat);
      }
   }
}



/*
 * Scan tape by reading block by block. Report what is
 * on the tape.  Note, this command does raw reads, and as such
 * will not work with fixed block size devices.
 */
static void scancmd()
{
   int stat;
   int blocks, tot_blocks, tot_files;
   int block_size;
   uint64_t bytes;


   blocks = block_size = tot_blocks = 0;
   bytes = 0;
   if (dev->state & ST_EOT) {
      printf("End of tape\n");
      return; 
   }
   tot_files = dev->file;
   printf("Starting scan at file %u\n", dev->file);
   for (;;) {
      if ((stat = read(dev->fd, buf, sizeof(buf))) < 0) {
	 dev->dev_errno = errno;
         printf("Bad status from read %d. ERR=%s\n", stat, strerror(dev->dev_errno));
	 if (blocks > 0)
            printf("%d block%s of %d bytes in file %d\n",        
                    blocks, blocks>1?"s":"", block_size, dev->file);
	 return;
      }
      if (stat != block_size) {
	 if (blocks > 0) {
            printf("%d block%s of %d bytes in file %d\n", 
                 blocks, blocks>1?"s":"", block_size, dev->file);
	    blocks = 0;
	 }
	 block_size = stat;
      }
      if (stat == 0) {		      /* EOF */
         printf("End of File mark.\n");
	 /* Two reads of zero means end of tape */
	 if (dev->state & ST_EOF)
	    dev->state |= ST_EOT;
	 else {
	    dev->state |= ST_EOF;
	    dev->file++;
	 }
	 if (dev->state & ST_EOT) {
            printf("End of tape\n");
	    break;
	 }
      } else {			      /* Got data */
	 dev->state &= ~ST_EOF;
	 blocks++;
	 tot_blocks++;
	 bytes += stat;
      }
   }
   tot_files = dev->file - tot_files;
   printf("Total files=%d, blocks=%d, bytes = %d\n", tot_files, tot_blocks, 
      (int)bytes);			   
}


static void rawfill_cmd()
{
   int stat;
   int rfd;
   uint32_t block_num = 0;
   uint32_t *p;
   int my_errno;

   rfd = open("/dev/urandom", O_RDONLY);
   if (rfd) {
      read(rfd, dev->buf, dev->buf_len);
   } else {
      printf("Cannot open /dev/urandom.\n");
      return;
   }
   p = (uint32_t *)dev->buf;
   printf("Begin writing blocks of %u bytes.\n", dev->buf_len);
   for ( ;; ) {
      *p = block_num;
      stat = write(dev->fd, dev->buf, dev->buf_len);
      if (stat == (int)dev->buf_len) {
	 if ((block_num++ % 100) == 0) {
            printf("+");
	    fflush(stdout);
	 }
	 continue;
      }
      break;
   }
   my_errno = errno;
   printf("\n");
   weofcmd();
   printf("Write failed.  Last block written=%d. stat=%d ERR=%s\n", (int)block_num, stat,
      strerror(my_errno));

}

/* Strip any trailing junk from the command */
void strip_trailing_junk(char *cmd)
{
   char *p;
   p = cmd + strlen(cmd) - 1;

   /* strip trailing junk from command */
   while ((p >= cmd) && (*p == '\n' || *p == '\r' || *p == ' '))
      *p-- = 0;
}

/* folded search for string - case insensitive */
int
fstrsch(char *a, char *b)   /* folded case search */
{
   register char *s1,*s2;
   register char c1=0, c2=0;

   s1=a;
   s2=b;
   while (*s1) {		      /* do it the fast way */
      if ((*s1++ | 0x20) != (*s2++ | 0x20))
	 return 0;		      /* failed */
   }
   while (*a) { 		      /* do it over the correct slow way */
      if (isupper(c1 = *a)) {
	 c1 = tolower((int)c1);
      }
      if (isupper(c2 = *b)) {
	 c2 = tolower((int)c2);
      }
      if (c1 != c2) {
	 return 0;
      }
      a++;
      b++;
   }
   return 1;
}
   

struct cmdstruct { char *key; void (*func)(); char *help; }; 
static struct cmdstruct commands[] = {
 {"help",       helpcmd,      "print this command"},
 {"quit",       quitcmd,      "quit tapetest"},   
 {"rawfill",    rawfill_cmd,  "use write() to fill tape"},
 {"rewind",     rewindcmd,    "rewind the tape"},
 {"rr",         rrcmd,        "raw read the tape"},
 {"wr",         wrcmd,        "raw write one block to the tape"},
 {"scan",       scancmd,      "read() tape block by block to EOT and report"}, 
 {"weof",       weofcmd,      "write an EOF on the tape"},
	     };
#define comsize (sizeof(commands)/sizeof(struct cmdstruct))

static void
do_tape_cmds()
{
   unsigned int i;
   int found;

   while (get_cmd("*")) {
      found = 0;
      for (i=0; i<comsize; i++)       /* search for command */
	 if (fstrsch(cmd,  commands[i].key)) {
	    (*commands[i].func)();    /* go execute command */
	    found = 1;
	    break;
	 }
      if (!found) {
         printf("%s is an illegal command\n", cmd);
      }
      if (quit) {
	 break;
      }
   }
}

static void helpcmd()
{
   unsigned int i;
   usage();
   printf("  Command    Description\n  =======    ===========\n");
   for (i=0; i<comsize; i++)
      printf("  %-10s %s\n", commands[i].key, commands[i].help);
   printf("\n");
}

static void usage()
{
   fprintf(stderr,
"Usage: tapetest [-d debug_level] [device_name]\n"
"       -dnn        set debug level to nn\n"
"       -?          print this message.\n"  
"\n");

}



/*	
 * Get next input command from terminal.  This
 * routine is REALLY primitive, and should be enhanced
 * to have correct backspacing, etc.
 */
int 
get_cmd(char *prompt)
{
   int i = 0;
   int ch;
   fprintf(stdout, prompt);

   /* We really should turn off echoing and pretty this
    * up a bit.
    */
   cmd[i] = 0;
   while ((ch = fgetc(stdin)) != EOF) { 
      if (ch == '\n') {
	 strip_trailing_junk(cmd);
	 return 1;
      } else if (ch == 4 || ch == 0xd3 || ch == 0x8) {
	 if (i > 0)
	    cmd[--i] = 0;
	 continue;
      } 
	 
      cmd[i++] = ch;
      cmd[i] = 0;
   }
   quit = 1;
   return 0;
}


>Fix:

	


See http://docs.freebsd.org/cgi/mid.cgi?db=irt&id=bilgkb$nk3$1@sea.gmane.org
>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list