/*
 * Copyright (c) 2003-2016
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*****************************************************************************
 * COPYRIGHT AND PERMISSION NOTICE
 * 
 * Copyright (c) 2001-2003 The Queen in Right of Canada
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, and/or sell
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that both the
 * above copyright notice(s) and this permission notice appear in supporting
 * documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE 
 * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
 * SOFTWARE.
 * 
 * Except as contained in this notice, the name of a copyright holder shall not
 * be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization of the
 * copyright holder.
 ***************************************************************************/

/*
 * This is a VFS layer for the native filesystem.
 * Each object is mapped to a file and each operation is on a file.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2016\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: vfs_fs.c 2913 2016-10-18 19:55:39Z brachman $";
#endif

#include "local.h"
#include "vfs.h"

#ifdef ENABLE_FTS
#include "dacs_fts.h"
#else
#include <fts.h>
#endif

static const char *log_module_name = "vfs_fs";

static int fs_open(Vfs_handle *handle, char *naming_context);
static int fs_close(Vfs_handle *handle);
static int fs_control(Vfs_handle *, Vfs_control_op op, va_list ap);
static int fs_get(Vfs_handle *handle, char *key, void **buffer,
				  size_t *length);
static int fs_getsize(Vfs_handle *handle, char *key, size_t *length);
static int fs_put(Vfs_handle *handle, char *key, void *buffer,
				  size_t length);
static int fs_delete(Vfs_handle *handle, char *key);
static int fs_exists(Vfs_handle *handle, char *key);
static int fs_rename(Vfs_handle *handle, char *oldkey, char *newkey);
static int fs_list(Vfs_handle *handle, int (*is_valid)(char *),
				   int (*compar)(const void *, const void *),
				   int (*add)(char *, char *, void ***), void ***names);

static Vfs_switch fs_conf = {
  "fs",
  fs_open,
  fs_close,
  fs_control,
  fs_get,
  fs_getsize,
  fs_put,
  fs_delete,
  fs_exists,
  fs_rename,
  fs_list
};

static Vfs_switch file_conf = {
  "file",
  fs_open,
  fs_close,
  fs_control,
  fs_get,
  fs_getsize,
  fs_put,
  fs_delete,
  fs_exists,
  fs_rename,
  fs_list
};

#include <dirent.h>

/*
 * Return the number of files in directory FILENAME, or -1 if an error occurs.
 */
static int
file_count(char *filename)
{
  int n;
  struct dirent *d;
  DIR *dirp;

  if ((dirp = opendir(filename)) == NULL)
	return(-1);

  n = 0;
  while ((d = readdir(dirp)) != NULL)
	n++;

  closedir(dirp);

  if (n < 2)
	return(-1);		/* Huh? */

  return(n - 2);
}

static char *
getpath(Vfs_handle *handle, char *key)
{
  char *path;

  if (handle->sd->naming_context != NULL) {
	if (key == NULL)
	  path = ds_xprintf("%s", handle->sd->naming_context);
	else
	  path = ds_xprintf("%s/%s", handle->sd->naming_context, key);
  }
  else
	path = ds_xprintf("%s", key);

  return(path);
}

static int
fs_open(Vfs_handle *handle, char *naming_context)
{

  if (naming_context == NULL)
	handle->sd->naming_context = DACS_HOME;
  else if (handle->delete_flag) {
	struct stat sb;

	if (stat(naming_context, &sb) == -1) {
	  if (errno != ENOENT)
		goto err;
	  return(0);
	}

	/*
	 * XXX This is busted for the files-within-a-directory case
	 * because we don't know their names yet.  We would need to delete them
	 * on the first access but only then, keeping track of the names in
	 * the handle.  But what would the list operation do?  Would it delete
	 * all listed files?
	 */
	if ((sb.st_mode & S_IFMT) == S_IFDIR) {
	  /* This is only a problem if the directory is not empty. */
	  if (file_count(naming_context) == 0)
		return(0);
	  errno = ENOTEMPTY;
	  goto err;
	}

	if ((sb.st_mode & S_IFMT) != S_IFREG) {
	  errno = EPERM;
	  goto err;
	}

	if (unlink(naming_context) == -1)
	  goto err;
  }

  return(0);

 err:
  handle->error_num = errno;
  handle->error_msg = strerror(errno);
  return(-1);
}

static int
fs_close(Vfs_handle *handle)
{

  return(0);
}

