// -*- c-basic-offset: 4; related-file-name: "../include/click/userutils.hh"-*-
/*
 * userutils.{cc,hh} -- utility routines for user-level + tools
 * Eddie Kohler
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 *
 * 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, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include <click/pathvars.h>

#include <click/straccum.hh>
#include <click/confparse.hh>
#include <click/userutils.hh>
#include <click/error.hh>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <dirent.h>
#include <stdarg.h>
#include <stdlib.h>

#if HAVE_DYNAMIC_LINKING && defined(HAVE_DLFCN_H)
# include <dlfcn.h>
#endif

CLICK_DECLS


bool
glob_match(const String &str, const String &pattern)
{
    const char *sdata = str.data();
    const char *pdata = pattern.data();
    int slen = str.length();
    int plen = pattern.length();
    int spos = 0, ppos = 0;
    Vector<int> glob_ppos, glob_spos1, glob_spos2;

    while (1) {
	while (ppos < plen)
	    switch (pdata[ppos]) {

	      case '?':
		if (spos >= slen)
		    goto done;
		spos++;
		ppos++;
		break;

	      case '*':
		glob_ppos.push_back(ppos + 1);
		glob_spos1.push_back(spos);
		glob_spos2.push_back(slen);
		spos = slen;
		ppos++;
		break;

	      case '[': {
		  if (spos >= slen)
		      goto done;

		  // find end of character class
		  int p = ppos + 1;
		  bool negated = false;
		  if (p < plen && pdata[p] == '^') {
		      negated = true;
		      p++;
		  }
		  int first = p;
		  if (p < plen && pdata[p] == ']')
		      p++;
		  while (p < plen && pdata[p] != ']')
		      p++;
		  if (p >= plen) // not a character class at all
		      goto ordinary;
	 
		  // parse character class
		  bool in = false;
		  for (int i = first; i < p && !in; i++) {
		      int c1 = pdata[i];
		      int c2 = c1;
		      if (i < p - 2 && pdata[i+1] == '-') {
			  c2 = pdata[i+2];
			  i += 2;
		      }
		      if (sdata[spos] >= c1 && sdata[spos] <= c2)
			  in = true;
		  }

		  if ((negated && in) || (!negated && !in))
		      goto done;
		  ppos = p + 1;
		  spos++;
		  break;
	      }

	      default:
	      ordinary:
		if (spos >= slen || sdata[spos] != pdata[ppos])
		    goto done;
		spos++;
		ppos++;
		break;
	
	    }

      done:
	if (spos == slen && ppos == plen)
	    return true;
	while (glob_ppos.size() && glob_spos1.back() == glob_spos2.back()) {
	    glob_ppos.pop_back();
	    glob_spos1.pop_back();
	    glob_spos2.pop_back();
	}
	if (glob_ppos.size()) {
	    glob_spos2.back()--;
	    spos = glob_spos2.back();
	    ppos = glob_ppos.back();
	} else
	    return false;
    }
}

String
percent_substitute(const String &pattern, int format1, ...)
{
  const char *results[256];
  memset(&results, 0, sizeof(results));

  va_list val;
  va_start(val, format1);
  while (format1 > 0) {
    assert(!results[format1]);
    results[format1] = va_arg(val, const char *);
    format1 = va_arg(val, int);
  }
  va_end(val);

  results[(uint8_t)'%'] = "%";

  const char *s = pattern.begin();
  const char *end = pattern.end();
  StringAccum sa;
  while (s < end) {
    const char *pct = find(s, end, '%');
    if (pct >= end - 1)
      break;
    int format = (unsigned char)(pct[1]);
    if (const char *str = results[format])
      sa << pattern.substring(s, pct) << str;
    else
      sa << pattern.substring(s, pct + 2);
    s = pct + 2;
  }

  if (s == pattern.begin())
    return pattern;
  else {
    sa << pattern.substring(s, pattern.end());
    return sa.take_string();
  }
}

int
click_strcmp(const String &a, const String &b)
{
    const char *ad = a.data(), *ae = a.data() + a.length();
    const char *bd = b.data(), *be = b.data() + b.length();
    
    while (ad < ae && bd < be) {
	if (isdigit(*ad) && isdigit(*bd)) {
	    // compare the two numbers, but don't treat them as numbers in
	    // case of overflow
	    // first, skip initial '0's
	    const char *iad = ad, *ibd = bd;
	    while (ad < ae && *ad == '0')
		ad++;
	    while (bd < be && *bd == '0')
		bd++;
	    int longer_zeros = (ad - iad) - (bd - ibd);
	    // skip to end of number
	    const char *nad = ad, *nbd = bd;
	    while (ad < ae && isdigit(*ad))
		ad++;
	    while (bd < be && isdigit(*bd))
		bd++;
	    // longer number must be larger
	    if ((ad - nad) != (bd - nbd))
		return (ad - nad) - (bd - nbd);
	    // otherwise, compare numbers with the same length
	    for (; nad < ad && nbd < bd; nad++, nbd++)
		if (*nad != *nbd)
		    return *nad - *nbd;
	    // finally, longer string of initial '0's wins
	    if (longer_zeros != 0)
		return longer_zeros;
	} else if (isdigit(*ad))
	    return (isalpha(*bd) ? -1 : 1);
	else if (isdigit(*bd))
	    return (isalpha(*ad) ? 1 : -1);
	else {
	    int d = tolower(*ad) - tolower(*bd);
	    if (d != 0)
		return d;
	    ad++;
	    bd++;
	}
    }

    if ((ae - ad) != (be - bd))
	return (ae - ad) - (be - bd);
    else {
	assert(a.length() == b.length());
	return memcmp(a.data(), b.data(), a.length());
    }
}

const char *
filename_landmark(const char *filename, bool file_is_expr)
{
    if (file_is_expr)
	return "<expr>";
    else if (!filename || !*filename || strcmp(filename, "-") == 0)
	return "<stdin>";
    else
	return filename;
}

String
shell_quote(const String &str, bool quote_tilde)
{
    StringAccum sa;
    const char *s, *last = str.begin();
    if (quote_tilde && str && str[0] == '~')
	sa << '\'';
    for (s = str.begin(); s < str.end(); s++) {
	if (isalnum(*s) || *s == '.' || *s == '/' || *s == '-' || *s == '_'
	    || *s == ',' || *s == '~')
	    /* do nothing */;
	else {
	    if (!sa)
		sa << str.substring(last, s) << '\'';
	    else
		sa << str.substring(last, s);
	    if (*s == '\'')
		sa << "\'\"\'\"\'";
	    else
		sa << *s;
	    last = s + 1;
	}
    }
    if (!sa)
	return str;
    else {
	sa << str.substring(last, s) << '\'';
	return sa.take_string();
    }
}

