/* Unison file synchronizer: src/osxsupport.c */
/* Copyright 1999-2008 (see COPYING for details) */

#include <caml/mlvalues.h>
#include <caml/alloc.h>
#include <caml/memory.h>
#ifdef __APPLE__
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/attr.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <errno.h>

extern void unix_error (int errcode, char * cmdname, value arg) Noreturn;
extern void uerror (char * cmdname, value arg) Noreturn;

CAMLprim value isMacOSX (value nothing) {
  CAMLparam0();
#ifdef __APPLE__
  CAMLreturn(Val_true);
#else
  CAMLreturn(Val_false);
#endif
}

CAMLprim value getFileInfos (value path, value need_size) {
#ifdef __APPLE__

  CAMLparam2(path, need_size);
  CAMLlocal3(res, fInfo, length);
  int retcode;
  struct attrlist attrList;
  unsigned long options = FSOPT_REPORT_FULLSIZE;
  struct {
    u_int32_t length;
    char      finderInfo [32];
    off_t     rsrcLength;
  } __attribute__ ((packed)) attrBuf;

  attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
  attrList.reserved = 0;
  attrList.commonattr = ATTR_CMN_FNDRINFO;
  attrList.volattr = 0;     /* volume attribute group */
  attrList.dirattr = 0;     /* directory attribute group */
  if (Bool_val (need_size))
    attrList.fileattr = ATTR_FILE_RSRCLENGTH;    /* file attribute group */
  else
    attrList.fileattr = 0;
  attrList.forkattr = 0;    /* fork attribute group */

  retcode = getattrlist(String_val (path), &attrList, &attrBuf,
                        sizeof attrBuf, options);

  if (retcode == -1) uerror("getattrlist", path);

  if (Bool_val (need_size)) {
    if (attrBuf.length != sizeof attrBuf)
      unix_error (EINVAL, "getattrlist", path);
  } else {
    if (attrBuf.length != sizeof (u_int32_t) + 32)
      unix_error (EINVAL, "getattrlist", path);
  }

  fInfo = caml_alloc_string (32);
  memcpy ((char *) String_val (fInfo), attrBuf.finderInfo, 32);
  if (Bool_val (need_size))
    length = caml_copy_int64(attrBuf.rsrcLength);
  else
    length = caml_copy_int64(0);

  res = caml_alloc_small(2, 0);
  Field (res, 0) = fInfo;
  Field (res, 1) = length;

  CAMLreturn (res);

#else

  unix_error (ENOSYS, "getattrlist", path);

#endif
}

CAMLprim value setFileInfos (value path, value fInfo) {
#ifdef __APPLE__

  CAMLparam2(path, fInfo);
  int retcode;
  struct attrlist attrList;
  unsigned long options = 0;
  struct {
    u_int32_t length;
    char      finderInfo [32];
  } __attribute__ ((packed))  attrBuf;

  attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
  attrList.reserved = 0;
  attrList.commonattr = ATTR_CMN_FNDRINFO;
  attrList.volattr = 0;     /* volume attribute group */
  attrList.dirattr = 0;     /* directory attribute group */
  attrList.fileattr = 0;    /* file attribute group */
  attrList.forkattr = 0;    /* fork attribute group */

  memcpy (attrBuf.finderInfo, String_val (fInfo), 32);

  retcode = setattrlist(String_val (path), &attrList, attrBuf.finderInfo,
                        sizeof attrBuf.finderInfo, options);

  if (retcode == -1 && errno == EACCES) {
    /* Unlike with normal Unix attributes, we cannot set OS X attributes
       if file is read-only.  Try making it writable temporarily. */
    struct stat st;
    int r = stat(String_val(path), &st);
    if (r == -1) uerror("setattrlist", path);
    r = chmod(String_val(path), st.st_mode | S_IWUSR);
    if (r == -1) uerror("setattrlist", path);
    /* Try again */
    retcode = setattrlist(String_val (path), &attrList, attrBuf.finderInfo,
                          sizeof attrBuf.finderInfo, options);
    /* Whether or not that worked, we should try to set the mode back. */
    chmod(String_val(path), st.st_mode);
  }

  if (retcode == -1) uerror("setattrlist", path);

  CAMLreturn (Val_unit);

#else

  unix_error (ENOSYS, "setattrlist", path);

#endif
}
