/*
 * This file is part of the Amalthea library.
 * Copyright (C) 2018-2025 Eugene 'Vindex' Stulin
 * Distributed under the BSL 1.0 or the GNU LGPL 3.0 or later.
 */

/**
 * The module provides mostly wrappers for low-level system functions,
 * including functions for mounting/unmounting file systems, functions for
 * working with loop devices, functions for getting information about users
 * and groups, functions for mappings, for working with signals, etc.
 */
module amalthea.sys;

import core.stdc.stdlib;
import core.sys.posix.pwd;
import core.sys.posix.grp;
import core.sys.posix.sys.ioctl : ioctl;
import unix_mman = core.sys.posix.sys.mman;
import linux_mman = core.sys.linux.sys.mman;
import unix_fcntl = core.sys.posix.fcntl;
import unix_unistd = core.sys.posix.unistd;
import core.stdc.errno;
import core.sys.posix.grp : group, getgrnam;
import unix_sig = core.sys.posix.signal;
import stdc_sig = core.stdc.signal;
import unix_stat = core.sys.posix.sys.stat;

public import amalthea.libcore;
import amalthea.fs;
import amalthea.langlocal;
import amalthea.terminal;

public import amalthea.time : getTimeString;

alias isTTY = amalthea.terminal.isTTY;
alias isTerminal = amalthea.terminal.isTerminal;

import
    std.algorithm,
    std.array,
    std.path,
    std.process,
    std.range,
    std.string,
    std.typecons;

import std.file : FileException;


/*******************************************************************************
 * Common function for calling external programs.
 */
int c_system(string cmd) {
    return core.stdc.stdlib.system(cmd.toStringz);
}


/*******************************************************************************
 * Returns file path to the application (searches among system directories).
 */
string getAppPath(string app) {
    string[] appsRootPaths = environment["PATH"].split(":");
    string appPath;
    foreach(p; appsRootPaths) {
        appPath = std.path.buildPath(p, app);
        if (amalthea.fs.exists(appPath)) {
            return appPath;
        }
    }
    return "";
}
unittest {
    assert(getAppPath("gcc") == "/usr/bin/gcc");
    assert(getAppPath("strange_program") == "");
}


extern(C) private int mkfifo(const char* pathname, uint mode);

/*******************************************************************************
 * The function creates named pipe.
 * Params:
 *     pipepath = Name for pipe file.
 *     mode     = Specifies the FIFO's permission.
 * See: `man 3 mkfifo`
 */
void createPipe(in string pipepath, uint mode = octal!666) {
    if (amalthea.fs.exists(pipepath)) {
        enforce(
            FileStat(pipepath).type == FileType.pipe,
            new FileException("File exists and is not a named pipe.")
        );
        remove(pipepath);
    }
    if (-1 == mkfifo(pipepath.toStringz, mode)) {
        throw new FileException("mkfifo()");
    }
}


/*******************************************************************************
 * Helper function for getting the standard path to the share directory
 * of the current program.
 */
string getAppShareDir() {
    string currentApp = readLink("/proc/self/exe");
    string shareDir = buildNormalizedPath(dirName(currentApp), "..", "share");
    return shareDir;
}


/*******************************************************************************
 * Helper function for getting the standard path to the help directory
 * of the current program.
 */
string getAppHelpDir() {
    string shareDir = getAppShareDir();
    string helpDir = buildNormalizedPath(shareDir, "help");
    return helpDir;
}


/*******************************************************************************
 * This function returns help text of the current program using
 * standard paths for help file.
 */
string readAppHelpText(string app, string lang = "") {
    if (lang == "") {
        lang = amalthea.langlocal.getSystemLanguage();
    }
    immutable helpDir = getAppHelpDir();
    string defaultHelpFile = buildPath(helpDir, lang, app, "help.txt");
    string englishHelpFile = buildPath(helpDir, "en_US", app, "help.txt");
    if (amalthea.fs.exists(defaultHelpFile)) {
        return readText(defaultHelpFile);
    } else if (amalthea.fs.exists(englishHelpFile)) {
        return readText(englishHelpFile);
    }
    throw new FileException("Help file doesn't exist.");
}


/*******************************************************************************
 * The function returns user name by user ID.
 */
string getNameByUID(uint uid) {
    auto p = getpwuid(uid);
    if (p is null) {
        return uid.to!string;
    }
    return (*p).pw_name.fromStringz.to!string;
}


