delorie.com/archives/browse.cgi   search  
Mail Archives: djgpp-workers/2000/12/24/09:44:59

Sender: richdawe AT bigfoot DOT com
Message-ID: <3A460B93.2347528B@bigfoot.com>
Date: Sun, 24 Dec 2000 14:43:31 +0000
From: Richard Dawe <richdawe AT bigfoot DOT com>
X-Mailer: Mozilla 4.51 [en] (X11; I; Linux 2.2.17 i586)
X-Accept-Language: de,fr
MIME-Version: 1.0
To: DJGPP workers <djgpp-workers AT delorie DOT com>
CC: Damian Yerrick <d_yerrick AT hotmail DOT com>
Subject: An implementation of /dev/zero for DJGPP
Reply-To: djgpp-workers AT delorie DOT com

Hello.

Please find below an implementation of /dev/zero for DJGPP. Some tests are
included - build the source standalone using 'gcc -g -DTEST -o dev_zero
dev_zero.c. Currently unimplemented are:

- fcntl() support
- ioctl() support
- link() support

dup() and dup2() on /dev/zero will be supported when FSEXT is supported by
those functions.

If this code looks sane, I'd like it to incorporated into DJGPP. So, where
is the best place to put this source? src/libc/fsext? Where should
init_dev_zero_handler() be called from in the libc startup sequence?

I also intend to use this code in the Fileutils 4.0 port to support the
popular command-line 'dd if=/dev/zero ...'.

Comments, criticism, patches, etc. welcome. Oh yeah, Merry Christmas too!
=)

Bye, Rich =]

--- start dev_zero.c ---
/*
 * dev_zero.c - An implementation of /dev/zero for DJGPP
 * Copyright (C) 2000 by Richard Dawe
 */

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <time.h>
#include <fcntl.h>
#include <io.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/fsext.h>

#define DEV_ZERO_PATH "/dev/zero"

typedef struct {
  int open_mode;  
  int dup_count; /* Support for dup() - reference count. */
} DEV_ZERO_DATA;

static int    dev_zero_inited = 0;

/* stat(), fstat() support */
static time_t dev_zero_atime;
static time_t dev_zero_ctime;
static ino_t  dev_zero_inode  = 0;
static time_t dev_zero_mtime;

/* TODO: Get this declaration in a better way. */
extern ino_t _invent_inode (const char *name,
                            unsigned time_stamp,
                            unsigned long fsize);

/* --------------------------
 * - dev_zero_stat_internal -
 * -------------------------- */

/* This sets up the "obvious" fields of 'struct stat'. */

static int
dev_zero_stat_internal (struct stat *sbuf)
{
  sbuf->st_atime   = dev_zero_atime;
  sbuf->st_ctime   = dev_zero_ctime;
  sbuf->st_dev     = -1;
  sbuf->st_gid     = getgid();
  sbuf->st_ino     = dev_zero_inode;
  /* Everyone can read & write the zero device; it's a character device.
*/
  sbuf->st_mode    =
S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH|S_IFCHR;
  sbuf->st_mtime   = dev_zero_mtime;
  sbuf->st_nlink   = 1;
  sbuf->st_size    = 0;
  sbuf->st_blksize = 0;
  sbuf->st_uid     = getuid();

#ifdef HAVE_ST_RDEV
  sbuf->st_rdev    = -1;
#endif

  return(1);
}

/* --------------------
 * - dev_zero_handler -
 * -------------------- */

