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);