/*******************************************************************************
 * The function returns group name by group ID.
 */
string getNameByGID(uint gid) {
    auto p = getgrgid(gid);
    if (p is null) {
        return gid.to!string;
    }
    return (*p).gr_name.fromStringz.to!string;
}


private extern(C)
int mount(
    const char* source,
    const char* target,
    const char* filesystemtype,
    ulong mountflags,
    const void* data
);


private extern(C)
int umount(const char* source);


/*******************************************************************************
 * Attaches the filesystem specified by source.
 * Params:
 *     source = Filesystem source.
 *     target = Location for mounting.
 *     fs     = Filesystem type,
 *              supported values are listed in "/proc/filesystems".
 *     flags  = Additional mount flags. Zero, by default.
 *     data   = Special data for required filesystem. Null pointer, by default.
 *
 * See also: `man 2 mount`
 */
void mount(
    string source, string target, string fs, ulong flags=0, void* data=null
) {
    int ret = mount(
        source.toStringz, target.toStringz, fs.toStringz, flags, data
    );
    enforce(-1 != ret, new FileException("mount"));
}


/*******************************************************************************
 * Removes the attachment of the filesystem mounted on target.
 * Params:
 *     target = Target directory with mounted filesystem.
 *
 * See also: `man 2 umount`
 */
void umount(string target) {
    enforce(-1 != umount(target.toStringz), new FileException("umount"));
}


/* Mount flags. */
enum MS_RDONLY      = 1;        //< Mount read-only.
enum MS_NOSUID      = 2;        //< Ignore suid and sgid bits.
enum MS_NODEV       = 4;        //< Disallow access to device special files.
enum MS_NOEXEC      = 8;        //< Disallow program execution.
enum MS_SYNCHRONOUS = 16;       //< Writes are synced at once.
enum MS_REMOUNT     = 32;       //< Alter flags of a mounted FS.
enum MS_MANDLOCK    = 64;       //< Allow mandatory locks on an FS.
enum MS_DIRSYNC     = 128;      //< Directory modifications are synchronous.
enum MS_NOSYMFOLLOW = 256;      //< Do not follow symlinks.
enum MS_NOATIME     = 1024;     //< Do not update access times.
enum MS_NODIRATIME  = 2048;     //< Do not update directory access times.
enum MS_BIND        = 4096;     //< Bind directory at different place.
enum MS_MOVE        = 8192;
enum MS_REC         = 16_384;
enum MS_SILENT      = 32_768;
enum MS_POSIXACL    = 1 << 16;  //< VFS does not apply the umask.
enum MS_UNBINDABLE  = 1 << 17;  //< Change to unbindable.
enum MS_PRIVATE     = 1 << 18;  //< Change to private.
enum MS_SLAVE       = 1 << 19;  //< Change to slave.
enum MS_SHARED      = 1 << 20;  //< Change to shared.
enum MS_RELATIME    = 1 << 21;  //< Update atime relative to mtime/ctime.
enum MS_KERNMOUNT   = 1 << 22;  //< This is a kern_mount call.
enum MS_I_VERSION   = 1 << 23;  //< Update inode I_version field.
enum MS_STRICTATIME = 1 << 24;  //< Always perform atime updates.
enum MS_LAZYTIME    = 1 << 25;  //< Update the on-disk [acm]times lazily.
enum MS_ACTIVE      = 1 << 30;
enum MS_NOUSER      = 1 << 31;


private enum LO_NAME_SIZE = 64;
private enum LO_KEY_SIZE = 64;


struct loop_info64 {
    ulong lo_device;           // ioctl r/o
    ulong lo_inode;            // ioctl r/o
    ulong lo_rdevice;          // ioctl r/o
    ulong lo_offset;
    ulong lo_sizelimit;        // bytes, 0 == max available
    uint  lo_number;           // ioctl r/o
    uint  lo_encrypt_type;     // obsolete, ignored
    uint  lo_encrypt_key_size; // ioctl w/o
    uint  lo_flags;
    ubyte[LO_NAME_SIZE] lo_file_name;
    ubyte[LO_NAME_SIZE] lo_crypt_name;
    ubyte[LO_KEY_SIZE] lo_encrypt_key; // ioctl w/o
    ulong[2] lo_init;
}


/*******************************************************************************
 * Set of functions for loop device management.
 * See also: `man 4 loop`
 */
class LoopDevControl {