static int
dev_zero_handler (__FSEXT_Fnumber n, int *rv, va_list args)
{
  int            emul       = 0; /* Emulated call? 1 => yes, 0 = no. */
  int            fd         = 0;
#ifdef DJGPP_SUPPORTS_FSEXT_DUP_NOW
  int            new_fd     = 0;
#endif /* DJGPP_SUPPORTS_FSEXT_DUP_NOW */
  DEV_ZERO_DATA *data       = NULL;  
  char          *filename   = NULL;
  int            open_mode  = 0;
  int            perm       = 0;
  mode_t         creat_mode = 0;  
  void          *buf        = NULL;
  size_t         buflen     = 0;
  off_t          offset     = 0;
  int            whence     = 0;
  struct stat   *sbuf       = NULL;

  switch(n) {
  case __FSEXT_nop:
    break;

  case __FSEXT_open:
    filename  = va_arg(args, char *);
    open_mode = va_arg(args, int);

    if (open_mode & O_CREAT)
      perm = va_arg(args, int);

    if (strcmp(filename, DEV_ZERO_PATH) != 0)
      break;
    
    /* It's for us. */
    emul = 1;

    /* TODO: Check whether we've already opened the zero device. */

    /* zero device _always_ exists. */
    if (open_mode & O_CREAT) {
      errno = EEXIST;
      *rv   = -1;       
      break;
    }

    /* Allocate a file descriptor for the device. */
    fd = __FSEXT_alloc_fd(dev_zero_handler);
    if (fd < 0) {
      *rv = fd;
      break;
    }

    /* Allocate some fd context. */
    data = (DEV_ZERO_DATA *) malloc(sizeof(*data));
    if (data == NULL) {
      errno = ENOMEM;
      *rv   = -1;
      break;
    }
    
    /* Set up the context. */
    data->open_mode = open_mode; /* Save open mode for read(), write() */
    data->dup_count = 1;

    /* Associate the context with the fd. */
    if (__FSEXT_set_data(fd, (void *) data) == NULL) {
      errno = ENOMEM; /* TODO: Right error? */
      *rv   = -1;
      break;
    }

    /* Done */
    *rv = fd;
    break;

  case __FSEXT_creat:
    filename   = va_arg(args, char *);
    creat_mode = va_arg(args, mode_t);

    if (strcmp(filename, DEV_ZERO_PATH) != 0)
      break;

    /* zero device _always_ exists. */
    emul  = 1;
    errno = EEXIST;
    *rv   = -1;
    break;

  case __FSEXT_read:
    fd     = va_arg(args, int);
    buf    = va_arg(args, void *);
    buflen = va_arg(args, size_t);

    /* This must be emulated, since the FSEXT has been called. */
    emul = 1;

    /* Get context */
    data = (DEV_ZERO_DATA *) __FSEXT_get_data(fd);
    if (data == NULL) {
      errno = EBADF; /* TODO: Right error? */
      *rv   = -1;
      break;
    }

    /* Can we actually read from the zero device? */
    if (   ((data->open_mode & O_ACCMODE) != O_RDONLY)
        && ((data->open_mode & O_ACCMODE) != O_RDWR) ) {
      errno = EACCES;
      *rv   = -1;
      break;
    }

    /* Is the specified length bigger than the max return value? */
    if (buflen > INT_MAX) {
      errno = EINVAL; /* TODO: Right error? */
      *rv   = -1;
      break;
    }

    /* Now read - just zero the buffer. */
    memset(buf, '\0', buflen);

    /* Update access time */
    time(&dev_zero_atime);

    *rv = (int) buflen;
    break;

  case __FSEXT_write:
    fd     = va_arg(args, int);
    buf    = va_arg(args, void *);
    buflen = va_arg(args, size_t);

    /* This must be emulated, since the FSEXT has been called. */
    emul = 1;

    /* Get context */
    data = (DEV_ZERO_DATA *) __FSEXT_get_data(fd);
    if (data == NULL) {
      errno = EBADF; /* TODO: Right error? */
      *rv   = -1;
      break;
    }

    /* Can we actually write to the zero device? */
    if (   ((data->open_mode & O_ACCMODE) != O_WRONLY)
        && ((data->open_mode & O_ACCMODE) != O_RDWR) ) {
      errno = EACCES;
      *rv   = -1;
      break;
    }

    /* Is the specified length bigger than the max return value? */
    if (buflen > INT_MAX) {
      errno = EINVAL; /* TODO: Right error? */
      *rv   = -1;
      break;
    }

    /* Now write - just ignore the buffer. */

    /* Update modification time */
    time(&dev_zero_mtime);
    
    *rv = (int) buflen;    
    break;

  case __FSEXT_ready:
    /* This must be emulated, since the FSEXT has been called. */
    emul = 1;

    *rv  = __FSEXT_ready_read | __FSEXT_ready_write;    
    break;

  case __FSEXT_close:
    fd = va_arg(args, int);

    /* This must be emulated, since the FSEXT has been called. */
    emul = 1;

    /* Get context */
    data = (DEV_ZERO_DATA *) __FSEXT_get_data(fd);
    if (data == NULL) {
      errno = EBADF; /* TODO: Right error? */
      *rv   = -1;
      break;
    }

    __FSEXT_set_data(fd, NULL);    
    __FSEXT_set_function(fd, NULL);

    /* Cope with dup()'d zero devices. */
    data->dup_count--;

    if (data->dup_count <= 0) {
      /* No longer referenced */
      free(data);
      _close(fd);
    }
    break;

  case __FSEXT_fcntl:
  case __FSEXT_ioctl:
    /* TODO: If appropriate? */
    break;

  case __FSEXT_lseek:
    fd     = va_arg(args, int);
    offset = va_arg(args, off_t);
    whence = va_arg(args, int);    

    /* This must be emulated, since the FSEXT has been called. */
    emul = 1;

    /* TODO: off_t -> int casting OK? */
    *rv = offset;
    break;

  case __FSEXT_link:
    /* Ignore request */
    break;

  case __FSEXT_unlink:    
    /* This must be emulated, since the FSEXT has been called. */
    emul  = 1;

    /* The zero device cannot be removed. */
    errno = EPERM;
    *rv   = -1;
    break;

  case __FSEXT_dup:
#ifdef DJGPP_SUPPORTS_FSEXT_DUP_NOW
    fd = va_arg(args, int);

    /* Allocate a new file descriptor. */
    new_fd = __FSEXT_alloc_fd(dev_zero_handler);
    if (new_fd < 0) {
      *rv = fd;
      break;
    }

    /* Get context */
    data = (DEV_ZERO_DATA *) __FSEXT_get_data(fd);
    if (data == NULL) {
      errno = EBADF; /* TODO: Right error? */
      *rv   = -1;
      break;
    }

    /* Associate the context with the new fd too. */
    if (__FSEXT_set_data(new_fd, (void *) data) == NULL) {
      errno = ENOMEM; /* TODO: Right error? */
      *rv   = -1;
      break;
    }

    data->dup_count++;

    /* Done */
    *rv = new_fd;
#endif /* DJGPP_SUPPORTS_FSEXT_DUP_NOW */
    break;

  case __FSEXT_dup2:
    /* TODO: When __FSEXT_dup is supported, add support for __FSEXT_dup2.
*/
    break;

  case __FSEXT_stat:
    filename  = va_arg(args, char *);
    sbuf      = va_arg(args, struct stat *);

    if (strcmp(filename, DEV_ZERO_PATH) != 0)
      break;
    
    /* It's for us. */
    emul = 1;

    /* Set up the stat buf */
    memset(sbuf, 0, sizeof(*sbuf));
    dev_zero_stat_internal(sbuf);
    /* TODO */

    *rv  = 0;
    break;

  case __FSEXT_fstat:
    fd   = va_arg(args, int);
    sbuf = va_arg(args, struct stat *);

    /* This must be emulated, since the FSEXT has been called. */
    emul = 1;

    /* Get context */
    data = (DEV_ZERO_DATA *) __FSEXT_get_data(fd);
    if (data == NULL) {
      errno = EBADF; /* TODO: Right error? */
      *rv   = -1;
      break;
    }

    /* Set up the stat buf */
    memset(sbuf, 0, sizeof(*sbuf));
    dev_zero_stat_internal(sbuf);

    *rv = 0;
    break;
  }  

  return(emul);
}