String
file_string(FILE *f, ErrorHandler *errh)
{
  StringAccum sa;
  while (!feof(f))
    if (char *x = sa.reserve(4096)) {
      size_t r = fread(x, 1, 4096, f);
      sa.forward(r);
    } else {
      if (errh)
	errh->error("file too large, out of memory");
      return String();
    }
  return sa.take_string();
}

String
file_string(String filename, ErrorHandler *errh)
{
  FILE *f;
  if (filename && filename != "-") {
    f = fopen(filename.c_str(), "rb");
    if (!f) {
      if (errh)
	errh->error("%s: %s", filename.c_str(), strerror(errno));
      return String();
    }
  } else {
    f = stdin;
    filename = "<stdin>";
  }

  String s;
  if (errh) {
    PrefixErrorHandler perrh(errh, filename + ": ");
    s = file_string(f, &perrh);
  } else
    s = file_string(f);
  
  if (f != stdin)
    fclose(f);
  return s;
}


String
unique_tmpnam(const String &pattern, ErrorHandler *errh)
{
  String tmpdir;
  if (const char *path = getenv("TMPDIR"))
    tmpdir = path;
#ifdef P_tmpdir
  else if (P_tmpdir)
    tmpdir = P_tmpdir;
#endif
  else
    tmpdir = "/tmp";

  const char *star = find(pattern, '*');
  String left, right;
  if (star < pattern.end()) {
    left = "/" + pattern.substring(pattern.begin(), star);
    right = pattern.substring(star + 1, pattern.end());
  } else
    left = "/" + pattern;
  
  int uniqueifier = getpid();
  while (1) {
    String name = tmpdir + left + String(uniqueifier) + right;
    int result = open(name.c_str(), O_WRONLY | O_CREAT | O_EXCL, S_IRWXU);
    if (result >= 0) {
      close(result);
      remove_file_on_exit(name);
      return name;
    } else if (errno != EEXIST) {
      errh->error("cannot create temporary file: %s", strerror(errno));
      return String();
    }
    uniqueifier++;
  }
}

