Chapter 8: The UNIX System Interface.

Original C examples from the existing C programming archive. Source text is preserved; these historical examples have not been compiled or tested against a current toolchain.

test.c

This allocator variant defines block headers, a free list and routines intended to obtain more memory and coalesce freed blocks. Its main does not exercise them. The allocation search never calls morecore when the list is exhausted, and the preserved =+ expression in coalescing is not +=, so this is unfinished code rather than a working allocator.

#include <unistd.h>
#include <stdio.h>

#define NALLOC 1024
typedef long Align;

union header
 {
    struct {
             union header *ptr;
	     unsigned size;
	   } s;
    Align x;
 };

typedef union header Header;
static Header base;
static Header *freep = NULL;
void *malloc(unsigned nbytes);
void free(void *ap);
Header *morecore(unsigned);

int main()
{
  
 return 0;
}

void *malloc(unsigned nbytes)
{
   Header *p, *prevp;
   unsigned nunits;

   nunits = (nbytes + sizeof(Header) -1) / sizeof(Header) + 1;
   if ((prevp = freep) == NULL)
    {
       base.s.ptr = freep = prevp = &base;
       base.s.size = 0;
    }
  
   for (p=prevp->s.ptr;  ; prevp=p, p=p->s.ptr)
    if (p->s.size >= nunits)
      {
        if (p->s.size == nunits) prevp->s.ptr = p->s.ptr;
        else
          {
             p->s.size -= nunits;
             p += p->s.size;
             p->s.size = nunits;
          }
   freep = prevp;
   return (void *)(p+1);
 }
}
 
Header *morecore(unsigned nu)
{
  char *cp;
  Header *up;
  
  if (nu < NALLOC) nu = NALLOC;
  cp = sbrk(nu * sizeof(Header));
  if (cp == (char *) -1) return NULL;
  up = (Header *) cp;
  up->s.size = nu;
  free((void *)(up +1));
  return freep;
}

void free(void *ap)
{
  Header *bp, *p;
  bp = (Header *)ap - 1;
  for (p=freep; !(bp>p && bp<p->s.ptr); p=p->s.ptr)
   if (p >= p->s.ptr && (bp > p || bp < p->s.ptr)) break;

  if (bp + bp->s.size == p->s.ptr)
   {
     bp->s.size =+ p->s.ptr->s.size;
     bp->s.ptr = p->s.ptr->s.ptr;
   }
  else
    bp->s.ptr = p->s.ptr;

  if (p + p->s.size == bp)
   {
     p->s.size += bp->s.size;
     p->s.ptr = bp->s.ptr;
   }
  else
    p->s.ptr = bp;
  freep = p;
}
   

dirent.h

This header declares the archive custom directory abstraction: a directory state structure, an entry structure and open/read/close prototypes. It is paired with fsize.c and ls1.c rather than a standalone executable. It assumes NAME_MAX is supplied by the build environment and should not be confused with the platform standard dirent interface.

#define MAX_PATH1 1024
#define DIRSIZ	14

struct dirent1
 {
    long ino;
   char name[NAME_MAX+1];
 };
typedef struct dirent1 Dirent;

struct dir1
 {
   int fd;
   Dirent d;
 };
typedef struct dir1 DIR1;

DIR1 *opendir1(char *dirname);
Dirent *readdir1(DIR1 *dfd);
void closedir1(DIR1 *dfd);

fsize.c

This file-size walker inspects metadata with stat and intends to recurse into directories through a callback. Its custom reader depends on historical directory representations. The preserved dot-entry condition omits an equality test for the double-dot name, so ordinary children are skipped; the old directory-reading strategy is also not portable to current Linux filesystems.

#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/dir.h>
#include "dirent.h"

void fsize1(char *name);
void dirwalk(char *dir, void (*fcn)(char *));

int main(int argc, char **argv)
{
  if (argc==1) fsize1(".");
  else
   while (--argc > 0)
     fsize1(*++argv);
  return 0;
}

void fsize1(char *name)
{
  struct stat stbuf;

  if (stat(name, &stbuf) == -1)
    {
      fprintf(stderr, "fsize: can't access %s\n", name);
      return;
    }
  if ((stbuf.st_mode & S_IFMT) == S_IFDIR)
    dirwalk(name, fsize1);
  printf("%8ld %s\n", stbuf.st_size, name);
}

 
void dirwalk(char *dir, void (*fcn)(char *))
{
  char name[MAX_PATH1];
  Dirent *dp;
  DIR1 *dfd;

  if ((dfd = opendir1(dir)) == NULL)
   {
     fprintf(stderr, "dirwalk: can't open %s\n", dir);
     return;
   }

  while ((dp = readdir1(dfd)) != NULL)
    {
      if (strcmp(dp->name, ".") == 0 || strcmp(dp->name, "..")) continue;
      if (strlen(dir) + strlen(dp->name) +2 > sizeof(name))
         fprintf(stderr, "dirwalk: name %s %s too lone\n", dir, dp->name);
      else
       {
         sprintf(name, "%s/%s", dir, dp->name);
         (*fcn)(name);
    }
  }

closedir1(dfd);
}

