
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <sys/attr.h>
#include <sys/time.h>
#include <errno.h>

#include "xattr_util.h"


#define BUFSIZE (1024)
#define MAXNAME (4*1024)


#define DEBUG 1

#ifdef DEBUG
#define WARNING (fprintf(stderr, "%s %d: ", __FILE__, __LINE__), perror(""))
#else
#define WARNING ((void) 0)
#endif



#ifdef __LP64__
#define PACKED __attribute__((packed))
#else
#define PACKED
#endif



struct attrbuf {
   uint32_t length;
   struct timespec ts;
} PACKED ;

/* NOTE: the man page for getattrlist says length should be an unsigned long,
 * but the code says its a uint32_t.  This only matters on a 
 * 64-bit machine, of course.
 * 
 * Right now, this will work even if compiled with -m64.
 * Note the use of packing to overcome alignment issues.
 *
 */
   

int GetCreationTime(const char *path, time_t* t)
{
   struct attrlist attrList;
   struct attrbuf  attrBuf;
   int err;

   memset(&attrList, 0, sizeof(attrList));

   attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
   attrList.commonattr  = ATTR_CMN_CRTIME;

   err = getattrlist(path, &attrList, &attrBuf, sizeof(attrBuf), FSOPT_NOFOLLOW);
   if (err == 0) {
      *t = attrBuf.ts.tv_sec;
      return 0;
   }
   else
      return -1;
}

/* NOTE: setting crtime on symlinks does nothing */

int SetCreationTime(const char *path, time_t t)
{
   struct attrlist attrList;
   struct attrbuf  attrBuf;
   int err;

   attrBuf.ts.tv_sec = t;
   attrBuf.ts.tv_nsec = 0;

   memset(&attrList, 0, sizeof(attrList));

   attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
   attrList.commonattr  = ATTR_CMN_CRTIME;

   err = setattrlist(path, &attrList, &attrBuf.ts, sizeof(attrBuf.ts), FSOPT_NOFOLLOW);

   return err;
}


/* the following works like chflags, but applies to symlinks as well:
 * on HFS+ filesystems, symlinks can have BSD flags.
 */

/* NOTE: the man page for getattrlist says t should be an unsigned long,
 * but the code says its a uint32_t.
 * Should verify in 64-bit environment.
 */

int hfs_chflags(const char *path, uint32_t t)
{
   struct attrlist attrList;
   int err;

   memset(&attrList, 0, sizeof(attrList));

   attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
   attrList.commonattr  = ATTR_CMN_FLAGS;

   err = setattrlist(path, &attrList, &t, sizeof(t), FSOPT_NOFOLLOW);

   return err;
}


/* the following works like chmod, but applies to symlinks as well.
 */

/* NOTE: the man page for getattrlist says t should be a mode_t,
 * but the code says its a uint32_t.  Since mode_t is a 16-bit integer,
 * this will *fail* if t is declared mode_t.
 * Should verify in 64-bit environment.
 */


int hfs_chmod(const char *path, uint32_t t)
{
   struct attrlist attrList;
   int err;

   memset(&attrList, 0, sizeof(attrList));

   attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
   attrList.commonattr  = ATTR_CMN_ACCESSMASK;

   err = setattrlist(path, &attrList, &t, sizeof(t), FSOPT_NOFOLLOW); 

   return err;
}



#if 1

/* These versions of set_time and set_ctime do not follow symlinks,
 * and also don't modify the atime.
 */

/* NOTE: setting mtime on symlinks does nothing */

int set_time(const char *path, const struct stat *sbuf)
{
   struct attrlist attrList;
   int err;

   struct timespec t;

   t.tv_sec = sbuf->st_mtime;
   t.tv_nsec = 0;

   memset(&attrList, 0, sizeof(attrList));

   attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
   attrList.commonattr  = ATTR_CMN_MODTIME;

   err = setattrlist(path, &attrList, &t, sizeof(t), FSOPT_NOFOLLOW); 

   return err;
}

/* sets mtime from ctime */
int set_ctime(const char *path, const struct stat *sbuf)
{
   struct attrlist attrList;
   int err;

   struct timespec t;

   t.tv_sec = sbuf->st_ctime;
   t.tv_nsec = 0;

   memset(&attrList, 0, sizeof(attrList));

   attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
   attrList.commonattr  = ATTR_CMN_MODTIME;

   err = setattrlist(path, &attrList, &t, sizeof(t), FSOPT_NOFOLLOW); 

   return err;
}


#else

/* These versions of set_time and set_ctime use good, old utimes.
 * For set_time, we avoid symlinks.
 * The way set_ctime is used, however, precludes this.
 */