/* -------------------------
 * - init_dev_zero_handler -
 * ------------------------- */

int
init_dev_zero_handler (void)
{
  if (dev_zero_inited)
    return(dev_zero_inited);

  __FSEXT_add_open_handler(dev_zero_handler);

  time(&dev_zero_ctime);
  dev_zero_atime  = dev_zero_mtime = dev_zero_ctime;
  dev_zero_inode  = _invent_inode(DEV_ZERO_PATH, 0, 0);
  dev_zero_inited = 1;

  return(1);
}

#ifdef TEST

#include <stdio.h>
#include <assert.h>

static int
jumble_buffer (char *buf, size_t buflen)
{
  size_t i;

  for (i = 0; i < buflen; i++) {
    buf[i] = random() % 0xff;
  }

  return(1);
}

static int
dump_stat (struct stat *sbuf)
{
  printf("st_atime   = %d\n"
         "st_ctime   = %d\n"
         "st_dev     = %d\n"
         "st_gid     = %d\n"
         "st_ino     = %d\n"
         "st_mode    = %d\n"
         "st_mtime   = %d\n"
         "st_nlink   = %d\n"
         "st_size    = %d\n"
         "st_blksize = %d\n"
         "st_uid     = %d\n",
         sbuf->st_atime, sbuf->st_ctime, sbuf->st_dev,
         sbuf->st_gid, sbuf->st_ino, sbuf->st_mode,
         sbuf->st_mtime, sbuf->st_nlink, sbuf->st_size,
         sbuf->st_blksize, sbuf->st_uid);

  return(1);
}

