/*
 * 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>
#include <dirent.h>
#include <utime.h>

OC_bool
path_should_skip_dirent(const char *s)
{
  if (s == NULL)
    THROW(ExNullArg, "No path to path_should_skip_dirent()");

  if (strcmp(s, ".") == 0)
    return TRUE;

  if (strcmp(s, "..") == 0)
    return TRUE;

  return FALSE;
}

const char *
path_resolvelink(const char *path)
{
  const char *newpath;
  char buf[PATH_MAX+1];
  size_t len;

  len = readlink(path, buf, PATH_MAX);
  if (len < 1)
    THROW(ExNoObject, format("Reading symbolic link %s: errno %d",
			     path, errno));

  buf[len] = 0;

  if (buf[0] == '/')
    return xstrdup(buf);

  newpath = path_dirname(path);
  newpath = path_join(newpath, buf);


  return newpath;
}

void
path_mkexecutable(const char *s, OC_bool shouldBeExec)
{
  if (chmod(s, shouldBeExec ? 0755 : 0644) < 0)
    THROW(ExNoAccess, 
	  format("Could not change permissions on \"%s\": errno %d",
		 s, errno));
}

int
path_mksymlink(const char *old, const char *new)
{
  return symlink(old, new);
}

/* If your system has a brain-damaged file system with unreasonably
 * short file name lengths you may need to break entity names into
 * pieces.
 */
const char *
path_tn_fixup(const char *en)
{
#ifdef BROKEN_FS_ENTITIES
#error "Incomplete code!"
#else
  return en;
#endif
}

const char *
path_curdir()
{
  return ".";
}

/* path_rootdir() may be a problem, because Windows doesn't really
 * have such a notion. */
const char *
path_rootdir()
{
  return "/";
}

const char *
path_parent_dir()
{
  return "..";
}

OC_bool
path_is_dir_sep(char c)
{
  return c == '/';
}

OC_bool
path_isAbsolute(const char *path)
{
  return (*path == '/');
}

OC_bool
path_isRelative(const char *path)
{
  return (*path != '/');
}

const char *
path_canonical(const char *path)
{
  uint32_t len = 0;
  const char *p = path;
  char *s, *sptr;

  /* Compute the worst-case length of the string */
  while (*p) {
    while (*p == '/' && p[1] == '/')
      p++;

    p++;
    len++;
  }

  len++;			/* for trailing null */

  s = (char *) GC_MALLOC_ATOMIC (sizeof(char) * len); 

  sptr = s;
  p = path;

  /* Regrettably, I do not see a way to do all of this in a single
   * pass cleanly. */

  /* Pass 1: Make a copy, stripping out redundant '/' characters: */
  do {
    *sptr++ = *p;
    while (*p == '/' && p[1] == '/')
      p++;
    
    p++;
  } while (*p);
  *sptr = 0;
  
  /* Pass 2: Remove trailing '/', if any. */
  while (sptr != s && sptr[-1] == '/') {
    --sptr;
    *sptr = 0;
  }
  
  /* Pass 3: Remove trailing '/.', if any. */
  while (sptr - s >= 2 && sptr[-1] == '.' && sptr[-2] == '/') {
    sptr -= 2;
    *sptr = 0;
  }
  
  /* Pass 3: Reduce instances of "/./" and /x/../ to "/". */
  sptr = s;
  p = s;

  while (*p) {
    while (p[0] == '/' && p[1] == '.') {
      if (p[2] == '/' || p[2] == 0) {
	/* Looking at "/./" or "/.\0" -- strip it */
	p += 2;
	continue;
      }

      if (p[2] == '.' && (p[3] == '/' || p[3] == 0)) {
	/* Looking at "/../" or "/..\0" -- strip it */

	/* There is an open issue here, which is interpretation of
	 * "../" after symlink. Regrettably, it is simply not true
	 * that "a/b/../c" is always "a/c", but we assume here that
	 * it is.
	 */
	assert (sptr != s);

	while (sptr != s && *--sptr != '/')
	  ;
	*sptr = 0;

	p += 3;
	continue;
      }

      /* No other stuff to strip, and leading '.' is a legitemate
       * character in a file name, so... */
      break;
    }

    if (*p)
      *sptr++ = *p++;
  }

  *sptr = 0;

  return s;
}