DIR1 *opendir1(char *dirname)
{
   int fd;
   struct stat stbuf;
   DIR1 *dp;
  
   if ((fd = open(dirname, O_RDONLY, 0)) == -1 || fstat(fd, &stbuf) == -1 ||
        (stbuf.st_mode & S_IFMT) != S_IFDIR || (dp = (DIR1 *) malloc(sizeof(DIR1))) == NULL)
       return NULL;
   dp->fd = fd;
   return dp;
}

void closedir1(DIR1 *dp)
{
  if (dp)
   {
     close(dp->fd);
     free(dp);
   }
}

Dirent *readdir1(DIR1 *dp)
{
  struct direct dirbuf;
  static Dirent d;

  while (read(dp->fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf))
    {
      if (dirbuf.d_ino ==0) continue;
      d.ino = dirbuf.d_ino;
      strncpy(d.name, dirbuf.d_name, DIRSIZ);
      d.name[DIRSIZ] = '\0';
      return &d;
   }
 return NULL;
}
          
 

getchar1.c

This unbuffered getchar replacement reads one byte from file descriptor zero for every call and returns either an unsigned byte value or EOF. It demonstrates the boundary between library-style interfaces and low-level reads. The code lacks the appropriate low-level I/O declaration and does not distinguish a read error from EOF.

/* unbuffered single character input */
#include<stdio.h>

#undef getchar

int getchar(void);

int main()
 {
  int c;
  while ((c=getchar())!=EOF) putchar(c); 
  return 0;
 } 

int getchar(void)
 {
   char c;
  
   return (read(0, &c, 1) == 1) ? (unsigned char) c : EOF;
 }

getchar2.c

This buffered variation reads a block into static storage and serves subsequent characters from memory until the block is consumed. That reduces read calls compared with the one-byte version. It is a teaching sketch: low-level declarations and read-error handling still need review, and it is not a complete replacement for standard I/O.

/*simple  buffered version */ 
#include<stdio.h>

#undef getchar

int getchar(void);

int main()
 {
  int c;
  while ((c=getchar())!=EOF) putchar(c); 
  return 0;
 } 

int getchar(void)
 {
   static char buf[BUFSIZ];
   static char *bufp = buf;
   static int n = 0;

   if (n == 0)
     {
        n = read(0, buf, sizeof(buf));
        bufp = buf;
     }
   return (--n >= 0) ? (unsigned char) *bufp++ : EOF;

 }

llio.c

This descriptor-level copier reads blocks from standard input and writes each block to standard output. It demonstrates descriptors zero and one without FILE streams. Real code must handle partial writes, interrupted calls and errors; the preserved loop ignores write results and lacks the proper low-level I/O header.

#include <stdio.h>

int main()
 {
   char buf[BUFSIZ];
   int n;
  
   while ((n=read(0, buf, BUFSIZ)) > 0)
      write(1, buf, n);
   return 0;
 } 

ls1.c

This archive variant repeats the custom recursive file-size listing approach from fsize.c, using a callback and fixed-size directory-entry records. It retains the same malformed dot-entry test and historical filesystem assumptions. Keeping both files makes the archive complete, but the second name does not imply a corrected implementation.

#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/dir.h>
#include "dirent.h"

void fsize1(char *name);
void dirwalk(char *dir, void (*fcn)(char *));

int main(int argc, char **argv)
{
  if (argc==1) fsize1(".");
  else
   while (--argc > 0)
     fsize1(*++argv);
  return 0;
}

void fsize1(char *name)
{
  struct stat stbuf;

  if (stat(name, &stbuf) == -1)
    {
      fprintf(stderr, "fsize: can't access %s\n", name);
      return;
    }
  if ((stbuf.st_mode & S_IFMT) == S_IFDIR)
    dirwalk(name, fsize1);
  printf("%8ld %s\n", stbuf.st_size, name);
}

 
void dirwalk(char *dir, void (*fcn)(char *))
{
  char name[MAX_PATH1];
  Dirent *dp;
  DIR1 *dfd;

  if ((dfd = opendir1(dir)) == NULL)
   {
     fprintf(stderr, "dirwalk: can't open %s\n", dir);
     return;
   }

  while ((dp = readdir1(dfd)) != NULL)
    {
      if (strcmp(dp->name, ".") == 0 || strcmp(dp->name, "..")) continue;
      if (strlen(dir) + strlen(dp->name) +2 > sizeof(name))
         fprintf(stderr, "dirwalk: name %s %s too lone\n", dir, dp->name);
      else
       {
         sprintf(name, "%s/%s", dir, dp->name);
         (*fcn)(name);
    }
  }

closedir1(dfd);
}

