/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <opencm.h>

#define USE_SYMLINKS

OC_bool
path_exists(const char *s)
{
  portstat_t ps;
  path_portstat(s, &ps);

  return (ps.exists ? TRUE : FALSE);
}

OC_bool
path_isfile(const char *s)
{
  portstat_t ps;
  path_portstat(s, &ps);

  if (ps.exists && ps.type == psFile)
    return TRUE;
  
  return FALSE;
}

OC_bool
path_issymlink(const char *s)
{
  portstat_t ps;
  path_portstat(s, &ps);

  if (ps.exists && ps.type == psSymlink)
    return TRUE;
  
  return FALSE;
}

OC_bool
path_isdir(const char *s)
{
  portstat_t ps;
  path_portstat(s, &ps);

  if (ps.exists && ps.type == psDir)
    return TRUE;
  
  return FALSE;
}

OC_bool
path_isexecutable(const char *s)
{
  portstat_t ps;
  path_portstat(s, &ps);

  if (ps.exists && ps.isExec)
    return TRUE;
  
  return FALSE;
}

off_t
path_file_length(const char *name)
{
  portstat_t ps;
  path_portstat(name, &ps);

  if (ps.exists)
    return ps.len;

  THROW(ExNoObject, 
	format("Could not stat \"%s\" or wrong file type", name));
}
  
int
path_link_tail(const char *old, const char *new)
{
#ifdef USE_SYMLINKS
  if (path_issymlink(old))
    old = path_resolvelink(old);

  return path_mksymlink(path_tail(old), new);
#else
  return link(old, new);
#endif
}

const char *
path_join(const char *dir, const char *tail)
{
  char *s = NULL;

  if (dir == NULL)
    THROW(ExNullArg, "no dir to path_join()");

  if (tail == NULL)
    THROW(ExNullArg, "no tail to path_join()");

  s =
     (char *) GC_MALLOC_ATOMIC(sizeof(char) * (strlen(dir) + strlen(tail) + 2)); 

  strcpy(s, dir);
  strcat(s, "/");
  strcat(s, tail);

  return s;
}

void
path_mkdir(const char *path)
{
  if (mkdir(path, 0777) < 0)
    THROW((errno == EEXIST) ? ExObjectExists : ExNoObject, 
	  format("Could not create directory %s (errno %d)", path, errno));
}

void
path_create(const char *path)
{
  if (creat(path, 0666) < 0)
    THROW(ExNoObject, 
	  format("Could not create file %s (errno %d)", path, errno));
}

void
path_rmdir(const char *path)
{
  if (rmdir(path) < 0)
    THROW(ExNoAccess, 
	  format("Could not rmdir \"%s\": errno %d\n", path, errno));
}

void
path_rmdir_recursive(const char *path)
{
  OC_DIR *dir;
  StrVec *sv = strvec_create();
  int i;
  const char* ent;

  dir = path_opendir(path, FALSE);

  /* If the directory isn't there, that obviously there are not any entities
     inside of it */
  if(!dir)
    return;

  while ((ent = path_readdir(dir))) {
    if (path_should_skip_dirent(ent))
      continue;

    strvec_append(sv, path_join(path, ent));
  }

  path_closedir(dir);

  for (i = 0; i < vec_size(sv); i++) {
    const char *subPath = vec_fetch(sv, i, const char *);

    if (path_isdir(subPath))
      path_rmdir_recursive(subPath);
    else
      path_remove(subPath);
  }

  if (rmdir(path) < 0)
    THROW(ExNoAccess, 
	  format("Could not rmdir \"%s\": errno %d\n", path, errno));
}

const char *
path_mktmpnm(const char *dir, const char *base)
{
  const char *path = path_join(dir, xstrcat(base, truename_NewName()));
  onthrow_remove(path);
  return path;
}

const char *
path_scratchdir()
{
  static const char *path = NULL;

  if (path)
    return path;

  path = getenv("TMPDIR");

#ifdef _PATH_VARTMP
  if (path == NULL)
    path = _PATH_VARTMP;
#endif

#ifdef _PATH_TMP
  if (path == NULL)
    path = _PATH_TMP;
#endif

#ifdef L_tmpdir
  if (path == NULL)
    path = L_tmpdir;
#endif

#ifdef P_tmpdir
  if (path == NULL)
    path = P_tmpdir;
#endif

  if (path == NULL &&
      access("/var/tmp", W_OK) == 0)
    path = "/var/tmp";

  if (path == NULL &&
      access("/usr/tmp", W_OK) == 0)
    path = "/usr/tmp";

  if (path == NULL)
    path = os_GetHomeDir();

  return path;
}

