From 593a95de0133deeb66b4073e57c018622d6495a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20-rsm-=20Marek?= <rsm@disroot.org>
Date: Tue, 6 Aug 2024 02:40:01 +0200
Subject: [PATCH] Exerc 8_3.

---
 .../8_3.flush_cat/Makefile                    |   3 +
 .../8_3.flush_cat/knr_io.c                    | 139 ++++++++++++++++++
 .../8_3.flush_cat/knr_io.h                    |  44 ++++++
 chapter_8.unix_interface/8_3.flush_cat/main.c |  26 ++++
 .../8_3.flush_cat/tests/0_usage_info.tin      |   0
 .../8_3.flush_cat/tests/0_usage_info.tout     |   1 +
 .../8_3.flush_cat/tests/1_err_file_open.targs |   1 +
 .../8_3.flush_cat/tests/1_err_file_open.terr  |   1 +
 .../8_3.flush_cat/tests/1_err_file_open.tin   |   0
 .../8_3.flush_cat/tests/1_err_file_open.tout  |   0
 .../8_3.flush_cat/tests/2_file.c              |  24 +++
 .../8_3.flush_cat/tests/2_one.targs           |   1 +
 .../8_3.flush_cat/tests/2_one.tin             |   0
 .../8_3.flush_cat/tests/2_one.tout            |  24 +++
 .../8_3.flush_cat/tests/3_file0.c             |  24 +++
 .../8_3.flush_cat/tests/3_file1.c             |  86 +++++++++++
 .../8_3.flush_cat/tests/3_file2.mk            |   3 +
 .../8_3.flush_cat/tests/3_many.targs          |   1 +
 .../8_3.flush_cat/tests/3_many.tin            |   0
 .../8_3.flush_cat/tests/3_many.tout           | 113 ++++++++++++++
 20 files changed, 491 insertions(+)
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/Makefile
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/knr_io.c
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/knr_io.h
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/main.c
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/0_usage_info.tin
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/0_usage_info.tout
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.targs
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.terr
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.tin
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.tout
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/2_file.c
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/2_one.targs
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/2_one.tin
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/2_one.tout
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/3_file0.c
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/3_file1.c
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/3_file2.mk
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/3_many.targs
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/3_many.tin
 create mode 100644 chapter_8.unix_interface/8_3.flush_cat/tests/3_many.tout