void
path_smkdir(const char *path)
{
  char *tmppath;
  char *slash;
    
  if (path == NULL)
    THROW(ExNullArg, "no path to path_smkdir()");

  tmppath = xstrdup(path);
  slash = tmppath;

  /* Skip past leading slashes so that the subsequent search for the
     rest of the path doesn't stop with a leading slash:
  */
  while (*slash == '/')
    slash++;
  
  while ((slash = strchr(slash, '/'))) {
    *slash = 0;			/* truncate the path */

    if (!path_exists(tmppath))
      path_mkdir(tmppath);

    *slash = '/';		/* untruncate the path */
    slash++;
  }

  if (!path_exists(tmppath))
    path_mkdir(tmppath);
}

const char *
path_car(const char *path)
{
  const char *end = path;
  while (*end && !path_is_dir_sep(*end))
    end++;

  if (*end == 0)
    return path;
  else
    return xstrndup(path, end-path);
}

const char *
path_cdr(const char *path)
{
  while (*path && !path_is_dir_sep(*path))
    path++;

  if (*path)
    return path+1;
  else
    return 0;
}

void
path_portstat(const char *fname, portstat_t *ps)
{
  struct stat sb;
  int result;
 
  if (fname == NULL || strcmp(fname, "") == 0)
    THROW(ExBadValue, "Null or empty filename passed to path_portstat().");

  memset(ps, 0, sizeof(*ps));

  /* Use "lstat" here so we don't traverse any symlinks! Originally we
     used just "stat", and ran into interesting results as OpenCM
     tried to follow the symlinks to their targets. */
  result = lstat(fname, &sb);
  if (result == -1) {
    if (errno == ENOENT) {
      ps->exists = FALSE;
      return;
    }
    /* A component of the path is not a directory:
         We don't want to just ignore this, because 'update' will fail when it
         tries to write to something inside the "directory". But making the
         error message more descriptive is nice, so users can understand what
         is wrong, and hopefully how to fix it (that may be a bit of a stretch,
         but whatever)
    */
    else if (errno == ENOTDIR) {
      THROW(ExNoObject,
            format("One or more components of %s is not a directory", fname));
    }
    else {
      THROW(ExNoObject,
            format("Could not stat path [%s]: errno %d", fname, errno));
    }
  }

  ps->exists = TRUE;
  ps->modTime = sb.st_mtime;
  ps->len     = sb.st_size;

  if (S_ISREG(sb.st_mode) && access(fname, X_OK) == 0)
    ps->isExec = TRUE;

  /* Decode the file type: */
  ps->type = '?';

  if (S_ISREG(sb.st_mode))
    ps->type = psFile;
  else if (S_ISDIR(sb.st_mode))
    ps->type = psDir;
  else if (S_ISLNK(sb.st_mode))
    ps->type = psSymlink;
  else if (S_ISCHR(sb.st_mode))
    ps->type = psChrDev;
}


void
path_set_mod_time(const char *path, const char *modTime)
{
  struct utimbuf tb;
  tb.modtime = os_FromISO(modTime);
  tb.actime = tb.modtime;

  utime(path, &tb);
}

struct OC_DIR_st
  {
    DIR* dir;
  };

OC_DIR* path_opendir(const char* dir_name, OC_bool must_exist)
{
  OC_DIR* dir = GC_MALLOC(sizeof(OC_DIR));
  dir->dir = opendir(dir_name);

  if(!dir->dir)
  {
    if(must_exist)
      THROW(ExNoObject, format("Couldn't open directory '%s'", dir_name));
    else
      return 0;
  }
  return dir;
}

const char* path_readdir(OC_DIR* dir)
{
  struct dirent* dir_ent = readdir(dir->dir);
  if(dir_ent == 0)
    return 0;
  return xstrdup(dir_ent->d_name);
}

void path_closedir(OC_DIR* dir)
{
  closedir(dir->dir);
}