static int
fs_control(Vfs_handle *handle, Vfs_control_op op, va_list ap)
{
  char **pptr;
  Vfs_lock_type lt;

  switch (op) {
  case VFS_GET_CONTAINER:
	pptr = va_arg(ap, char **);
	*pptr = ds_xprintf("file:%s", handle->sd->naming_context);
	break;

  case VFS_SET_LOCK:
	lt = va_arg(ap, int);
	if (lt == VFS_SHLOCK)
	  ;
	else if (lt == VFS_EXLOCK)
	  ;
	else if (lt == VFS_NOLOCK)
	  ;
	else {
	  log_msg((LOG_ERROR_LEVEL, "Invalid control request for lock: %d", lt));
	  return(-1);
	}
	/* At present, this operation doesn't make sense. */
    log_msg((LOG_ERROR_LEVEL, "Invalid control request: %d", op));

	break;

  case VFS_SET_FIELD_SEP:
	/* Nothing to do... */
	break;

  case VFS_GET_FIELD_SEP:
	return(-1);
	/*NOTREACHED*/
	break;

  default:
    log_msg((LOG_ERROR_LEVEL, "Invalid control request: %d", op));
    return(-1);
    /*NOTREACHED*/
  }

  return(0);
}

static int
getsize(char *path, size_t *length)
{
  int st;
  struct stat sb;

  st = stat(path, &sb);
  if (st == -1)
	return(-1);

  *length = sb.st_size;

  return(0);
}

/*
 * This assumes that a regular file (not a stream) is being read.
 * That was adequate for the original functionality that was needed,
 * such as slurping up a password file, but is obviously lacking for other
 * purposes.
 */
static int
fs_get(Vfs_handle *handle, char *key, void **buffer, size_t *length)
{
  int st;
  char *b, *path;
  size_t n, nread;
  FILE *fp;

  path = getpath(handle, key);

  if (getsize(path, &n) == -1) {
	handle->error_num = errno;
	handle->error_msg = strdup(strerror(errno));
	free(path);
	return(-1);
  }

  b = (char *) malloc(n + 1);

  if ((fp = fopen(path, "r")) == NULL) {
	handle->error_num = errno;
	handle->error_msg = strdup(strerror(errno));
	free(path);
	return(-1);
  }

#ifdef NOTDEF
  /*
   * Some systems do not support flock(), which is a shame because it has
   * better semantics than lockf() or fcntl().
   */
  if (flock(fileno(fp), LOCK_SH) == -1) {
	handle->error_num = errno;
	handle->error_msg = strdup(strerror(errno));
	return(-1);
  }
#endif

  free(path);

  st = 0;
  if ((nread = fread(b, 1, n, fp)) != n) {
	if (ferror(fp)) {
	  handle->error_num = errno;
	  handle->error_msg = strdup(strerror(errno));
	}
	else
	  handle->error_msg = strdup("fread did not return correct byte count");
	st = -1;
  }

  if (ferror(fp)) {
	if (handle->error_msg == NULL) {
	  handle->error_num = errno;
	  handle->error_msg = strdup(strerror(errno));
	}
	st = -1;
  }

  if (fclose(fp) == EOF) {
	if (handle->error_msg == NULL) {
	  handle->error_num = errno;
	  handle->error_msg = strdup(strerror(errno));
	}
	st = -1;
  }

  if (st == 0) {
	b[n] = '\0';
	if (length != NULL)
	  *length = n;
	*buffer = (void *) b;
	return(0);
  }

  free(b);
  return(st);
}

static int
fs_getsize(Vfs_handle *handle, char *key, size_t *length)
{
  int st;
  char *path;

  path = getpath(handle, key);

  st = getsize(path, length);
  free(path);

  return(st);
}

static int
fs_put(Vfs_handle *handle, char *key, void *buffer, size_t length)
{
  int st;
  char *mode, *path;
  size_t nwritten;
  FILE *fp;

  path = getpath(handle, key);

  mode = handle->append_flag ? "a" : "w";

  if ((fp = fopen(path, mode)) == NULL) {
	handle->error_num = errno;
	handle->error_msg = strdup(strerror(errno));
	free(path);
	return(-1);
  }
  free(path);

  if (handle->lock_flag != VFS_NOLOCK) {
#ifdef NOTDEF
	if (flock(fileno(fp), LOCK_EX) == -1) {
	  handle->error_num = errno;
	  handle->error_msg = strdup(strerror(errno));
	  return(-1);
	}
#else
	if (lockf(fileno(fp), F_LOCK, 0) == -1) {
	  handle->error_num = errno;
	  handle->error_msg = strdup(strerror(errno));
	  return(-1);
	}
#endif
  }

  st = 0;
  if ((nwritten = fwrite(buffer, 1, length, fp)) != length) {
	if (ferror(fp)) {
	  handle->error_num = errno;
	  handle->error_msg = strdup(strerror(errno));
	}
	else
	  handle->error_msg = strdup("fwrite did not return correct byte count");
	st = -1;
  }
  if (ferror(fp)) {
	if (handle->error_msg == NULL) {
	  handle->error_num = errno;
	  handle->error_msg = strdup(strerror(errno));
	}
	st = -1;
  }
  if (fclose(fp) == EOF) {
	if (handle->error_msg == NULL) {
	  handle->error_num = errno;
	  handle->error_msg = strdup(strerror(errno));
	}
	st = -1;
  }

  return(st);
}