    /***************************************************************************
     * Creates loop_info64 structure for loop device with required number.
     *
     * Params:
     *     loopNumber = Loop device number.
     *
     * Returns:
     *     loop_info64 structure object.
     */
    static loop_info64 getLoopStatus(long loopNumber) {
        string loopDevicePath = format!"/dev/loop%s"(loopNumber);
        int fd = unix_fcntl.open(loopDevicePath.toStringz, unix_fcntl.O_RDWR);
        enforce(-1 != fd, new FileException(loopDevicePath));
        loop_info64 info;
        int ret = ioctl(fd, LOOP_GET_STATUS64, &info);
        enforce(-1 != ret, new FileException("ioctl, " ~ loopDevicePath));
        return info;
    }

    /***************************************************************************
     * Returns true if loop device exists, but busy.
     *
     * Params:
     *     loopNumber = Loop device number.
     *
     * Returns:
     *     true or false.
     */
    static bool doesLoopDeviceExistAndIsBusy(uint loopNumber) {
        string loopDevicePath = format!"/dev/loop%s"(loopNumber);
        if (!amalthea.fs.exists(loopDevicePath)) {
            return false;
        }
        return isLoopDeviceBusy(loopNumber);
    }

    /***************************************************************************
     * Returns true if loop device is busy.
     *
     * Params:
     *     loopNumber = Loop device number.
     *
     * Returns:
     *     true or false.
     */
    static bool isLoopDeviceBusy(uint loopNumber) {
        string loopDevicePath = format!"/dev/loop%s"(loopNumber);
        int fd = unix_fcntl.open(loopDevicePath.toStringz, unix_fcntl.O_RDWR);
        enforce(-1 != fd, new FileException(loopDevicePath));
        loop_info64 info;
        errno = 0;
        int ret = ioctl(fd, LOOP_GET_STATUS64, &info);
        return ret != -1 && errno != ENXIO;
    }

    /***************************************************************************
     * Allocates or finds a free loop device for use.
     *
     * Returns:
     *     free loop device number.
     */
    static uint getFreeLoopDevice() {
        int ctlFd = unix_fcntl.open(
            LOOP_CONTROL_FILE.toStringz, unix_fcntl.O_RDWR
        );
        enforce(-1 != ctlFd, new FileException(LOOP_CONTROL_FILE));
        scope(exit) unix_unistd.close(ctlFd);
        int devNumber = ioctl(ctlFd, LOOP_CTL_GET_FREE);
        enforce(devNumber >= 0, new FileException("ioctl"));
        return cast(uint)devNumber;
    }

    /***************************************************************************
     * Adds the new loop device.
     *
     * Params:
     *     loopNumber = Loop device number.
     */
    static void addLoopDevice(ulong loopNumber) {
        loopIo(loopNumber, LOOP_CTL_ADD);
    }

    /***************************************************************************
     * Associates the loop device with the required file.
     *
     * Params:
     *     filePath = File path to associate.
     *     loopNumber = Loop device number.
     */
    static void associateFileWithDevice(string filePath, ulong loopNumber) {
        associatedFileControl(filePath, loopNumber, LOOP_SET_FD);
    }

    /***************************************************************************
     * Associates the loop device with the required file.
     *
     * Params:
     *     filePath = File path to associate.
     *     loopDevice = Loop device path.
     */
    static void associateFileWithDevice(string filePath, string loopDevice) {
        associatedFileControl(filePath, loopDevice, LOOP_SET_FD);
    }


    /***************************************************************************
     * Disassociates the loop device with the required file.
     *
     * Params:
     *     filePath = Associated file.
     *     loopNumber = Loop device number.
     */
    static void disassociateFileWithDevice(string filePath, ulong loopNumber) {
        associatedFileControl(filePath, loopNumber, LOOP_CLR_FD);
    }

    /***************************************************************************
     * Disassociates the loop device with the required file.
     *
     * Params:
     *     filePath = Associated file.
     *     loopDevice = Loop device path.
     */
    static void disassociateFileWithDevice(string filePath, string loopDevice) {
        associatedFileControl(filePath, loopDevice, LOOP_CLR_FD);
    }

