#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>

#include "commands.h"
#include "fs.h"
#include "mkswap.h"

static int copyfd(int to, int from);

static int copyfd(int to, int from) {
   char buf[1024];
   int size;

   while ((size = read(from, buf, sizeof(buf))) > 0) {
       if (write(to, buf, size) != size) {
           fprintf(stderr, "error writing output: %s\n", strerror(errno));
           return 1;
       }
   }

   if (size < 0) {
       fprintf(stderr, "error reading input: %s\n", strerror(errno));
       return 1;
   }

   return 0;
}

static int catFile(char * filename) {
   int fd;
   int rc;

   fd = open(filename, O_RDONLY);
   if (fd < 0) {
       fprintf(stderr, "cannot open %s: %s\n", filename, strerror(errno));
       return 1;
   }

   rc = copyfd(1, fd);
   close(fd);

   return rc;
}

int catCommand(int argc, char ** argv) {
   char ** argptr = argv + 1;
   int rc;

   if (!*argptr) {
       return copyfd(1, 0);
   } else {
       while (*argptr) {
           rc = catFile(*argptr);
           if (rc) return rc;
           argptr++;
       }
   }

   return 0;
}

int lsmodCommand(int argc, char ** argv) {
   puts("Module:        #pages:  Used by:");
   catFile("/proc/modules");

   return 0;
}

#define MOUNT_USAGE fprintf(stderr, "usage: mount -t <fs> <device> <dir>\n" \
                       "       (if /dev/ is left off the device name, a " \
                       "temporary node will be created)\n")

int mountCommand(int argc, char ** argv) {
   char * dev, * dir;
   char * fs;

   if (argc < 2) {
       return catFile("/proc/mounts");
   } else if (argc == 3) {
       if (strchr(argv[1], ':'))
           fs = "nfs";
       else
           fs = "ext2";
       dev = argv[1];
       dir = argv[2];
   } else if (argc != 5) {
       MOUNT_USAGE;
       return 1;
   } else {
       if (strcmp(argv[1], "-t")) {
           MOUNT_USAGE;
           return 1;
       }

       fs = argv[2];
       dev = argv[3];
       dir = argv[4];

   }

   if (!strncmp(dev, "/dev/", 5) && access(dev, X_OK))
       dev += 5;

   if (doMount(dev, dir, fs, 0, 1))
       return 1;

   return 0;
}

int umountCommand(int argc, char ** argv) {
   if (argc != 2) {
       fprintf(stderr, "umount expects a single argument\n");
       return 1;
   }

   if (umount(argv[1])) {
       fprintf(stderr, "error unmounting %s: %s\n", argv[1], strerror(errno));
       return 1;
   }

   return 0;
}

int mkdirCommand(int argc, char ** argv) {
   char ** argptr = argv + 1;

   if (argc < 2) {
       fprintf(stderr, "umount expects one or more arguments\n");
       return 1;
   }

   while (*argptr) {
       if (mkdir(*argptr, 0755)) {
           fprintf(stderr, "error creating directory %s: %s\n", *argptr,
                       strerror(errno));
           return 1;
       }

       argptr++;
   }

   return 0;
}

int mknodCommand(int argc, char ** argv) {
   int major, minor;
   char * path;
   int mode = 0600;
   char *end;

   if (argc != 5 && argc != 2) {
       fprintf(stderr, "usage: mknod <path> [b|c] <major> <minor> or mknod <path>\n");
       return 1;
   }

   path = argv[1];

   if (argc == 2) {
       end = path + strlen(path) - 1;
       while (end > path && *end != '/') end--;

       if (devMakeInode(end, path)) {
           return 1;
       }

       return 0;
   }

   if (!strcmp(argv[2], "b"))
       mode |= S_IFBLK;
   else if (!strcmp(argv[2], "c"))
       mode |= S_IFCHR;
   else {
       fprintf(stderr, "unknown node type %s\n", argv[2]);
       return 1;
   }

   major = strtol(argv[3], &end, 0);
   if (*end) {
       fprintf(stderr, "bad major number %s\n", argv[3]);
       return 1;
   }

   minor = strtol(argv[4], &end, 0);
   if (*end) {
       fprintf(stderr, "bad minor number %s\n", argv[4]);
       return 1;
   }

   if (mknod(path, mode, makedev(major, minor))) {
       fprintf(stderr, "mknod failed: %s\n", strerror(errno));
       return 1;
   }

   return 0;
}

int rmCommand(int argc, char ** argv) {
   char ** argptr = argv + 1;

   if (argc < 2) {
       fprintf(stderr, "rm expects one or more arguments "
               "(no flags are supported");
       return 1;
   }

   while (*argptr) {
       if (unlink(*argptr)) {
           fprintf(stderr, "unlink of %s failed: %s\n", *argptr,
                       strerror(errno));
           return 1;
       }

       argptr++;
   }

   return 0;
}

int chmodCommand(int argc, char ** argv) {
   char ** argptr = argv + 2;
   int mode;
   char * end;

   if (argc < 3) {
       fprintf(stderr, "usage: chmod <mode> <one or files>\n");
       return 1;
   }

   mode = strtol(argv[1], &end, 8);
   if (*end) {
       fprintf(stderr, "illegal mode %s\n", argv[1]);
       return 1;
   }

   while (*argptr) {
       if (chmod(*argptr, mode)) {
           fprintf(stderr, "error in chmod of %s to 0%o: %s\n", *argptr,
                       mode, strerror(errno));
           return 1;
       }

       argptr++;
   }

   return 0;
}

int mkswapCommand(int argc, char ** argv) {
   if (argc != 2) {
       fprintf(stderr, "mkswap <device>");
       return 1;
   }

   if (!strncmp(argv[0], "/dev/", 5) && access(argv[0], X_OK))
       argv[1] += 5;

   enableswap(argv[1], 0, 0);

   return 0;
}

int swaponCommand(int argc, char ** argv) {
   if (argc != 2) {
       fprintf(stderr, "swapon <file>");
       return 1;
   }

   if (swapon(argv[1])) {
       perror("swapon:");
       return 1;
   }

   return 0;
}