// 19.Aug.2003 - atexit() is called after String::static_cleanup(), so use
// char *s instead of Strings
static Vector<char *> *remove_files;

static void
remover(char *fn)
{
    struct stat s;
    if (stat(fn, &s) < 0)
	return;
    if (S_ISDIR(s.st_mode)) {
	DIR *dir = opendir(fn);
	if (!dir)
	    return;
	while (struct dirent *d = readdir(dir)) {
	    if (d->d_name[0] == '.'
		&& (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0))
		continue;
	    if (char *nfn = new char[strlen(fn) + strlen(d->d_name) + 2]) {
		sprintf(nfn, "%s/%s", fn, d->d_name);
		remover(nfn);
		delete[] nfn;
	    }
	}
	closedir(dir);
	rmdir(fn);
    } else
	unlink(fn);
}

extern "C" {
static void
signal_handler(int)
{
  exit(2);
}

static void
atexit_remover()
{
    if (remove_files)
	for (int i = 0; i < remove_files->size(); i++) {
	    remover((*remove_files)[i]);
	    delete[] (*remove_files)[i];
	}
}
}

void
remove_file_on_exit(const String &file)
{
    if (file) {
	if (!remove_files) {
	    remove_files = new Vector<char *>;
	    signal(SIGINT, signal_handler);
	    signal(SIGTERM, signal_handler);
	    signal(SIGPIPE, signal_handler);
	    atexit(atexit_remover);
	}
	if (char *x = new char[file.length() + 1]) {
	    memcpy(x, file.data(), file.length());
	    x[file.length()] = 0;
	    remove_files->push_back(x);
	}
    }
}


static const char* the_clickpath = 0;

const char *
clickpath()
{
    if (!the_clickpath) {
	the_clickpath = getenv("CLICKPATH");
	if (!the_clickpath)
	    the_clickpath = "";
    }
    return the_clickpath;
}

void
set_clickpath(const char* p)
{
    // small memory leak, since we cannot be sure that putenv() copies its
    // argument
    char* env = new char[strlen(p) + 11];
    sprintf(env, "CLICKPATH=%s", p);
    putenv(env);
    the_clickpath = env + 10;
}


static bool
path_find_file_2(const String &filename, const String &path,
		 String default_path, String subdir,
		 Vector<String>& results, bool exit_early)
{
    if (subdir && subdir.back() != '/')
	subdir += "/";

    const char *begin = path.begin();
    const char *end = path.end();
    int before_size = results.size();
    
    do {
	String dir = path.substring(begin, find(begin, end, ':'));
	begin = dir.end() + 1;
    
	if (!dir && default_path) {
	    // look in default path
	    if (path_find_file_2(filename, default_path, "", 0, results, exit_early) && exit_early)
		return true;
      
	} else if (dir) {
	    if (dir.back() != '/')
		dir += "/";
	    String fn;
	    // look for 'dir/subdir/click/filename' and 'dir/subdir/filename'
	    if (subdir) {
		fn = dir + subdir + "click/" + filename;
		if (access(fn.c_str(), F_OK) >= 0)
		    goto found;
		fn = dir + subdir + filename;
		if (access(fn.c_str(), F_OK) >= 0)
		    goto found;
	    }
	    // look for 'dir/filename'
	    fn = dir + filename;
	    if (access(fn.c_str(), F_OK) >= 0) {
	      found:
		results.push_back(fn);
		if (exit_early)
		    return true;
	    }
	}
    } while (begin < end);

    return results.size() == before_size;
}