int
main (int argc, char *argv[])
{
  char           buf[32768];
  int            fd     = 0;
#ifdef DJGPP_SUPPORTS_FSEXT_DUP_NOW
  int            new_fd = 0;
#endif /* DJGPP_SUPPORTS_FSEXT_DUP_NOW */
  fd_set         readfds, writefds;
  struct timeval tv;
  struct stat    sbuf;
  int            n      = 0;
  size_t         i      = 0;

  if (!init_dev_zero_handler()) {
    fprintf(stderr, "init_dev_zero_handler() failed\n");
    return(EXIT_FAILURE);
  }

  /* - Generic tests of /dev/zero. - */
  fd = open(DEV_ZERO_PATH, O_RDWR);
  if (fd == -1) {
    fprintf(stderr,
            "Unable to open " DEV_ZERO_PATH ": %s\n", strerror(errno));
    return(EXIT_FAILURE);
  }

  /* Write buffer into /dev/zero. */
  jumble_buffer(buf, sizeof(buf));

  n = write(fd, buf, sizeof(buf));
  if (n < 0) {
    fprintf(stderr,
            "Unable to write %lu bytes to " DEV_ZERO_PATH ": %s\n",
            sizeof(buf), strerror(errno));
    return(EXIT_FAILURE);
  }

  assert(((size_t) n) == sizeof(buf));

  /* Zero buffer by reading from /dev/zero. */
  n = read(fd, buf, sizeof(buf));
  if (n < 0) {
    fprintf(stderr,
            "Unable to read %lu bytes from " DEV_ZERO_PATH ": %s\n",
            sizeof(buf), strerror(errno));
    return(EXIT_FAILURE);
  }

  assert(((size_t) n) == sizeof(buf));

  for (i = 0; i < sizeof(buf); i++) {
    if (buf[i] != '\0') {
      fprintf(stderr, "Byte %lu in read data is non-zero\n", i);
      return(EXIT_FAILURE);
    }
  }

  close(fd);

  /* - Test /dev/zero opened read-only. - */
  fd = open(DEV_ZERO_PATH, O_RDONLY);
  if (fd == -1) {
    fprintf(stderr,
            "Unable to open " DEV_ZERO_PATH " read-only: %s\n",
            strerror(errno));
    return(EXIT_FAILURE);
  }

  /* Check that writing fails. */
  jumble_buffer(buf, sizeof(buf));

  n = write(fd, buf, sizeof(buf));
  if (n >= 0) {
    fprintf(stderr,
            "Able to write %lu bytes to "
            DEV_ZERO_PATH " when read-only: %s\n",
            sizeof(buf), strerror(errno));
    return(EXIT_FAILURE);
  }

  /* Zero buffer by reading from /dev/zero. */
  n = read(fd, buf, sizeof(buf));

  if (n < 0) {
    fprintf(stderr,
            "Unable to read %lu bytes from "
            DEV_ZERO_PATH " when read-only: %s\n",
            sizeof(buf), strerror(errno));
    return(EXIT_FAILURE);
  }

  assert(((size_t) n) == sizeof(buf));

  close(fd);

  /* - Test /dev/zero opened write-only. - */
  fd = open(DEV_ZERO_PATH, O_WRONLY);
  if (fd == -1) {
    fprintf(stderr,
            "Unable to open " DEV_ZERO_PATH " write-only: %s\n",
            strerror(errno));
    return(EXIT_FAILURE);
  }

  /* Write buffer into /dev/zero. */
  jumble_buffer(buf, sizeof(buf));

  n = write(fd, buf, sizeof(buf));
  if (n < 0) {
    fprintf(stderr,
            "Unable to write %lu bytes to "
            DEV_ZERO_PATH " when write-only: %s\n",
            sizeof(buf), strerror(errno));
    return(EXIT_FAILURE);
  }

  assert(((size_t) n) == sizeof(buf));

  /* Check that reading fails. */  
  n = read(fd, buf, sizeof(buf));
  if (n >= 0) {
    fprintf(stderr,
            "Able to read %lu bytes from "
            DEV_ZERO_PATH " when write-only: %s\n",
            sizeof(buf), strerror(errno));
    return(EXIT_FAILURE);
  }

  close(fd);

  /* - Check that creat() fails. - */
  fd = creat(DEV_ZERO_PATH, S_IRUSR|S_IWUSR);
  if (fd >= 0) {
    fprintf(stderr,
            "creat() succeeded in creating "
            DEV_ZERO_PATH " - it should fail\n");
    return(EXIT_FAILURE);
  }

  /* - Check that open() fails, when using O_CREAT. - */
  fd = open(DEV_ZERO_PATH, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
  if (fd >= 0) {
    fprintf(stderr,
            "open() succeeded in creating "
            DEV_ZERO_PATH " - it should fail\n");
    return(EXIT_FAILURE);
  }

  /* - Check select() support. - */
  fd = open(DEV_ZERO_PATH, O_RDWR);
  if (fd == -1) {
    fprintf(stderr,
            "Unable to open " DEV_ZERO_PATH ": %s\n", strerror(errno));
    return(EXIT_FAILURE);
  }

  FD_ZERO(&readfds);
  FD_ZERO(&writefds);
  memset(&tv, 0, sizeof(tv));

  FD_SET(fd, &readfds);
  FD_SET(fd, &writefds);

  n = select(fd + 1, &readfds, &writefds, NULL, &tv);
  if (n < 0) {
    fprintf(stderr,
            "select() on " DEV_ZERO_PATH "failed: %s\n", strerror(errno));
    return(EXIT_FAILURE);
  }

  if (!FD_ISSET(fd, &readfds)) {
    fprintf(stderr, "Expected " DEV_ZERO_PATH " to be ready for
reading\n");
    return(EXIT_FAILURE);
  }

  if (!FD_ISSET(fd, &writefds)) {
    fprintf(stderr, "Expected " DEV_ZERO_PATH " to be ready for
writing\n");
    return(EXIT_FAILURE);
  }

  close(fd);

  /* TODO: Test seeking, ioctl, fcntl, link, etc. */

  /* - Check link() fails - */
  /* TODO */

  /* - Check unlink() fails - */
  n = unlink(DEV_ZERO_PATH);
  if (n >= 0) {
    fprintf(stderr,
            "unlink() succeeded in removing "
            DEV_ZERO_PATH " - it should fail\n");
    return(EXIT_FAILURE);
  }

  /* - Check dup works - */
#ifdef DJGPP_SUPPORTS_FSEXT_DUP_NOW
  fd = open(DEV_ZERO_PATH, O_RDWR);
  if (fd == -1) {
    fprintf(stderr,
            "Unable to open " DEV_ZERO_PATH ": %s\n", strerror(errno));
    return(EXIT_FAILURE);
  }

  new_fd = dup(fd);
  if (new_fd < 0) {
    fprintf(stderr,
            "Unable do dup file descriptor for " DEV_ZERO_PATH ": %s\n",
            strerror(errno));
    return(EXIT_FAILURE);
  }

  close(fd);

  /* Zero buffer by reading from /dev/zero. */
  n = read(new_fd, buf, sizeof(buf));
  if (n < 0) {
    fprintf(stderr,
            "Unable to read %lu bytes from " DEV_ZERO_PATH ": %s\n",
            sizeof(buf), strerror(errno));
    return(EXIT_FAILURE);
  }

  assert(((size_t) n) == sizeof(buf));

  close(new_fd);
#endif /* DJGPP_SUPPORTS_FSEXT_DUP_NOW */

  /* - Check stat() works - */
  if (stat(DEV_ZERO_PATH, &sbuf) < 0) {
    fprintf(stderr,
            "Unable to stat() " DEV_ZERO_PATH ": %s\n", strerror(errno));
    return(EXIT_FAILURE);
  }

  printf("stat() result:\n");
  dump_stat(&sbuf);

  /* - Check fstat() works - */
  fd = open(DEV_ZERO_PATH, O_RDWR);
  if (fd == -1) {
    fprintf(stderr,
            "Unable to open " DEV_ZERO_PATH ": %s\n", strerror(errno));
    return(EXIT_FAILURE);
  }

  sleep(1);

  n = read(fd, buf, sizeof(buf));
  if (n < 0) {
    fprintf(stderr,
            "Unable to read %lu bytes from " DEV_ZERO_PATH ": %s\n",
            sizeof(buf), strerror(errno));
    return(EXIT_FAILURE);
  }

  sleep(1);

  n = write(fd, buf, sizeof(buf));
  if (n < 0) {
    fprintf(stderr,
            "Unable to write %lu bytes to " DEV_ZERO_PATH ": %s\n",
            sizeof(buf), strerror(errno));
    return(EXIT_FAILURE);
  }

  if (fstat(fd, &sbuf) < 0) {
    fprintf(stderr,
            "Unable to fstat() " DEV_ZERO_PATH ": %s\n", strerror(errno));
    return(EXIT_FAILURE);
  }

  printf("fstat() result:\n");
  dump_stat(&sbuf);

  close(fd);

  return(EXIT_SUCCESS);
}

#endif /* TEST */
--- end dev_zero.c ---

- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019