diff --git a/chapter_8.unix_interface/8_3.flush_cat/Makefile b/chapter_8.unix_interface/8_3.flush_cat/Makefile
new file mode 100644
index 0000000..c61f224
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/Makefile
@@ -0,0 +1,3 @@
+BINARY=flush_cat
+
+include ../../Exercise.mk
diff --git a/chapter_8.unix_interface/8_3.flush_cat/knr_io.c b/chapter_8.unix_interface/8_3.flush_cat/knr_io.c
new file mode 100644
index 0000000..3d68a71
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/knr_io.c
@@ -0,0 +1,139 @@
+# include <stdio.h>
+# include <stdlib.h>
+# include <unistd.h>
+# include <fcntl.h>
+# include "knr_io.h"
+
+enum knr_flags {
+  KNR_FREAD  = 01,  /* file open for reading */
+  KNR_FWRITE = 02,  /* file open for writing */
+  KNR_FUNBUF = 04,  /* file is unbuffered */
+  KNR_FEOF   = 010, /* EOF has occurred on this file */
+  KNR_FERR   = 020, /* error occurred on this file */
+  KNR_FWL    = 030  /* wrote less then buffer size */
+};
+
+KNR_FILE knr_iob[KNR_OPEN_MAX] = {    /* stdin, stdout, stderr */
+  { 0, (char *) 0, (char *) 0, KNR_FREAD, 0 },
+  { 0, (char *) 0, (char *) 0, KNR_FWRITE, 1 },
+  { 0, (char *) 0, (char *) 0, KNR_FWRITE | KNR_FUNBUF, 2 }
+};
+
+/* knr_fopen:  open file, return KNR_FILE* on success, KNR_NULL on error */
+KNR_FILE *knr_fopen (char *name, char *mode)
+{
+  int fd;
+  KNR_FILE *fp;
+
+  if (*mode != 'r' && *mode != 'w' && *mode != 'a')
+    return KNR_NULL;
+  for (fp = knr_iob; fp < knr_iob + KNR_OPEN_MAX; fp++)
+    if ((fp->flag & (KNR_FREAD | KNR_FWRITE)) == 0)
+      break;              /* found free slot */
+  if (fp >= knr_iob + KNR_OPEN_MAX)   /* no free slots */
+    return KNR_NULL;
+
+  if (*mode == 'w')
+    fd = creat(name, KNR_PERMS);
+  else if (*mode == 'a') {
+    if ((fd = open(name, O_WRONLY, 0)) == -1)
+      fd = creat(name, KNR_PERMS);
+    lseek(fd, 0L, 2);
+  } else
+    fd = open(name, O_RDONLY, 0);
+  if (fd == -1)           /* couldn't access name */
+    return KNR_NULL;
+  fp->fd = fd;
+  fp->cnt = 0;
+  fp->base = KNR_NULL;
+  fp->flag = (*mode == 'r') ? KNR_FREAD : KNR_FWRITE;
+  return fp;
+}
+
+/* knr_fillbuf:  allocate and fill input buffer */
+int knr_fillbuf (KNR_FILE *fp)
+{
+  int bufsize;
+
+  if ((fp->flag & (KNR_FREAD | KNR_FEOF | KNR_FERR)) != KNR_FREAD)
+    return KNR_EOF;
+  bufsize = (fp->flag & KNR_FUNBUF) ? 1 : KNR_BUFSIZ;
+  if (fp->base == KNR_NULL)         /* no buffer yet */
+    if ((fp->base = (char*) malloc(bufsize)) == NULL)
+      return KNR_EOF;                   /* can't get buffer */
+  fp->ptr = fp->base;
+  fp->cnt = read(fp->fd, fp->ptr, bufsize);
+  if (--fp->cnt < 0) {
+    if (fp->cnt == -1)
+      fp->flag |= KNR_FEOF;
+    else
+      fp->flag |= KNR_FERR;
+    fp->cnt = 0;
+    return KNR_EOF;
+  }
+  return (unsigned char) *fp->ptr++;
+}
+
+/* knr_flushbuf:  flush output buffer, store c, return c on success, KNR_FWL on error */
+int knr_flushbuf (int c, KNR_FILE *fp)
+{
+  int bufsize;
+  
+  if ((fp->flag & (KNR_FWRITE | KNR_FEOF | KNR_FERR)) != KNR_FWRITE)
+    return KNR_FWL;
+  bufsize = (fp->flag & KNR_FUNBUF) ? 1 : KNR_BUFSIZ;
+  if (fp->base == KNR_NULL) {       /* no buffer yet */
+    if ((fp->base = (char*) malloc(bufsize)) == NULL)
+      return KNR_FWL;
+  }
+  else {
+    fp->cnt = write(fp->fd, fp->base, fp->ptr - fp->base);
+    if (fp->cnt < fp->ptr - fp->base) {
+      if (fp->cnt == -1)
+        fp->flag |= KNR_FWL;
+      fp->flag |= KNR_FERR;
+      return KNR_FWL;
+    }
+  }
+  fp->cnt = bufsize;
+  fp->ptr = fp->base;
+  fp->cnt -= 1;
+  return *fp->ptr++ = c;
+}
+
+/* knr_fflush:  flush output buffer, return 0 on success, KNR_FWL on error */
+int knr_fflush (KNR_FILE *fp)
+{
+  int bufsize;
+  
+  if ((fp->flag & (KNR_FWRITE | KNR_FEOF | KNR_FERR)) != KNR_FWRITE)
+    return KNR_FWL;
+  bufsize = (fp->flag & KNR_FUNBUF) ? 1 : KNR_BUFSIZ;
+  if (fp->base == KNR_NULL)        /* no buffer yet */
+    return KNR_FWL;
+  else {
+    fp->cnt = write(fp->fd, fp->base, fp->ptr - fp->base);
+    if (fp->cnt < fp->ptr - fp->base) {
+      if (fp->cnt == -1)
+        fp->flag |= KNR_FWL;
+      fp->flag |= KNR_FERR;
+      return KNR_FWL;
+    }
+  }
+  fp->cnt = bufsize;
+  fp->ptr = fp->base;
+  return 0;
+}
+
+/* knr_fclose:  close a stream, return 0 on success, KNR_EOF on error */
+int knr_fclose (KNR_FILE *fp)
+{
+  if ((fp->flag & (KNR_FWRITE | KNR_FEOF | KNR_FERR)) == KNR_FWRITE)
+    if (knr_fflush(fp) != 0)
+      return KNR_EOF;
+  if (close(fp->fd) == -1)
+    return KNR_EOF;
+  free(fp->base);
+  fp->flag = 0;
+  return 0;
+}
diff --git a/chapter_8.unix_interface/8_3.flush_cat/knr_io.h b/chapter_8.unix_interface/8_3.flush_cat/knr_io.h
new file mode 100644
index 0000000..3f83bbe
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/knr_io.h
@@ -0,0 +1,44 @@
+# ifndef KNR_IO_H
+# define KNR_IO_H
+
+# define KNR_PERMS 0666 /* RW for owner, group, others */
+
+# define KNR_NULL     0
+# define KNR_EOF      (-1)
+# define KNR_BUFSIZ   128
+# define KNR_OPEN_MAX 20 /* max #files open at once */
+
+typedef struct knr_iobuf {
+  int   cnt;    /* characters left */
+  char *ptr;  /* next character position */
+  char *base; /* location of buffer */
+  int   flag;   /* mode of file access */
+  int   fd;     /* file descriptor */
+} KNR_FILE;
+extern KNR_FILE knr_iob[KNR_OPEN_MAX];
+
+# define knr_stdin   (&knr_iob[0])
+# define knr_stdout  (&knr_iob[1])
+# define knr_stderr  (&knr_iob[2])
+
+/* knr_fopen:  open file, return KNR_FILE* on success, KNR_NULL on error */
+KNR_FILE *knr_fopen (char *name, char *mode);
+/* knr_fillbuf:  allocate and fill input buffer */
+int knr_fillbuf (KNR_FILE *fp);
+/* knr_flushbuf:  flush output buffer, store c, return c on success, KNR_FWL on error */
+int knr_flushbuf (int c, KNR_FILE *fp);
+/* knr_fflush:  flush output buffer, return 0 on success, KNR_FWL on error */
+int knr_fflush (KNR_FILE *fp);
+/* knr_fclose:  close a stream, return 0 on success, KNR_EOF on error */
+int knr_fclose (KNR_FILE *fp);
+
+# define knr_feof(p)    ((p)->flag & KNR_EOF) != 0)
+# define knr_ferror(p)  ((p)->flag & KNR_ERR) != 0)
+# define knr_fileno(p)  ((p)->fd)
+
+# define knr_getc(p)    (--(p)->cnt >= 0 ? (unsigned char) *(p)->ptr++ : knr_fillbuf(p))
+# define knr_putc(x,p)  (--(p)->cnt >= 0 ? *(p)->ptr++ = (x)           : knr_flushbuf((x),p))
+# define knr_getchar()  knr_getc(knr_stdin)
+# define knr_putchar(x) knr_putc((x), knr_stdout)
+
+# endif
diff --git a/chapter_8.unix_interface/8_3.flush_cat/main.c b/chapter_8.unix_interface/8_3.flush_cat/main.c
new file mode 100644
index 0000000..0d53c53
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/main.c
@@ -0,0 +1,26 @@
+# include <stdio.h>
+# include <error.h>
+# include "knr_io.h"
+
+
+int main (int argc, char *argv[])
+{
+  KNR_FILE *fp;
+  int c;
+  
+  if (argc == 1)
+    printf("Usage: %s <file1> <...fileN>\n", argv[0]);
+  else
+    while (--argc > 0) {
+      if ((fp = knr_fopen(*++argv, "r")) == KNR_NULL)
+        error(1, 0, "Can't open file %s.", *argv);
+      while ((c = knr_getc(fp)) != KNR_EOF)
+        knr_putchar(c);
+      if (knr_fclose(fp) != 0)
+        error(2, 0, "Can't close file %s.", *argv);
+    }
+  knr_fflush(knr_stdout);
+  knr_fflush(knr_stderr);
+
+  return 0;
+}
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/0_usage_info.tin b/chapter_8.unix_interface/8_3.flush_cat/tests/0_usage_info.tin
new file mode 100644
index 0000000..e69de29
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/0_usage_info.tout b/chapter_8.unix_interface/8_3.flush_cat/tests/0_usage_info.tout
new file mode 100644
index 0000000..10675e1
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/tests/0_usage_info.tout
@@ -0,0 +1 @@
+Usage: ./flush_cat <file1> <...fileN>
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.targs b/chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.targs
new file mode 100644
index 0000000..c4414a8
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.targs
@@ -0,0 +1 @@
+no_such_file.txt
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.terr b/chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.terr
new file mode 100644
index 0000000..05393a6
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.terr
@@ -0,0 +1 @@
+./flush_cat: Can't open file no_such_file.txt.
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.tin b/chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.tin
new file mode 100644
index 0000000..e69de29
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.tout b/chapter_8.unix_interface/8_3.flush_cat/tests/1_err_file_open.tout
new file mode 100644
index 0000000..e69de29
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/2_file.c b/chapter_8.unix_interface/8_3.flush_cat/tests/2_file.c
new file mode 100644
index 0000000..0eb05c7
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/tests/2_file.c
@@ -0,0 +1,24 @@
+# include <stdio.h>
+# include <error.h>
+# include "knr_io.h"
+
+
+int main (int argc, char *argv[])
+{
+  KNR_FILE *fp;
+  int c;
+  
+  if (argc == 1)
+    printf("Usage: %s <file1> <...fileN>\n", argv[0]);
+  else
+    while (--argc > 0) {
+      if ((fp = knr_fopen(*++argv, "r")) == KNR_NULL)
+        error(1, 0, "Can't open file %s.", *argv);
+      while ((c = knr_getc(fp)) != KNR_EOF)
+        putchar(c);
+      if (knr_fclose(fp) != 0)
+        error(2, 0, "Can't close file %s.", *argv);
+    }
+
+  return 0;
+}
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/2_one.targs b/chapter_8.unix_interface/8_3.flush_cat/tests/2_one.targs
new file mode 100644
index 0000000..edff2ec
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/tests/2_one.targs
@@ -0,0 +1 @@
+tests/2_file.c
\ No newline at end of file
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/2_one.tin b/chapter_8.unix_interface/8_3.flush_cat/tests/2_one.tin
new file mode 100644
index 0000000..e69de29
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/2_one.tout b/chapter_8.unix_interface/8_3.flush_cat/tests/2_one.tout
new file mode 100644
index 0000000..0eb05c7
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/tests/2_one.tout
@@ -0,0 +1,24 @@
+# include <stdio.h>
+# include <error.h>
+# include "knr_io.h"
+
+
+int main (int argc, char *argv[])
+{
+  KNR_FILE *fp;
+  int c;
+  
+  if (argc == 1)
+    printf("Usage: %s <file1> <...fileN>\n", argv[0]);
+  else
+    while (--argc > 0) {
+      if ((fp = knr_fopen(*++argv, "r")) == KNR_NULL)
+        error(1, 0, "Can't open file %s.", *argv);
+      while ((c = knr_getc(fp)) != KNR_EOF)
+        putchar(c);
+      if (knr_fclose(fp) != 0)
+        error(2, 0, "Can't close file %s.", *argv);
+    }
+
+  return 0;
+}
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/3_file0.c b/chapter_8.unix_interface/8_3.flush_cat/tests/3_file0.c
new file mode 100644
index 0000000..0eb05c7
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/tests/3_file0.c
@@ -0,0 +1,24 @@
+# include <stdio.h>
+# include <error.h>
+# include "knr_io.h"
+
+
+int main (int argc, char *argv[])
+{
+  KNR_FILE *fp;
+  int c;
+  
+  if (argc == 1)
+    printf("Usage: %s <file1> <...fileN>\n", argv[0]);
+  else
+    while (--argc > 0) {
+      if ((fp = knr_fopen(*++argv, "r")) == KNR_NULL)
+        error(1, 0, "Can't open file %s.", *argv);
+      while ((c = knr_getc(fp)) != KNR_EOF)
+        putchar(c);
+      if (knr_fclose(fp) != 0)
+        error(2, 0, "Can't close file %s.", *argv);
+    }
+
+  return 0;
+}
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/3_file1.c b/chapter_8.unix_interface/8_3.flush_cat/tests/3_file1.c
new file mode 100644
index 0000000..b0f63e5
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/tests/3_file1.c
@@ -0,0 +1,86 @@
+# include <stdio.h>
+# include <stdlib.h>
+# include <unistd.h>
+# include <fcntl.h>
+# include "knr_io.h"
+
+KNR_FILE knr_iob[KNR_OPEN_MAX] = {    /* stdin, stdout, stderr */
+  { 0, (char *) 0, (char *) 0, 0, 1, 0, 0, 0, 0 },
+  { 0, (char *) 0, (char *) 0, 1, 0, 1, 0, 0, 0 },
+  { 0, (char *) 0, (char *) 0, 2, 0, 1, 1, 0, 0 }
+};
+
+/* knr_fopen:  open file, return KNR_FILE* on success, KNR_NULL on error */
+KNR_FILE *knr_fopen (char *name, char *mode)
+{
+  int fd;
+  KNR_FILE *fp;
+
+  if (*mode != 'r' && *mode != 'w' && *mode != 'a')
+    return KNR_NULL;
+  for (fp = knr_iob; fp < knr_iob + KNR_OPEN_MAX; fp++)
+    if (fp->f_read == 0 && fp->f_write == 0)
+      break;              /* found free slot */
+  if (fp >= knr_iob + KNR_OPEN_MAX)   /* no free slots */
+    return KNR_NULL;
+
+  if (*mode == 'w')
+    fd = creat(name, KNR_PERMS);
+  else if (*mode == 'a') {
+    if ((fd = open(name, O_WRONLY, 0)) == -1)
+      fd = creat(name, KNR_PERMS);
+    lseek(fd, 0L, 2);
+  } else
+    fd = open(name, O_RDONLY, 0);
+  if (fd == -1)           /* couldn't access name */
+    return KNR_NULL;
+  fp->fd = fd;
+  fp->cnt = 0;
+  fp->base = KNR_NULL;
+  if (*mode == 'r')
+    fp->f_read = 1;
+  else
+    fp->f_write = 1;
+  return fp;
+}
+
+/* knr_fillbuf:  allocate and fill input buffer */
+int knr_fillbuf (KNR_FILE *fp)
+{
+  int bufsize;
+
+  if (fp->f_read == 0 || fp->f_eof == 1 || fp->f_err == 1)
+    return KNR_EOF;
+  bufsize = fp->f_unbuf==1 ? 1 : KNR_BUFSIZ;
+  if (fp->base == KNR_NULL)         /* no buffer yet */
+    if ((fp->base = (char*) malloc(bufsize)) == NULL)
+      return KNR_EOF;                   /* can't get buffer */
+  fp->ptr = fp->base;
+  fp->cnt = read(fp->fd, fp->ptr, bufsize);
+  if (--fp->cnt < 0) {
+    if (fp->cnt == -1)
+      fp->f_eof = 1;
+    else
+      fp->f_err = 1;
+    fp->cnt = 0;
+    return KNR_EOF;
+  }
+  return (unsigned char) *fp->ptr++;
+}
+
+/* knr_flushbuf:  flush buffer (TODO LATER) */
+int knr_flushbuf (int, KNR_FILE *)
+{
+  return 0;
+}
+
+/* knr_fclose:  close a stream, return 0 on success, KNR_EOF on error */
+int knr_fclose (KNR_FILE *fp)
+{
+  int r;
+  
+  if ((r = close(fp->fd)) == -1)
+    return KNR_EOF;
+  fp->f_read = fp->f_write = fp->f_unbuf = fp->f_eof = fp->f_err = 0;
+  return 0;
+}
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/3_file2.mk b/chapter_8.unix_interface/8_3.flush_cat/tests/3_file2.mk
new file mode 100644
index 0000000..e407b12
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/tests/3_file2.mk
@@ -0,0 +1,3 @@
+BINARY=fopen_fields
+
+include ../../Exercise.mk
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/3_many.targs b/chapter_8.unix_interface/8_3.flush_cat/tests/3_many.targs
new file mode 100644
index 0000000..a97ba8f
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/tests/3_many.targs
@@ -0,0 +1 @@
+tests/3_file0.c tests/3_file1.c tests/3_file2.mk
\ No newline at end of file
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/3_many.tin b/chapter_8.unix_interface/8_3.flush_cat/tests/3_many.tin
new file mode 100644
index 0000000..e69de29
diff --git a/chapter_8.unix_interface/8_3.flush_cat/tests/3_many.tout b/chapter_8.unix_interface/8_3.flush_cat/tests/3_many.tout
new file mode 100644
index 0000000..c7158b5
--- /dev/null
+++ b/chapter_8.unix_interface/8_3.flush_cat/tests/3_many.tout
@@ -0,0 +1,113 @@
+# include <stdio.h>
+# include <error.h>
+# include "knr_io.h"
+
+
+int main (int argc, char *argv[])
+{
+  KNR_FILE *fp;
+  int c;
+  
+  if (argc == 1)
+    printf("Usage: %s <file1> <...fileN>\n", argv[0]);
+  else
+    while (--argc > 0) {
+      if ((fp = knr_fopen(*++argv, "r")) == KNR_NULL)
+        error(1, 0, "Can't open file %s.", *argv);
+      while ((c = knr_getc(fp)) != KNR_EOF)
+        putchar(c);
+      if (knr_fclose(fp) != 0)
+        error(2, 0, "Can't close file %s.", *argv);
+    }
+
+  return 0;
+}
+# include <stdio.h>
+# include <stdlib.h>
+# include <unistd.h>
+# include <fcntl.h>
+# include "knr_io.h"
+
+KNR_FILE knr_iob[KNR_OPEN_MAX] = {    /* stdin, stdout, stderr */
+  { 0, (char *) 0, (char *) 0, 0, 1, 0, 0, 0, 0 },
+  { 0, (char *) 0, (char *) 0, 1, 0, 1, 0, 0, 0 },
+  { 0, (char *) 0, (char *) 0, 2, 0, 1, 1, 0, 0 }
+};
+
+/* knr_fopen:  open file, return KNR_FILE* on success, KNR_NULL on error */
+KNR_FILE *knr_fopen (char *name, char *mode)
+{
+  int fd;
+  KNR_FILE *fp;
+
+  if (*mode != 'r' && *mode != 'w' && *mode != 'a')
+    return KNR_NULL;
+  for (fp = knr_iob; fp < knr_iob + KNR_OPEN_MAX; fp++)
+    if (fp->f_read == 0 && fp->f_write == 0)
+      break;              /* found free slot */
+  if (fp >= knr_iob + KNR_OPEN_MAX)   /* no free slots */
+    return KNR_NULL;
+
+  if (*mode == 'w')
+    fd = creat(name, KNR_PERMS);
+  else if (*mode == 'a') {
+    if ((fd = open(name, O_WRONLY, 0)) == -1)
+      fd = creat(name, KNR_PERMS);
+    lseek(fd, 0L, 2);
+  } else
+    fd = open(name, O_RDONLY, 0);
+  if (fd == -1)           /* couldn't access name */
+    return KNR_NULL;
+  fp->fd = fd;
+  fp->cnt = 0;
+  fp->base = KNR_NULL;
+  if (*mode == 'r')
+    fp->f_read = 1;
+  else
+    fp->f_write = 1;
+  return fp;
+}
+
+/* knr_fillbuf:  allocate and fill input buffer */
+int knr_fillbuf (KNR_FILE *fp)
+{
+  int bufsize;
+
+  if (fp->f_read == 0 || fp->f_eof == 1 || fp->f_err == 1)
+    return KNR_EOF;
+  bufsize = fp->f_unbuf==1 ? 1 : KNR_BUFSIZ;
+  if (fp->base == KNR_NULL)         /* no buffer yet */
+    if ((fp->base = (char*) malloc(bufsize)) == NULL)
+      return KNR_EOF;                   /* can't get buffer */
+  fp->ptr = fp->base;
+  fp->cnt = read(fp->fd, fp->ptr, bufsize);
+  if (--fp->cnt < 0) {
+    if (fp->cnt == -1)
+      fp->f_eof = 1;
+    else
+      fp->f_err = 1;
+    fp->cnt = 0;
+    return KNR_EOF;
+  }
+  return (unsigned char) *fp->ptr++;
+}
+
+/* knr_flushbuf:  flush buffer (TODO LATER) */
+int knr_flushbuf (int, KNR_FILE *)
+{
+  return 0;
+}
+
+/* knr_fclose:  close a stream, return 0 on success, KNR_EOF on error */
+int knr_fclose (KNR_FILE *fp)
+{
+  int r;
+  
+  if ((r = close(fp->fd)) == -1)
+    return KNR_EOF;
+  fp->f_read = fp->f_write = fp->f_unbuf = fp->f_eof = fp->f_err = 0;
+  return 0;
+}
+BINARY=fopen_fields
+
+include ../../Exercise.mk