#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <atalk/adouble.h>

/* translate between ADLOCK styles and specific locking mechanisms */
#define XLATE_FLOCK(type) ((type) == ADLOCK_RD ? LOCK_SH : \
((type) == ADLOCK_WR ? LOCK_EX : \
 ((type) == ADLOCK_CLR ? LOCK_UN : -1)))

#define XLATE_FCNTL_LOCK(type) ((type) == ADLOCK_RD ? F_RDLCK : \
((type) == ADLOCK_WR ? F_WRLCK : \
 ((type) == ADLOCK_CLR ? F_UNLCK : -1))) 
     
/* byte-range locks. ad_lock is used by afp_bytelock and afp_openfork
 * to establish locks. both ad_lock and ad_tmplock take 0, 0, 0 to
 * signify locking of the entire file. in the absence of working 
 * byte-range locks, this will default to file-wide flock-style locks.
 */
int ad_flock_lock(struct adouble *ad, const u_int32_t eid, const int type,
		  off_t off, const int end, const off_t len)
{
  int err, lock_type;
  
  lock_type = XLATE_FLOCK(type);
  if (eid == ADEID_DFORK) {
    if ((err = flock(ad_dfileno(ad), lock_type | LOCK_NB)) == 0)
      ad_dlock(ad).l_type = lock_type;
  } else if ((err = flock(ad_hfileno(ad), lock_type | LOCK_NB)) == 0)
    ad_hlock(ad).l_type = lock_type;

  if (err) {
    if ((EWOULDBLOCK != EAGAIN) && (errno == EWOULDBLOCK))
      errno = EAGAIN;
    return -1;
  } 

  return off; /* lie about the actual offset */
}

int ad_fcntl_lock(struct adouble *ad, const u_int32_t eid, const int type,
		  off_t off, const int end, const off_t len)
{
  struct flock lock, *savelock;
  int fd;
  
  if (eid == ADEID_DFORK) {
    if (end && ((off += ad_size(ad, eid)) < 0))
      return -1;
    fd = ad_dfileno(ad);
    savelock = &ad_dlock(ad);
  } else {
    if (end && ((off += ad_getentrylen(ad, eid)) < 0))
      return -1;
    fd = ad_hfileno(ad);
    savelock = &ad_hlock(ad);
  }

  lock.l_start = off;
  lock.l_type = XLATE_FCNTL_LOCK(type);
  lock.l_whence = SEEK_SET;
  lock.l_len = len;

  /* okay, lock it. */
  if (fcntl(fd, F_SETLK, &lock) < 0)
    return -1;

  memcpy(savelock, &lock, sizeof(lock));
  return off;
}


/* ad_tmplock is used by afpd to lock actual read/write operations. 
 * it saves the current lock state before attempting to lock to prevent
 * mixups. if byte-locks don't exist, it will lock the entire file with
 * an flock. we can be a little smart here by just upgrading/downgrading
 * locks.
 */
int ad_flock_tmplock(struct adouble *ad, const u_int32_t eid, const int type,
	             off_t off, const int end, const off_t len) 
{
  int fd, oldlock, lock_type;
  
  if (eid == ADEID_DFORK) {
    oldlock = ad_dlock(ad).l_type;
    fd = ad_dfileno(ad);
  } else {
    oldlock = ad_hlock(ad).l_type;
    fd = ad_hfileno(ad);
  }

  /* if we already have a write lock, we don't need to do anything */
  if (oldlock == LOCK_EX) {
    return 0;
  }

  /* if we have a read lock, upgrade it if necessary */
  lock_type = XLATE_FLOCK(type);
  if (oldlock == LOCK_SH) {
    if (lock_type == LOCK_EX) 
      return flock(fd, LOCK_EX | LOCK_NB);
    else if (lock_type == LOCK_UN) /* reset it */
      return flock(fd, LOCK_SH | LOCK_NB);
    else /* do nothing */
      return 0;
  }

  /* if we don't already have a lock, just do it. */
  return flock(fd, lock_type | LOCK_NB);
}

/* fnctl-style locks don't lock within the same process.
 * as a result, we have to check things explicitly.  */
int ad_fcntl_tmplock(struct adouble *ad, const u_int32_t eid, const int type,
	             off_t off, const int end, const off_t len)
{
  struct flock lock, *savelock;
  int fd, lock_type;

  if (eid == ADEID_DFORK) {
    if (end && (off += ad_size(ad, eid)) < 0)
      return -1;
    fd = ad_dfileno(ad);
    savelock = &ad_dlock(ad);
 } else {
    if (end && ((off += ad_getentrylen(ad, eid)) < 0))
      return -1;
    fd = ad_hfileno(ad);
    savelock = &ad_hlock(ad);
  }

  lock_type = XLATE_FCNTL_LOCK(type);
  lock.l_type = lock_type;
  lock.l_whence = SEEK_SET;
  lock.l_start = off;
  lock.l_len = len;

  /* same process semantics:
   * check to see if we already have a whole-file lock. if so, we just
   * follow the same semantics as flock. */
  if (savelock->l_type != F_UNLCK && savelock->l_start == 0 && 
      savelock->l_len == 0) {
    /* if we already have a write lock, do nothing */
    if (savelock->l_type == F_WRLCK)
      return 0;
    
    /* upgrade a read lock if necessary */
    if (savelock->l_type == F_RDLCK) {
      if (lock_type == F_WRLCK) {
	return fcntl(fd, F_SETLK, &lock);
      } else if (lock_type == F_UNLCK) {/* reset the lock */
	lock.l_type = F_RDLCK;
	return fcntl(fd, F_SETLK, savelock);
      } else 
	return 0;
    }
    
    /* we don't have a whole-file lock. just fall-through. */
  }

  /* set a lock. restore on an unlock if necessary. */
  return fcntl(fd, F_SETLK, lock_type == F_UNLCK &&
	       savelock->l_type != F_UNLCK ? savelock : &lock);
}