int set_time(const char *fname, const struct stat *sbuf)
{
   struct timeval tval[2];

   if (S_ISLNK(sbuf->st_mode)) return 0; /* can't set time of symlink */

   tval[0].tv_sec = time(NULL);
   tval[0].tv_usec = 0;
   tval[1].tv_sec = sbuf->st_mtime;
   tval[1].tv_usec = 0;

   return utimes(fname, tval);
}

/* sets mtime from ctime */
int set_ctime(const char *fname, const struct stat *sbuf)
{
   struct timeval tval[2];

   tval[0].tv_sec = time(NULL);
   tval[0].tv_usec = 0;
   tval[1].tv_sec = sbuf->st_ctime;
   tval[1].tv_usec = 0;

   return utimes(fname, tval);
}

#endif



int make_writable(const char *fname, const struct stat *sbuf)
{
   return hfs_chmod(fname, sbuf->st_mode | S_IWUSR | S_IWGRP | S_IWOTH); 
}

int restore_writable(const char *fname, const struct stat *sbuf)
{
   return hfs_chmod(fname, sbuf->st_mode); 
}

int has_locks(const struct stat *sbuf)
{
   return (sbuf->st_flags & (UF_IMMUTABLE | UF_APPEND)) != 0;
}

int remove_locks(const char *fname, const struct stat *sbuf)
{
   return hfs_chflags(fname, sbuf->st_flags & (~(UF_IMMUTABLE | UF_APPEND)));
}

int has_xattr(const char *fname)
{
   return listxattr(fname, 0, 0, XATTR_NOFOLLOW) > 0;
}





static
int write_int4(uint32_t x, FILE *f)
{
   unsigned char buf[4];

   buf[3] = x & 0xffu; x = x >> 8;
   buf[2] = x & 0xffu; x = x >> 8;
   buf[1] = x & 0xffu; x = x >> 8;
   buf[0] = x & 0xffu; x = x >> 8;

   if (fwrite(buf, 1, 4, f) == 4)
      return 0;
   else
      return -1;
}

static
int read_int4(uint32_t *xp, FILE *f)
{
   unsigned char buf[4];
   uint32_t x;

   if (fread(buf, 1, 4, f) != 4)
      return -1;

   x = 0;
   x = x << 8; x += buf[0] & 0xffu;
   x = x << 8; x += buf[1] & 0xffu;
   x = x << 8; x += buf[2] & 0xffu;
   x = x << 8; x += buf[3] & 0xffu;

   *xp = x;
   return 0;
}

static
int read_sint4(int32_t *xp, FILE *f)
{
   uint32_t uxp;

   if (read_int4(&uxp, f)) return -1;

   *xp = uxp; /* NOTE: non-standard unsigned to signed conversion,
               * but should work with gcc anyway.
               */

   return 0;
}


static
int write_int2(uint16_t x, FILE *f)
{
   unsigned char buf[2];

   buf[1] = x & 0xffu; x = x >> 8;
   buf[0] = x & 0xffu; x = x >> 8;

   if (fwrite(buf, 1, 2, f) == 2)
      return 0;
   else
      return -1;
}

static
int read_int2(uint16_t *xp, FILE *f)
{
   unsigned char buf[2];
   uint32_t x;

   if (fread(buf, 1, 2, f) != 2)
      return -1;

   x = 0;
   x = x << 8; x += buf[0] & 0xffu;
   x = x << 8; x += buf[1] & 0xffu;

   *xp = x;
   return 0;
}

static
int read_str(char *buf, int len, FILE *f)
{
   int c, k;

   k = 0;
   for (;;) {
      c = getc(f);
      if (c == EOF) return -1;
      if (k >= len) return -1;
      buf[k] = c;
      k++;
      if (c == 0) return 0;
   }
}


/* Container format: 
 *  - header (10 bytes):
 *      - magic (8 bytes)
 *      - version (2 bytes)
 *  - optional: create time (4 bytes) -- included only if
 *                                        CRTIME_FLAG of version is set
 *  - number of xattrs (2 bytes) 
 *  - for each xattr:
 *      - name (null-terminated string)
 *      - attrlen (4 bytes)
 *      - attr (attrlen bytes)
 * 
 * All numbers in network byte order (high-order byte first)
 */

#define MAGIC1 (0x30bc83f9u)
#define MAGIC2 (0x22f0f8dfu)

/* version is a 16-bit integer.  The low-order 4 bits are reserved
 * for the version number, and the remaining bits for various flags.
 */