    /***************************************************************************
     * Gets all loop devices related to the specified backing file.
     *
     * Params:
     *     backingFile = Associated file.
     *
     * Returns:
     *     Array of paths to loop devices.
     */
    static string[] findDevicesByFile(string backingFile) {
        string[] devices;
        foreach(FileEntry entry; DirReader("/dev")) {
            string name = baseName(entry.path);
            if (name.startsWith("loop")) {
                string bf = getBackingFile(name);
                if (backingFile == bf) {
                    devices ~= entry.path;
                }
            }
        }
        return devices;
    }

    /***************************************************************************
     * Returns the associated file for the specified loop device.
     */
    static string getBackingFile(uint loopDeviceNo) nothrow {
        string loopDeviceName;
        collectException(format!"loop%d"(loopDeviceNo), loopDeviceName);
        return getBackingFile(loopDeviceName);
    }

    /***************************************************************************
     * Returns the associated file for the specified loop device.
     */
    static string getBackingFile(string loopDeviceName) nothrow {
        enum pathTemplate = "/sys/class/block/%s/loop/backing_file";
        string backingFile;
        try {
            string infoFile = format!pathTemplate(loopDeviceName);
            backingFile = readText(infoFile)[0 .. $-1];
        } catch (Exception e) {}
        return backingFile;
    }

private static:

    extern(C) {
        enum LOOP_SET_FD = 0x4C00;
        enum LOOP_CLR_FD = 0x4C01;
        enum LOOP_SET_STATUS = 0x4C02;
        enum LOOP_GET_STATUS = 0x4C03;
        enum LOOP_SET_STATUS64 = 0x4C04;
        enum LOOP_GET_STATUS64 = 0x4C05;
        enum LOOP_CHANGE_FD = 0x4C06;
        enum LOOP_SET_CAPACITY = 0x4C07;
        enum LOOP_SET_DIRECT_IO = 0x4C08;
        enum LOOP_SET_BLOCK_SIZE = 0x4C09;
        enum LOOP_CONFIGURE = 0x4C0A;

        enum LOOP_CTL_ADD = 0x4C80;
        enum LOOP_CTL_REMOVE = 0x4C81;
        enum LOOP_CTL_GET_FREE = 0x4C82;
    }

    immutable LOOP_CONTROL_FILE = "/dev/loop-control";

    void loopIo(ulong number, int ioctlFlag) {
        int ctlFd = unix_fcntl.open(
            LOOP_CONTROL_FILE.toStringz, unix_fcntl.O_RDWR
        );
        enforce(-1 != ctlFd, new FileException(LOOP_CONTROL_FILE));
        scope(exit) unix_unistd.close(ctlFd);
        int devNumber = ioctl(ctlFd, ioctlFlag, number);
        auto errMsg = format!"loop device %s, ioctl error"(number);
        enforce(devNumber >= 0, new FileException(errMsg));
    }

    void associatedFileControl(string filePath, ulong loopNumber, int flag) {
        string loopDevice = "/dev/loop" ~ loopNumber.to!string;
        associatedFileControl(filePath, loopDevice, flag);
    }

    void associatedFileControl(string filePath, string device, int flag) {
        int fd, loop;
        fd = unix_fcntl.open(filePath.toStringz, unix_fcntl.O_RDWR);
        enforce(-1 != fd, new FileException(filePath));
        loop = unix_fcntl.open(device.toStringz, unix_fcntl.O_RDWR);
        enforce(-1 != loop, new FileException(device));
        int ret = ioctl(loop, flag, fd);
        enforce(-1 != ret, new FileException("ioctl"));
    }
}


alias loopctl = LoopDevControl;


/*******************************************************************************
 * Returns group numeric identifier, given the known group name.
 */
uint getGroupIdByName(string groupName) {
    if (groupName.empty) {
        return -1;
    }
    group* g = getgrnam(groupName.toStringz);
    return (g is null) ? -1 : g.gr_gid;
}
deprecated alias getGroupIdWithName = getGroupIdByName;


private extern(C)
int getgroups(int size, gid_t* list) nothrow @nogc;

private extern(C)
int getgrouplist(
    const char* user, gid_t group, gid_t* groups, int* ngroups
) nothrow @nogc;


/*******************************************************************************
 * Returns all system group IDs (if UID isn't specified)
 * or group IDs that the specified user is associated with.
 */
