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 rename $tmp, $target or return 0; 17 return 1; 18} 19 20 21# Atomically update /etc/static to point at the etc files of the 22# current configuration. 23atomicSymlink $etc, $static or die; 24 25 26# Remove dangling symlinks that point to /etc/static. These are 27# configuration files that existed in a previous configuration but not 28# in the current one. For efficiency, don't look under /etc/nixos 29# (where all the NixOS sources live). 30sub cleanup { 31 if ($File::Find::name eq "/etc/nixos") { 32 $File::Find::prune = 1; 33 return; 34 } 35 if (-l $_) { 36 my $target = readlink $_; 37 if (substr($target, 0, length $static) eq $static) { 38 my $x = "/etc/static/" . substr($File::Find::name, length "/etc/"); 39 unless (-l $x) { 40 print STDERR "removing obsolete symlink ‘$File::Find::name’...\n"; 41 unlink "$_"; 42 } 43 } 44 } 45} 46 47find(\&cleanup, "/etc"); 48 49 50# Use /etc/.clean to keep track of copied files. 51my @oldCopied = read_file("/etc/.clean", chomp => 1, err_mode => 'quiet'); 52open CLEAN, ">>/etc/.clean"; 53 54 55# For every file in the etc tree, create a corresponding symlink in 56# /etc to /etc/static. The indirection through /etc/static is to make 57# switching to a new configuration somewhat more atomic. 58my %created; 59my @copied; 60 61sub link { 62 my $fn = substr $File::Find::name, length($etc) + 1 or next; 63 my $target = "/etc/$fn"; 64 File::Path::make_path(dirname $target); 65 $created{$fn} = 1; 66 if (-e "$_.mode") { 67 my $mode = read_file("$_.mode"); chomp $mode; 68 if ($mode eq "direct-symlink") { 69 atomicSymlink readlink("$static/$fn"), $target or warn; 70 } else { 71 my $uid = read_file("$_.uid"); chomp $uid; 72 my $gid = read_file("$_.gid"); chomp $gid; 73 copy "$static/$fn", "$target.tmp" or warn; 74 chown int($uid), int($gid), "$target.tmp" or warn; 75 chmod oct($mode), "$target.tmp" or warn; 76 rename "$target.tmp", $target or warn; 77 } 78 push @copied, $fn; 79 print CLEAN "$fn\n"; 80 } elsif (-l "$_") { 81 atomicSymlink "$static/$fn", $target or warn; 82 } 83} 84 85find(\&link, $etc); 86 87 88# Delete files that were copied in a previous version but not in the 89# current. 90foreach my $fn (@oldCopied) { 91 if (!defined $created{$fn}) { 92 $fn = "/etc/$fn"; 93 print STDERR "removing obsolete file ‘$fn’...\n"; 94 unlink "$fn"; 95 } 96} 97 98 99# Rewrite /etc/.clean. 100close CLEAN; 101write_file("/etc/.clean", map { "$_\n" } @copied);