#define VERSION (1)
#define VERSION_MASK     (0x000f)
#define CRTIME_FLAG      (0x0010)
#define IMMUTABLE_FLAG   (0x0020)
#define APPEND_FLAG      (0x0040)

int write_header(uint16_t v, FILE *f)
{
   if (write_int4(MAGIC1, f) || write_int4(MAGIC2, f) || write_int2(v, f))
      return -1;
   else
      return 0;
}

int read_header(uint16_t *vp, FILE *f)
{
   uint32_t m1, m2;
   uint16_t v;

   if (read_int4(&m1, f) || read_int4(&m2, f) || read_int2(&v, f))
      return -1;

   if (m1 != MAGIC1 || m2 != MAGIC2)
      return -1;

   *vp = v;
   return 0;
}



/* read xattr's from file fname and store in container cname. 
 *    cname == "" => xattr's written to stdout
 * sbuf: should be stat struct for fname
 * crtime: flag requesting creation time preservatoion
 *
 * retval: -1 on error
 *         0 otherwise
 *
 * design options: follow symlinks? no
 *                 create container if no xattr's? yes
 */

int split_xattr(const char *fname, const struct stat *sbuf,
                  const char *cname, int crtime)
{
   char *namebuf=0, *attrbuf=0;
   FILE *cfp=0;

   int retval = -1;

   char *attrname, *bp;
   long numxattrs, i, namesz, attrnamesz, attrsz, bufsize;

   uint16_t v;
   time_t crt;

   namesz = listxattr(fname, 0, 0, XATTR_NOFOLLOW);

   /* NOTE: if namesz <= 0 (which is the same criteria used in has_xattr)
    * then the file will be treated as if it has no xattrs.
    * In particular, this will be the case if the file is unreadable.
    */
   
   numxattrs = 0;

   if (namesz > 0) {
      namebuf = (char *) malloc(namesz);
      if (!namebuf) {
         WARNING;
         goto done;
      }

      bufsize = BUFSIZE;
      attrbuf = (char *) malloc(BUFSIZE);
      if (!attrbuf) {
         WARNING;
         goto done; 
      }

      if (listxattr(fname, namebuf, namesz, XATTR_NOFOLLOW) != namesz) {
         WARNING;
         goto done;
      }

      for (i = 0; i < namesz; i++) {
         if (!namebuf[i]) numxattrs++;
      }

   }

   v = VERSION;

   if (crtime) {

      if (GetCreationTime(fname, &crt)) {
         WARNING;
         goto done;
      }

      v |= CRTIME_FLAG;
      
   }

   if (sbuf->st_flags & UF_IMMUTABLE) v |= IMMUTABLE_FLAG;
   if (sbuf->st_flags & UF_APPEND) v |= APPEND_FLAG;

   if (cname[0] != 0) {
      cfp = fopen(cname, "w");
   }
   else {
      cfp = stdout;
   }

   if (!cfp) {
      WARNING;
      goto done; 
   }

   if (write_header(v, cfp)) {
      WARNING;
      goto done;
   }

   if (crtime) {

      /* NOTE: on 64-bit machines with 64-bit time_t, crt could
       * get truncated here.
       */

      if (write_int4(crt, cfp)) {
         WARNING;
         goto done;
      }
   }

   if (numxattrs >= (1L << 16)) {
      WARNING;
      goto done;
   }

   if (write_int2(numxattrs, cfp)) {
      WARNING;
      goto done;
   }

   attrname = namebuf;
   for (i = 0; i < numxattrs; i++) {
      attrnamesz = strlen(attrname);

      attrsz = getxattr(fname, attrname, 0, 0, 0, XATTR_NOFOLLOW);

      if (attrsz < 0) {
         WARNING;
         goto done;
      }

      if (attrsz > (1L << 20)) {
         fprintf(stderr, "WARNING: large attribute %s %s %ld\n",
                 fname, attrname, attrsz);
      }

      if (attrsz > bufsize) {
         bufsize = attrsz;
         bp = realloc(attrbuf, bufsize);
         if (!bp) {
            WARNING;
            goto done;
         }
         attrbuf = bp;
      }

      if (getxattr(fname, attrname, attrbuf, bufsize, 0, XATTR_NOFOLLOW)
           != attrsz) {
         WARNING;
         goto done;
      }

      if (attrnamesz >= MAXNAME) {
         WARNING;
         goto done;
      }

      if (fwrite(attrname, 1, attrnamesz+1, cfp) != attrnamesz+1) {
         WARNING;
         goto done;
      }

      if (attrsz > (1L << 30)) {
         WARNING;
         goto done;
      }

      if (write_int4(attrsz, cfp)) { 
         WARNING;
         goto done;
      }

      if (fwrite(attrbuf, 1, attrsz, cfp) != attrsz) {
         WARNING;
         goto done;
      }

      attrname += attrnamesz + 1;

   }

   retval = 0;


done:

   if (cfp && cfp != stdout) fclose(cfp);
   if (attrbuf) free(attrbuf);
   if (namebuf) free(namebuf);

   return retval;

}