gid_t[] getGroups(gid_t uid = uid_t.max) {
    gid_t[] groups;
    int ngroups;
    if (uid == uid_t.max) {
        ngroups = getgroups(0, null);
        enforce(ngroups != 1, new SysException("getgroups"));
        groups = new gid_t[ngroups];
        int ret = getgroups(ngroups, groups.ptr);
        enforce(ret != 1, new SysException("getgroups"));
    } else {
        passwd* p = getpwuid(uid);
        const char* userNamePtr = (*p).pw_name;
        int ret = getgrouplist(userNamePtr, (*p).pw_gid, null, &ngroups);
        enforce(ret != 1, new SysException("getgrouplist"));
        groups = new gid_t[ngroups];
        ret = getgrouplist(userNamePtr, (*p).pw_gid, groups.ptr, &ngroups);
        enforce(ret != 1, new SysException("getgrouplist"));
    }
    return groups;
}


/**
 * Exit codes for programs.
 * Codes and descriptions were copied from https://man.openbsd.org/sysexits.3
 */
enum {
    /**
     * The command was used incorrectly, e.g., with the wrong number
     * of arguments, a bad flag, bad syntax in a parameter, or whatever.
     */
    EX_USAGE = 64,

    /**
     * The input data was incorrect in some way.
     * This should only be used for user's data and not system files.
     */
    EX_DATAERR = 65,

    /**
     * An input file (not a system file) did not exist or was not readable.
     * This could also include errors like “No message” to a mailer
     * (if it cared to catch it).
     */
    EX_NOINPUT = 66,

    /**
     * The user specified did not exist.
     * This might be used for mail addresses or remote logins.
     */
    EX_NOUSER = 67,

    /**
     * The host specified did not exist.
     * This is used in mail addresses or network requests.
     */
    EX_NOHOST = 68,

    /**
     * A service is unavailable. This can occur if a support program or file
     * does not exist. This can also be used as a catch-all message when
     * something you wanted to do doesn't work, but you don't know why.
     */
    EX_UNAVAILABLE = 69,

    /**
     * An internal software error has been detected. This should be limited
     * to non-operating system related errors if possible.
     */
    EX_SOFTWARE = 70,

    /**
     * An operating system error has been detected.
     * This is intended to be used for such things as “cannot fork”,
     * or “cannot create pipe”. It includes things like getuid(2) returning
     * a user that does not exist in the passwd file.
     */
    EX_OSERR = 71,

    /**
     * Some system file (e.g., /etc/passwd, /var/run/utmp) does not exist,
     * cannot be opened, or has some sort of error (e.g., syntax error).
     */
    EX_OSFILE = 72,

    /**
     * A (user specified) output file cannot be created.
     */
    EX_CANTCREAT = 73,

    /**
     * An error occurred while doing I/O on some file.
     */
    EX_IOERR = 74,

    /**
     * Temporary failure, indicating something that is not really an error.
     * For example that a mailer could not create a connection, and the request
     * should be reattempted later.
     */
    EX_TEMPFAIL = 75,

    /**
     * The remote system returned something that was “not possible”
     * during a protocol exchange.
     */
    EX_PROTOCOL= 76,

    /**
     * You did not have sufficient permission to perform the operation.
     * This is not intended for file system problems, which should use
     * EX_NOINPUT or EX_CANTCREAT, but rather for higher level permissions.
     */
    EX_NOPERM = 77,

    /**
     * Something was found in an unconfigured or misconfigured state.
     */
    EX_CONFIG = 78
}


/// Gets memory page size. See: `man getpagesize`
extern(C) int getpagesize() pure nothrow @nogc;


/**
 * Enumeration of access modes for mappings.
 */
enum MapMode {
    none   = unix_mman.PROT_NONE,
    rdOnly = unix_mman.PROT_READ,
    wrOnly = unix_mman.PROT_WRITE,
    exec   = unix_mman.PROT_EXEC,
    rdWr   = unix_mman.PROT_READ | unix_mman.PROT_WRITE,
}


/*******************************************************************************
 * Creates a new mapping for the specified file.
 * Params:
 *     f    = related file object 
 *     size = size of allocatable memory in bytes
 *     mode = access mode (read/write, by default)
 */
void* createMapping(File f, size_t size, MapMode mode = MapMode.rdWr) {
    auto flags = unix_mman.MAP_SHARED;
    void* p = unix_mman.mmap(null, size, mode, flags, f.fileno, 0);
    enforce(p != unix_mman.MAP_FAILED, new SysException("mmap"));
    return p;
}


