views:

107

answers:

5

For my OS class I have the assignment of implementing Unix's cat command with system calls (no scanf or printf). Here's what I got so far:

(Edited thanks to responses)

#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>



main(void)
{


   int fd1; 
   int fd2;

   char *buffer1;
   buffer1 = (char *) calloc(100, sizeof(char));


   char *buffer2;
   buffer2 = (char *)calloc(100, sizeof(char));

   fd1 = open("input.in", O_RDONLY);    
   fd2 = open("input2.in", O_RDONLY);


   while(eof1){ //<-lseek condition to add here
   read (fd1, buffer1, /*how much to read here?*/ );
   write(1, buffer1, sizeof(buffer1)-1);     
   }


   while (eof2){ 

    read (fd2,buffer2, /*how much to read here?*/);  
    write(1, buffer2, sizeof(buffer2)-1);

    }

}

The examples I have seen only show read with a known number of bytes. I don't know how much bytes each of the read files will have, so how do I specify read's last paramether?

A: 

Use the stat function to find the size of your files before you read them. Alternatively, you can read chunks until you get an EOF.

Nathon
+4  A: 
  • Before you can read into a buffer, you have to allocate one. Either on the stack (easiest) or with mmap.
  • perror is a complicated library function, not a system call.
  • exit is not a system call on Linux. But _exit is.
  • Don't write more bytes than you have read before.
  • Or, in general: Read the documentation on all these system calls.

Edit: Here is my code, using only system calls. The error handling is somewhat limited, since I didn't want to re-implement perror.

#include <fcntl.h>
#include <unistd.h>

static int
cat_fd(int fd) {
  char buf[4096];
  ssize_t nread;

  while ((nread = read(fd, buf, sizeof buf)) > 0) {
    ssize_t ntotalwritten = 0;
    while (ntotalwritten < nread) {
      ssize_t nwritten = write(STDOUT_FILENO, buf + ntotalwritten, nread - ntotalwritten);
      if (nwritten < 1)
        return -1;
      ntotalwritten += nwritten;
    }
  }

  return nread == 0 ? 0 : -1;
}

static int
cat(const char *fname) {
  int fd, success;

  if ((fd = open(fname, O_RDONLY)) == -1)
    return -1;

  success = cat_fd(fd);

  if (close(fd) != 0)
    return -1;

  return success;
}


int
main(int argc, char **argv) {
  int i;

  if (argc == 1) {
    if (cat_fd(STDIN_FILENO) != 0)
      goto error;
  } else {
    for (i = 1; i < argc; i++) {
      if (cat(argv[i]) != 0)
        goto error;
    }
  }
  return 0;

error:
  write(STDOUT_FILENO, "error\n", 6);
  return 1;
}
Roland Illig
perror and exit don't matter. there's only printf and scanf to avoid.
omgzor
@omgzor - then you really aren't "implementing Unix's cat command solely with system calls"
jschmier
I took them out anyway to avoid further mention of the thing in the responses.
omgzor
@jschmier: yeah, you're right. Took away the 'solely' :)
omgzor
+3  A: 

You need to read as many bytes as will fit in the buffer. Right now, you don't have a buffer yet, all you got is a pointer to a buffer. That isn't initialized to anything. Chicken-and-egg, you therefore don't know how many bytes to read either.

Create a buffer.

Hans Passant
With which size? c = (char *) calloc(100, sizeof(char));
omgzor
That's what I have on my guide. It specifies 100 chars, If you don't know how many chars the file has what would you write on the calloc?
omgzor
You could normally benefit from a bigger buffer, but 100 will do. Just use a loop to read a piece into that buffer, and write that piece out to stdout (read() will tell you how much it actually filled into the buffer, so don't write more than that on each loop)
nos
Whichever size you like. Big buffer: faster but fatter. Small buffer: leaner but slower.
Amadan
`calloc` is not a system call.
Roland Illig
+2  A: 

There is usually no need to read the entire file in one gulp. Choosing a buffer size that is the same or a multiple of the host operating system's memory page size is a good way to go. 1 or 2 X the page size is probably good enough.

Using buffers that are too big can actually cause your program to run worse because they put pressure on the virtual memory system and can cause paging.

nategoose
+2  A: 

You could use open, fstat, mmap, madvise and write to make a very efficient cat command.

If Linux specific you could use open, fstat, fadvise and splice to make an even more efficient cat command.

The advise calls are to specify the SEQUENTIAL flags which will tell the kernel to do aggressive read-ahead on the file.

If you like to be polite to the rest of the system and minimize buffer cache use, you can do your copy in chunks of 32 megabytes or so and use the advise DONTNEED flags on the parts already read.

Note:

The above will only work if the source is a file. If the fstat fails to provide a size then you must fall back to using an allocated buffer and read, write. You can use splice too.

Zan Lynx