/* Our little helpers.  Exported functions are prototyped in sundries.h.
 * $Header: /usr/src/mount/RCS/sundries.c,v 1.1 1992/09/06 13:30:53 root Exp root $
 */

#include "sundries.h"


/* File pointer for /etc/mtab.  */
FILE *F_mtab;

/* File pointer for lock.  */
FILE *F_lock;

/* File descriptor for lock.  Value tested in unlock_mtab() to remove race.  */
int lock = -1;

/* String list constructor.  (car() and cdr() are defined in "sundries.h").  */
string_list
cons (char * const a, const string_list b)
{
  string_list p;

  p = xmalloc (sizeof *p);

  car (p) = a;
  cdr (p) = b;
  return p;
}

void *
xmalloc (size_t size)
{
  void *t;

  if (size == 0)
    return NULL;

  t = malloc (size);
  if (t == NULL)
    die (2, "not enough memory");
  
  return t;
}

char *
xstrdup (const char *s)
{
  char *t;

  if (s == NULL)
    return NULL;
 
  t = strdup (s);

  if (t == NULL)
    die (2, "not enough memory");

  return t;
}

/* Call this with SIG_BLOCK to block and SIG_UNBLOCK to unblock.  */
void
block_signals (int how)
{
  sigset_t sigs;

  sigfillset (&sigs);
  sigprocmask (how, &sigs, (sigset_t *) 0);
}


/* Non-fatal error.  Print message and return.  */
void
error (const char *fmt, ...)
{
  va_list args;

  va_start (args, fmt);
  vfprintf (stderr, fmt, args);
  fprintf (stderr, "\n");
  va_end (args);
}

/* Fatal error.  Print message and exit.  */
void volatile
die (int err, const char *fmt, ...)
{
  va_list args;

  va_start (args, fmt);
  vfprintf (stderr, fmt, args);
  fprintf (stderr, "\n");
  va_end (args);

  exit (err);
}

/* Ensure that the lock is released if we are interrupted.  */
static void volatile
handler (int sig)
{
  die (2, "%s", sys_siglist[sig]);
}

/* Create the lock file.  The lock file will be removed if we catch a signal
   or when we exit.  The value of lock is tested to remove the race.  */
void
lock_mtab (void)
{
  int sig = 0;
  struct sigaction sa;

  /* If this is the first time, ensure that the lock will be removed.  */
  if (lock < 0)
    {
      sa.sa_handler = handler;
      sigfillset (&sa.sa_mask);
  
      /* We should use sigismember() but it is broken for libc <= 4.2.  */
#if 0
      while (sigismember (&sa.sa_mask, ++sig) != -1)
#else
      while (++sig < _NSIG)
#endif
	sigaction (sig, &sa, (struct sigaction *) 0);
    }

  if ((lock = open (MOUNTED_LOCK, O_WRONLY|O_CREAT|O_EXCL, 0644)) < 0)
    die (2, "can't create lock file %s: %s", MOUNTED_LOCK, strerror (errno));

  F_lock = fdopen (lock, "w");
}

/* Remove lock file.  */
void
unlock_mtab (void)
{
  endmntent (F_lock);
  if (lock != -1)
    unlink (MOUNTED_LOCK);
}


