#include "globals.hh" #include "misc.hh" #include "local-store.hh" #include #include #include #include #include #include #include #include #include namespace nix { static string gcLockName = "gc.lock"; static string tempRootsDir = "temproots"; static string gcRootsDir = "gcroots"; /* Acquire the global GC lock. This is used to prevent new build processes from starting after the temporary root files have been read. To be precise: when they try to create a new temporary root file, they will block until the garbage collector has finished / yielded the GC lock. */ int LocalStore::openGCLock(LockType lockType) { Path fnGCLock = (format("%1%/%2%") % settings.nixStateDir % gcLockName).str(); debug(format("acquiring global GC lock `%1%'") % fnGCLock); AutoCloseFD fdGCLock = open(fnGCLock.c_str(), O_RDWR | O_CREAT, 0600); if (fdGCLock == -1) throw SysError(format("opening global GC lock `%1%'") % fnGCLock); closeOnExec(fdGCLock); if (!lockFile(fdGCLock, lockType, false)) { printMsg(lvlError, format("waiting for the big garbage collector lock...")); lockFile(fdGCLock, lockType, true); } /* !!! Restrict read permission on the GC root. Otherwise any process that can open the file for reading can DoS the collector. */ return fdGCLock.borrow(); } static void makeSymlink(const Path & link, const Path & target) { /* Create directories up to `gcRoot'. */ createDirs(dirOf(link)); /* Create the new symlink. */ Path tempLink = (format("%1%.tmp-%2%-%3%") % link % getpid() % rand()).str(); createSymlink(target, tempLink); /* Atomically replace the old one. */ if (rename(tempLink.c_str(), link.c_str()) == -1) throw SysError(format("cannot rename `%1%' to `%2%'") % tempLink % link); } void Loc
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2014 John Darrington <jmd@gnu.org>
;;; Copyright © 2014, 2015 Mark H Weaver <mhw@netris.org>
;;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
;;; Copyright © 2015, 2018 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2016, 2017, 2018, 2022 Efraim Flashner <efraim@flashner.co.il>
;;; Copyright © 2018–2021 Tobias Geerinckx-Rice <me@tobias.gr>
;;; Copyright © 2019 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;; Copyright © 2021 Marius Bakke <marius@gnu.org>
;;; Copyright © 2021 Brice Waegeneire <brice@waegenei.re>
;;; Copyright © 2022 Petr Hodina <phodina@protonmail.com>
;;; Copyright © 2024 Artyom V. Poptsov <poptsov.artyom@gmail.com>
;;; Copyright © 2024 Zheng Junjie <873216071@qq.com>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu packages ntp)
  #:use-module (gnu packages)
  #:use-module (gnu packages autotools)
  #:use-module (gnu packages base)
  #:use-module (gnu packages bison)
  #:use-module (gnu packages libevent)
  #:use-module (gnu packages linux)
  #:use-module (gnu packages nettle)
  #:use-module (gnu packages pkg-config)
  #:use-module (gnu packages readline)
  #:use-module (gnu packages ruby)
  #:use-module (gnu packages tls)
  #:use-module (guix build-system gnu)
  #:use-module (guix download)
  #:use-module (guix gexp)
  #:use-module (guix git-download)
  #:use-module ((guix licenses) #:prefix l:)
  #:use-module (guix packages)
  #:use-module (guix utils)
  #:use-module (srfi srfi-1))

