/*
 *----------------------------------------------------------------------
 *           "THE BEER-WARE LICENSE" (Revision 42):
 * <jeang3nie@HitchHiker-Linux.org> wrote this file. As long as you
 * retain this notice you can do whatever you want with this stuff. If
 * we meet some day, and you think this stuff is worth it, you can buy
 * me a beer in return.
 * ---------------------------------------------------------------------
 *            ______   _______  _          _________
 *           (  __  \ (  ___  )( (    /|( )\__   __/
 *           | (  \  )| (   ) ||  \  ( ||/    ) (
 *           | |   ) || |   | ||   \ | |      | |
 *           | |   | || |   | || (\ \) |      | |
 *           | |   ) || |   | || | \   |      | |
 *           | (__/  )| (___) || )  \  |      | |
 *           (______/ (_______)|/    )_)      )_(
 *
 *         _______  _______  _       _________ _______
 *        (  ____ )(  ___  )( \    /|\__   __/(  ____ \
 *        | (    )|| (   ) ||  \  ( |   ) (   | (    |/
 *        | (____)|| (___) ||   \ | |   | |   | |
 *        |  _____)|  ___  || (\ \) |   | |   | |
 *        | (      | (   ) || | \   |   | |   | |
 *        | )      | )   ( || )  \  |___) (___| (____|\
 *        |/       |/     \||/    \_)\_______/(_______/
 *
 */
#define _XOPEN_SOURCE

#include <ctype.h>     // isprint
#include <errno.h>     // errno
#include <stdint.h>    // uint8_t
#include <libgen.h>    // basename
#include <stdio.h>     // fopen, fclose, fseek, ftell, fflush, fwrite
#include <stdlib.h>    // exit, system
#include <string.h>    // strcomp
#include <unistd.h>    // getopt

static const char *__progname;
const int slice = 4096; // read/write 4k at a time
uint8_t Sflag;          // sync
uint8_t uflag;          // unlink
uint8_t vflag;          // verbose
uint8_t wflag;          // wipe
uint8_t zflag;          // zero
uint8_t pass = 0;
uint8_t cpass = 1; // current pass
uint8_t tpass = 0;

static void usage() {
  fprintf(stderr, "usage: %s [-hnSuvz] [FILE1]... [FILE2]...\n", __progname);
}

unsigned long int _findsize(char file[]) {
  // opening the file in read mode
  FILE *fp = fopen(file, "r");
  // checking if the file exist or not
  if (fp == NULL) {
    perror("fopen");
    exit(EXIT_FAILURE);
  }
  // seek to the file end
  fseek(fp, 0L, SEEK_END);
  // calculating the size of the file
  unsigned long int fsize = ftell(fp);
  // closing the file
  fclose(fp);

  return fsize;
}

void progress(int i) {
  unsigned int c;
  if (i < 10)
    fprintf(stderr, "   %i%% [", i);
  else if (i < 100)
    fprintf(stderr, "  %i%% [", i);
  else
    fprintf(stderr, " %i%% [", i);
  for (c = 0; c <= (i / 2); c++) {
    fprintf(stderr, "#");
  }
  while (c <= 50) {
    fprintf(stderr, "-");
    c++;
  }
  fprintf(stderr, "]");
  fprintf(stderr, "\r");
  fflush(stderr);
}

