/*
 * Support functions.  Exported functions are prototyped in sundries.h.
 *
 * added fcntl locking by Kjetil T. (kjetilho@math.uio.no) - aeb, 950927
 *
 * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
 * - added Native Language Support
 *
 */
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <mntent.h>		/* for MNTTYPE_SWAP */

#include "canonicalize.h"

#include "sundries.h"
#include "nls.h"

int mount_quiet;
int verbose;
int nocanonicalize;
char *progname;

char *
xstrndup (const char *s, int n) {
     char *t;

     if (s == NULL)
	  die (EX_SOFTWARE, _("bug in xstrndup call"));

     t = xmalloc(n+1);
     strncpy(t,s,n);
     t[n] = 0;

     return t;
}

/* reallocates its first arg - typical use: s = xstrconcat3(s,t,u); */
char *
xstrconcat3 (char *s, const char *t, const char *u) {
     size_t len = 0;

     len = (s ? strlen(s) : 0) + (t ? strlen(t) : 0) + (u ? strlen(u) : 0);

     if (!len)
	     return NULL;
     if (!s) {
	     s = xmalloc(len + 1);
	     *s = '\0';
     }
     else
	     s = xrealloc(s, len + 1);
     if (t)
	     strcat(s, t);
     if (u)
	     strcat(s, u);
     return s;
}

/* frees its first arg - typical use: s = xstrconcat4(s,t,u,v); */
char *
xstrconcat4 (char *s, const char *t, const char *u, const char *v) {
     size_t len = 0;

     len = (s ? strlen(s) : 0) + (t ? strlen(t) : 0) +
		(u ? strlen(u) : 0) + (v ? strlen(v) : 0);

     if (!len)
	     return NULL;
     if (!s) {
	     s = xmalloc(len + 1);
	     *s = '\0';
     }
     else
	     s = xrealloc(s, len + 1);
     if (t)
	     strcat(s, t);
     if (u)
	     strcat(s, u);
     if (v)
	     strcat(s, v);
     return s;


}

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

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


/* Non-fatal error.  Print message and return.  */
/* (print the message in a single printf, in an attempt
    to avoid mixing output of several threads) */
void
error (const char *fmt, ...) {
     va_list args;

     if (mount_quiet)
	  return;
     va_start (args, fmt);
     vfprintf (stderr, fmt, args);
     va_end (args);
     fputc('\n', stderr);
}

/* Fatal error.  Print message and exit.  */
void __attribute__ ((noreturn)) 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);
}

/* True if fstypes match.  Null *TYPES means match anything,
   except that swap types always return false. */
/* Accept nonfs,proc,devpts and nonfs,noproc,nodevpts
   with the same meaning. */
int
matching_type (const char *type, const char *types) {
     int no;			/* negated types list */
     int len;
     const char *p;

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

     no = 0;
     if (!strncmp(types, "no", 2)) {
	  no = 1;
	  types += 2;
     }

     /* Does type occur in types, separated by commas? */
     len = strlen(type);
     p = types;
     while(1) {
	     if (!strncmp(p, "no", 2) && !strncmp(p+2, type, len) &&
		 (p[len+2] == 0 || p[len+2] == ','))
		     return 0;
	     if (strncmp(p, type, len) == 0 &&
		 (p[len] == 0 || p[len] == ','))
		     return !no;
	     p = strchr(p,',');
	     if (!p)
		     break;
	     p++;
     }
     return no;
}

/* Returns 1 if needle found or noneedle not found in haystack
 * Otherwise returns 0
 */
static int
check_option(const char *haystack, const char *needle) {
     const char *p, *r;
     int len, needle_len, this_len;
     int no;

     no = 0;
     if (!strncmp(needle, "no", 2)) {
	  no = 1;
	  needle += 2;
     }
     needle_len = strlen(needle);
     len = strlen(haystack);

     for (p = haystack; p < haystack+len; p++) {
	  r = strchr(p, ',');
	  if (r) {
	       this_len = r-p;
	  } else {
	       this_len = strlen(p);
	  }
	  if (this_len != needle_len) {
	       p += this_len;
	       continue;
	  }
	  if (strncmp(p, needle, this_len) == 0)
	       return !no; /* foo or nofoo was found */
	  p += this_len;
     }

     return no;  /* foo or nofoo was not found */
}


/* Returns 1 if each of the test_opts options agrees with the entire
 * list of options.
 * Returns 0 if any noopt is found in test_opts and opt is found in options.
 * Returns 0 if any opt is found in test_opts but is not found in options.
 * Unlike fs type matching, nonetdev,user and nonetdev,nouser have
 * DIFFERENT meanings; each option is matched explicitly as specified.
 */
int
matching_opts (const char *options, const char *test_opts) {
     const char *p, *r;
     char *q;
     int len, this_len;

     if (test_opts == NULL)
	  return 1;
     if (options == NULL)
	  options = "";

     len = strlen(test_opts);
     q = alloca(len+1);
     if (q == NULL)
          die (EX_SYSERR, _("not enough memory"));

     for (p = test_opts; p < test_opts+len; p++) {
	  r = strchr(p, ',');
	  if (r) {
	       this_len = r-p;
	  } else {
	       this_len = strlen(p);
	  }
	  if (!this_len) continue; /* if two ',' appear in a row */
	  strncpy(q, p, this_len);
	  q[this_len] = '\0';
	  if (!check_option(options, q))
	       return 0; /* any match failure means failure */
	  p += this_len;
     }

     /* no match failures in list means success */
     return 1;
}

int
is_pseudo_fs(const char *type)
{
	if (type == NULL || *type == '/')
		return 0;
	if (streq(type, "none") ||
	    streq(type, "proc") ||
	    streq(type, "tmpfs") ||
	    streq(type, "sysfs") ||
	    streq(type, "usbfs") ||
	    streq(type, "cgroup") ||
	    streq(type, "cpuset") ||
	    streq(type, "rpc_pipefs") ||
	    streq(type, "devpts") ||
	    streq(type, "securityfs") ||
	    streq(type, "debugfs"))
		return 1;
	return 0;
}

/* 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''.  Anything we cannot parse
   we return unmodified.   */
char *
canonicalize_spec (const char *path)
{
	char *res;

	if (!path)
		return NULL;
	if (nocanonicalize || is_pseudo_fs(path))
		return xstrdup(path);

	res = canonicalize_path(path);
	if (!res)
		die(EX_SYSERR, _("not enough memory"));
	return res;
}

char *canonicalize (const char *path)
{
	char *res;

	if (!path)
		return NULL;
	else if (nocanonicalize)
		return xstrdup(path);

	res = canonicalize_path(path);
	if (!res)
		die(EX_SYSERR, _("not enough memory"));
	return res;
}