(define-public chrony
  (package
    (name "chrony")
    (version "4.6.1")
    (source
     (origin
       (method git-fetch)
       (uri (git-reference
             (url "https://gitlab.com/chrony/chrony.git/")
             (commit version)))
       (file-name (git-file-name name version))
       (sha256
        (base32 "14z53iia83s25g9hpdh79j4gqi9lbp37nx1jcvxi8av3zlgzrz2p"))))
    (build-system gnu-build-system)
    (arguments
     (list
      #:modules
      '((srfi srfi-26)
        (guix build utils)
        (guix build gnu-build-system))
      #:configure-flags
      #~(list "--enable-scfilter"
              "--with-ntp-era=0"
              "--with-sendmail=sendmail"
              "--with-user=chrony")
      #:phases
      #~(modify-phases %standard-phases
          (add-after 'unpack 'set-CC
            (lambda _
              (setenv "CC" #$(cc-for-target))))
          (add-after 'unpack 'stay-inside-out
            ;; Simply setting CHRONYVARDIR to something nonsensical at install
            ;; time would result in nonsense file names in man pages.
            (lambda _
              (substitute* "Makefile.in"
                (("mkdir -p \\$\\(DESTDIR\\)\\$\\(CHRONYVARDIR\\)") ":"))))
          (add-after 'install 'install-more-documentation
            (lambda _
              (let* ((doc (string-append #$output "/share/doc/"
                                         #$name "-" #$version)))
                (for-each (cut install-file <> doc)
                          (list "README"))
                (copy-recursively "examples"
                                  (string-append doc "/examples"))))))))
    (native-inputs
     (list bison ruby-asciidoctor pkg-config))
    (inputs
     (list gnutls libcap libseccomp nettle))
    (home-page "https://chrony-project.org/")
    (synopsis "System clock synchronization service that speaks NTP")
    (description
     "Chrony keeps your system time accurate.  It synchronises your computer's
clock with @acronym{NTP, Network Time Protocol} servers, reference clocks such
as GPS receivers, or even manual input of the correct time from a wristwatch.

Chrony will determine the rate at which the computer gains or loses time, and
compensate for it.  It can also operate as an
NTPv4 (@url{https://www.rfc-editor.org/rfc/rfc5905, RFC 5905}) server and peer to
tell time to other computers on the network.

It's designed to perform well even under adverse conditions: congested
networks, unreliable clocks drifting with changes in temperature, and devices
or virtual machines that are frequently turned off and connect to the Internet
for only a few minutes at a time.

Typical accuracy when synchronised over the Internet is several milliseconds.
On a local network this can reach tens of microseconds.  With hardware
time-stamping or reference clock, sub-microsecond accuracy is possible.")
    (license l:gpl2)))

(define-public ntp
  (package
   (name "ntp")
   (version "4.2.8p18")
   (source
     (origin
       (method url-fetch)
       (uri (list (string-append
                   "https://www.eecis.udel.edu/~ntp/ntp_spool/ntp4/ntp-"
                   (version-major+minor version)
                   "/ntp-" version ".tar.gz")
                  (string-append
                   "http://archive.ntp.org/ntp4/ntp-"
                   (version-major+minor version)
                   "/ntp-" version ".tar.gz")))
       (sha256
        (base32 "1rb8yksqxjcsjvww9kwnw1242qzszwixh916jj254a8szgrwb16g"))
       (modules '((guix build utils)))
       (snippet
        '(begin
           ;; Remove the bundled copy of libevent, but we must keep
           ;; sntp/libevent/build-aux since configure.ac contains
           ;; AC_CONFIG_AUX_DIR([sntp/libevent/build-aux])
           (rename-file "sntp/libevent/build-aux"
                        "sntp/libevent:build-aux")
           (delete-file-recursively "sntp/libevent")
           (mkdir "sntp/libevent")
           (rename-file "sntp/libevent:build-aux"
                        "sntp/libevent/build-aux")))))
   (native-inputs (list which pkg-config))
   (inputs
    (cons* openssl
           libevent
           ;; Build with POSIX capabilities support on GNU/Linux.  This allows
           ;; 'ntpd' to run as non-root (when invoked with '-u'.)
           (if (target-linux?)
               (list libcap)
               '())))
   (arguments
    (list
     ;; Pass "--with-yielding-select=yes" so that 'configure' knows whether
     ;; 'select' yields when using pthreads in a cross-compilation context.
     #:configure-flags
     #~(list "--with-yielding-select=yes")
     #:phases
     #~(modify-phases %standard-phases
         (add-after 'unpack 'disable-network-test
           (lambda _
             (substitute* "tests/libntp/Makefile.in"
               (("test-decodenetnum\\$\\(EXEEXT\\) ") "")))))))
   (build-system gnu-build-system)
   (synopsis "Real time clock synchronization system")
   (description "NTP is a system designed to synchronize the clocks of
computers over a network.")
   (license (l:x11-style
             "https://www.eecis.udel.edu/~mills/ntp/html/copyright.html"
             "A non-copyleft free licence from the University of Delaware"))
   (home-page "https://www.ntp.org")))

(define-public openntpd
  (package
    (name "openntpd")
    (version "6.8p1")
    (source (origin
              (method url-fetch)
              (uri (string-append
                    "mirror://openbsd/OpenNTPD/openntpd-" version ".tar.gz"))
              (sha256
               (base32
                "0ijsylc7a4jlpxsqa0jq1w1c7333id8pcakzl7a5749ria1xp0l5"))))
    (build-system gnu-build-system)
    (arguments
     `(#:configure-flags
       (let* ((libressl (assoc-ref %build-inputs "libressl"))
              (libressl-version ,(package-version
                                  (car (assoc-ref (package-inputs this-package)
                                                  "libressl")))))
         (list "--with-privsep-user=ntpd"
               "--localstatedir=/var"
               (string-append "--with-cacert=" libressl
                              "/share/libressl-" libressl-version
                              "/cert.pem")))
       #:phases
       (modify-phases %standard-phases
         (add-after 'unpack 'modify-install-locations
           (lambda _
             ;; Don't try to create /var/run or /var/db
             (substitute* "src/Makefile.in"
               (("DESTDIR\\)\\$\\(localstatedir") "TMPDIR"))
             #t)))))
    (inputs
     (list libressl)) ; enable TLS time constraints. See ntpd.conf(5).
    (home-page "https://www.openntpd.org/")
    (synopsis "NTP client and server by the OpenBSD Project")
    (description "OpenNTPD is the OpenBSD Project's implementation of a client
and server for the Network Time Protocol.  Its design goals include being
secure, easy to configure, and accurate enough for most purposes, so it's more
minimalist than ntpd.")
    (properties
     '((release-monitoring-url . "https://cdn.openbsd.org/pub/OpenBSD/OpenNTPD")))
    ;; A few of the source files are under bsd-3.
    (license (list l:isc l:bsd-3))))
void LocalStore::deleteGarbage(GCState & state, const Path & path) { unsigned long long bytesFreed; /* When deduplication is on, store items always have at least two links: the one at PATH, and one in /gnu/store/.links. In that case, increase bytesFreed when PATH has two or fewer links. */ size_t linkThreshold = (settings.autoOptimiseStore && isStorePath(path)) ? 2 : 1; deletePath(path, bytesFreed, linkThreshold); state.results.bytesFreed += bytesFreed; } void LocalStore::deletePathRecursive(GCState & state, const Path & path) { checkInterrupt(); unsigned long long size = 0; if (isValidPath(path)) { PathSet referrers; queryReferrers(path, referrers); foreach (PathSet::iterator, i, referrers) if (*i != path) deletePathRecursive(state, *i); size = queryPathInfo(path).narSize; invalidatePathChecked(path); } struct stat st; if (lstat(path.c_str(), &st)) { if (errno == ENOENT) return; throw SysError(format("getting status of %1%") % path); } if (state.options.maxFreed != ULLONG_MAX) { auto freed = state.results.bytesFreed + state.bytesInvalidated; double fraction = ((double) freed) / (double) state.options.maxFreed; unsigned int percentage = (fraction > 1. ? 1. : fraction) * 100.; printMsg(lvlInfo, format("[%1%%%] deleting '%2%'") % percentage % path); } else { auto freed = state.results.bytesFreed + state.bytesInvalidated; freed /= 1024ULL * 1024ULL; printMsg(lvlInfo, format("[%1% MiB] deleting '%2%'") % freed % path); } state.results.paths.insert(path); /* If the path is not a regular file or symlink, move it to the trash directory. The move is to ensure that later (when we're not holding the global GC lock) we can delete the path without being afraid that the path has become alive again. Otherwise delete it right away. */ if (state.moveToTrash && S_ISDIR(st.st_mode)) { // Estimate the amount freed using the narSize field. FIXME: // if the path was not valid, need to determine the actual // size. try { if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1) throw SysError(format("making `%1%' writable") % path); Path tmp = state.trashDir + "/" + baseNameOf(path); if (rename(path.c_str(), tmp.c_str())) throw SysError(format("unable to rename `%1%' to `%2%'") % path % tmp); state.bytesInvalidated += size; } catch (SysError & e) { if (e.errNo == ENOSPC) { printMsg(lvlInfo, format("note: can't create move `%1%': %2%") % path % e.msg()); deleteGarbage(state, path); } } } else deleteGarbage(state, path); if (state.results.bytesFreed + state.bytesInvalidated > state.options.maxFreed) { printMsg(lvlInfo, format("deleted or invalidated more than %1% bytes; stopping") % state.options.maxFreed); throw GCLimitReached(); } } bool LocalStore::canReachRoot(GCState & state, PathSet & visited, const Path & path) { if (visited.find(path) != visited.end()) return false; if (state.alive.find(path) != state.alive.end()) { return true; } if (state.dead.find(path) != state.dead.end()) { return false; } if (state.roots.find(path) != state.roots.end()) { printMsg(lvlDebug, format("cannot delete `%1%' because it's a root") % path); state.alive.insert(path); return true; } visited.insert(path); if (!isValidPath(path)) return false; PathSet incoming; /* Don't delete this path if any of its referrers are alive. */ queryReferrers(path, incoming); /* If gc-keep-derivations is set and this is a derivation, then don't delete the derivation if any of the outputs are alive. */ if (state.gcKeepDerivations && isDerivation(path)) { PathSet outputs = queryDerivationOutputs(path); foreach (PathSet::iterator, i, outputs) if (isValidPath(*i) && queryDeriver(*i) == path) incoming.insert(*i); } /* If gc-keep-outputs is set, then don't delete this path if there are derivers of this path that are not garbage. */ if (state.gcKeepOutputs) { PathSet derivers = queryValidDerivers(path); foreach (PathSet::iterator, i, derivers) incoming.insert(*i); } foreach (PathSet::iterator, i, incoming) if (*i != path) if (canReachRoot(state, visited, *i)) { state.alive.insert(path); return true; } return false; } void LocalStore::tryToDelete(GCState & state, const Path & path) { checkInterrupt(); if (path == linksDir || path == state.trashDir) return; startNest(nest, lvlDebug, format("considering whether to delete `%1%'") % path); if (!isValidPath(path)) { /* A lock file belonging to a path that we're building right now isn't garbage. */ if (isActiveTempFile(state, path, ".lock")) return; /* Don't delete .chroot directories for derivations that are currently being built. */ if (isActiveTempFile(state, path, ".chroot")) return; } PathSet visited; if (canReachRoot(state, visited, path)) { printMsg(lvlDebug, format("cannot delete `%1%' because it's still reachable") % path); } else { /* No path we visited was a root, so everything is garbage. But we only delete ‘path’ and its referrers here so that ‘nix-store --delete’ doesn't have the unexpected effect of recursing into derivations and outputs. */ state.dead.insert(visited.begin(), visited.end()); if (state.shouldDelete) deletePathRecursive(state, path); } } /* Unlink all files in /nix/store/.links that have a link count of 1, which indicates that there are no other links and so they can be safely deleted. FIXME: race condition with optimisePath(): we might see a link count of 1 just before optimisePath() increases the link count. */ void LocalStore::removeUnusedLinks(const GCState & state) { AutoCloseDir dir = opendir(linksDir.c_str()); if (!dir) throw SysError(format("opening directory `%1%'") % linksDir); long long actualSize = 0, unsharedSize = 0; struct dirent * dirent; while (errno = 0, dirent = readdir(dir)) { checkInterrupt(); string name = dirent->d_name; if (name == "." || name == "..") continue; Path path = linksDir + "/" + name; #ifdef HAVE_STATX # define st_size stx_size # define st_nlink stx_nlink struct statx st; if (statx(AT_FDCWD, path.c_str(), AT_SYMLINK_NOFOLLOW | AT_STATX_DONT_SYNC, STATX_SIZE | STATX_NLINK, &st) == -1) #else struct stat st; if (lstat(path.c_str(), &st) == -1) #endif throw SysError(format("statting `%1%'") % path); if (st.st_nlink != 1) { actualSize += st.st_size; unsharedSize += (st.st_nlink - 1) * st.st_size; continue; } printMsg(lvlTalkative, format("deleting unused link `%1%'") % path); if (unlink(path.c_str()) == -1) throw SysError(format("deleting `%1%'") % path); state.results.bytesFreed += st.st_size; #undef st_size #undef st_nlink } struct stat st; if (stat(linksDir.c_str(), &st) == -1) throw SysError(format("statting `%1%'") % linksDir); long long overhead = st.st_size; printMsg(lvlInfo, format("note: currently hard linking saves %.2f MiB") % ((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0))); } void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) { GCState state(results); state.options = options; state.trashDir = settings.nixStore + "/trash"; state.gcKeepOutputs = settings.gcKeepOutputs; state.gcKeepDerivations = settings.gcKeepDerivations; /* Using `--ignore-liveness' with `--delete' can have unintended consequences if `gc-keep-outputs' or `gc-keep-derivations' are true (the garbage collector will recurse into deleting the outputs or derivers, respectively). So disable them. */ if (options.action == GCOptions::gcDeleteSpecific && options.ignoreLiveness) { state.gcKeepOutputs = false; state.gcKeepDerivations = false; } state.shouldDelete = options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific; /* Acquire the global GC root. This prevents a) New roots from being added. b) Processes from creating new temporary root files. */ AutoCloseFD fdGCLock = openGCLock(ltWrite); /* Find the roots. Since we've grabbed the GC lock, the set of permanent roots cannot increase now. */ printMsg(lvlError, format("finding garbage collector roots...")); Roots rootMap = options.ignoreLiveness ? Roots() : findRoots(); foreach (Roots::iterator, i, rootMap) state.roots.insert(i->second); /* Add additional roots returned by 'guix gc --list-busy'. This is typically used to add running programs to the set of roots (to prevent them from being garbage collected). */ if (!options.ignoreLiveness) addAdditionalRoots(*this, state.roots); /* Read the temporary roots. This acquires read locks on all per-process temporary root files. So after this point no paths can be added to the set of temporary roots. */ FDs fds; readTempRoots(state.tempRoots, fds); state.roots.insert(state.tempRoots.begin(), state.tempRoots.end()); /* After this point the set of roots or temporary roots cannot increase, since we hold locks on everything. So everything that is not reachable from `roots' is garbage. */ if (state.shouldDelete) { if (pathExists(state.trashDir)) deleteGarbage(state, state.trashDir); try { createDirs(state.trashDir); } catch (SysError & e) { if (e.errNo == ENOSPC) { printMsg(lvlInfo, format("note: can't create trash directory: %1%") % e.msg()); state.moveToTrash = false; } } } /* Now either delete all garbage paths, or just the specified paths (for gcDeleteSpecific). */ if (options.action == GCOptions::gcDeleteSpecific) { foreach (PathSet::iterator, i, options.pathsToDelete) { assertStorePath(*i); tryToDelete(state, *i); if (state.dead.find(*i) == state.dead.end()) throw Error(format("cannot delete path `%1%' since it is still alive") % *i); } } else if (options.maxFreed > 0) { if (state.shouldDelete) printMsg(lvlError, format("deleting garbage...")); else printMsg(lvlError, format("determining live/dead paths...")); try { AutoCloseDir dir = opendir(settings.nixStore.c_str()); if (!dir) throw SysError(format("opening directory `%1%'") % settings.nixStore); /* Read the store and immediately delete all paths that aren't valid. When using --max-freed etc., deleting invalid paths is preferred over deleting unreachable paths, since unreachable paths could become reachable again. We don't use readDirectory() here so that GCing can start faster. */ Paths entries; struct dirent * dirent; while (errno = 0, dirent = readdir(dir)) { checkInterrupt(); string name = dirent->d_name; if (name == "." || name == "..") continue; Path path = settings.nixStore + "/" + name; if (isValidPath(path)) entries.push_back(path); else tryToDelete(state, path); } dir.close(); /* Now delete the unreachable valid paths. Randomise the order in which we delete entries to make the collector less biased towards deleting paths that come alphabetically first (e.g. /nix/store/000...). This matters when using --max-freed etc. */ vector entries_(entries.begin(), entries.end()); random_shuffle(entries_.begin(), entries_.end()); foreach (vector::iterator, i, entries_) tryToDelete(state, *i); } catch (GCLimitReached & e) { } } if (state.options.action == GCOptions::gcReturnLive) { state.results.paths = state.alive; return; } if (state.options.action == GCOptions::gcReturnDead) { state.results.paths = state.dead; return; } /* Allow other processes to add to the store from here on. */ fdGCLock.close(); fds.clear(); /* Delete the trash directory. */ printMsg(lvlInfo, format("deleting `%1%'") % state.trashDir); deleteGarbage(state, state.trashDir); /* Clean up the links directory. */ if (options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcDeleteSpecific) { printMsg(lvlError, format("deleting unused links...")); removeUnusedLinks(state); } /* While we're at it, vacuum the database. */ //if (options.action == GCOptions::gcDeleteDead) vacuumDB(); } }