line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# This file causes a list of directories to be removed or moved off |
4
|
|
|
|
|
|
|
# the users home directory into a given other directory. Usually this |
5
|
|
|
|
|
|
|
# is used to relief NFS home directories of the burden of caches and |
6
|
|
|
|
|
|
|
# other performance needing directories. |
7
|
|
|
|
|
|
|
# |
8
|
|
|
|
|
|
|
# Copyright (C) 2010-2015 by Axel Beckert <beckert@phys.ethz.ch>, |
9
|
|
|
|
|
|
|
# Department of Physics, ETH Zurich. |
10
|
|
|
|
|
|
|
# |
11
|
|
|
|
|
|
|
# This program is free software: you can redistribute it and/or modify |
12
|
|
|
|
|
|
|
# it under the terms of the GNU General Public License as published by |
13
|
|
|
|
|
|
|
# the Free Software Foundation, either version 2 of the License, or |
14
|
|
|
|
|
|
|
# (at your option) any later version. |
15
|
|
|
|
|
|
|
# |
16
|
|
|
|
|
|
|
# This program is distributed in the hope that it will be useful, but |
17
|
|
|
|
|
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of |
18
|
|
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
19
|
|
|
|
|
|
|
# General Public License for more details. |
20
|
|
|
|
|
|
|
# |
21
|
|
|
|
|
|
|
# You should have received a copy of the GNU General Public License |
22
|
|
|
|
|
|
|
# along with this program. If not, see http://www.gnu.org/licenses/. |
23
|
|
|
|
|
|
|
# |
24
|
|
|
|
|
|
|
|
25
|
88
|
|
|
88
|
|
59358
|
use strict; |
|
88
|
|
|
|
|
83
|
|
|
88
|
|
|
|
|
1974
|
|
26
|
88
|
|
|
88
|
|
209
|
use warnings; |
|
88
|
|
|
|
|
54
|
|
|
88
|
|
|
|
|
1241
|
|
27
|
88
|
|
|
88
|
|
1050
|
use 5.010; |
|
88
|
|
|
|
|
133
|
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# Globally define version |
30
|
88
|
|
|
|
|
4092139
|
our $VERSION = '0.4~dev'; |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# Load Modules |
33
|
88
|
|
|
88
|
|
11736
|
use Config::File; |
|
88
|
|
|
|
|
366480
|
|
|
88
|
|
|
|
|
2436
|
|
34
|
88
|
|
|
88
|
|
123
|
use Getopt::Std; $Getopt::Std::STANDARD_HELP_VERSION = 1; |
|
88
|
|
|
|
|
77343
|
|
|
88
|
|
|
|
|
2071
|
|
|
88
|
|
|
|
|
3671
|
|
35
|
88
|
|
|
88
|
|
265
|
use File::Path qw(mkpath rmtree); |
|
88
|
|
|
|
|
346
|
|
|
88
|
|
|
|
|
3090
|
|
36
|
88
|
|
|
88
|
|
266
|
use File::Basename; |
|
88
|
|
|
|
|
47
|
|
|
88
|
|
|
|
|
3626
|
|
37
|
88
|
|
|
88
|
|
11156
|
use File::BaseDir qw(config_home); |
|
88
|
|
|
|
|
53620
|
|
|
88
|
|
|
|
|
3423
|
|
38
|
88
|
|
|
88
|
|
16996
|
use File::Slurp; |
|
88
|
|
|
|
|
556613
|
|
|
88
|
|
|
|
|
3807
|
|
39
|
88
|
|
|
88
|
|
13548
|
use File::Touch; |
|
88
|
|
|
|
|
461152
|
|
|
88
|
|
|
|
|
2926
|
|
40
|
88
|
|
|
88
|
|
21494
|
use File::Rsync; |
|
88
|
|
|
|
|
1083516
|
|
|
88
|
|
|
|
|
1745
|
|
41
|
88
|
|
|
88
|
|
15881
|
use File::Which; |
|
88
|
|
|
|
|
41680
|
|
|
88
|
|
|
|
|
2876
|
|
42
|
88
|
|
|
88
|
|
320
|
use IO::Handle; |
|
88
|
|
|
|
|
95
|
|
|
88
|
|
|
|
|
1528
|
|
43
|
88
|
|
|
88
|
|
11730
|
use String::Expand; |
|
88
|
|
|
|
|
34855
|
|
|
88
|
|
|
|
|
2716
|
|
44
|
88
|
|
|
88
|
|
288
|
use Data::Dumper; |
|
88
|
|
|
|
|
66
|
|
|
88
|
|
|
|
|
251314
|
|
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# Determine default value for target directory |
47
|
88
|
|
|
|
|
103
|
my $default_target = '/tmp'; |
48
|
88
|
100
|
|
|
|
1202
|
if (defined($ENV{TMPDIR})) { # defined() doesn't autovivicate |
49
|
44
|
|
|
|
|
57
|
$default_target = $ENV{TMPDIR}; |
50
|
|
|
|
|
|
|
} |
51
|
88
|
50
|
|
|
|
1774
|
if (-r '/proc/mounts') { |
52
|
88
|
|
|
|
|
80
|
my $runtime_dir = '/run/user'; |
53
|
88
|
100
|
|
|
|
162
|
if (defined($ENV{XDG_RUNTIME_DIR})) { # defined() doesn't autovivicate |
54
|
44
|
|
|
|
|
51
|
$runtime_dir = $ENV{XDG_RUNTIME_DIR}; |
55
|
|
|
|
|
|
|
} |
56
|
88
|
|
|
|
|
211
|
$runtime_dir .= "/$<"; # typically something like /run/user/1000 |
57
|
|
|
|
|
|
|
|
58
|
88
|
|
|
|
|
184
|
my @mounts = read_file('/proc/mounts'); |
59
|
88
|
|
|
|
|
15330
|
foreach my $mount (@mounts) { |
60
|
2816
|
|
|
|
|
4054
|
my @mount = split(/\s+/, $mount); |
61
|
2816
|
100
|
|
|
|
3376
|
if ($mount[1] eq $runtime_dir) { |
62
|
44
|
|
|
|
|
38
|
$default_target = $runtime_dir; |
63
|
44
|
|
|
|
|
116
|
last; |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
} |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# Configuration variables to be used in configuration files |
69
|
88
|
|
|
|
|
193
|
my $CONFIG = { |
70
|
|
|
|
|
|
|
TARGETDIR => $default_target, |
71
|
|
|
|
|
|
|
FILELAYOUT => '.unburden-%u/%s', |
72
|
|
|
|
|
|
|
}; |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
# Just show what would be done |
75
|
88
|
|
|
|
|
78
|
my $DRYRUN = undef; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# Undo feature |
78
|
88
|
|
|
|
|
65
|
my $REVERT = 0; |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# Defaul base name |
81
|
88
|
|
|
|
|
67
|
my $BASENAME = 'unburden-home-dir'; |
82
|
88
|
|
|
|
|
67
|
my $LISTSUFFIX = 'list'; |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# Declare and initialise some variables |
85
|
88
|
|
|
|
|
104
|
my %OPTIONS = (); |
86
|
88
|
|
|
|
|
69
|
my $FILTER = undef; |
87
|
88
|
|
|
|
|
23330
|
my $UID = getpwuid($<); |
88
|
88
|
|
|
|
|
121
|
my $USE_LSOF = 1; |
89
|
88
|
|
|
|
|
71
|
my $LSOF_CMD = undef; |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
# Some messages for Getopt::Std |
92
|
|
|
|
|
|
|
sub VERSION_MESSAGE { |
93
|
6
|
|
|
6
|
|
127
|
my ($fh, $getoptpkg, $getoptversion, $cmdlineargs) = @_; |
94
|
|
|
|
|
|
|
|
95
|
6
|
|
|
|
|
50
|
say $fh "Unburden Home Directory $VERSION\n"; |
96
|
|
|
|
|
|
|
|
97
|
6
|
|
|
|
|
11
|
return; |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub HELP_MESSAGE { |
101
|
4
|
|
|
4
|
|
14
|
my ($fh, $getoptpkg, $getoptversion, $cmdlineargs) = @_; |
102
|
|
|
|
|
|
|
|
103
|
4
|
|
|
|
|
14
|
say $fh "Usage: $0 [ -F | -n | -u | -b basename | (-c|-C) conffile | -f filter | (-l|-L) listfile ] |
104
|
|
|
|
|
|
|
$0 ( -h | --help | --version ) |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
Options with parameters: |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
-b use the given string as basename instead of \"$BASENAME\". |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
-c read an additional configuration file |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
-C read only the given configuration file |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
-f just unburden those directory matched by the given filter (a perl |
115
|
|
|
|
|
|
|
regular expression) -- it matches the already unburdened |
116
|
|
|
|
|
|
|
directories if used together with -u. |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
-l read an additional list file |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
-L read only the given list file |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
Options without parameters: |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
-F Do not check if to-be-(re)moved files and directories are still |
125
|
|
|
|
|
|
|
in use (aka *F*orce (re)moving). |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
-n dry run (show what would be done) |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
-u undo (reverse the functionality and put stuff back into the home |
130
|
|
|
|
|
|
|
directory) |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
-h, --help show this help |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
--version show the program's version |
135
|
|
|
|
|
|
|
"; |
136
|
|
|
|
|
|
|
|
137
|
4
|
|
|
|
|
4
|
return; |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
# Parse command line options |
141
|
88
|
|
|
|
|
268
|
getopts('hnuf:Fb:c:C:l:L:', \%OPTIONS); |
142
|
|
|
|
|
|
|
|
143
|
84
|
|
|
|
|
4408
|
foreach my $key (keys %OPTIONS) { |
144
|
350
|
100
|
|
|
|
637
|
if ($key eq 'h') { |
|
|
100
|
|
|
|
|
|
145
|
2
|
|
|
|
|
13
|
my $fh = IO::Handle->new_from_fd(fileno(STDOUT),'w'); |
146
|
2
|
|
|
|
|
92
|
VERSION_MESSAGE($fh); |
147
|
2
|
|
|
|
|
3
|
HELP_MESSAGE($fh); |
148
|
2
|
|
|
|
|
76
|
exit 0; |
149
|
|
|
|
|
|
|
} |
150
|
4
|
|
|
|
|
10
|
elsif ($key eq 'b') { $BASENAME = $OPTIONS{b}; } |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# By default check for a system wide and a user configuration and list file |
154
|
82
|
|
|
|
|
352
|
my @CONFFILES = ("/etc/$BASENAME", |
155
|
|
|
|
|
|
|
"$ENV{HOME}/.$BASENAME", |
156
|
|
|
|
|
|
|
config_home($BASENAME).'/config'); |
157
|
82
|
|
|
|
|
1983
|
my @LISTFILES = ("/etc/$BASENAME.$LISTSUFFIX", |
158
|
|
|
|
|
|
|
"$ENV{HOME}/.$BASENAME.$LISTSUFFIX", |
159
|
|
|
|
|
|
|
config_home($BASENAME)."/$LISTSUFFIX"); |
160
|
|
|
|
|
|
|
|
161
|
82
|
|
|
|
|
703
|
foreach my $key (keys %OPTIONS) { |
162
|
343
|
100
|
|
|
|
641
|
if ($key eq 'C') { @CONFFILES = ($OPTIONS{C}); } |
|
77
|
100
|
|
|
|
108
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
163
|
76
|
|
|
|
|
83
|
elsif ($key eq 'c') { push(@CONFFILES, $OPTIONS{c}); } |
164
|
77
|
|
|
|
|
107
|
elsif ($key eq 'L') { @LISTFILES = ($OPTIONS{L}); } |
165
|
77
|
|
|
|
|
87
|
elsif ($key eq 'l') { push(@LISTFILES, $OPTIONS{l}); } |
166
|
12
|
|
|
|
|
8
|
elsif ($key eq 'n') { $DRYRUN = 1; } |
167
|
8
|
|
|
|
|
17
|
elsif ($key eq 'u') { $REVERT = 1; } |
168
|
6
|
|
|
|
|
2
|
elsif ($key eq 'F') { $USE_LSOF = 0; } |
169
|
|
|
|
|
|
|
elsif ($key eq 'f') { |
170
|
6
|
|
|
|
|
5
|
eval { $FILTER = qr/$OPTIONS{f}/; }; |
|
6
|
|
|
|
|
67
|
|
171
|
6
|
100
|
|
|
|
10
|
if ($@) { |
172
|
2
|
|
|
|
|
3
|
report_serious_problem("parameter to -f", $OPTIONS{f}); |
173
|
2
|
|
|
|
|
37
|
exit 2; |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# Check for configuration files and read them |
179
|
80
|
|
|
|
|
98
|
foreach my $configfile (@CONFFILES) { |
180
|
117
|
100
|
|
|
|
590
|
if ( -e $configfile ) { |
181
|
|
|
|
|
|
|
# Workaround RT#98542 in Config::File 1.50 and earlier |
182
|
109
|
|
|
|
|
242
|
my $cf = Config::File::read_config_file($configfile); |
183
|
109
|
100
|
|
|
|
21270
|
if (defined($cf)) { |
184
|
80
|
|
|
|
|
427
|
$CONFIG = { %$CONFIG, %$cf }; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# Fix some values |
190
|
80
|
|
|
|
|
140
|
$UID =~ s/\s+//gs; |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# Expand environment variables |
193
|
80
|
|
|
|
|
217
|
expand_strings($CONFIG, \%ENV); |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# Remove quotes and line-feeds from values |
196
|
80
|
|
|
|
|
4198
|
foreach my $key (keys %$CONFIG) { |
197
|
160
|
|
|
|
|
138
|
chomp($CONFIG->{$key}); |
198
|
160
|
|
|
|
|
214
|
$CONFIG->{$key} =~ s/^([\'\"])(.*)\1$/$2/; |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# Set proper umask when creating files or directories. Save current |
202
|
|
|
|
|
|
|
# umask before. |
203
|
80
|
|
|
|
|
224
|
my $OLDUMASK = umask(); |
204
|
80
|
|
|
|
|
75
|
umask(077); |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# Initialize rsync object |
207
|
|
|
|
|
|
|
my $rsync = File::Rsync->new( |
208
|
|
|
|
|
|
|
archive => 1, |
209
|
|
|
|
|
|
|
verbose => 1, |
210
|
|
|
|
|
|
|
outfun => sub { |
211
|
134
|
|
|
134
|
|
970510
|
my $output = shift; |
212
|
134
|
|
|
|
|
139
|
chomp($output); |
213
|
134
|
100
|
|
|
|
617
|
say $output unless $output =~ m(^sent |^total size|^\s*$); |
214
|
|
|
|
|
|
|
}, |
215
|
|
|
|
|
|
|
errfun => sub { |
216
|
|
|
|
|
|
|
# uncoverable subroutine |
217
|
0
|
|
|
0
|
|
0
|
chomp; # uncoverable statement |
218
|
0
|
|
|
|
|
0
|
warn "$_[0]\n"; # uncoverable statement |
219
|
|
|
|
|
|
|
}, |
220
|
80
|
|
|
|
|
673
|
); |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
# Check for lsof in search path |
223
|
80
|
|
|
|
|
18056
|
my $which_lsof = which('lsof'); |
224
|
|
|
|
|
|
|
# Extra check for crappy distributions which place lsof outside a |
225
|
|
|
|
|
|
|
# user's $PATH. Fixes GH#8. |
226
|
80
|
50
|
66
|
|
|
5497
|
if (!$which_lsof and -x '/usr/sbin/lsof') { |
227
|
0
|
|
|
|
|
0
|
$which_lsof = '/usr/sbin/lsof'; |
228
|
|
|
|
|
|
|
} |
229
|
80
|
100
|
|
|
|
150
|
if (!$which_lsof) { |
230
|
2
|
|
|
|
|
50
|
warn "WARNING: lsof not found, not checking for files in use.\n"; |
231
|
2
|
|
|
|
|
3
|
$USE_LSOF = 0; |
232
|
|
|
|
|
|
|
} else { |
233
|
78
|
|
|
|
|
89
|
$LSOF_CMD = $which_lsof; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
# Standard Error reporting function; Warning |
237
|
|
|
|
|
|
|
sub report_problem { |
238
|
8
|
|
|
8
|
|
172
|
warn "WARNING: Can't handle $_[0]: $_[1]"; |
239
|
8
|
|
|
|
|
15
|
return; |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# Standard Error reporting function; Error |
243
|
|
|
|
|
|
|
sub report_serious_problem { |
244
|
12
|
|
|
12
|
|
269
|
warn "ERROR: Can't handle $_[0]: $_[1]"; |
245
|
12
|
|
|
|
|
15
|
return; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
# Actually move a directory or file |
249
|
|
|
|
|
|
|
sub move { |
250
|
34
|
|
|
34
|
|
44
|
my ($from, $to) = @_; |
251
|
34
|
|
|
|
|
138
|
say "Moving $from -> $to"; |
252
|
34
|
100
|
|
|
|
68
|
unless ($DRYRUN) { |
253
|
30
|
100
|
|
|
|
80
|
if (-d $from) { |
254
|
22
|
|
|
|
|
42
|
$from .= '/'; |
255
|
22
|
|
|
|
|
24
|
$to .= '/'; |
256
|
|
|
|
|
|
|
|
257
|
22
|
|
|
|
|
179
|
my $rc = $rsync->exec( |
258
|
|
|
|
|
|
|
src => $from, |
259
|
|
|
|
|
|
|
dst => $to, |
260
|
|
|
|
|
|
|
); |
261
|
22
|
|
|
|
|
5018
|
rmtree($from); |
262
|
|
|
|
|
|
|
} else { |
263
|
8
|
|
|
|
|
11840
|
my $rc = system(qw(mv -v), $from, $to); |
264
|
8
|
|
|
|
|
140
|
return !($? >> 8); |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
} |
267
|
26
|
|
|
|
|
125
|
return 1; |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
# Create a symlink. Create its parent directories if they don't yet |
271
|
|
|
|
|
|
|
# exist. |
272
|
|
|
|
|
|
|
sub create_symlink_and_parents { |
273
|
12
|
|
|
12
|
|
11
|
my ($old, $new) = @_; |
274
|
12
|
|
|
|
|
12
|
create_parent_directories($new); |
275
|
12
|
|
|
|
|
23
|
say "Symlinking $new -> $old"; |
276
|
12
|
100
|
|
|
|
14
|
unless ($DRYRUN) { |
277
|
|
|
|
|
|
|
# uncoverable branch true |
278
|
8
|
50
|
|
|
|
90
|
symlink($old, $new) |
279
|
|
|
|
|
|
|
or die "Couldn't symlink $new -> $old: $!"; |
280
|
|
|
|
|
|
|
} |
281
|
12
|
|
|
|
|
16
|
return; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
# Create those parent directories for a given file or directory name |
285
|
|
|
|
|
|
|
# which don't yet exist. |
286
|
|
|
|
|
|
|
sub create_parent_directories { |
287
|
50
|
|
|
50
|
|
41
|
my $file = shift; |
288
|
50
|
|
|
|
|
1910
|
my $parent_dir = dirname($file); |
289
|
50
|
100
|
|
|
|
194
|
unless (-d $parent_dir) { |
290
|
16
|
|
|
|
|
52
|
say "Create parent directories for $file"; |
291
|
16
|
100
|
|
|
|
598
|
mkpath($parent_dir, { verbose => 1 }) unless $DRYRUN; |
292
|
|
|
|
|
|
|
} |
293
|
50
|
|
|
|
|
52
|
return; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# In case of uppercase type letters, create symlinks as replacement |
297
|
|
|
|
|
|
|
# for directories files which may not even exist yet. Common cases are |
298
|
|
|
|
|
|
|
# trash directories which are created when something gets put into the |
299
|
|
|
|
|
|
|
# trashcan, etc. |
300
|
|
|
|
|
|
|
sub possibly_create_non_existing_stuff { |
301
|
12
|
|
|
12
|
|
12
|
my ($type, $item, $target) = @_; |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
# Shall we create not yet existing directories or files as symlink? |
304
|
|
|
|
|
|
|
# Case 1: directory |
305
|
12
|
100
|
|
|
|
27
|
if ( $type eq 'D' ) { |
|
|
50
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# TODO: Refactor create_symlink_and_parents so that its |
307
|
|
|
|
|
|
|
# create_parent_directories call isn't redundant in this case. |
308
|
4
|
|
|
|
|
17
|
say "Create directory $target and parents"; |
309
|
4
|
100
|
|
|
|
225
|
mkpath($target, { verbose => 1 }) unless $DRYRUN; |
310
|
4
|
|
|
|
|
9
|
create_symlink_and_parents($target, $item); |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# Case 2: file |
314
|
|
|
|
|
|
|
elsif ( $type eq 'F' ) { |
315
|
8
|
|
|
|
|
14
|
create_parent_directories($target); |
316
|
8
|
|
|
|
|
33
|
say "Touching $target"; |
317
|
8
|
100
|
|
|
|
22
|
touch($target) unless $DRYRUN; |
318
|
8
|
|
|
|
|
359
|
create_symlink_and_parents($target, $item) |
319
|
|
|
|
|
|
|
} |
320
|
12
|
|
|
|
|
13
|
return 0; |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
# Dangling links may happen if the destination directory has been |
324
|
|
|
|
|
|
|
# weeped, e.g. due to being on an tmpfs mount or by tmpreaper, etc. |
325
|
|
|
|
|
|
|
sub fix_dangling_links { |
326
|
10
|
|
|
10
|
|
11
|
my ($type, $itemexpanded, $target) = @_; |
327
|
10
|
|
|
|
|
20
|
my $link = readlink($itemexpanded); |
328
|
10
|
|
|
|
|
12
|
my $is_dir = type_is_directory($type); |
329
|
10
|
|
|
|
|
11
|
my $is_file = type_is_file($type); |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
# Accept existing symlinks or unburden-home-dir.list entries for |
332
|
|
|
|
|
|
|
# directories with or without trailing slash |
333
|
10
|
100
|
|
|
|
14
|
if ($is_dir) { |
334
|
6
|
|
|
|
|
6
|
$link =~ s{/$}{}; |
335
|
6
|
|
|
|
|
7
|
$itemexpanded =~ s{/$}{}; |
336
|
6
|
|
|
|
|
6
|
$target =~ s{/$}{}; |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
# Check if link target is wanted target |
340
|
10
|
100
|
|
|
|
28
|
if ( $link ne $target ) { |
341
|
2
|
|
|
|
|
4
|
report_problem($itemexpanded, "$link not equal $target"); |
342
|
2
|
|
|
|
|
2
|
return 1; |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
# Check if target exists and is same type |
346
|
8
|
100
|
|
|
|
21
|
if ( -e $target ) { |
347
|
6
|
|
|
|
|
9
|
my $unexpected_type = check_for_unexpected_type($type, $target); |
348
|
6
|
100
|
|
|
|
13
|
return $unexpected_type if $unexpected_type; |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
# Symlink is there, but file or directory not |
351
|
|
|
|
|
|
|
else { |
352
|
2
|
|
|
|
|
2
|
create_object_of_type($type, $target); |
353
|
|
|
|
|
|
|
} |
354
|
4
|
|
|
|
|
7
|
return 0; |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
# Find pid and command in lsof output |
358
|
|
|
|
|
|
|
sub parse_lsof_output { |
359
|
34
|
|
|
34
|
|
68
|
my ($output) = @_; |
360
|
34
|
|
|
|
|
72
|
chomp($output); |
361
|
34
|
|
|
|
|
144
|
my @lines = split(/\n/, $output); |
362
|
|
|
|
|
|
|
|
363
|
34
|
|
|
|
|
82
|
my $result = ''; |
364
|
34
|
|
|
|
|
39
|
my $pid; |
365
|
|
|
|
|
|
|
my $cmd; |
366
|
|
|
|
|
|
|
|
367
|
34
|
|
|
|
|
139
|
foreach my $line (@lines) { |
368
|
6
|
100
|
|
|
|
36
|
if ($line =~ /^p(.*)$/) { |
|
|
100
|
|
|
|
|
|
369
|
2
|
|
|
|
|
10
|
$pid = $1; |
370
|
2
|
|
|
|
|
5
|
$cmd = undef; |
371
|
|
|
|
|
|
|
} elsif ($line =~ /^c(.*)$/) { |
372
|
2
|
|
|
|
|
5
|
$cmd = $1; |
373
|
|
|
|
|
|
|
# uncoverable branch true |
374
|
2
|
50
|
|
|
|
8
|
unless ($pid) { |
375
|
|
|
|
|
|
|
# uncoverable statement |
376
|
0
|
|
|
|
|
0
|
report_problem("lsof output", "No pid before command: $line"); |
377
|
0
|
|
|
|
|
0
|
next; # uncoverable statement |
378
|
|
|
|
|
|
|
} |
379
|
2
|
|
|
|
|
10
|
$result .= sprintf(" %5i (%s)\n", $pid, $cmd); |
380
|
2
|
|
|
|
|
2
|
$pid = undef; |
381
|
|
|
|
|
|
|
} else { |
382
|
|
|
|
|
|
|
# uncoverable statement |
383
|
2
|
|
|
|
|
9
|
report_problem("unexpected line in lsof output", $line); |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
34
|
|
|
|
|
103
|
return $result; |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# Check if files in to be moved directories are currently in use. |
392
|
|
|
|
|
|
|
sub files_in_use { |
393
|
36
|
|
|
36
|
|
31
|
my ($item) = @_; |
394
|
36
|
|
|
|
|
37
|
my $lsof_output = undef; |
395
|
|
|
|
|
|
|
|
396
|
36
|
100
|
|
|
|
80
|
if (-d $item) { |
|
|
100
|
|
|
|
|
|
397
|
26
|
|
|
|
|
1685627
|
$lsof_output = `$LSOF_CMD -F c +D '$item'`; |
398
|
|
|
|
|
|
|
} elsif (-f _) { |
399
|
8
|
|
|
|
|
542194
|
$lsof_output = `$LSOF_CMD -F c '$item'`; |
400
|
|
|
|
|
|
|
} else { |
401
|
2
|
|
|
|
|
5
|
report_problem("checking open files in $item", "neither file nor directory"); |
402
|
2
|
|
|
|
|
8
|
return; |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
|
405
|
34
|
|
|
|
|
382
|
my $lsof_parsed = parse_lsof_output($lsof_output); |
406
|
|
|
|
|
|
|
|
407
|
34
|
100
|
|
|
|
132
|
if ($lsof_parsed) { |
408
|
2
|
|
|
|
|
5
|
report_problem($item, "in use, not (re)moving. Process list:\n$lsof_parsed"); |
409
|
2
|
|
|
|
|
24
|
return 1; |
410
|
|
|
|
|
|
|
} else { |
411
|
32
|
|
|
|
|
434
|
return 0; |
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
# Move a directory or file (higher level function) |
416
|
|
|
|
|
|
|
sub action_move { |
417
|
28
|
|
|
28
|
|
38
|
my ($itemexpanded, $target) = @_; |
418
|
|
|
|
|
|
|
|
419
|
28
|
|
|
|
|
77
|
create_parent_directories($target); |
420
|
|
|
|
|
|
|
# uncoverable branch true |
421
|
28
|
50
|
|
|
|
60
|
move($itemexpanded, $target) |
422
|
|
|
|
|
|
|
or die "Couldn't move $itemexpanded -> $target: $!"; |
423
|
28
|
|
|
|
|
119
|
return; |
424
|
|
|
|
|
|
|
} |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
# Handle directory or file which should be emptied (higher level function) |
427
|
|
|
|
|
|
|
sub action_delete_and_recreate { |
428
|
8
|
|
|
8
|
|
11
|
my ($type, $itemexpanded, $target) = @_; |
429
|
|
|
|
|
|
|
|
430
|
8
|
|
|
|
|
14
|
my $is_file = type_is_file($type); |
431
|
8
|
|
|
|
|
10
|
my $is_dir = type_is_directory($type); |
432
|
|
|
|
|
|
|
|
433
|
8
|
|
|
|
|
52
|
say "Delete $itemexpanded"; |
434
|
8
|
100
|
|
|
|
15
|
unless ($DRYRUN) { |
435
|
4
|
100
|
|
|
|
551
|
$is_dir and rmtree($itemexpanded, { verbose => 1 }) ; |
436
|
|
|
|
|
|
|
# uncoverable condition right |
437
|
4
|
100
|
50
|
|
|
78
|
$is_file and (unlink($itemexpanded) |
438
|
|
|
|
|
|
|
or die "Couldn't delete $itemexpanded: $!"); |
439
|
|
|
|
|
|
|
} |
440
|
8
|
|
|
|
|
19
|
create_object_of_type($type, $target); |
441
|
|
|
|
|
|
|
|
442
|
8
|
|
|
|
|
7
|
return; |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
# Generic create function for both, directories and files |
446
|
|
|
|
|
|
|
sub create_object_of_type { |
447
|
10
|
|
|
10
|
|
13
|
my ($type, $target) = @_; |
448
|
|
|
|
|
|
|
|
449
|
10
|
|
|
|
|
22
|
say "Create $target"; |
450
|
10
|
100
|
|
|
|
17
|
unless ($DRYRUN) { |
451
|
6
|
100
|
|
|
|
9
|
if (type_is_directory($type)) { |
|
|
50
|
|
|
|
|
|
452
|
4
|
|
|
|
|
385
|
mkpath($target, { verbose => 1 }); |
453
|
|
|
|
|
|
|
} |
454
|
|
|
|
|
|
|
elsif (type_is_file($type)) { |
455
|
2
|
|
|
|
|
5
|
create_parent_directories($target); |
456
|
2
|
|
|
|
|
4
|
say "Touching $target"; |
457
|
|
|
|
|
|
|
# uncoverable branch true |
458
|
2
|
50
|
|
|
|
18
|
touch($target) or die "Couldn't touch $target: $!"; |
459
|
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
10
|
|
|
|
|
151
|
return; |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
# Create a symlink |
466
|
|
|
|
|
|
|
sub create_symlink { |
467
|
36
|
|
|
36
|
|
53
|
my ($itemexpanded, $target) = @_; |
468
|
|
|
|
|
|
|
|
469
|
36
|
|
|
|
|
109
|
say "Symlinking $target -> $itemexpanded"; |
470
|
36
|
100
|
|
|
|
70
|
unless ($DRYRUN) { |
471
|
|
|
|
|
|
|
# uncoverable branch true |
472
|
30
|
50
|
|
|
|
412
|
symlink($target, $itemexpanded) |
473
|
|
|
|
|
|
|
or die "Couldn't symlink $target -> $itemexpanded: $!"; |
474
|
|
|
|
|
|
|
} |
475
|
36
|
|
|
|
|
40
|
return; |
476
|
|
|
|
|
|
|
} |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
# Check if the expected type of an object is "directory" |
479
|
|
|
|
|
|
|
sub type_is_directory { |
480
|
174
|
|
|
174
|
|
679
|
return (lc(shift) eq 'd'); |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
# Check if the expected type of an object is "file" |
484
|
|
|
|
|
|
|
sub type_is_file { |
485
|
104
|
|
|
104
|
|
414
|
return (lc(shift) eq 'f'); |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
# Check if an object has an unexpected type (higher level function) |
489
|
|
|
|
|
|
|
sub check_for_unexpected_type { |
490
|
48
|
|
|
48
|
|
58
|
my ($type, $itemexpanded) = @_; |
491
|
|
|
|
|
|
|
|
492
|
48
|
|
|
|
|
76
|
my $is_file = type_is_file($type); |
493
|
48
|
|
|
|
|
98
|
my $is_dir = type_is_directory($type); |
494
|
|
|
|
|
|
|
|
495
|
48
|
100
|
100
|
|
|
275
|
if ($is_file and !-f $itemexpanded) { |
496
|
6
|
|
|
|
|
15
|
report_serious_problem($itemexpanded, |
497
|
|
|
|
|
|
|
'Unexpected type (not a file)'); |
498
|
6
|
|
|
|
|
7
|
return 1; |
499
|
|
|
|
|
|
|
} |
500
|
|
|
|
|
|
|
|
501
|
42
|
100
|
100
|
|
|
394
|
if ($is_dir and !-d $itemexpanded) { |
502
|
4
|
|
|
|
|
5
|
report_serious_problem($itemexpanded, |
503
|
|
|
|
|
|
|
'Unexpected type (not a directory)'); |
504
|
4
|
|
|
|
|
4
|
return 1; |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
|
507
|
38
|
|
|
|
|
55
|
return; |
508
|
|
|
|
|
|
|
} |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
# Top-level function run once per to-be-changed-item |
511
|
|
|
|
|
|
|
sub do_it { |
512
|
44
|
|
|
44
|
|
59
|
my ($type, $itemexpanded, $target, $action) = @_; |
513
|
|
|
|
|
|
|
|
514
|
44
|
100
|
100
|
|
|
163
|
if ( $USE_LSOF and files_in_use($itemexpanded) ) { |
515
|
2
|
|
|
|
|
7
|
return 0; |
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
|
518
|
42
|
|
|
|
|
133
|
my $unexpected_type = check_for_unexpected_type($type, $itemexpanded); |
519
|
42
|
100
|
|
|
|
66
|
return $unexpected_type if $unexpected_type; |
520
|
|
|
|
|
|
|
|
521
|
36
|
100
|
100
|
|
|
233
|
if ( $action eq 'r' or $action eq 'd' ) { |
|
|
50
|
|
|
|
|
|
522
|
8
|
|
|
|
|
18
|
action_delete_and_recreate($type, $itemexpanded, $target); |
523
|
|
|
|
|
|
|
} |
524
|
|
|
|
|
|
|
elsif ( $action eq 'm' ) { |
525
|
28
|
|
|
|
|
50
|
action_move($itemexpanded, $target); |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
|
528
|
36
|
|
|
|
|
133
|
create_symlink($itemexpanded, $target); |
529
|
|
|
|
|
|
|
|
530
|
36
|
|
|
|
|
74
|
return 0; |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
# Parse and fill placeholders in target definition |
534
|
|
|
|
|
|
|
sub calculate_target { |
535
|
76
|
|
|
76
|
|
67
|
my $replacement = shift; |
536
|
76
|
|
|
|
|
105
|
my $target = $CONFIG->{FILELAYOUT}; |
537
|
|
|
|
|
|
|
|
538
|
76
|
|
|
|
|
90
|
$target =~ s|%u|$UID|g; |
539
|
76
|
|
|
|
|
154
|
$target =~ s|%s|$replacement|g; |
540
|
|
|
|
|
|
|
|
541
|
76
|
|
|
|
|
239
|
return $CONFIG->{TARGETDIR}."/$target"; |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
# Parse and fill wildcards |
545
|
|
|
|
|
|
|
sub fill_in_wildcard_matches { |
546
|
74
|
|
|
74
|
|
73
|
my ($itemglob, $itemexpanded, $target) = @_; |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
# Replace %<n> (e.g. %1) with the n-th wildcard match. Uses perl |
549
|
|
|
|
|
|
|
# here as it would be too complicated and way less readable if |
550
|
|
|
|
|
|
|
# written as (bourne) shell script. |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
# Change from globbing to regexp |
553
|
74
|
|
|
|
|
74
|
$itemglob =~ s/\?/(.)/g; |
554
|
74
|
|
|
|
|
71
|
$itemglob =~ s/\*/(.*)/g; |
555
|
|
|
|
|
|
|
|
556
|
74
|
|
|
|
|
536
|
my @result = $itemexpanded =~ m($itemglob)g; |
557
|
|
|
|
|
|
|
|
558
|
74
|
|
|
|
|
89
|
$target =~ s/\%(\d+)/$result[$1-1]/eg; |
|
24
|
|
|
|
|
48
|
|
559
|
|
|
|
|
|
|
|
560
|
74
|
|
|
|
|
100
|
return $target; |
561
|
|
|
|
|
|
|
} |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
# Check if the path to something to unburden already contains a symlink |
564
|
|
|
|
|
|
|
sub symlink_in_path { |
565
|
82
|
|
|
82
|
|
75
|
my $path = shift; |
566
|
|
|
|
|
|
|
# Remove home directory, i.e. check just from below the home directory |
567
|
|
|
|
|
|
|
# uncoverable branch false |
568
|
82
|
50
|
|
|
|
751
|
if ($path =~ s($ENV{HOME}/?)()) { |
569
|
|
|
|
|
|
|
# Split up into components, but remove the last one (which we |
570
|
|
|
|
|
|
|
# are requested to handle, so we shouldn't check that now) |
571
|
82
|
|
|
|
|
181
|
my @path_elements = split(m(/), $path); |
572
|
82
|
|
|
|
|
73
|
pop(@path_elements); |
573
|
|
|
|
|
|
|
|
574
|
82
|
|
|
|
|
170
|
foreach my $i (0..$#path_elements) { |
575
|
94
|
|
|
|
|
194
|
my $path_to_check = $ENV{HOME}.'/'.join('/', @path_elements[0..$i]); |
576
|
|
|
|
|
|
|
#say "Check if $path_to_check is a symlink"; |
577
|
94
|
100
|
|
|
|
333
|
return $path_to_check if -l $path_to_check; |
578
|
|
|
|
|
|
|
} |
579
|
66
|
|
|
|
|
163
|
return 0; |
580
|
|
|
|
|
|
|
} else { |
581
|
|
|
|
|
|
|
# uncoverable statement |
582
|
0
|
|
|
|
|
0
|
report_serious_problem("Can't find home directory ($ENV{HOME}) in $path!"); |
583
|
|
|
|
|
|
|
} |
584
|
|
|
|
|
|
|
} |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
# Handle replacement requests and check if they're sane |
587
|
|
|
|
|
|
|
sub replace { |
588
|
|
|
|
|
|
|
# replace $type $i $item $replacement |
589
|
82
|
|
|
82
|
|
125
|
my ($type, $itemexpanded, $itemglob, $replacement, $action) = @_; |
590
|
|
|
|
|
|
|
|
591
|
82
|
100
|
|
|
|
129
|
if (my $symlink = symlink_in_path($itemexpanded)) { |
592
|
16
|
|
|
|
|
186
|
warn "Skipping '$itemexpanded' due to symlink in path: $symlink\n"; |
593
|
16
|
|
|
|
|
74
|
return 0; |
594
|
|
|
|
|
|
|
} |
595
|
|
|
|
|
|
|
|
596
|
66
|
|
|
|
|
118
|
my $target = fill_in_wildcard_matches($itemglob, $itemexpanded, |
597
|
|
|
|
|
|
|
calculate_target($replacement)); |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
# Check if the source exists |
600
|
66
|
100
|
100
|
|
|
421
|
if ( ! -e $itemexpanded and ! -l $itemexpanded ) { |
|
|
100
|
|
|
|
|
|
601
|
12
|
|
|
|
|
25
|
possibly_create_non_existing_stuff($type, $itemexpanded, $target); |
602
|
|
|
|
|
|
|
} |
603
|
|
|
|
|
|
|
# Check if source is already a symlink |
604
|
|
|
|
|
|
|
elsif ( -l $itemexpanded ) { |
605
|
10
|
|
|
|
|
29
|
fix_dangling_links($type, $itemexpanded, $target); |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
# TODO: Check available disk space |
609
|
|
|
|
|
|
|
# Should use report_serious_problem |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
# No symlink yet, then actually move or remove! |
612
|
|
|
|
|
|
|
else { |
613
|
44
|
|
|
|
|
105
|
do_it($type, $itemexpanded, $target, $action); |
614
|
|
|
|
|
|
|
} |
615
|
|
|
|
|
|
|
|
616
|
66
|
|
|
|
|
664
|
return; |
617
|
|
|
|
|
|
|
} |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
# Core functionality of the undo feature |
620
|
|
|
|
|
|
|
sub revert { |
621
|
8
|
|
|
8
|
|
9
|
my ($itemexpanded, $item_in_home, $target_glob) = @_; |
622
|
|
|
|
|
|
|
|
623
|
8
|
|
|
|
|
19
|
$item_in_home = "$ENV{HOME}/" . |
624
|
|
|
|
|
|
|
fill_in_wildcard_matches($target_glob, $itemexpanded, $item_in_home); |
625
|
8
|
|
|
|
|
53
|
say "Trying to revert $itemexpanded to $item_in_home"; |
626
|
|
|
|
|
|
|
|
627
|
8
|
50
|
|
|
|
40
|
if (-l $item_in_home) { |
628
|
8
|
|
|
|
|
31
|
my $link_target = readlink($item_in_home); |
629
|
8
|
|
|
|
|
8
|
$itemexpanded =~ s{/$}{}; |
630
|
8
|
|
|
|
|
7
|
$link_target =~ s{/$}{}; |
631
|
|
|
|
|
|
|
|
632
|
8
|
100
|
|
|
|
15
|
if ($itemexpanded eq $link_target) { |
633
|
6
|
|
|
|
|
6
|
say "Removing symlink $item_in_home"; |
634
|
6
|
100
|
|
|
|
89
|
unlink($item_in_home) unless $DRYRUN; |
635
|
6
|
|
|
|
|
16
|
move($itemexpanded, $item_in_home); |
636
|
|
|
|
|
|
|
} else { |
637
|
2
|
|
|
|
|
42
|
warn "Ignoring symlink $item_in_home as it points to $link_target ". |
638
|
|
|
|
|
|
|
"and not to $itemexpanded as expected.\n"; |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
|
642
|
8
|
|
|
|
|
89
|
return; |
643
|
|
|
|
|
|
|
} |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
# Parse wildcards backwards |
646
|
|
|
|
|
|
|
sub exchange_wildcards_and_replacements { |
647
|
10
|
|
|
10
|
|
9
|
my ($wildcard, $replacement) = @_; |
648
|
10
|
|
|
|
|
9
|
my $i = 1; |
649
|
10
|
|
|
|
|
28
|
while ($replacement =~ /\%(\d+)/) { |
650
|
12
|
|
|
|
|
9
|
my $number = $1; |
651
|
12
|
|
|
|
|
14
|
my $prev = $number-1; |
652
|
12
|
|
|
|
|
223
|
$wildcard =~ s/^(([^*]*[*?]){$prev}[^*]*)([?*])/"$1\%".$i++/e; |
|
12
|
|
|
|
|
21
|
|
653
|
12
|
|
|
|
|
16
|
my $wildcardtype = $3; |
654
|
12
|
|
|
|
|
32
|
$replacement =~ s/\%(\d+)/$wildcardtype/; |
655
|
|
|
|
|
|
|
} |
656
|
10
|
|
|
|
|
17
|
return ($wildcard, $replacement); |
657
|
|
|
|
|
|
|
} |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
# Main loop over all items in list files |
660
|
80
|
|
|
|
|
107
|
for my $list (@LISTFILES) { |
661
|
118
|
100
|
|
|
|
404
|
next unless -e $list; |
662
|
110
|
100
|
|
|
|
235
|
unless (-r _) { |
663
|
2
|
|
|
|
|
55
|
warn "List file $list isn't readable, skipping"; |
664
|
2
|
|
|
|
|
3
|
next; |
665
|
|
|
|
|
|
|
} |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
# Clean up this and that |
668
|
108
|
|
|
|
|
72
|
my $list_fh; |
669
|
|
|
|
|
|
|
# uncoverable branch true |
670
|
108
|
50
|
|
|
|
1010
|
open($list_fh, '<', $list) or die "Can't open $list: $!"; |
671
|
108
|
|
|
|
|
8503
|
while (<$list_fh>) { |
672
|
116
|
100
|
|
|
|
592
|
next if /^#|^ *$/; |
673
|
|
|
|
|
|
|
|
674
|
110
|
|
|
|
|
117
|
chomp; |
675
|
110
|
|
|
|
|
277
|
my ($action, $type, $item, $replacement) = split; |
676
|
|
|
|
|
|
|
|
677
|
110
|
100
|
|
|
|
229
|
next unless defined $action; |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
# Expand environment variables in item and replacement only |
680
|
108
|
100
|
|
|
|
431
|
$item = expand_string($item, \%ENV) if defined($item); |
681
|
108
|
100
|
|
|
|
2468
|
$replacement = expand_string($replacement, \%ENV) if defined($replacement); |
682
|
|
|
|
|
|
|
|
683
|
108
|
100
|
100
|
|
|
1244
|
if (not (defined($item) and defined($replacement) and |
|
|
|
100
|
|
|
|
|
684
|
|
|
|
|
|
|
# $item can't be '' since $replacement is undef then |
685
|
|
|
|
|
|
|
$replacement ne '')) { |
686
|
6
|
|
|
|
|
139
|
warn "Can't parse '$_', skipping..."; |
687
|
6
|
|
|
|
|
19
|
next; |
688
|
|
|
|
|
|
|
} |
689
|
102
|
100
|
100
|
|
|
205
|
unless ( type_is_directory($type) or type_is_file($type) ) { |
690
|
2
|
|
|
|
|
51
|
warn "Can't parse type '$type', must be 'd', 'D', 'f' or 'F', skipping..."; |
691
|
2
|
|
|
|
|
7
|
next; |
692
|
|
|
|
|
|
|
} |
693
|
100
|
100
|
100
|
|
|
838
|
if ( $action ne 'd' and $action ne 'r' and $action ne 'm' ) { |
|
|
|
100
|
|
|
|
|
694
|
2
|
|
|
|
|
58
|
warn "Can't parse action '$action', must be 'd', 'r' or 'm', skipping..."; |
695
|
2
|
|
|
|
|
7
|
next; |
696
|
|
|
|
|
|
|
} |
697
|
|
|
|
|
|
|
|
698
|
98
|
100
|
|
|
|
211
|
if ( $item =~ m(^(\.\.)?/) ) { |
699
|
4
|
|
|
|
|
106
|
warn "$item would be outside of the home directory, skipping...\n"; |
700
|
4
|
|
|
|
|
14
|
next; |
701
|
|
|
|
|
|
|
} |
702
|
|
|
|
|
|
|
|
703
|
94
|
100
|
|
|
|
165
|
if ($REVERT) { |
704
|
10
|
|
|
|
|
23
|
($item, $replacement) = exchange_wildcards_and_replacements($item, $replacement); |
705
|
|
|
|
|
|
|
|
706
|
10
|
|
|
|
|
25
|
my $replacement_path = calculate_target($replacement); |
707
|
10
|
|
|
|
|
317
|
for my $i (glob($replacement_path)) { |
708
|
10
|
100
|
|
|
|
17
|
if (defined($FILTER)) { |
709
|
4
|
100
|
|
|
|
22
|
next unless ($i =~ $FILTER); |
710
|
|
|
|
|
|
|
} |
711
|
8
|
|
|
|
|
13
|
revert($i, $item, $replacement); |
712
|
|
|
|
|
|
|
} |
713
|
|
|
|
|
|
|
} else { |
714
|
84
|
|
|
|
|
1855
|
for my $i (glob("$ENV{HOME}/$item")) { |
715
|
84
|
100
|
|
|
|
145
|
if (defined($FILTER)) { |
716
|
4
|
100
|
|
|
|
18
|
next unless ($i =~ $FILTER); |
717
|
|
|
|
|
|
|
} |
718
|
82
|
|
|
|
|
139
|
replace($type, $i, $item, $replacement, $action); |
719
|
|
|
|
|
|
|
} |
720
|
|
|
|
|
|
|
} |
721
|
|
|
|
|
|
|
} |
722
|
108
|
|
|
|
|
673
|
close($list_fh); |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
# Restore original umask |
726
|
80
|
|
|
|
|
2162
|
umask($OLDUMASK); |