/* Create mtab with a root entry.  */
static void
create_mtab (void)
{
  struct mntent *fstab;

  if ((F_mtab = setmntent (MOUNTED, "a+")) == NULL)
    die (1, "mount: can't open %s for writing: %s", MOUNTED, strerror (errno));

  /* Find the root entry by looking it up in fstab, which might be wrong.
     We could statfs "/" followed by a slew of stats on /dev/ but then
     we'd have to unparse the mount options as well....  */
  if ((fstab = getfsfile ("/")) || (fstab = getfsfile ("root")))
    {
      if (addmntent (F_mtab, fstab) == 1)
	die (1, "mount: error writing %s: %s", MOUNTED, strerror (errno));
    }
  if (fchmod (fileno (F_mtab), S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0)
    die (1, "mount: error changing mode of %s: %s", MOUNTED, strerror (errno));
  endmntent (F_mtab);
}

/* Open mtab.  */
void
open_mtab (const char *mode)
{
  if (fopen (MOUNTED, "r") == NULL)
    create_mtab ();
  if ((F_mtab = setmntent (MOUNTED, mode)) == NULL)
    die (2, "can't open %s: %s", MOUNTED, strerror (errno));
}

/* Close mtab.  */
void
close_mtab (void)
{
  if (fchmod (fileno (F_mtab), S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0)
    die (1, "mount: error changing mode of %s: %s", MOUNTED, strerror (errno));
  endmntent (F_mtab);
}

/* Parse a -t typelist into a string list.  */
string_list
parse_types (char *types)
{
  string_list list;
  char *t;

  if (types == NULL)
    return NULL;

  list = cons (strtok (types, ","), NULL);

  while ((t = strtok (NULL, ",")) != NULL)
    list = cons (t, list);

  return list;
}

/* True if fstypes match.  Null *TYPES means match anything,
   except that swap types always return false.  This routine
   has some ugliness to deal with ``no'' types.  */
int
matching_type (const char *type, string_list types)
{
  char *notype;
  int no;			/* true if a "no" type match, ie -t nominix */

  if (streq (type, MNTTYPE_SWAP))
    return 0;
  if (types == NULL)
    return 1;

  if ((notype = alloca (strlen (type) + 3)) == NULL)
    die (2, "mount: out of memory");
  sprintf (notype, "no%s", type);
  no = (car (types)[0] == 'n') && (car (types)[1] == 'o');

  /* If we get a match and the user specified a positive match type (e.g.
     "minix") we return true.  If we match and a negative match type (e.g.
     "nominix") was specified we return false.  */
  while (types != NULL)
    if (streq (type, car (types)))
      return !no;
    else if (streq (notype, car (types)))
      return 0;			/* match with "nofoo" always returns false */
    else
      types = cdr (types);

  /* No matches, so if the user specified a positive match type return false,
     if a negative match type was specified, return true.  */
  return no;
}

/* Make a canonical pathname from PATH.  Returns a freshly malloced string.
   It is up the *caller* to ensure that the PATH is sensible.  i.e.
   canonicalize ("/dev/fd0/.") returns "/dev/fd0" even though ``/dev/fd0/.''
   is not a legal pathname for ``/dev/fd0.''  */
char *
canonicalize (const char *path)
{
  char *canonical = xmalloc (PATH_MAX + 1);
  char *p = canonical;
  
  if (path == NULL)
    return NULL;
  
  if (streq (path, "none"))
    {
      strcpy (canonical, path);
      return canonical;
    }
  if (strchr (path, ':') != NULL)
    {
      strcpy(canonical, path);
      return canonical;
    }
  if (*path == '/')
    {
      /* We've already got an absolute path in PATH, but we need at
	 least one char in canonical[] on entry to the loop below.  */
      *p = *path++;
    }
  else
    {
      getcwd (canonical, PATH_MAX);
      p = canonical + strlen (canonical) - 1;
      if (*p != '/')
	*++p = '/';
    }
  
  /* There is at least one character in canonical[],
     and the last char in canonical[], *p, is '/'.  */
  while ((*path != '\0') && (p < canonical + PATH_MAX))
    if (*p != '/')
      {
	*++p = *path++;
      }
    else
      {
	if (path[0] == '/')
	  {
	    path++;		/* eliminate duplicate slashes (``//'') */
	  }
	else if ((path[0] == '.') && ((path[1] == '\0') || (path[1] == '/')))
	{
	  path++;		/* eliminate ``/.'' */
	}
	else if ((path[0] == '.') && (path[1] == '.')
		 && ((path[2] == '\0') || (path[2] == '/')))
	  {
	    while ((p > canonical) && (*--p != '/'))
	      /* ascend on ``/../'' */
	      ;
	    path += 2;
	  }
	else
	  {
	    *++p = *path++;	/* not a special case, just copy char */
	  }
      }
  if (p >= (canonical + PATH_MAX))
    die (2, "mount: path too long");

  if (*p == '/')
    --p;			/* eliminate trailing slash */

  *++p = '\0';
  
  return canonical;
}
