// This file is part of the cwiclo project
//
// Copyright (c) 2018 by Mike Sharov <msharov@users.sourceforge.net>
// This file is free software, distributed under the ISC License.

#include "algo.h"
#include "sysutil.h"
#include "appl.h"
#include <fcntl.h>
#include <paths.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <time.h>
#if __has_include(<arpa/inet.h>)
    #include <arpa/inet.h>
#endif
#if __has_include(<execinfo.h>) && !defined(NDEBUG)
    #include <execinfo.h>
    #define HAVE_BACKTRACE
#endif

//{{{ File descriptor and path utilities -------------------------------
namespace cwiclo {

#ifdef __linux__ // closefrom is in libc on bsd
void closefrom (int fd)
{
    for (int tofd = getdtablesize(); fd < tofd; ++fd)
	close (fd);
}
#endif

int make_fd_nonblocking (int fd)
{
    auto f = fcntl (fd, F_GETFL);
    return f < 0 ? f : fcntl (fd, F_SETFL, f| O_NONBLOCK);
}

int make_fd_blocking (int fd)
{
    auto f = fcntl (fd, F_GETFL);
    return f < 0 ? f : fcntl (fd, F_SETFL, f&~(O_NONBLOCK));
}

#ifndef UC_VERSION

int mkpath (const char* path, mode_t mode)
{
    char pbuf [PATH_MAX];
    size_t n = snprintf (ARRAY_BLOCK(pbuf), "%s", path);
    if (n >= size(pbuf)) {
	errno = ENAMETOOLONG;
	return -1;
    }
    for (auto f = begin(pbuf), l = f+n; f < l; ++f) {
	f = find (f, l, '/');
	if (!f)
	    f = l;
	*f = 0;
	if (0 > mkdir (pbuf, mode) && errno != EEXIST)
	    return -1;
	*f = '/';
    }
    return 0;
}

int rmpath (const char* path)
{
    char pbuf [PATH_MAX];
    if (size(pbuf) <= size_t(snprintf (ARRAY_BLOCK(pbuf), "%s", path))) {
	errno = ENAMETOOLONG;
	return -1;
    }
    while (pbuf[0]) {
	if (0 > rmdir (pbuf))
	    return (errno == ENOTEMPTY || errno == EACCES) ? 0 : -1;
	auto pslash = strrchr (pbuf, '/');
	if (!pslash)
	    pslash = pbuf;
	*pslash = 0;
    }
    return 0;
}

#endif

const char* find_exe_in_path (const char* exe, char* exepath, size_t exepathsz)
{
    if (exe[0] == '/') // already a path
	return !access (exe, X_OK) ? exe : nullptr;

    const char* pathenv = getenv("PATH");
    if (!pathenv)
	pathenv = _PATH_DEFPATH;

    auto exesz = zstr::length (exe);
    for (auto pi = pathenv; *pi;) {
	auto pe = pi;
	auto pep = exepath, pepe = pep+exepathsz-(sizeof('/')+exesz+sizeof('\0'));
	while (*pe && *pe != ':') {
	    if (pep >= pepe)
		return nullptr;
	    *pep++ = *pe++;
	}
	*pep++ = '/';
	copy_n (exe, exesz+1, pep);
	if (!access (exepath, X_OK))
	    return exepath;
	if (*pe)
	    ++pe;
	pi = pe;
    }
    return nullptr;
}

//}}}-------------------------------------------------------------------
//{{{ Debug logging

// Hexdumps up to 16 bytes of vp into linebuf
// linebuf must be at least 98 bytes
size_t hexdump_line (const void* vp, size_t n, char* linebuf)
{
    //{{{ CP437 UTF8 table
    static const char c_437table[256][4] = {
	   " ", "\u263A", "\u263B", "\u2665", "\u2666", "\u2663", "\u2660", "\u2022",
	"\u25D8", "\u25CB", "\u25D9", "\u2642", "\u2640", "\u266A", "\u266B", "\u263C",
	"\u25BA", "\u25C4", "\u2195", "\u203C", "\u00B6", "\u00A7", "\u25AC", "\u21A8",
	"\u2191", "\u2193", "\u2192", "\u2190", "\u221F", "\u2194", "\u25B2", "\u25BC",
	   " ", "!", "\"", "#", "$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/",
	   "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
	   "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
	   "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_",
	   "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
	   "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "\u2302",
	"\u00C7", "\u00FC", "\u00E9", "\u00E2", "\u00E4", "\u00E0", "\u00E5", "\u00E7",
	"\u00EA", "\u00EB", "\u00E8", "\u00EF", "\u00EE", "\u00EC", "\u00C4", "\u00C5",
	"\u00C9", "\u00E6", "\u00C6", "\u00F4", "\u00F6", "\u00F2", "\u00FB", "\u00F9",
	"\u00FF", "\u00D6", "\u00DC", "\u00A2", "\u00A3", "\u00A5", "\u20A7", "\u0192",
	"\u00E1", "\u00ED", "\u00F3", "\u00FA", "\u00F1", "\u00D1", "\u00AA", "\u00BA",
	"\u00BF", "\u2310", "\u00AC", "\u00BD", "\u00BC", "\u00A1", "\u00AB", "\u00BB",
	"\u2591", "\u2592", "\u2593", "\u2502", "\u2524", "\u2561", "\u2562", "\u2556",
	"\u2555", "\u2563", "\u2551", "\u2557", "\u255D", "\u255C", "\u255B", "\u2510",
	"\u2514", "\u2534", "\u252C", "\u251C", "\u2500", "\u253C", "\u255E", "\u255F",
	"\u255A", "\u2554", "\u2569", "\u2566", "\u2560", "\u2550", "\u256C", "\u2567",
	"\u2568", "\u2564", "\u2565", "\u2559", "\u2558", "\u2552", "\u2553", "\u256B",
	"\u256A", "\u2518", "\u250C", "\u2588", "\u2584", "\u258C", "\u2590", "\u2580",
	"\u03B1", "\u00DF", "\u0393", "\u03C0", "\u03A3", "\u03C3", "\u00B5", "\u03C4",
	"\u03A6", "\u0398", "\u03A9", "\u03B4", "\u221E", "\u03C6", "\u03B5", "\u2229",
	"\u2261", "\u00B1", "\u2265", "\u2264", "\u2320", "\u2321", "\u00F7", "\u2248",
	"\u00B0", "\u2219", "\u00B7", "\u221A", "\u207F", "\u00B2", "\u25A0", "\u00A0"
    };
    //}}}
    auto l = linebuf;
    auto p = static_cast<const uint8_t*>(vp);
    for (auto i = 0u; i < 16; ++i) {
	if (i < n) {
	    static const char c_hex[17] = "0123456789abcdef";
	    *l++ = c_hex[p[i]>>4];
	    *l++ = c_hex[p[i]&0xf];
	} else {
	    *l++ = ' ';
	    *l++ = ' ';
	}
	*l++ = ' ';
    }
    for (auto i = 0u; i < 16; ++i) {
	if (i < n)
	    for (auto cs = c_437table[p[i]]; *cs;)
		*l++ = *cs++;
	else
	    *l++ = ' ';
    }
    *l++ = '\n';
    *l = 0;
    return distance (linebuf, l);
}

ssize_t write_hexdump (int fd, const void* vp, size_t n)
{
    ssize_t r = 0;
    for (auto p = static_cast<const uint8_t*>(vp), pe = p+n; p < pe; p += 16) {
	char line [128];
	auto linelen = hexdump_line (p, distance(p,pe), line);
	auto lr = write (fd, line, linelen);
	if (lr <= 0)
	    return lr;
	r += lr;
    }
    return r;
}

#ifndef NDEBUG
void debug_printf (const char* fmt, ...)
{
    if (debug_tracing_on()) {
	va_list args;
	va_start (args, fmt);
	vfprintf (stderr, fmt, args);
	va_end (args);
	fflush (stderr);
    }
}

void debug_hexdump (const void* vp, size_t n)
{
    if (!debug_tracing_on())
	return;
    fflush (stderr);
    write_hexdump (STDERR_FILENO, vp, n);
}

#ifdef HAVE_BACKTRACE
static char* debug_reformat_backtrace_symline (const char* symline, char* o, const char* oend)
{
    //
    // Symbol names are formatted thus: "file(function+0x42) [0xAddress]"
    //
    // Parse out the function name
    //
    auto fnstart = strchr (symline, '(');
    if (!fnstart)
	fnstart = symline;
    else
	++fnstart;
    auto fnend = strchr (fnstart, '+');
    if (!fnend) {
	fnend = strchr (fnstart, ')');
	if (!fnend)
	    fnend = fnstart + zstr::length(fnstart);
    }
    //
    // Parse out the address
    //
    auto addrstart = strchr (fnend, '['), addrend = addrstart;
    if (addrstart) {
	addrend = strchr (++addrstart, ']');
	if (!addrend)
	    addrstart = 0;
    }
    //
    // Print address first, left-padded, with name on right
    //
    size_t facsz = zstr::length("0x")+sizeof(void*)*2,
	    fasz = distance (addrstart, addrend),
	    fnsz = distance (fnstart, fnend);

    if ((ptrdiff_t)(max(fasz,facsz) + zstr::length("\t") + fnsz + zstr::length("\n")) >= oend-o)
	return o;
    for (auto i = fasz; i < facsz; ++i)
	*o++ = ' ';
    o = copy_n (addrstart, fasz, o);
    *o++ = '\t';
    o = copy_n (fnstart, fnsz, o);
    *o++ = '\n';
    return o;
}
#endif // HAVE_BACKTRACE

size_t debug_get_backtrace (char* buf, size_t bufsz [[maybe_unused]], unsigned from [[maybe_unused]])
{
    auto o = buf;
#ifdef HAVE_BACKTRACE
    const auto oend = buf + bufsz - 1;
    void* frames[32];
    if (unsigned nf = backtrace (ARRAY_BLOCK(frames)); nf > from) {
	if (auto syms = backtrace_symbols (frames, nf); syms) {
	    for (auto s = from; s < nf; ++s)
		o = debug_reformat_backtrace_symline (syms[s], o, oend);
	    free (syms);
	}
    }
#endif // HAVE_BACKTRACE
    *o = 0;
    return distance (buf, o);
}

#endif // !NDEBUG
//}}}-------------------------------------------------------------------
//{{{ Miscellaneous

// Randomly initializes the random number generator
void srandrand (void)
{
    struct timespec now;
    clock_gettime (CLOCK_REALTIME, &now);
    uint32_t seed = now.tv_sec;
    seed ^= getppid();
    seed = bit_ror (seed, 16);
    seed ^= getpid();
    seed ^= now.tv_nsec;
    srand (seed);
}

string substitute_environment_vars (const string_view& s)
{
    string r = s;
    string::iterator vars = nullptr;
    bool inquote = false;
    char lc = 0;
    for (auto i = r.begin();; ++i) {
	if (vars && (i >= r.end() || !(*i == '_' || (*i >= 'a' && *i <= 'z') || (*i >= 'A' && *i <= 'Z')))) {
	    auto termc = exchange (*i, 0);
	    const char* ev = getenv (1+vars);
	    if (!ev)
		ev = "";
	    auto evn = zstr::length (ev);
	    i = evn + r.replace (vars, i, ev, evn);
	    *i = termc;
	    vars = nullptr;
	}
	if (i >= r.end())
	    return r;
	if (*i == '\'' && lc != '\\')
	    inquote = !inquote;
	else if (!inquote && *i == '$' && lc != '\\')
	    vars = i;
	lc = *i;
    }
}

//}}}-------------------------------------------------------------------
//{{{ Socket utilities

int socket_enable_credentials_passing (int sockfd, bool enable)
{
    int sov = enable;
    return setsockopt (sockfd, SOL_SOCKET, SO_PASSCRED, &sov, sizeof(sov));
}

#ifndef UC_VERSION

unsigned sd_listen_fds (void)
{
    const char* e = getenv("LISTEN_PID");
    if (!e || getpid() != pid_t(atoi(e)))
	return 0;
    e = getenv("LISTEN_FDS");
    return e ? atoi(e) : 0;
}

int sd_listen_fd_by_name (const char* name)
{
    const char* na = getenv("LISTEN_FDNAMES");
    if (!na)
	return -1;
    auto namelen = strlen(name);
    for (auto fdi = 0u; *na; ++fdi) {
	const char *ee = strchr(na,':');
	if (!ee)
	    ee = na+strlen(na);
	if (equal_n (name, namelen, na, ee-na))
	    return fdi < sd_listen_fds() ? SD_LISTEN_FDS_START+fdi : -1;
	if (!*ee)
	    break;
	na = ee+1;
    }
    return -1;
}

#endif // UC_VERSION

int create_sockaddr_un (struct sockaddr_un* addr, const char* sockname)
{
    zero_fill_n (addr, 1);
    addr->sun_family = PF_LOCAL;
    auto psockpath = begin (addr->sun_path);
    auto sockpathsz = size (addr->sun_path);
    if (sockname[0] == '@') {
	++sockname;
	#ifdef __linux__
	    --sockpathsz;
	    *psockpath++ = 0;
	#endif
    }
    auto pathlen = zstr::length (sockname);
    if (pathlen >= sockpathsz) {
	errno = ENAMETOOLONG;
	return -1;
    }
    copy_n (sockname, pathlen+1, psockpath);
    return psockpath+pathlen-pointer_cast<char>(addr);
}

int connect_to_socket (const sockaddr* addr, socklen_t addrlen)
{
    debug_printf ("Connecting to socket %s\n", debug_socket_name(addr));
    auto fd = socket (addr->sa_family, SOCK_STREAM| SOCK_NONBLOCK| SOCK_CLOEXEC, 0);
    if (fd < 0)
	return fd;
    if (0 > connect (fd, addr, addrlen) && errno != EINPROGRESS && errno != EINTR) {
	debug_printf ("[E] connect failed: %s\n", strerror(errno));
	close (exchange (fd, -1));
    }
    return fd;
}

int connect_to_local_socket (const char* sockname)
{
    int sfd = -1;
    sockaddr_un addr;
    do {
	auto addrlen = create_sockaddr_un (&addr, socket_path_from_name(sockname).c_str());
	if (addrlen < 0)
	    return sfd;
	sfd = connect_to_socket (pointer_cast<sockaddr>(&addr), addrlen);
	if (sfd < 0 && sockname[0] == '@') {
	    ++sockname;
	    continue;	// If abstract socket connection fails, try the file socket
	}
    } while (false);
    return sfd;
}

string local_socket_path (int fd)
{
    string name;
    sockaddr_storage ss = {};
    socklen_t l = sizeof(ss);
    if (getsockname (fd, pointer_cast<sockaddr>(&ss), &l) < 0)
	return name;
    if (ss.ss_family != PF_LOCAL)
	return name;
    auto sun = pointer_cast<sockaddr_un>(&ss);
    if (!sun->sun_path[0])
	sun->sun_path[0] = '@';
    name.assign (sun->sun_path, l-offsetof(sockaddr_un, sun_path));
    return name;
}

// Abstract sockets live outside the filesystem,
// so the server side must do manual permissions checking.
//
uid_t uid_filter_for_local_socket (int fd)
{
    sockaddr_storage ss = {};
    socklen_t l = sizeof(ss);
    if (getsockname (fd, pointer_cast<sockaddr>(&ss), &l) < 0)
	return 0;
    if (ss.ss_family != PF_LOCAL)
	return 0;

    auto sun = pointer_cast<sockaddr_un>(&ss);
    if (!sun->sun_path[0]) {
	// Find existing path component and use its uid as filter
	auto pdirn = &sun->sun_path[1];
	debug_printf ("Using abstract socket %s\n", pdirn);
	auto pdire = pointer_cast<char>(&ss)+l;
	while (--pdire > pdirn) {
	    struct stat st;
	    if (exchange (*pdire, 0) == '/' && 0 == stat (pdirn, &st) && S_ISDIR(st.st_mode)) {
		debug_printf ("Setting uid filter to %d, owner of %s\n", st.st_uid, pdirn);
		return st.st_uid;
	    }
	}
    }
    return 0;
}

int launch_pipe (const char* exe, const char* arg)
{
    char exepathbuf [PATH_MAX];
    auto exepath = find_exe_in_path (exe, ARRAY_BLOCK(exepathbuf));
    if (!exepath) {
	errno = ENOENT;
	return -1;
    }

    // Create socket pipe, will be connected to stdin in server
    enum { socket_ClientSide, socket_ServerSide, socket_N };
    int socks [socket_N];
    if (0 > socketpair (PF_LOCAL, SOCK_STREAM| SOCK_NONBLOCK, 0, socks))
	return -1;

    if (auto fr = fork(); fr < 0) {
	close (exchange (socks[socket_ClientSide], -1));
	close (socks[socket_ServerSide]);
	return socks[socket_ClientSide];
    } else if (!fr) {
	// Server side

	// setup socket-activation-style fd passing
	char pids [16];
	setenv ("LISTEN_PID", uint_to_text(getpid(), pids), true);
	setenv ("LISTEN_FDS", "1", true);
	setenv ("LISTEN_FDNAMES", c_accepted_connection_name, true);

	const int fd = SD_LISTEN_FDS_START+0;
	dup2 (socks[socket_ServerSide], fd);
	closefrom (fd+1);

	const char* args[3] = { exe, arg, nullptr };
	execv (exepath, const_cast<char* const*>(args));

	// If exec failed, log the error and exit
	syslog (LOG_ERR, "failed to launch pipe to '%s': %s\n", exepath, strerror(errno));
	exit (EXIT_FAILURE);
    }
    // Client side
    close (socks[socket_ServerSide]);
    return socks[socket_ClientSide];
}

string socket_path_from_name (const string_view& name)
{
    string r = substitute_environment_vars (name);
    auto i = r.begin();

    // Abstract sockets
    if (*i == '@') {
	#ifdef __linux__
	    ++i;
	#else // not supported on BSD; fallback to path
	    i = r.erase (i);
	#endif
    }

    // Absolute path name
    char pathtype = *i;
    if (pathtype == '/')
	return r;

    // System socket in /run
    #ifdef __linux__
	auto rundir = "/run";
    #else // unless on BSD, where there is no /run
	auto rundir = "/var/run";
    #endif

    // User socket in standard location, from $XDG_RUNTIME_DIR
    if (pathtype == '~') {
	if (auto urdir = getenv ("XDG_RUNTIME_DIR"); urdir)
	    rundir = urdir;
	*i = '/';
    } else
	i = r.insert (i, '/');
    r.insert (i, rundir);
    return r;
}

const char* debug_socket_name (const struct sockaddr* addr)
{
    static char s_snbuf [256];
    if (addr->sa_family == PF_LOCAL) {
	auto a = pointer_cast<sockaddr_un>(addr);
	snprintf (ARRAY_BLOCK(s_snbuf), "%c%s", a->sun_path[0] ? a->sun_path[0] : '@', &a->sun_path[1]);
    } else if (addr->sa_family == PF_INET) {
	auto a = pointer_cast<sockaddr_in>(addr);
	char addrbuf [64];
	snprintf (ARRAY_BLOCK(s_snbuf), "%s:%hu", inet_ntop (PF_INET, &a->sin_addr, ARRAY_BLOCK(addrbuf)), a->sin_port);
    } else if (addr->sa_family == PF_INET6) {
	auto a = pointer_cast<sockaddr_in6>(addr);
	char addrbuf [64];
	snprintf (ARRAY_BLOCK(s_snbuf), "%s:%hu", inet_ntop (PF_INET6, &a->sin6_addr, ARRAY_BLOCK(addrbuf)), a->sin6_port);
    } else
	snprintf (ARRAY_BLOCK(s_snbuf), "SF%u", addr->sa_family);
    return s_snbuf;
}

//}}}-------------------------------------------------------------------
//{{{ chrono

namespace chrono {

auto system_clock::now (void) -> rep
{
    struct timespec t;
    if (0 > clock_gettime (CLOCK_REALTIME, &t))
	return 0;
    return t.tv_nsec/1000000 + t.tv_sec*1000;
}

} // namespace chrono
} // namespace cwiclo
//}}}-------------------------------------------------------------------