String
clickpath_find_file(const String &filename, const char *subdir,
		    String default_path, ErrorHandler *errh)
{
    const char *path = clickpath();
    String was_default_path = default_path;

    if (filename && filename[0] == '/')
	return filename;
    if (!path && default_path)
	path = ":";
    Vector<String> fns;
    path_find_file_2(filename, path, default_path, subdir, fns, true);

    // look in 'PATH' for binaries
    if (!fns.size() && subdir
	&& (strcmp(subdir, "bin") == 0 || strcmp(subdir, "sbin") == 0))
	if (const char *path_variable = getenv("PATH"))
	    path_find_file_2(filename, path_variable, "", 0, fns, true);
  
    if (!fns.size() && errh) {
	if (default_path) {
	    // CLICKPATH set, left no opportunity to use default path
	    errh->fatal("cannot find file '%s'\nin CLICKPATH '%s'", filename.c_str(), path);
	} else if (!path) {
	    // CLICKPATH not set
	    errh->fatal("cannot find file '%s'\nin install directory '%s'\n(Try setting the CLICKPATH environment variable.)", filename.c_str(), was_default_path.c_str());
	} else {
	    // CLICKPATH set, left opportunity to use default pathb
	    errh->fatal("cannot find file '%s'\nin CLICKPATH or '%s'", filename.c_str(), was_default_path.c_str());
	}
    }
    
    return fns.size() ? fns[0] : String();
}

void
clickpath_expand_path(const char* subdir, const String& default_path, Vector<String>& result)
{
    const char* path = clickpath();
    if (!path && default_path)
	path = ":";
    int first = result.size();
    path_find_file_2(".", path, default_path, subdir, result, false);
    // remove trailing '/.'s
    for (String* x = result.begin() + first; x < result.end(); x++)
	*x = x->substring(x->begin(), x->end() - 2);
}

bool
path_allows_default_path(String path)
{
    const char *begin = path.begin();
    const char *end = path.end();
    while (1) {
	const char *colon = find(begin, end, ':');
	if (colon == begin)
	    return true;
	else if (colon == end)
	    return false;
	else
	    begin = colon + 1;
    }
}

String
click_mktmpdir(ErrorHandler *errh)
{
    String tmpdir;
    if (const char *path = getenv("TMPDIR"))
	tmpdir = path;
#ifdef P_tmpdir
    else if (P_tmpdir)
	tmpdir = P_tmpdir;
#endif
    else
	tmpdir = "/tmp";
  
    int uniqueifier = getpid();
    while (1) {
	String tmpsubdir = tmpdir + "/clicktmp" + String(uniqueifier);
	int result = mkdir(tmpsubdir.c_str(), 0700);
	if (result >= 0) {
	    remove_file_on_exit(tmpsubdir);
	    return tmpsubdir + "/";
	}
	if (result < 0 && errno != EEXIST) {
	    if (errh)
		errh->fatal("cannot create temporary directory: %s", strerror(errno));
	    return String();
	}
	uniqueifier++;
    }
}

void
parse_tabbed_lines(const String &str, Vector<String> *fields1, ...)
{
    va_list val;
    Vector<void *> tabs;
    va_start(val, fields1);
    while (fields1) {
	tabs.push_back((void *)fields1);
	fields1 = va_arg(val, Vector<String> *);
    }
    va_end(val);

    const char *begin = str.begin();
    const char *end = str.end();
  
    while (begin < end) {
	// read a line
	String line = str.substring(begin, find(begin, end, '\n'));
	begin = line.end() + 1;

	// break into words
	Vector<String> words;
	cp_spacevec(line, words);

	// skip blank lines & comments
	if (words.size() > 0 && words[0][0] != '#')
	    for (int i = 0; i < tabs.size(); i++) {
		Vector<String> *vec = (Vector<String> *)tabs[i];
		if (i < words.size())
		    vec->push_back(cp_unquote(words[i]));
		else
		    vec->push_back(String());
	    }
    }
}

