/* shred.c - Overwrite a file to securely delete
 *
 * Copyright 2014 Rob Landley <rob@landley.net>
 *
 * No standard

USE_SHRED(NEWTOY(shred, "<1zxus#<1n#<1o#<0f", TOYFLAG_USR|TOYFLAG_BIN))

config SHRED
  bool "shred"
  default y
  help
    usage: shred [-fuz] [-n COUNT] [-s SIZE] FILE...

    Securely delete a file by overwriting its contents with random data.

    -f        Force (chmod if necessary)
    -n COUNT  Random overwrite iterations (default 1)
    -o OFFSET Start at OFFSET
    -s SIZE   Use SIZE instead of detecting file size
    -u        unlink (actually delete file when done)
    -x        Use exact size (default without -s rounds up to next 4k)
    -z        zero at end

    Note: data journaling filesystems render this command useless, you must
    overwrite all free space (fill up disk) to erase old data on those.
*/

#define FOR_shred
#include "toys.h"

GLOBALS(
  long offset;
  long iterations;
  long size;

  int ufd;
)

void shred_main(void)
{
  char **try;

  if (!(toys.optflags & FLAG_n)) TT.iterations++;
  TT.ufd = xopenro("/dev/urandom");

  // We don't use loopfiles() here because "-" isn't stdin, and want to
  // respond to files we can't open via chmod.

  for (try = toys.optargs; *try; try++) {
    off_t pos = 0, len = TT.size;
    int fd = open(*try, O_RDWR), iter = 0, throw;

    // do -f chmod if necessary
    if (fd == -1 && (toys.optflags & FLAG_f)) {
      chmod(*try, 0600);
      fd = open(*try, O_RDWR);
    }
    if (fd == -1) {
      perror_msg_raw(*try);
      continue;
    }

    // determine length
    if (!len) len = fdlength(fd);
    if (len<1) {
      error_msg("%s: needs -s", *try);
      close(fd);
      continue;
    }

    // Loop through, writing to this file
    for (;;) {
      // Advance to next -n or -z?

      if (pos >= len) {
        pos = -1;
        if (++iter == TT.iterations && (toys.optargs && FLAG_z)) {
          memset(toybuf, 0, sizeof(toybuf));
          continue;
        }
        if (iter >= TT.iterations) break;
      }

      if (pos < TT.offset) {
        if (TT.offset != lseek(fd, TT.offset, SEEK_SET)) {
          perror_msg_raw(*try);
          break;
        }
        pos = TT.offset;
      }

      // Determine length, read random data if not zeroing, write.

      throw = sizeof(toybuf);
      if (toys.optflags & FLAG_x)
        if (len-pos < throw) throw = len-pos;

      if (iter != TT.iterations) xread(TT.ufd, toybuf, throw);
      if (throw != writeall(fd, toybuf, throw)) perror_msg_raw(*try);
      pos += throw;
    }
    if (toys.optflags & FLAG_u)
      if (unlink(*try)) perror_msg("unlink '%s'", *try);
  }
}