private void* anon_map_linux_style(size_t size) {
    auto flags = linux_mman.MAP_ANONYMOUS | unix_mman.MAP_PRIVATE;
    void* p = unix_mman.mmap(null, size, MapMode.rdWr, flags, -1, 0);
    enforce(unix_mman.MAP_FAILED != p, new SysException("mmap"));
    return p;
}


private void* anon_map_bsd_style(size_t size) {
    enum zeroDevice = "/dev/zero";
    int fd = unix_fcntl.open(zeroDevice, unix_fcntl.O_RDWR);
    enforce(-1 != fd, new FileException(zeroDevice));
    scope(exit) unix_unistd.close(fd);

    auto flags = unix_mman.MAP_PRIVATE;
    void* p = unix_mman.mmap(null, size, MapMode.rdWr, flags, fd, 0);
    enforce(unix_mman.MAP_FAILED != p, new SysException("mmap"));
    return p;
}


/*******************************************************************************
 * Allocates memory using anonymous mapping.
 * New memory will contain zeros.
 * Params:
 *     size = size of allocatable memory in bytes
 * Returns: pointer to allocatable memory area.
 */
void* createAnonymousMapping(size_t size) {
    version (linux) {
        return anon_map_linux_style(size);
    } else {
        return anon_map_bsd_style(size);
    }
}
alias anonMapping = createAnonymousMapping;


/*******************************************************************************
 * Releases the mapping.
 * Params:
 *     p = pointer to mapping
 *     size = size used during the creation of the mapping
 */
void destroyMapping(T)(T* p, size_t size) {
    int ret = unix_mman.munmap(cast(void*)(p), size);
    enforce(ret == 0, new SysException("munmap"));
}


version (SPARC_Any) {
    private enum SIGPWR_NO = 29;
    private enum SIGWINCH_NO = 28;
} else version (MIPS_Any) {
    private enum SIGPWR_NO = 19;
    private enum SIGWINCH_NO = 20;
} else version (HPPA_Any) {
    private enum SIGPWR_NO = 19;
    private enum SIGWINCH_NO = 23;
} else {
    private enum SIGPWR_NO = 30;
    private enum SIGWINCH_NO = 28;
}

/**
 * UNIX signal enumeration.
 */
enum Signal {
    SIGHUP    = unix_sig.SIGHUP,     //< Hangup
    SIGINT    = stdc_sig.SIGINT,     //< Interrupt from keyboard
    SIGQUIT   = unix_sig.SIGQUIT,    //< Quit
    SIGILL    = stdc_sig.SIGILL,     //< Illegal instruction
    SIGTRAP   = unix_sig.SIGTRAP,    //< Trace/breakpoint trap
    SIGABRT   = stdc_sig.SIGABRT,    //< Aborted
    SIGIOT    = SIGABRT,             //< A synonym for SIGABRT
    SIGBUS    = unix_sig.SIGBUS,     //< Bus error
    SIGFPE    = stdc_sig.SIGFPE,     //< Floating point exception
    SIGKILL   = unix_sig.SIGKILL,    //< Killed
    SIGUSR1   = unix_sig.SIGUSR1,    //< User defined signal #1
    SIGUSR2   = unix_sig.SIGUSR2,    //< User defined signal #2
    SIGSEGV   = stdc_sig.SIGSEGV,    //< Segmentation fault
    SIGPIPE   = unix_sig.SIGPIPE,    //< Broken pipe
    SIGALRM   = unix_sig.SIGALRM,    //< Alarm clock
    SIGTERM   = stdc_sig.SIGTERM,    //< Terminated
    SIGCHLD   = unix_sig.SIGCHLD,    //< Child exited
    SIGCONT   = unix_sig.SIGCONT,    //< Continued
    SIGSTOP   = unix_sig.SIGSTOP,    //< Stop process
    SIGTSTP   = unix_sig.SIGTSTP,    //< Stop typed at terminal
    SIGTTIN   = unix_sig.SIGTTIN,    //< Terminal input for background process
    SIGTTOU   = unix_sig.SIGTTOU,    //< Terminal output for background process
    SIGURG    = unix_sig.SIGURG,     //< Urgent I/O condition on socket
    SIGXCPU   = unix_sig.SIGXCPU,    //< CPU time limit exceeded
    SIGXFSZ   = unix_sig.SIGXFSZ,    //< File size limit exceeded
    SIGVTALRM = unix_sig.SIGVTALRM,  //< Virtual timer expired
    SIGPROF   = unix_sig.SIGPROF,    //< Profiling timer expired
    SIGWINCH  = SIGWINCH_NO,         //< Window changed
    SIGPOLL   = unix_sig.SIGPOLL,    //< Pollable event, synonym for SIGIO
    SIGIO     = SIGPOLL,             //< I/O possible
    SIGPWR    = SIGPWR_NO,           //< Power failure
    SIGSYS    = unix_sig.SIGSYS,     //< Bad system call
    SIGUNUSED = SIGSYS               //< A synonym for SIGSYS
}