static int
fs_delete(Vfs_handle *handle, char *key)
{
  int st;
  char *path;

  path = getpath(handle, key);

  if ((st = unlink(path)) == -1) {
	handle->error_num = errno;
	handle->error_msg = strdup(strerror(errno));
	free(path);
	return(-1);
  }

  free(path);

  return(0);
}

static int
fs_exists(Vfs_handle *handle, char *key)
{
  int st;
  char *path;

  path = getpath(handle, key);

  if ((st = access(path, R_OK)) == -1) {
	if (errno == ENOENT)
	  return(0);
	handle->error_num = errno;
	handle->error_msg = strdup(strerror(errno));
	free(path);
	return(-1);
  }

  free(path);

  return(1);
}

static int
fs_rename(Vfs_handle *handle, char *oldkey, char *newkey)
{
  int st;
  char *newpath, *oldpath;

  newpath = getpath(handle, newkey);
  oldpath = getpath(handle, oldkey);

  if ((st = rename(oldpath, newpath)) == -1) {
	handle->error_num = errno;
	handle->error_msg = strdup(strerror(errno));
	free(oldpath);
	free(newpath);
	return(-1);
  }

  free(oldpath);
  free(newpath);

  return(0);
}

static int
fs_list(Vfs_handle *handle, int (*is_valid)(char *),
		int (*compar)(const void *, const void *),
		int (*add)(char *, char *, void ***), void ***names)
{
  int n;
  char *heir[2];
  FTS *ftsp;
  FTSENT *f;

  if (handle->sd->naming_context == NULL)
	heir[0] = "/";
  else
	heir[0] = handle->sd->naming_context;
  heir[1] = NULL;
  ftsp = fts_open(heir,
				  FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV,
				  (int (*)(const FTSENT **, const FTSENT **)) compar);
  if (ftsp == NULL) {
	handle->error_num = errno;
	handle->error_msg = strdup(strerror(errno));
	return(-1);
  }

  n = 0;
  while ((f = fts_read(ftsp)) != NULL) {
	if (f->fts_info == FTS_NS && f->fts_errno == ENOENT) {
	  /*
	   * If this file was deleted but a directory entry still (mistakenly)
	   * exists, ignore the entry.
	   * This was seen to occur on FreeBSD 6.2/7.0, maybe because of filesystem
	   * corruption.
	   */
	  continue;
	}

	if (f->fts_info == FTS_ERR
		|| f->fts_info == FTS_NS
		|| f->fts_info == FTS_DNR) {
	  errno = f->fts_errno;
	  handle->error_num = errno;
	  handle->error_msg = ds_xprintf("%s: %s", f->fts_path, strerror(errno));
	  return(-1);
	}
	if (f->fts_level > 0
		&& (f->fts_info == FTS_D || f->fts_info == FTS_DP)
		&& is_valid != NULL && is_valid(f->fts_name) != 1) {
	  if (fts_set(ftsp, f, FTS_SKIP) == -1) {
		handle->error_num = errno;
		handle->error_msg = ds_xprintf("%s: %s", f->fts_path, strerror(errno));
		return(-1);
	  }
	}
	else if (f->fts_info == FTS_F) {
	  if (add(handle->sd->naming_context, f->fts_path, names) == 1)
		n++;
	}
  }
  if (errno != 0) {
	errno = f->fts_errno;
	handle->error_num = errno;
	handle->error_msg = strdup(strerror(errno));
	return(-1);
  }

  if (fts_close(ftsp) == -1) {
	handle->error_num = errno;
	handle->error_msg = strdup(strerror(errno));
	return(-1);
  }

  return(n);
}

Vfs_switch *
vfs_file_init(char *store_name)
{

  if (strcaseeq(store_name, "fs"))
	  return(&fs_conf);
  else if (strcaseeq(store_name, "file"))
	  return(&file_conf);
  return(NULL);
}

void
vfs_file_term(char *store_name)
{

}