/*
 * attempt to remove all xattrs from file fname.
 * Return 0 on success, -1 on failure.
 */

int strip_xattr(const char *fname)
{
   char *namebuf=0;

   int retval = -1;

   char *attrname;
   long numxattrs, i, namesz, attrnamesz;
   int fail;


   namesz = listxattr(fname, 0, 0, XATTR_NOFOLLOW);
   
   if (namesz < 0) {
      WARNING;
      goto done;
   }

   if (namesz == 0) {
      retval = 0;
      goto done;
   }
   else {
      namebuf = (char *) malloc(namesz);
      if (!namebuf) {
         WARNING;
         goto done;
      }
   }

   if (listxattr(fname, namebuf, namesz, XATTR_NOFOLLOW) != namesz) {
      WARNING;
      goto done;
   }

   numxattrs = 0;
   for (i = 0; i < namesz; i++) {
      if (!namebuf[i]) numxattrs++;
   }


   fail = 0;
   attrname = namebuf;
   for (i = 0; i < numxattrs; i++) {
      attrnamesz = strlen(attrname);

      if (removexattr(fname, attrname, XATTR_NOFOLLOW)) {
         WARNING;
         fail = 1;
      }

      attrname += attrnamesz + 1;
   }

   if (!fail) retval = 0;

done:
   if (namebuf) free(namebuf);

   return retval;
}



/* read xattr's from container cname and set them in file fname
 *    cname == NULL => all xattr's and locks stripped from fname
 *    cname == ""   => xattr's read from stdin
 *
 * sbuf: should be stat struct for fname
 *
 * retval: -1 on recoverable error (xattr container completely consumed)
 *         -2 on unrecoverable error
 *          0 otherwise
 *
 * options: follow symlinks? no
 *
 * NOTE: This tries to keep going in the face of errors as best as possible.
 * This is especially important in conjunction with the joinf_xattr program.
 */

int join_xattr(const char *fname, const struct stat *sbuf, const char *cname)
{
   char *attrbuf=0, *attrname=0;
   FILE *cfp=0;

   int retval = 0;

   char *bp;
   long numxattrs, i, attrsz, bufsize;
   uint16_t v, x;
   uint32_t xx;
   int32_t sxx;


   /* remove any locks */

   if (has_locks(sbuf)) {
      if (remove_locks(fname, sbuf)) {
         WARNING;
         retval = -1;
      }
   }

   /* setting xattrs requires write permission */

   if (make_writable(fname, sbuf)) {
      WARNING;
      retval = -1; 
   }

   /* remove all xattrs first.
    * NOTE: writing the resource fork actually does not truncate it,
    * so removing the resource fork first is essential.
    */

   if (strip_xattr(fname)) {
      WARNING;
      retval = -1;
   }

   /* if no cname is given, the effect is to just strip all locks and xattrs */

   if (!cname) {
      goto restore;
   }

   if (cname[0] != 0) {
      cfp = fopen(cname, "r");
   }
   else {
      cfp = stdin;
   }

   if (!cfp) {
      WARNING;
      retval = -2; goto done;
   }

   if (read_header(&v, cfp) || (v & VERSION_MASK) != VERSION) {
      WARNING;
      retval = -2; goto done;
   }


   if (v & CRTIME_FLAG) {


      if (((time_t) -1) < 0) { 

         /* time_t is signed -- the usual case */

         /* This code sequence will ensure that we get proper
          * sign extension, even if time_t is 64-bits.
          */

         if (read_sint4(&sxx, cfp)) {
            WARNING;
            retval = -2; goto done;
         }

         if (SetCreationTime(fname, sxx)) {
            WARNING;
            retval = -1;
         }

      }
      else {

         /* time_t is unsigned */

         if (read_int4(&xx, cfp)) {
            WARNING;
            retval = -2; goto done;
         }

         if (SetCreationTime(fname, xx)) {
            WARNING;
            retval = -1;
         }

      }
   }

   if (read_int2(&x, cfp)) {
      WARNING;
      retval = -2; goto done;
   }

   numxattrs = x;

   if (numxattrs == 0) {
      goto restore;
   }

   attrname = (char *) malloc(MAXNAME);
   if (!attrname) {
      WARNING;
      retval = -2; goto done;
   }

   bufsize = BUFSIZE;
   attrbuf = (char *) malloc(BUFSIZE);
   if (!attrbuf) {
      WARNING;
      retval = -2; goto done; 
   }

   for (i = 0; i < numxattrs; i++) {

      if (read_str(attrname, MAXNAME, cfp)) {
         WARNING;
         retval = -2; goto done;
      }

      if (read_int4(&xx, cfp)) {
         WARNING;
         retval = -2; goto done;
      }

      if (xx > (1UL << 30)) {
         WARNING;
         retval = -2; goto done;
      }

      attrsz = xx;

      if (attrsz > bufsize) {
         bufsize = attrsz;
         bp = realloc(attrbuf, bufsize);
         if (!bp) {
            WARNING;
            retval = -2; goto done;
         }
         attrbuf = bp;
      }

      if (fread(attrbuf, 1, attrsz, cfp) != attrsz) {
         WARNING;
         retval = -2; goto done;
      }

      if (setxattr(fname, attrname, attrbuf, attrsz, 0, XATTR_NOFOLLOW)) {
         WARNING;
         retval = -1; 
      }
   }

restore:

   /* NOTE: writing the resource fork can change the mtime,
    * so we restore it here.
    */

   if (set_time(fname, sbuf)) {
      WARNING;
      retval = -1;
   }

   /* restore the original permission flags */

   if (restore_writable(fname, sbuf)) {
      WARNING;
      retval = -1;
   }

   /* set locks is any -- this must be done last! */

   if (cname && ((v & IMMUTABLE_FLAG) || (v & APPEND_FLAG))) {
      if (hfs_chflags(fname, (sbuf->st_flags & (~(UF_IMMUTABLE | UF_APPEND))) |
                         ((v & IMMUTABLE_FLAG) ? UF_IMMUTABLE : 0) |
                         ((v & APPEND_FLAG) ? UF_APPEND : 0) )) {
         WARNING;
         retval = -1;
      }
   }
                         

done:
   if (attrbuf) free(attrbuf);
   if (attrname) free(attrname);
   if (cfp && cfp != stdin) fclose(cfp);

   return retval;
   
}