/// Alias for `extern(C) void function(int)`. Used in signal handlers.
alias c_sigfn_t = extern(C) void function(int);
/// Alias for `extern(D) void function(Signal)`. Used in signal handlers.
alias d_sigfn_t = extern(D) void function(Signal);
/// Alias for `extern(D) void function(int)`. Used in signal handlers.
alias d_sigfn_int_t = extern(D) void function(int);

/*******************************************************************************
 * The function sets the handler for the signal number.
 * Params:
 *     sig = signal number of Signal or int-like type
 *     handler = signal handler of c_sigfn_t or d_sigfn_t type 
 */
void addSignalHandler(S, H)(S sig, H handler)
if (
    (is(S == Signal) || is(S : int)) &&
    (is(H == c_sigfn_t) || is(H == d_sigfn_t) || is(H == d_sigfn_int_t))
) {
    unix_sig.sigset_t sigset;
    unix_sig.sigemptyset(&sigset);

    unix_sig.sigaction_t sigact;
    sigact.sa_handler = cast(c_sigfn_t)handler;
    sigact.sa_mask = sigset;
    sigact.sa_flags = unix_sig.SA_RESTART;

    if (-1 == unix_sig.sigaction(sig, &sigact, null)) {
        string sigName = (cast(Signal)sig).to!string;
        string msg = format!"Unable to add signal %d (%s)"(sig, sigName);
        throw new SysException(msg);
    }
}


extern(C) private char* strsignal(int signum) pure nothrow @nogc;


/*******************************************************************************
 * Gets UNIX signal string view.
 *
 * Params:
 *     signal = explored signal value
 * Returns:
 *     string interpretation of the signal
 */
string getSignalDescription(Signal signal) {
    return getSignalDescription(cast(int)signal);
}
// ditto
string getSignalDescription(int signum) {
    return strsignal(signum).fromStringz.idup;
}


/*******************************************************************************
 * Gets a list of mount points related to the specified block device.
 */
string[] findMountPoints(string blockDevicePath) {
    auto f = File("/proc/mounts", "r");
    if (blockDevicePath.empty) {
        return null;
    }
    string[] result;
    foreach (line; f.byLine()) {
        auto fields = line.split(" ");
        if (fields[0] == blockDevicePath) {
            result ~= fields[1].idup;
        }
    }
    return result;
}


/*******************************************************************************
 * Gets a list of devices related to the specified mount point.
 */
string[] findDevicesByMountPoint(string mountPoint) {
    auto f = File("/proc/mounts", "r");
    string[] result;
    foreach (line; f.byLine()) {
        string[] fields = line.split(" ").map!(a => a.idup).array;
        if (fields[1] == mountPoint) {
            result ~= fields[0];
        }
    }
    return result;
}


/*******************************************************************************
 * Returns the file mode creation mask. See also: `man 2 umask`.
 */
mode_t getCurrentUmask() {
    mode_t mask = unix_stat.umask(0);
    unix_stat.umask(mask);
    return mask;
}


/// Exception for functions of this module
class SysException : Exception { mixin RealizeErrnoException; }


// -------------------------------------------------------------------------- //
// -------------------------- Deprecated things ----------------------------- //
// -------------------------------------------------------------------------- //

deprecated void freeAnonymousMapping(void* p, size_t size) {
    int ret = unix_mman.munmap(p, size);
    enforce(ret == 0, new SysException("munmap"));
}
deprecated alias free_anonymous_mapping = freeAnonymousMapping;
deprecated alias create_anonymous_mapping = createAnonymousMapping;

deprecated void setupSignalHandler(int sig, c_sigfn_t handler) {
    addSignalHandler(sig, handler);
}
deprecated void setupSignalHandler(int sig, void function(int) handler) {
    addSignalHandler(sig, handler);
}
deprecated void setupSignalHandler(Signal sig, void function(Signal) handler) {
    addSignalHandler(sig, handler);
}
