Search code examples
cfilemmapread-write

mmap invalid argument - reading multiple files from command line and writing to one output file


I am writing a C program that takes more than one file from the command line and combines them into one output file. If the output file is not given, it is created. I copy the file content by using mmap.

However, when I run the following code, I get "mmap: invalid argument" and I am not sure why.

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

int main(int argc, char *argv[])
{
  int opt, output=0, len; 
  int fd_o, fd_i;
  char *addr=NULL;
  struct stat stat_input;
  const char *errmsg="Invalid argument:\n";
  const char *usage=
      "combine to one\n";

  while((opt = getopt(argc, argv, "o"))!= -1) {
      switch(opt){
      case 'o': 
        output=1;
        fd_o=open(argv[argc-1], O_WRONLY|O_CREAT|O_TRUNC, 0644);
        if (fd_o == -1){
          perror("open");
          exit(1);
        }
        break;
      case '?':
        write(2, errmsg, strlen(errmsg));
        write(2, usage, strlen(usage));
        exit(1);
    }
  }

  if (output==0){
    write(2, errmsg, strlen(errmsg));
    write(2, usage, strlen(usage));
    exit(1);
  }
 
  for(; optind < argc; optind++){
    fd_i=open(argv[optind], O_RDONLY);
    if (fd_i == -1){
      perror("open");
      exit(1);
}

    fstat(fd_i, &stat_input);
    len=stat_input.st_size;
    addr=mmap(0, len, PROT_READ, MAP_SHARED, fd_i, 0);
    if (addr == MAP_FAILED) {
      close(fd_i);
      perror("mmap");
      exit(1);
    }
    write(fd_o, *addr, len);
    close(fd_i);
    munmap(addr,len);
  }
  close(fd_o);
}


Solution

  • I think the problem is actually your option parsing. As it stands, you always use the last argument as the output file if -o is specified at all. So if you run ./myprog f1 f2 -o out, then all of f1, f2, out will be used as input files. And if you run ./myprog -o out f1 f2 then all of out, f1, f2 will be inputs, and output will go to f2.

    I presume you didn't expect to use the output file as input, and if it's empty, you will attempt to create a map of length 0, which will fail with "Invalid argument". My guess is this is the cause of your problem.

    The correct way to use getopt to process an option with an argument is to use "o:" as the option string, and then when getopt returns 'o', the variable optarg will point to the option's argument; that's what you should use as your output filename instead of argv[argc-1].

    A couple other bugs:

    • write(fd_o, *addr, len); should be write(fd_o, addr, len); since write expects a pointer to its data. You should get a compiler warning about this.

    • You'll want to test for len != 0 before calling mmap in case someone really does run the program on an empty input file.

    • I'm not sure why you're using write() for your usage message instead of just fprintf(stderr, ...). If for some reason stdio functions are not acceptable (even though you seem to have no problem with perror() elsewhere), then the standard function dprintf may make your life a bit easier.