DIR1 *opendir1(char *dirname)
{
   int fd;
   struct stat stbuf;
   DIR1 *dp;
  
   if ((fd = open(dirname, O_RDONLY, 0)) == -1 || fstat(fd, &stbuf) == -1 ||
        (stbuf.st_mode & S_IFMT) != S_IFDIR || (dp = (DIR1 *) malloc(sizeof(DIR1))) == NULL)
       return NULL;
   dp->fd = fd;
   return dp;
}

void closedir1(DIR1 *dp)
{
  if (dp)
   {
     close(dp->fd);
     free(dp);
   }
}

Dirent *readdir1(DIR1 *dp)
{
  struct direct dirbuf;
  static Dirent d;

  while (read(dp->fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf))
    {
      if (dirbuf.d_ino ==0) continue;
      d.ino = dirbuf.d_ino;
      strncpy(d.name, dirbuf.d_name, DIRSIZ);
      d.name[DIRSIZ] = '\0';
      return &d;
   }
 return NULL;
}
          
 

lseek.c

The get helper seeks to a requested offset and reads a selected number of bytes, illustrating random-access file I/O. main repeatedly reads prefixes of an input file. The preserved program does not validate its argument, terminate the read buffer before printing it as a string or handle open failure safely, so its displayed output is not reliable.

#include<stdio.h>
#include<fcntl.h>

int get(int fd, long pos, char *buf, int n);

int main(int argc, char *argv[])
{
   int fd, i=0;
   long pos;
   char buf[BUFSIZ];
  if (( fd = open(argv[1], O_RDONLY, 0)) == -1)
      printf("can't open %s", argv[1]);
 
 for(i=0; i<20; i+=2) 
  { get(fd, 0L, buf, i);
    printf("%s\n",buf);
  }

 return 0;
}

int get(int fd, long pos, char *buf, int n)
{
  if (lseek(fd, pos, 0) >= 0)
    return read(fd, buf, n);
  else
    return -1;
}

malloc.c

This allocator sketch manages a circular free list of header-sized units, splitting blocks and intending to merge adjacent free regions. The morecore helper requests additional memory through historical sbrk. The allocation loop never invokes it when no block fits, and =+ breaks one coalescing update; the empty main provides no working allocator demonstration.

#include <unistd.h>
#include<stdio.h>

#define NALLOC 1024
typedef long Align;

union header
 {
    struct {
             union header *ptr;
	     unsigned size;
	   } s;
    Align x;
 };

typedef union header Header;
static Header base;
static Header *freep = NULL;
void *malloc(unsigned nbytes);
void free(void *ap);
Header *morecore(unsigned);

int main()
{
 return 0;
}

void *malloc(unsigned nbytes)
{
   Header *p, *prevp;
   unsigned nunits;

   nunits = (nbytes + sizeof(Header) -1) / sizeof(Header) + 1;
   if ((prevp = freep) == NULL)
    {
       base.s.ptr = freep = prevp = &base;
       base.s.size = 0;
    }
  
   for (p=prevp->s.ptr;  ; prevp=p, p=p->s.ptr)
    if (p->s.size >= nunits)
      {
        if (p->s.size == nunits) prevp->s.ptr = p->s.ptr;
        else
          {
             p->s.size -= nunits;
             p += p->s.size;
             p->s.size = nunits;
          }
   freep = prevp;
   return (void *)(p+1);
 }
}
 
Header *morecore(unsigned nu)
{
  char *cp;
  Header *up;
  
  if (nu < NALLOC) nu = NALLOC;
  cp = sbrk(nu * sizeof(Header));
  if (cp == (char *) -1) return NULL;
  up = (Header *) cp;
  up->s.size = nu;
  free((void *)(up +1));
  return freep;
}

void free(void *ap)
{
  Header *bp, *p;
  bp = (Header *)ap - 1;
  for (p=freep; !(bp>p && bp<p->s.ptr); p=p->s.ptr)
   if (p >= p->s.ptr && (bp > p || bp < p->s.ptr)) break;

  if (bp + bp->s.size == p->s.ptr)
   {
     bp->s.size =+ p->s.ptr->s.size;
     bp->s.ptr = p->s.ptr->s.ptr;
   }
  else
    bp->s.ptr = p->s.ptr;

  if (p + p->s.size == bp)
   {
     p->s.size += bp->s.size;
     p->s.ptr = bp->s.ptr;
   }
  else
    p->s.ptr = bp;
  freep = p;
}
   

stdunfinished.c

