/*

Copyright (C) 2018 Anders Blomdell <anders.blomdell@control.lth.se>

This file is part of hashtoc.

Hashtoc is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program. If not, see <https://www.gnu.org/licenses/>.

*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <openssl/md5.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include "libhash.h"

#define BLOCKSIZE 4096
#define XATTR_MD5HASH "trusted.md5.control.lth.se"


struct __attribute__((__packed__)) hash_attr {
  time_t hash_time;
  time_t mtime;
  uint32_t mtime_nsec;
  uint8_t hash[MD5_DIGEST_LENGTH];
};

int hash_md5_buf(char *buf,
                 size_t length,
                 unsigned char *hash)
{
  MD5_CTX ctx;
  
  MD5_Init(&ctx);
  MD5_Update(&ctx, buf, length);
  MD5_Final(hash, &ctx);

  return 0;
}

int hash_md5_file(char *filename,
                  time_t mtime,
                  int64_t mtime_nsec,
                  int flags,
                  time_t maxage,
                  unsigned char *hash)
{
  int fd;
  int result = 0, recalc = 1;
  struct hash_attr hash_attr;

  if (flags & FLAGS_CLEAR_XATTR) {
    if (removexattr(filename, XATTR_MD5HASH) != 0) {
      if (errno != ENODATA) {
        result = -1;
        fprintf(stderr, "Failed to remove xattr '%s'\n", filename);
        goto out;
      } else if ((flags & FLAGS_VERBOSE_MASK) > FLAGS_VERBOSE2) {
        fprintf(stderr, "No xattr for '%s'\n", filename);
      }
    } else {
      if ((flags & FLAGS_VERBOSE_MASK) > FLAGS_VERBOSE2) {
        fprintf(stderr, "Removed xattr for '%s'\n", filename);
      }
    }
  }
  if (flags & FLAGS_NO_CALC_HASH) {
    // We should not calculate hash
    goto out;
  }

  if (flags & FLAGS_READ_XATTR) {
    int err;

    err = getxattr(filename, XATTR_MD5HASH, &hash_attr, sizeof(hash_attr));
    if (err < 0 && errno != ENODATA) {
      result = -1;
      goto out;
    }
    if (err != sizeof(hash_attr)) {
      recalc = 1;
    } else if (le64toh(hash_attr.mtime) != mtime) {
      recalc = 1;
    } else if (le32toh(hash_attr.mtime_nsec) != mtime_nsec) {
      recalc = 1;
    } else if (flags & FLAGS_MAX_AGE && le64toh(hash_attr.hash_time) < maxage) {
      recalc = 1;
    } else {
      recalc = 0;
      memcpy(hash, hash_attr.hash, MD5_DIGEST_LENGTH);
    }
  }

  if (recalc == 0) {
    goto out;
  }
  
  fd = open (filename, O_RDONLY);
  if (fd < 0) {
    result = -1;
    goto out;
  } else {
    MD5_CTX ctx;
    unsigned char buffer[BLOCKSIZE];
    int count;
    time_t now;
      
    time(&now);
    MD5_Init(&ctx);
    while ((count = read(fd, buffer, BLOCKSIZE)) > 0) {
      MD5_Update(&ctx, buffer, count);
    }
    MD5_Final(hash, &ctx);

    if (count < 0) {
      result = -1;
      fremovexattr(fd, XATTR_MD5HASH);
      goto out_close;
    }

    if (flags & FLAGS_WRITE_XATTR) {
      int err;
      
      hash_attr.hash_time = htole64(now);
      hash_attr.mtime = htole64(mtime);
      hash_attr.mtime_nsec = htole32(mtime_nsec);
      memcpy(hash_attr.hash, hash, MD5_DIGEST_LENGTH);
      err = fsetxattr(fd, XATTR_MD5HASH, &hash_attr, sizeof(hash_attr), 0);
      if (err < 0) {
        result = -1;
        fremovexattr(fd, XATTR_MD5HASH);
        goto out_close;
      }
    } else if (flags & FLAGS_READ_XATTR) {
      fremovexattr(fd, XATTR_MD5HASH);
    }
  }
out_close:
  close(fd);
out:
  return result;
}