at 24.11-pre 4.5 kB view raw
1use strict; 2use File::Find; 3use File::Copy; 4use File::Path; 5use File::Basename; 6use File::Slurp; 7 8my $etc = $ARGV[0] or die; 9my $static = "/etc/static"; 10 11sub atomicSymlink { 12 my ($source, $target) = @_; 13 my $tmp = "$target.tmp"; 14 unlink $tmp; 15 symlink $source, $tmp or return 0; 16 if (rename $tmp, $target) { 17 return 1; 18 } else { 19 unlink $tmp; 20 return 0; 21 } 22} 23 24 25# Atomically update /etc/static to point at the etc files of the 26# current configuration. 27atomicSymlink $etc, $static or die; 28 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. 32sub isStatic { 33 my $path = shift; 34 35 if (-l $path) { 36 my $target = readlink $path; 37 return substr($target, 0, length "/etc/static/") eq "/etc/static/"; 38 } 39 40 if (-d $path) { 41 opendir DIR, "$path" or return 0; 42 my @names = readdir DIR or die; 43 closedir DIR; 44 45 foreach my $name (@names) { 46 next if $name eq "." || $name eq ".."; 47 unless (isStatic("$path/$name")) { 48 return 0; 49 } 50 } 51 return 1; 52 } 53 54 return 0; 55} 56 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). 61sub cleanup { 62 if ($File::Find::name eq "/etc/nixos") { 63 $File::Find::prune = 1; 64 return; 65 } 66 if (-l $_) { 67 my $target = readlink $_; 68 if (substr($target, 0, length $static) eq $static) { 69 my $x = "/etc/static/" . substr($File::Find::name, length "/etc/"); 70 unless (-l $x) { 71 print STDERR "removing obsolete symlink ‘$File::Find::name’...\n"; 72 unlink "$_"; 73 } 74 } 75 } 76} 77 78find(\&cleanup, "/etc"); 79 80 81# Use /etc/.clean to keep track of copied files. 82my @oldCopied = read_file("/etc/.clean", chomp => 1, err_mode => 'quiet'); 83open CLEAN, ">>/etc/.clean"; 84 85 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. 89my %created; 90my @copied; 91 92sub link { 93 my $fn = substr $File::Find::name, length($etc) + 1 or next; 94 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'}) { 97 return; 98 } 99 100 my $target = "/etc/$fn"; 101 File::Path::make_path(dirname $target); 102 $created{$fn} = 1; 103 104 # Rename doesn't work if target is directory. 105 if (-l $_ && -d $target) { 106 if (isStatic $target) { 107 rmtree $target or warn; 108 } else { 109 warn "$target directory contains user files. Symlinking may fail."; 110 } 111 } 112 113 if (-e "$_.mode") { 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"; 117 } else { 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"; 128 } 129 } 130 push @copied, $fn; 131 print CLEAN "$fn\n"; 132 } elsif (-l "$_") { 133 atomicSymlink "$static/$fn", $target or warn "could not create symlink $target"; 134 } 135} 136 137find(\&link, $etc); 138 139 140# Delete files that were copied in a previous version but not in the 141# current. 142foreach my $fn (@oldCopied) { 143 if (!defined $created{$fn}) { 144 $fn = "/etc/$fn"; 145 print STDERR "removing obsolete file ‘$fn’...\n"; 146 unlink "$fn"; 147 } 148} 149 150 151# Rewrite /etc/.clean. 152close CLEAN; 153write_file("/etc/.clean", map { "$_\n" } sort @copied); 154 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. 158open TAG, ">>/etc/NIXOS"; 159close TAG;