aboutsummaryrefslogtreecommitdiff
#define _XOPEN_SOURCE 600

#include "config.h"

#include <cerrno>
#include <algorithm>
#include <vector>
#include <map>

#include <strings.h> // for strcasecmp

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>

#include "archive.hh"
#include "util.hh"


namespace nix {

static string archiveVersion1 = "nix-archive-1";

static string caseHackSuffix = "~nix~case~hack~";

PathFilter defaultPathFilter;


static void dumpContents(const Path & path, size_t size,
    Sink & sink)
{
    writeString("contents", sink);
    writeLongLong(size, sink);

    AutoCloseFD fd = open(path.c_str(), O_RDONLY);
    if (fd == -1) throw SysError(format("opening file `%1%'") % path);

    unsigned char buf[65536];
    size_t left = size;

    while (left > 0) {
        size_t n = left > sizeof(buf) ? sizeof(buf) : left;
        readFull(fd, buf, n);
        left -= n;
        sink(buf, n);
    }

    writePadding(size, sink);
}


static void dump(const Path & path, Sink & sink, PathFilter & filter)
{
    struct stat st;
    if (lstat(path.c_str(), &st))
        throw SysError(format("getting attributes of path `%1%'") % path);

    writeString("(", sink);

    if (S_ISREG(st.st_mode)) {
        writeString("type", sink);
        writeString("regular", sink);
        if (st.st_mode & S_IXUSR) {
            writeString("executable", sink);
            writeString("", sink);
        }
        dumpContents(path, (size_t) st.st_size, sink);
    }

    else if (S_ISDIR(st.st_mode)) {
        writeString("type", sink);
        writeString("directory", sink);

        /* If we're on a case-insensitive system like Mac OS X, undo
           the case hack applied by restorePath(). */
        std::map<string, string> unhacked;
        for (auto & i : readDirectory(path))
	    unhacked[i.name] = i.name;

        for (auto & i : unhacked)
            if (filter(path + "/" + i.first)) {
                writeString("entry", sink);
                writeString("(", sink);
                writeString("name", sink);
                writeString(i.first, sink);
                writeString("node", sink);
                dump(path + "/" + i.second, sink, filter);
                writeString(")", sink);
            }
    }

    else if (S_ISLNK(st.st_mode)) {
        writeString("type", sink);
        writeString("symlink", sink);
        writeString("target", sink);
        writeString(readLink(path), sink);
    }

    else throw Error(format("file `%1%' has an unsupported type") % path);

    writeString(")", sink);
}


void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
{
    writeString(archiveVersion1, sink);
    dump(path, sink, filter);
}


static SerialisationError badArchive(string s)
{
    return SerialisationError("bad archive: " + s);
}


#if 0
static void skipGeneric(Source & source)
{
    if (readString(source) == "(") {
        while (readString(source) != ")")
            skipGeneric(source);
    }
}
#endif


static void parseContents(ParseSink & sink, Source & source, const Path & path)
{
    unsigned long long size = readLongLong(source);

    sink.preallocateContents(size);

    unsigned long long left = size;
    unsigned char buf[65536];

    while (left) {
        checkInterrupt();
        unsigned int n = sizeof(buf);
        if ((unsigned long long) n > left) n = left;
        source(buf, n);
        sink.receiveContents(buf, n);
        left -= n;
    }

    readPadding(size, source);
}


struct CaseInsensitiveCompare
{
    bool operator() (const string & a, const string & b) const
    {
        return strcasecmp(a.c_str(), b.c_str()) < 0;
    }
};


static void parse(ParseSink & sink, Source & source, const Path & path)
{
    string s;

    s = readString(source);
    if (s != "(") throw badArchive("expected open tag");

    enum { tpUnknown, tpRegular, tpDirectory, tpSymlink } type = tpUnknown;

    std::map<Path, int, CaseInsensitiveCompare> names;

    while (1) {
        checkInterrupt();

        s = readString(source);

        if (s == ")") {
            break;
        }

        else if (s == "type") {
            if (type != tpUnknown)
                throw badArchive("multiple type fields");
            string t = readString(source);

            if (t == "regular") {
                type = tpRegular;
                sink.createRegularFile(path);
            }

            else if (t == "directory") {
                sink.createDirectory(path);
                type = tpDirectory;
            }

            else if (t == "symlink") {
                type = tpSymlink;
            }

            else throw badArchive("unknown file type " + t);

        }

        else if (s == "contents" && type == tpRegular) {
            parseContents(sink, source, path);
        }

        else if (s == "executable" && type == tpRegular) {
            readString(source);
            sink.isExecutable();
        }

        else if (s == "entry" && type == tpDirectory) {
            string name, prevName;

            s = readString(source);
            if (s != "(") throw badArchive("expected open tag");

            while (1) {
                checkInterrupt();

                s = readString(source);

                if (s == ")") {
                    break;
                } else if (s == "name") {
                    name = readString(source);
                    if (name.empty() || name == "." || name == ".." || name.find('/') != string::npos || name.find((char) 0) != string::npos)
                        throw Error(format("NAR contains invalid file name `%1%'") % name);
                    if (name <= prevName)
                        throw Error("NAR directory is not sorted");
                    prevName = name;
                } else if (s == "node") {
                    if (s.empty()) throw badArchive("entry name missing");
                    parse(sink, source, path + "/" + name);
                } else
                    throw badArchive("unknown field " + s);
            }
        }

        else if (s == "target" && type == tpSymlink) {
            string target = readString(source);
            sink.createSymlink(path, target);
        }

        else
            throw badArchive("unknown field " + s);
    }
}


void parseDump(ParseSink & sink, Source & source)
{
    string version;
    try {
        version = readString(source);
    } catch (SerialisationError & e) {
        /* This generally means the integer at the start couldn't be
           decoded.  Ignore and throw the exception below. */
    }
    if (version != archiveVersion1)
        throw badArchive("input doesn't look like a normalized archive");
    parse(sink, source, "");
}


struct RestoreSink : ParseSink
{
    Path dstPath;
    AutoCloseFD fd;