/* just reads and skips an xattr container -- used in conjunction
 * with the joinf_xattr program.
 *
 * Return values are as in join_xattr, but all errors are
 * "unrecoverable", so the return value is -2 on error, 0
 * otherwise.
 */


int skip_xattr(const char *cname)
{
   char *attrbuf=0, *attrname=0;
   FILE *cfp=0;

   int retval = -2;

   char *bp;
   long numxattrs, i, attrsz, bufsize;
   uint16_t v, x;
   uint32_t xx;


   if (!cname) {
      goto restore;
   }

   if (cname[0] != 0) {
      cfp = fopen(cname, "r");
   }
   else {
      cfp = stdin;
   }

   if (!cfp) {
      WARNING;
      goto done;
   }

   if (read_header(&v, cfp) || (v & VERSION_MASK) != VERSION) {
      WARNING;
      goto done;
   }


   if (v & CRTIME_FLAG) {

      if (read_int4(&xx, cfp)) {
         WARNING;
         goto done;
      }

   }

   if (read_int2(&x, cfp)) {
      WARNING;
      goto done;
   }

   numxattrs = x;

   if (numxattrs == 0) {
      goto restore;
   }

   attrname = (char *) malloc(MAXNAME);
   if (!attrname) {
      WARNING;
      goto done;
   }

   bufsize = BUFSIZE;
   attrbuf = (char *) malloc(BUFSIZE);
   if (!attrbuf) {
      WARNING;
      goto done; 
   }

   for (i = 0; i < numxattrs; i++) {

      if (read_str(attrname, MAXNAME, cfp)) {
         WARNING;
         goto done;
      }

      if (read_int4(&xx, cfp)) {
         WARNING;
         goto done;
      }

      if (xx > (1UL << 30)) {
         WARNING;
         goto done;
      }

      attrsz = xx;

      if (attrsz > bufsize) {
         bufsize = attrsz;
         bp = realloc(attrbuf, bufsize);
         if (!bp) {
            WARNING;
            goto done;
         }
         attrbuf = bp;
      }

      if (fread(attrbuf, 1, attrsz, cfp) != attrsz) {
         WARNING;
         goto done;
      }

   }

restore:

   retval = 0;

done:
   if (attrbuf) free(attrbuf);
   if (attrname) free(attrname);
   if (cfp && cfp != stdin) fclose(cfp);

   return retval;
}
