8 my $etc = $ARGV[0] or die;
9 my $static = "/etc/static";
12 my ($source, $target) = @_;
13 my $tmp = "$target.tmp";
15 symlink $source, $tmp or return 0;
16 if (rename $tmp, $target) {
25 # Atomically update /etc/static to point at the etc files of the
26 # current configuration.
27 atomicSymlink
$etc, $static or die;
29 # Returns 1 if the argument points to the files in /etc/static. That
30 # means either argument is a symlink to a file in /etc/static or a
31 # directory with all children being static.
36 my $target = readlink $path;
37 return substr($target, 0, length "/etc/static/") eq "/etc/static/";
41 opendir DIR
, "$path" or return 0;
42 my @names = readdir DIR
or die;
45 foreach my $name (@names) {
46 next if $name eq "." || $name eq "..";
47 unless (isStatic
("$path/$name")) {
57 # Remove dangling symlinks that point to /etc/static. These are
58 # configuration files that existed in a previous configuration but not
59 # in the current one. For efficiency, don't look under /etc/nixos
60 # (where all the NixOS sources live).
62 if ($File::Find
::name
eq "/etc/nixos") {
63 $File::Find
::prune
= 1;
67 my $target = readlink $_;
68 if (substr($target, 0, length $static) eq $static) {
69 my $x = "/etc/static/" . substr($File::Find
::name
, length "/etc/");
71 print STDERR
"removing obsolete symlink ‘$File::Find::name’...\n";
78 find
(\
&cleanup
, "/etc");
81 # Use /etc/.clean to keep track of copied files.
82 my @oldCopied = read_file
("/etc/.clean", chomp => 1, err_mode
=> 'quiet');
83 open CLEAN
, ">>/etc/.clean";
86 # For every file in the etc tree, create a corresponding symlink in
87 # /etc to /etc/static. The indirection through /etc/static is to make
88 # switching to a new configuration somewhat more atomic.
93 my $fn = substr $File::Find
::name
, length($etc) + 1 or next;
95 # nixos-enter sets up /etc/resolv.conf as a bind mount, so skip it.
96 if ($fn eq "resolv.conf" and $ENV{'IN_NIXOS_ENTER'}) {
100 my $target = "/etc/$fn";
101 File
::Path
::make_path
(dirname
$target);
104 # Rename doesn't work if target is directory.
105 if (-l
$_ && -d
$target) {
106 if (isStatic
$target) {
107 rmtree
$target or warn;
109 warn "$target directory contains user files. Symlinking may fail.";
114 my $mode = read_file
("$_.mode"); chomp $mode;
115 if ($mode eq "direct-symlink") {
116 atomicSymlink
readlink("$static/$fn"), $target or warn "could not create symlink $target";
118 my $uid = read_file
("$_.uid"); chomp $uid;
119 my $gid = read_file
("$_.gid"); chomp $gid;
120 copy
"$static/$fn", "$target.tmp" or warn;
121 $uid = getpwnam $uid unless $uid =~ /^\+/;
122 $gid = getgrnam $gid unless $gid =~ /^\+/;
123 chown int($uid), int($gid), "$target.tmp" or warn;
124 chmod oct($mode), "$target.tmp" or warn;
125 unless (rename "$target.tmp", $target) {
126 warn "could not create target $target";
127 unlink "$target.tmp";
133 atomicSymlink
"$static/$fn", $target or warn "could not create symlink $target";
140 # Delete files that were copied in a previous version but not in the
142 foreach my $fn (@oldCopied) {
143 if (!defined $created{$fn}) {
145 print STDERR
"removing obsolete file ‘$fn’...\n";
151 # Rewrite /etc/.clean.
153 write_file
("/etc/.clean", map { "$_\n" } sort @copied);
155 # Create /etc/NIXOS tag if not exists.
156 # When /etc is not on a persistent filesystem, it will be wiped after reboot,
157 # so we need to check and re-create it during activation.
158 open TAG
, ">>/etc/NIXOS";