at 16.09-beta 3.8 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 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# Returns 1 if the argument points to the files in /etc/static. That 26# means either argument is a symlink to a file in /etc/static or a 27# directory with all children being static. 28sub isStatic { 29 my $path = shift; 30 31 if (-l $path) { 32 my $target = readlink $path; 33 return substr($target, 0, length "/etc/static/") eq "/etc/static/"; 34 } 35 36 if (-d $path) { 37 opendir DIR, "$path" or return 0; 38 my @names = readdir DIR or die; 39 closedir DIR; 40 41 foreach my $name (@names) { 42 next if $name eq "." || $name eq ".."; 43 unless (isStatic("$path/$name")) { 44 return 0; 45 } 46 } 47 return 1; 48 } 49 50 return 0; 51} 52 53# Remove dangling symlinks that point to /etc/static. These are 54# configuration files that existed in a previous configuration but not 55# in the current one. For efficiency, don't look under /etc/nixos 56# (where all the NixOS sources live). 57sub cleanup { 58 if ($File::Find::name eq "/etc/nixos") { 59 $File::Find::prune = 1; 60 return; 61 } 62 if (-l $_) { 63 my $target = readlink $_; 64 if (substr($target, 0, length $static) eq $static) { 65 my $x = "/etc/static/" . substr($File::Find::name, length "/etc/"); 66 unless (-l $x) { 67 print STDERR "removing obsolete symlink ‘$File::Find::name’...\n"; 68 unlink "$_"; 69 } 70 } 71 } 72} 73 74find(\&cleanup, "/etc"); 75 76 77# Use /etc/.clean to keep track of copied files. 78my @oldCopied = read_file("/etc/.clean", chomp => 1, err_mode => 'quiet'); 79open CLEAN, ">>/etc/.clean"; 80 81 82# For every file in the etc tree, create a corresponding symlink in 83# /etc to /etc/static. The indirection through /etc/static is to make 84# switching to a new configuration somewhat more atomic. 85my %created; 86my @copied; 87 88sub link { 89 my $fn = substr $File::Find::name, length($etc) + 1 or next; 90 my $target = "/etc/$fn"; 91 File::Path::make_path(dirname $target); 92 $created{$fn} = 1; 93 94 # Rename doesn't work if target is directory. 95 if (-l $_ && -d $target) { 96 if (isStatic $target) { 97 rmtree $target or warn; 98 } else { 99 warn "$target directory contains user files. Symlinking may fail."; 100 } 101 } 102 103 if (-e "$_.mode") { 104 my $mode = read_file("$_.mode"); chomp $mode; 105 if ($mode eq "direct-symlink") { 106 atomicSymlink readlink("$static/$fn"), $target or warn; 107 } else { 108 my $uid = read_file("$_.uid"); chomp $uid; 109 my $gid = read_file("$_.gid"); chomp $gid; 110 copy "$static/$fn", "$target.tmp" or warn; 111 chown int($uid), int($gid), "$target.tmp" or warn; 112 chmod oct($mode), "$target.tmp" or warn; 113 rename "$target.tmp", $target or warn; 114 } 115 push @copied, $fn; 116 print CLEAN "$fn\n"; 117 } elsif (-l "$_") { 118 atomicSymlink "$static/$fn", $target or warn; 119 } 120} 121 122find(\&link, $etc); 123 124 125# Delete files that were copied in a previous version but not in the 126# current. 127foreach my $fn (@oldCopied) { 128 if (!defined $created{$fn}) { 129 $fn = "/etc/$fn"; 130 print STDERR "removing obsolete file ‘$fn’...\n"; 131 unlink "$fn"; 132 } 133} 134 135 136# Rewrite /etc/.clean. 137close CLEAN; 138write_file("/etc/.clean", map { "$_\n" } @copied);