int _shredit(char *file, char *dev, unsigned long int _size) {
  char buf[slice];        // store our data in a buffer
  FILE *dp;               // the file stream for reading
  unsigned long int done; // number of bytes written
  uint8_t percent;        // percentage finished
  unsigned long int _tsize = (_size);
  char *data;             // data type

  /* hide the cursor */
  if (vflag)
    system("tput civis");

  /* Open the appropriate device file for reading */
  if (strcmp(dev, "r") == 0) {
    dp = fopen("/dev/urandom", "r");
    data = "random";
  } else if (strcmp(dev, "z") == 0) {
    dp = fopen("/dev/zero", "r");
    data = "000000";
  } else {
    // function called incorrectly (shutting up the compiler)
    exit(1);
  }

  /* opening the device file failed */
  if (dp == NULL) {
    perror("fopen");
    exit(EXIT_FAILURE);
  }

  /* Open our file for writing */
  FILE *fp = fopen(file, "w");
  /* opening the file failed */
  if (fp == NULL) {
    perror("fopen");
    exit(EXIT_FAILURE);
  }

  while (_size > slice) {
    if (fread(&buf, 1, slice, dp) != slice) {
      perror("fread");
      exit(EXIT_FAILURE);
    }

    if (fwrite(&buf, 1, slice, fp) != slice) {
      perror("fwrite");
      exit(EXIT_FAILURE);
    }

    if (vflag) {
      done = _tsize - _size;
      percent = done * 100 / _tsize;
      fprintf(stderr, "%s: %s: pass %i/%i (%s)", __progname, file, cpass, tpass,
              data);
      progress(percent);
    }
    _size -= slice;
  }
  /* write the remaning bytes */
  if (fread(&buf, 1, _size, dp) != _size) {
    perror("fread");
    exit(EXIT_FAILURE);
  }
  if (fwrite(&buf, 1, _size, fp) != _size) {
    perror("fwrite");
    exit(EXIT_FAILURE);
  }
  if (vflag) {
    done = _tsize - slice;
    fprintf(stderr, "%s: %s: pass %i/%i (%s)",
      __progname, file, cpass, tpass, data);
    progress(100);
    fprintf(stderr, "\n");
  }

  /* flush and sync to disk */
  if (Sflag) {
    if (vflag)
      fprintf(stderr, "Syncing to disk\n");
    if (fflush(fp) != 0) {
      perror("fflush");
      exit(EXIT_FAILURE);
    }
    if (fsync(fileno(fp)) != 0) {
      perror("fsync");
      exit(EXIT_FAILURE);
    }
  }

  /* Put our cursor back */
  if (vflag)
    system("tput cvvis");

  // close streams
  if (fclose(dp) != 0) {
    perror("fclose");
    exit(EXIT_FAILURE);
  }
  if (fclose(fp) != 0) {
    perror("fclose");
    exit(EXIT_FAILURE);
  }
  return 0;
}

int _unlinkit(char *file) {
  if (unlink(file) == 0) {
    if (vflag)
      fprintf(stderr, "unlink: %s\n", file);
    return 0;
  } else {
    perror("unlink");
    exit(EXIT_FAILURE);
  }
}

long parselong(char *buf) {
  errno = 0;
  char *endptr;
  int num = strtol(optarg, &endptr, 10);
  if (errno != 0) {
        perror("Error parsing integer from input: ");
        exit(EXIT_FAILURE);
  } else if (num == 0 && endptr == optarg) {
        fprintf(stderr, "Error: invalid input: %s\n", optarg);
        exit(EXIT_FAILURE);
  } else if (*endptr != '\0') {
        fprintf(stderr, "Error: invalid input: %s\n", optarg);
        exit(EXIT_FAILURE);
  }
  return num;
}

int main(int argc, char *argv[]) {
  unsigned int index;
  unsigned int c;
  __progname = basename(argv[0]);

  while ((c = getopt(argc, argv, "hSuvwzn:")) != -1)
    switch (c) {
    case 'h':
      usage();
      return 0;
      break;
    case 'n':
      pass = parselong(optarg);
      break;
    case 'S':
      Sflag = 1;
      break;
    case 'u':
      uflag = 1;
      break;
    case 'v':
      vflag = 1;
      break;
    case 'w':
      wflag = 1;
      break;
    case 'z':
      zflag = 1;
      break;
    case '?':
      if (optopt == 'n')
        fprintf(stderr, "Option -%c requires an argument.\n", optopt);
      else if (isprint(optopt)) {
        usage();
        exit(1);
      }
    default:
      usage();
    }

  // no arguments supplied
  if (argv[optind] == NULL) {
    fprintf(stderr, "Mandatory argument(s) missing\n");
    exit(1);
  }

  for (index = optind; index < argc; index++) {
    /* get the file size */
    unsigned long int _size = _findsize(argv[index]);
    /* number of passes */
    if (pass == 0)
      pass = 3;
    cpass = 1; // reset current pass to 1 for each file
    if (zflag)
      tpass = (pass + 1); // zero adds an extra pass to total
    else
      tpass = (pass);
    while (cpass <= pass) {
      _shredit(argv[index], "r", _size);
      cpass++;
    }

    /* final pass with zeros to cover our tracks somewhat */
    if (zflag) {
      _shredit(argv[index], "z", _size);
    }

    /* remove the file */
    _unlinkit(argv[index]);
  }
}