This explicitly unfinished stdio sketch defines a custom FILE representation, stream flags and getc/putc macros, then implements part of opening and buffer filling over file descriptors. Several required definitions, declarations and flush behavior are missing or incomplete. It explains the intended layering of buffered I/O over kernel calls, not a usable standard-library substitute.

#include<fcntl.h>

#define PERMS 0666
#define	NULL	0
#define	EOF	(-1)
#define BUFSIZ	1024
#define	OPEN_MAX 20

typedef struct _iobuf
      {
	int cnt;
	char *ptr;
	char *base;
	int flag;
	int fd;
      }  FILE;

extern FILE _iob[OPEN_MAX];

#define stdin (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])

enum flags
 {
  _READ  = 01,
  _WRITE = 02,
  _UNBUF = 04,
  _EOF   = 010,
  _ERR   = 020
 };


FILE _iob[OPEN_MAX] =
{
  { 0, (char *) 0, (char *) 0, _READ, 0},
  { 0, (char *) 0, (char *) 0, _WRITE, 1},
  { 0, (char *) 0, (char *) 0, _WRITE | _UNBUF, 2},
};


int _fillbuf(FILE *);
int _flushbuf(int, FILE *);

#define feof(p)		(((p)->flag & _EOF) != 0)      
#define ferror(p)	(((p)->flag & _ERR) != 0)
#define fileno(p)	((p)->fd)
#define getc(p)		(--(p)->cnt >=0 ? (unsigned char) *(p)->ptr++ : _fillbuf(p))
#define putc(x,p)	(--(p)->cnt >=0 ? *(p)->ptr++ = (x) : _flushbuf((x),p))
#define getchar()  	getc(stdin)	
#define putchar(x)	putc((x), stdout)

FILE *fopen(char *name, char *mode);


int main(int argc, char **argv)
{
  FILE *fp1;
  int c;
  if (fp1=fopen(argv[1],"r")) putchar(getc(fp1));
  return 0;
}

FILE *fopen(char *name, char *mode)
 {
   int fd;
   FILE *fp;

  if (*mode != 'r' && *mode != 'w' && *mode != 'a') return NULL;
  for (fp = _iob; fp < _iob + OPEN_MAX; fp++)
    if ((fp->flag & (_READ | _WRITE)) == 0) break;
  if (fp >= _iob + OPEN_MAX) return NULL;
 
  if (*mode == 'w') fd = creat(name, PERMS);
  else if (*mode == 'a')
    {
       if ((fd = open(name, O_WRONLY, 0)) == -1)
         fd = creat(name, PERMS);
       lseek(fd, 0L, 2);
    }
  else
      fd = open(name, O_RDONLY, 0);
  
  if (fd == -1) return NULL;
  
  fp->fd = fd;
  fp->cnt = 0;
  fp->base = NULL;
  fp->flag = (*mode == 'r') ? _READ : _WRITE;
  return fp;
}

int _fillbuf(FILE *fp)
 {
   int bufsize;
   
   if ((fp->flag & (_READ | _EOF | _ERR)) != _READ) return EOF;
   bufsize = (fp->flag & _UNBUF) ? 1 : BUFSIZ;
   if (fp->base == NULL) 
     if ((fp->base = (char *) malloc(bufsize)) == NULL) return EOF;
   fp->ptr = fp->base;
   fp->cnt = read(fp->fd, fp->ptr, bufsize);
   if (--fp->cnt < 0)
     {
       if (fp->cnt == -1)
         fp->flag |= _EOF;
       else
         fp->flag |= _ERR;
       fp->cnt = 0;
       return EOF;
     }
  return (unsigned char) *fp->ptr++;
 }


cp1.c

This low-level file copier opens a source, creates or truncates a destination and transfers blocks, reporting failures through a variadic error function. Creating with mode 0666 is still filtered by the process umask. The preserved implementation has incomplete read/partial-write handling, missing declarations and no safe same-file protection, so it must not be used on important files.

#include<stdio.h>
#include<stdarg.h>
#include<fcntl.h>

#define PERMS 0666

void error(char *, ...);

int main(int argc, char *argv[])
{

   int f1, f2, n;
   char buf[BUFSIZ];

   if (argc != 3) error("Usage: cp1 from to");
   if ((f1 = open(argv[1], O_RDONLY, 0)) == -1)
       error("cp1: can't open %s", argv[1]);
   if ((f2 = creat(argv[2], PERMS)) == -1)
       error("cp1: can't create %s, mode %03o", argv[2], PERMS);

   while ((n = read(f1, buf, BUFSIZ)) > 0)
     if (write(f2, buf, n) != n)
        error("cp1: write error on file %s", argv[2]);
 
   return 0;
}

void error(char *fmt, ...)
 {
    va_list args;

    va_start(args, fmt);

    fprintf(stderr, "error: ");
    vfprintf(stderr, fmt, args);
    fprintf(stderr, "\n");

    va_end(args);
    exit(1);
 }