ArchiveElement
init_archive_element(const String &name, int mode)
{
    ArchiveElement ae;
    ae.name = name;
    ae.date = time(0);
    ae.uid = geteuid();
    ae.gid = getegid();
    ae.mode = mode;
    ae.data = String();
    return ae;
}


bool
compressed_data(const unsigned char *buf, int len)
{
    return (len >= 3
	    && ((buf[0] == 037 && buf[1] == 0235)
		|| (buf[0] == 037 && buf[1] == 0213)
		|| (buf[0] == 'B' && buf[1] == 'Z' && buf[2] == 'h')));
}

FILE *
open_uncompress_pipe(const String &filename, const unsigned char *buf, int, ErrorHandler *errh)
{
    StringAccum cmd;
    if (buf[0] == 'B')
	cmd << "bzcat";
    else if (access("/usr/bin/gzcat", X_OK) >= 0)
	cmd << "/usr/bin/gzcat";
    else
	cmd << "zcat";
    cmd << ' ' << shell_quote(filename);
    if (FILE *p = popen(cmd.c_str(), "r"))
	return p;
    else {
	errh->error("'%s': %s", cmd.c_str(), strerror(errno));
	return 0;
    }
}

enum {
    COMP_COMPRESS = 1, COMP_GZIP = 2, COMP_BZ2 = 3
};

int
compressed_filename(const String &filename)
{
    if (filename.length() >= 2 && memcmp(filename.end() - 2, ".Z", 2) == 0)
	return COMP_COMPRESS;
    if (filename.length() >= 3 && memcmp(filename.end() - 3, ".gz", 3) == 0)
	return COMP_GZIP;
    else if (filename.length() >= 4 && memcmp(filename.end() - 4, ".bz2", 4) == 0)
	return COMP_BZ2;
    else
	return 0;
}

FILE *
open_compress_pipe(const String &filename, ErrorHandler *errh)
{
    StringAccum cmd;
    int c = compressed_filename(filename);
    switch (c) {
      case COMP_COMPRESS:
	cmd << "compress";
	break;
      case COMP_GZIP:
	cmd << "gzip";
	break;
      case COMP_BZ2:
	cmd << "bzip2";
	break;
      default:
	errh->error("%s: unknown compression extension", filename.c_str());
	errno = EINVAL;
	return 0;
    }
    cmd << " > " << shell_quote(filename);
    if (FILE *p = popen(cmd.c_str(), "w"))
	return p;
    else {
	errh->error("'%s': %s", cmd.c_str(), strerror(errno));
	return 0;
    }
}

#if HAVE_DYNAMIC_LINKING

extern "C" {
typedef int (*init_module_func)(void);
}

int
clickdl_load_package(String package, ErrorHandler *errh)
{
#ifndef RTLD_GLOBAL
# define RTLD_GLOBAL 0
#endif
#ifndef RTLD_NOW
  void *handle = dlopen((char *)package.c_str(), RTLD_LAZY | RTLD_GLOBAL);
#else
  void *handle = dlopen((char *)package.c_str(), RTLD_NOW | RTLD_GLOBAL);
#endif
  if (!handle)
    return errh->error("package %s", dlerror());
  void *init_sym = dlsym(handle, "init_module");
  if (!init_sym)
    return errh->error("package '%s' has no 'init_module'", package.c_str());
  init_module_func init_func = (init_module_func)init_sym;
  if ((*init_func)() != 0)
    return errh->error("error initializing package '%s'", package.c_str());
  return 0;
}

#endif

CLICK_ENDDECLS


syntax highlighted by Code2HTML, v. 0.9.1