void
path_remove(const char *name)
{
  int result = 0;

  if (path_exists(name))
    result = unlink(name);

  if (result < 0 && errno != ENOENT)
    THROW(ExNoAccess, 
	  format("Could not remove\"%s\" (errno %d)", name, errno));
}

void
path_remove_editor_file(const char *name)
{
  const char *emacs_turdFile = xstrcat(name, "~");

  if (path_exists(name))
    path_remove(name);

  if (path_exists(emacs_turdFile))
    path_remove(emacs_turdFile);
}

/* NOTE: path_rename needs to be an atomic operation, and therefore
   cannot perform the path_smkdir() for us. We should perhaps consider
   introducing path_srename() and replacing path_rename() with
   path_atomic_rename() to make this explicit. */
void
path_rename(const char *oldpath, const char *newpath)
{
  int result = rename(oldpath, newpath);
  
  if (result < 0)
    THROW(ExNoAccess, 
	  format("Could not rename\"%s\" to \"%s\" (errno %d)", 
		 oldpath, newpath, errno));
}

void
path_srename(const char *oldpath, const char* newpath)
{
  const char *newDir = path_dirname(newpath);
  path_smkdir(newDir);

  path_rename(oldpath, newpath);
}

const char *
path_current_directory()
{
#ifdef __unix__
#ifdef PATH_MAX
  int len = PATH_MAX; 
#else
  int len = 255; /* POSIX guarantees this (or does it?) */
#endif
  char * dir = (char *) GC_MALLOC_ATOMIC(sizeof(char) * len); 
  char *cwd;

  do {
    cwd =  getcwd(dir, len);
    if (cwd == NULL) {
      if (errno == ERANGE) {
	len *= 2;
	dir = (char *) GC_MALLOC_ATOMIC(sizeof(char) * len); 
      }
      else
	return NULL;
    }
  } while (cwd == NULL);

  return dir;
#else
#  error "path_current_directory() not implemented"
#endif
}

OC_bool
path_same_dir(const char *d1, const char *d2)
{
#ifdef __unix__
  struct stat cur_dir;
  struct stat parent_dir;

  if (stat(d1, &cur_dir) < 0)
    return FALSE;
  if (stat(d2, &parent_dir) < 0)
    return FALSE;

  if (cur_dir.st_ino == parent_dir.st_ino &&
      cur_dir.st_dev == parent_dir.st_dev)
    return TRUE;

  return FALSE;
#else
#  error "path_same_dir() not implemented"
#endif
}

/* Return TRUE if /prefix/ is /path/ or if /prefix/ is a directory on
 * the path to /path/. We assume that both /prefix/ and /path/ are
 * canonical, but this still leaves the complication that the prefix
 * may have a trailing '/'. */
OC_bool
path_isprefix(const char *prefix, const char *path)
{
  while(prefix) {
    const char *prefixCar = path_car(prefix);
    const char *pathCar = path_car(path);

    if (!nmequal(prefixCar, pathCar))
      return FALSE;
    
    prefix = path_cdr(prefix);
    path = path_cdr(path);

    if (prefix && !path)
      return FALSE;
  }

  return TRUE;
}

const char *
path_tail(const char *path)
{
  const char *tail = path_cdr(path);

  if (tail)
    return path_tail(tail);
  else
    return path;
}

/* This should work for windows too, because in any path where a
 * path_dirname() exists, the colon will be followed by a backslash.
 */
const char *
path_dirname(const char *path) 
{
  const char *p = path;
  const char *last_dirsep = 0;

  for (; *p; p++) {
    if (path_is_dir_sep(*p))
      last_dirsep = p;
  }

  if (last_dirsep)
    return xstrndup(path, last_dirsep - path);
  else if (path_isAbsolute(path))
    return path_rootdir();
  else
    return path_curdir();
}