    void createDirectory(const Path & path)
    {
        Path p = dstPath + path;
        if (mkdir(p.c_str(), 0777) == -1)
            throw SysError(format("creating directory `%1%'") % p);
    };

    void createRegularFile(const Path & path)
    {
        Path p = dstPath + path;
        fd.close();
        fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666);
        if (fd == -1) throw SysError(format("creating file `%1%'") % p);
    }

    void isExecutable()
    {
        struct stat st;
        if (fstat(fd, &st) == -1)
            throw SysError("fstat");
        if (fchmod(fd, st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1)
            throw SysError("fchmod");
    }

    void preallocateContents(unsigned long long len)
    {
#if HAVE_POSIX_FALLOCATE
        if (len) {
            errno = posix_fallocate(fd, 0, len);
            /* Note that EINVAL may indicate that the underlying
               filesystem doesn't support preallocation (e.g. on
               OpenSolaris).  Since preallocation is just an
               optimisation, ignore it. */
            if (errno && errno != EINVAL)
                throw SysError(format("preallocating file of %1% bytes") % len);
        }
#endif
    }

    void receiveContents(unsigned char * data, unsigned int len)
    {
        writeFull(fd, data, len);
    }

    void createSymlink(const Path & path, const string & target)
    {
        Path p = dstPath + path;
        nix::createSymlink(target, p);
    }
};


void restorePath(const Path & path, Source & source)
{
    RestoreSink sink;
    sink.dstPath = path;
    parseDump(sink, source);
}


}
04cfefbbaa4fcd56dad3d5'>installer: Open manual in the selected language on tty2....Fixes <https://bugs.gnu.org/40624>. Reported by Florian Pelz <pelzflorian@pelzflorian.de>. Regression introduced in b5c2d93d7a223155898dd0ed6932f6acf78ac454. * gnu/installer.scm (apply-locale): Remove 'lambda' around 'stop-service' and 'start-service' calls. Ludovic Courtès 2020-04-11Merge branch 'master' into core-updatesMarius Bakke 2020-04-09installer: Allow Alt+Shift toggle from non-Latin keyboard layouts....Fixes <https://bugs.gnu.org/40493>. * gnu/installer/newt/keymap.scm (%non-latin-layouts): New variable. (%non-latin-variants): New variable. (%latin-layout+variants): New variable. (toggleable-latin-layout): New procedure to compute combined layouts. (run-keymap-page): Use it. (keyboard-layout->configuration): Apply it in config.scm. (run-layout-page): Mention Alt+Shift. * gnu/installer/keymap.scm (kmscon-update-keymap): Pass on XKB options. * gnu/installer/record.scm (<installer>): Adjust code comments. * gnu/installer.scm (apply-keymap): Pass on XKB options. (installer-steps): Adjust code comments. * gnu/packages/patches/kmscon-runtime-keymap-switch.patch: Apply XKB options. Florian Pelz 2020-04-08Merge branch 'master' into core-updates... Conflicts: etc/news.scm gnu/local.mk gnu/packages/check.scm gnu/packages/cross-base.scm gnu/packages/gimp.scm gnu/packages/java.scm gnu/packages/mail.scm gnu/packages/sdl.scm gnu/packages/texinfo.scm gnu/packages/tls.scm gnu/packages/version-control.scm Marius Bakke 2020-04-08installer: Turn help menu into parameters menu....* gnu/local.mk (INSTALLER_MODULES): Rename help.scm into parameters.scm. * po/guix/POTFILES.in: Ditto. * gnu/installer/record.scm (<installer>): Rename help-menu into parameter-menu and help-page into parameters-page. * gnu/installer/newt/parameters.scm: Renamed from help.scm. Update information messages. * gnu/installer/newt.scm: Update accordingly. * gnu/installer/newt/keymap.scm: Ditto. Mathieu Othacehe 2020-04-06installer: Hide shepherd messages....* gnu/installer.scm (apply-locale): Set "shepherd-message-port" instead of redirecting stderr to make sure that nothing is printed on console. Mathieu Othacehe 2020-04-06installer: Add a help page....* gnu/installer/newt/help.scm: New file. * gnu/local.mk (INSTALLER_MODULES): Add it. * po/guix/POTFILES.in: Add it. * gnu/installer/record.scm (<installer>): Add 'help-menu' and 'help-page' fields, (installer-help-menu, installer-help-page): new exported procedures. * gnu/installer/newt.scm (init): Set the help line, (help-menu, help-page): new procedures used ... (newt-installer): ... here. * gnu/installer/newt/keymap.scm (run-layout-page): Add a context argument to differenciate the help context from the main one, (run-keymap-page): add a context argument and pass it to run-layout-page. * gnu/installer.scm (compute-keymap-step): Add a context argument and pass it to 'installer-keymap-page', (installer-steps): set the help menu and pass the appropriate context to compute-keymap-step calls, (guile-newt): update to revision 2. Mathieu Othacehe 2020-04-06installer: Remove trailing tabs....* gnu/installer.scm (installer-steps): Remove trailing tabs. Mathieu Othacehe 2020-03-27Merge branch 'master' into core-updates... Conflicts: gnu/packages/icu4c.scm gnu/packages/man.scm gnu/packages/python-xyz.scm guix/scripts/environment.scm guix/scripts/pack.scm guix/scripts/package.scm guix/scripts/pull.scm guix/store.scm Marius Bakke 2020-03-22installer: Do not include the host (guix config)....Previously, "locales.drv" would depend on the host's (guix config). Thus, the derivation would depend on details of the user's installation. * gnu/installer.scm (not-config?): New procedure. (build-compiled-file): Pass it to 'source-module-closure' and use 'make-config.scm'. Ludovic Courtès 2020-03-10Merge branch 'master' into core-updatesMarius Bakke 2020-03-05installer: Use a Guile-Newt snapshot that supports 'form-watch-fd'....* gnu/installer.scm (guile-newt): New variable. Ludovic Courtès 2020-03-04Merge branch 'master' into core-updatesMarius Bakke 2020-02-22installer: Log important bits to syslog....* gnu/installer.scm (installer-program): Log crashes with 'syslog'. * gnu/installer/parted.scm (luks-format-and-open, luks-close) (mount-user-partitions, umount-user-partitions): Add 'syslog' calls. * gnu/installer/steps.scm (run-installer-steps): Log the running step with 'syslog'. * gnu/installer/utils.scm (run-shell-command): Add calls to 'syslog'. Ludovic Courtès 2020-02-14Merge branch 'master' into core-updatesMarius Bakke 2020-02-12installer: Fix installer restart dialog....* gnu/installer/newt/final.scm (run-install-failed-page): Propose between installer resume or restart. Do actually resume the installation by raising an &installer-step-abort condition if "Resume" button is pressed. Otherwise, keep going as the installer will be restarted by login. * gnu/installer.scm (installer-program): Remove the associated TODO comment. Mathieu Othacehe 2020-02-11system: Stop using canonical-package....Usage of canonical-package outside of thunked fields breaks cross-compilation, see: https://lists.gnu.org/archive/html/guix-devel/2019-12/msg00410.html. * gnu/installer.scm (installer-program): Remove canonical-package. * gnu/services/base.scm (<nscd-cache>): Ditto, (%base-services): ditto. * gnu/services/xorg.scm: Remove useless canonical-package import. * gnu/system.scm (%base-packages): Remove canonical-package. * gnu/system/install.scm (%installation-services): Ditto, (installation-os): ditto. * gnu/system/locale.scm (single-locale-directory): Ditto. Mathieu Othacehe 2020-01-05installer: Add JFS support....* gnu/installer/newt/partition.scm (run-fs-type-page): Add ‘jfs’ to the list box. * gnu/installer/parted.scm (user-fs-type-name, user-fs-type->mount-type) (partition-filesystem-user-type): Add ‘jfs’ mapping (create-jfs-file-system): New procedure. (format-user-partitions): Use it. * gnu/installer.scm (set-installer-path): Add jfsutils. Tobias Geerinckx-Rice 2019-08-26installer: Partition as the last step....Multiple users have been understandably displeased to find out that their network card was unsupported, and Internet access mandatory, after having already formatted their partitions. * gnu/installer.scm (installer-steps): Run the ‘partition’ step just before the ‘final’ one. Tobias Geerinckx-Rice 2019-07-25maint: Switch to Guile-JSON 3.x....Guile-JSON 3.x is incompatible with Guile-JSON 1.x, which we relied on until now: it maps JSON dictionaries to alists (instead of hash tables), and JSON arrays to vectors (instead of lists). This commit is about adjusting all the existing code to this new mapping. * m4/guix.m4 (GUIX_CHECK_GUILE_JSON): New macro. * configure.ac: Use it. * doc/guix.texi (Requirements): Mention the Guile-JSON version. * guix/git-download.scm (git-fetch)[guile-json]: Use GUILE-JSON-3. * guix/import/cpan.scm (string->license): Expect vectors instead of lists. (module->dist-name): Use 'json-fetch' instead of 'json-fetch-alist'. (cpan-fetch): Likewise. * guix/import/crate.scm (crate-fetch): Likewise, and call 'vector->list' for DEPS. * guix/import/gem.scm (rubygems-fetch): Likewise. * guix/import/json.scm (json-fetch-alist): Remove. * guix/import/pypi.scm (pypi-fetch): Use 'json-fetch' instead of 'json-fetch-alist'. (latest-source-release, latest-wheel-release): Call 'vector->list' on RELEASES. * guix/import/stackage.scm (stackage-lts-info-fetch): Use 'json-fetch' instead of 'json-fetch-alist'. (lts-package-version): Use 'vector->list'. * guix/import/utils.scm (hash-table->alist): Remove. (alist->package): Pass 'vector->list' on the inputs fields, and default to the empty vector. * guix/scripts/import/json.scm (guix-import-json): Remove call to 'hash-table->alist'. * guix/swh.scm (define-json-reader): Expect pair? or null? instead of hash-table?. [extract-field]: Use 'assoc-ref' instead of 'hash-ref'. (json->branches): Use 'map' instead of 'hash-map->list'. (json->checksums): Likewise. (json->directory-entries, origin-visits): Call 'vector->list' on the result of 'json->scm'. * tests/import-utils.scm ("alist->package with dependencies"): New test. * gnu/installer.scm (build-compiled-file)[builder]: Use GUILE-JSON-3. * gnu/installer.scm (installer-program)[installer-builder]: Likewise. * gnu/installer/locale.scm (iso639->iso639-languages): Use 'assoc-ref' instead of 'hash-ref', and pass vectors through 'vector->list'. (iso3166->iso3166-territories): Likewise. * gnu/system/vm.scm (system-docker-image)[build]: Use GUILE-JSON-3. * guix/docker.scm (manifest, config): Adjust for Guile-JSON 3. * guix/scripts/pack.scm (docker-image)[build]: Use GUILE-JSON-3. * guix/import/github.scm (fetch-releases-or-tags): Update docstring. (latest-released-version): Use 'assoc-ref' instead of 'hash-ref'. Pass the result of 'fetch-releases-or-tags' to 'vector->list'. * guix/import/launchpad.scm (latest-released-version): Likewise. Ludovic Courtès 2019-05-15installer: Increase backtrace verbosity....* gnu/installer.scm (installer-program): Set terminal-width to 200 to make guile backtraces more verbose. Mathieu Othacehe 2019-05-14installer: Add btrfs-progs to PATH....* gnu/installer.scm (installer-program): Add btrfs-progs to PATH. Danny Milosavljevic 2019-05-13installer: Use 'glibc-supported-locales'....The list of locales supported by glibc is now built from source. * gnu/installer/locale.scm (locale-string->locale): Add optional 'codeset' parameter and honor it. (supported-locales->locales): Rewrite to 'read' from SUPPORTED-LOCALES. * gnu/installer.scm (compute-locale-step): Pass the result of 'glibc-supported-locales' instead of the "aux-files/SUPPORTED" file. * gnu/installer/aux-files/SUPPORTED: Remove. * gnu/local.mk (dist_installer_DATA): Remove it. Ludovic Courtès 2019-04-26installer: Actually reboot when the user presses "Reboot."...* gnu/installer/newt/final.scm (run-install-success-page): Return 'success. * gnu/installer.scm (installer-program): Check the result of the 'final step and reboot upon success. Ludovic Courtès 2019-04-26installer: Run wrapped program with 'execl', not 'system'....'system' invokes /bin/sh, which is certainly not needed here. * gnu/installer.scm (installer-program): Use 'execl', not 'system'. Ludovic Courtès 2019-04-17installer: Translate keyboard layout names....* gnu/installer.scm (installer-program)[installer-builder]: Call 'bindtextdomain' for "xkeyboard-config". * gnu/installer/newt/keymap.scm (run-keymap-page): Add calls to 'gettext'. Ludovic Courtès 2019-04-17installer: Display language and territory names natively....* gnu/installer.scm (installer-program): Add calls to 'bindtextdomain'. * gnu/installer/newt/locale.scm (run-locale-page) <language, territory>: Add calls to 'gettext'. Ludovic Courtès 2019-04-12installer: Choosing a locale opens the translated manual on tty2....Suggested by Florian Pelz. * gnu/system/install.scm (%installation-node-names): New variable. (log-to-info): Expect the chosen locale as an argument. Compute the language, Info file name, and node name. Install the locale. (documentation-shepherd-service): Add 'locale' parameter to the 'start' action and honor it. Set GUIX_LOCPATH and TERM as environment variables for the process. * gnu/installer.scm (apply-locale): Use (gnu services herd). Call 'stop-service' and 'start-service' with the chosen locale. Ludovic Courtès 2019-04-07installer: Generalize desktop environments to system services....* gnu/installer/services.scm (<desktop-environment>): Rename to... (<system-service>): ... this. Add a 'type' field. (%desktop-environments): Rename to... (%system-services): ... this. (desktop-system-service?): New procedure. (desktop-environments->configuration): Rename to... (system-services->configuration): ... this. Determine the base list of services based on whether SERVICES contains at least one "desktop" service. * gnu/installer/newt/services.scm (run-desktop-environments-cbt-page): Adjust accordingly. * gnu/installer.scm (installer-steps): Likewise. Ludovic Courtès 2019-04-07installer: Move the 'locale' step before the 'welcome' step....* gnu/installer.scm (installer-steps): Move the 'locale step before 'welcome. Ludovic Courtès 2019-03-27installer: Produce an 'initrd-modules' field if needed....* gnu/installer/parted.scm (root-user-partition?): New procedure. (bootloader-configuration): Use it. (user-partition-missing-modules, initrd-configuration): New procedures. (user-partitions->configuration): Call 'initrd-configuration'.o * gnu/installer.scm (not-config?): Rename to... (module-to-import?): ... this. Add cases to exclude non-installer and non-build (gnu …) modules. (installer-program)[installer-builder]: Add GUIX to the extension list. Ludovic Courtès 2019-03-25installer: Set the system's 'keyboard-layout' field....* gnu/installer/newt/keymap.scm (keyboard-layout->configuration): New procedure. * gnu/installer.scm (compute-keymap-step): Return RESULT. (installer-steps) <'keymap>: Add 'configuration-formatter' field. (installer-program): Use (gnu installer newt keymap). * gnu/installer/parted.scm (bootloader-configuration): Set 'keyboard-layout'. Ludovic Courtès