3 # update-all-config.pl - Update all out-of-date config
8 BEGIN {*VERSION = \'2.0'}
15 use lib
"__BASEDIR__";
23 $shbin = $Girocco::Config
::posix_sh_bin
;
24 defined($shbin) && $shbin ne "" or $shbin = "/bin/sh";
27 exit(&main
(@ARGV)||0);
29 my ($dryrun, $force, $quiet);
32 pod2usage
(-exitval
=> 2);
36 pod2usage
(-verbose
=> 2, -exitval
=> 0);
40 print basename
($0), " version ", $VERSION, "\n";
44 my ($dmode, $dperm, $drwxmode, $fmode, $fmodeoct, $fperm, $wall);
48 $drwxmode='ug+rwx,o+rx';
62 close(DATA
) if fileno(DATA
);
63 Getopt
::Long
::Configure
('bundling');
65 'help|h' => sub {do_help
},
66 'version|V' => sub {do_version
},
67 'dry-run|n' => \
$dryrun,
71 $dryrun and $quiet = 0;
73 -f jailed_file
("/etc/group") or
74 die "Girocco group file not found: " . jailed_file
("/etc/group") . "\n";
76 if (!defined($Girocco::Config
::owning_group
) || $Girocco::Config
::owning_group
eq "") {
77 die "\$Girocco::Config::owning_group unset, refusing to run without --force\n" unless $force;
85 warn "Mode 666 in effect\n" unless $quiet;
86 } elsif (($owning_group_id = scalar(getgrnam($Girocco::Config
::owning_group
))) !~ /^\d+$/) {
87 die "\$Girocco::Config::owning_group invalid ($Girocco::Config::owning_group), refusing to run\n";
90 my @allprojs = Girocco
::Project
::get_full_list
;
93 my $root = $Girocco::Config
::reporoot
;
94 $root or die "\$Girocco::Config::reporoot is invalid\n";
96 $root ne "" or $root = "/";
97 $root = realpath
($root);
99 my %projnames = map {($_ => 1)} @allprojs;
103 -d
$_ and $_ = realpath
($_);
106 if (!exists($projnames{$_})) {
107 warn "$_: unknown to Girocco (not in etc/group)\n"
114 @projects = sort {lc($a) cmp lc($b)} @allprojs;
119 foreach (@projects) {
120 my $projdir = "$root/$_.git";
122 warn "$_: does not exist -- skipping\n" unless $quiet;
125 if (!is_git_dir
($projdir)) {
126 warn "$_: is not a .git directory -- skipping\n" unless $quiet;
129 if (-e
"$projdir/.noconfig") {
130 warn "$_: found .noconfig -- skipping\n" unless $quiet;
133 if (!chdir($projdir)) {
134 warn "$_: chdir to project directory failed: $!\n" unless $quiet;
137 process_one_project
($_) or $bad = 1;
143 my (@mkdirs, @mkfiles);
144 my (@fixdpermsdirs, @fixdpermsrwx, @fixfpermsfiles, @fixfpermsdirs);
146 @mkdirs = qw(refs info hooks ctags htmlcache bundles reflogs objects objects/info);
147 @mkfiles = qw(config info/lastactivity);
148 @fixdpermsdirs = qw(. refs info ctags htmlcache bundles reflogs objects objects/info);
149 @fixdpermsrwx = qw(refs objects);
150 @fixfpermsfiles = qw(HEAD config description packed-refs README.html info/lastactivity
151 info/alternates info/http-alternates info/packs);
152 @fixfpermsdirs = qw(ctags);
155 my (@boolvars, @falsevars, @false0vars, @truevars);
157 @boolvars = qw(gitweb.statusupdates);
158 @falsevars = qw(core.ignorecase receive.denynonfastforwards);
159 @false0vars = qw(gc.auto receive.autogc);
160 @truevars = qw(receive.updateserverinfo repack.writebitmaps transfer.fsckobjects);
166 return defined($_[0]) ?
$_[0] : $_[1];
173 open $duperr, '>&2' or last;
174 my $errfd = POSIX
::open(File
::Spec
->devnull, &POSIX
::O_RDWR
);
175 defined($errfd) or close($duperr), $duperr = undef, last;
176 POSIX
::dup2
($errfd, 2) or close($duperr), $duperr = undef;
177 POSIX
::close($errfd);
180 my $ans = open $fd, '-|', "find", @_;
181 if ($noe && defined($duperr) && defined(fileno($duperr))) {
182 POSIX
::dup2
(fileno($duperr), 2);
185 $ans or die "find failed: $!\n";
189 sub openfind
{ return openfind_
(0, @_); }
190 sub openfindne
{ return openfind_
(1, @_); }
192 sub process_one_project
201 warn "$proj: bypassing project, exists but not directory: $_\n" unless $quiet;
202 $reallybad = $bad = 1;
205 do_mkdir
($proj, $_) or $bad = 1, last;
209 return 0 if $reallybad;
211 -d
$_ && check_dperm
($proj, $_) or $bad = 1 foreach (@fixdpermsdirs);
212 my $fp = openfindne
(@fixdpermsrwx, qw(-xdev -type d ( ! -path objects/?? -o -prune ) ! -perm
), "-$drwxmode", "-print");
215 change_dpermrwx
($proj, $_) or $bad = 1;
217 close($fp) or $bad = 1;
218 $fp = openfind
(qw(. -xdev -type d ( ! -path ./objects/?? -o -prune ) ! -perm
-a
+rx
-print));
221 change_dpermrx
($proj, $_) or $bad = 1;
223 close($fp) or $bad = 1;
228 warn "$proj: bypassing project, exists but not file: $_\n" unless $quiet;
229 $reallybad = $bad = 1;
233 my $result = "(dryrun)";
237 open($tf, '>', $_) && close ($tf) or $result = "FAILED", $bad = 1;
239 pmsg
($proj, "$_: created", $result) unless $quiet;
242 return 0 if $reallybad;
244 $dryrun || check_fperm
($proj, "config") or $bad = 1;
245 my $config = read_config_file_hash
("config", !$quiet);
246 if (!defined($config)) {
247 warn "$proj: could not read config file -- skipping\n" unless $quiet;
251 my $do_config = sub {
252 my ($item, $val) = @_;
253 my $oldval = defval
($config->{$item},"");
254 my $result = "(dryrun)";
257 system($Girocco::Config
::git_bin
, "config", "--file", "config", "--replace-all", $item, $val) == 0 or
258 $result = "FAILED", $bad = 1;
260 if (!exists($config->{$item})) {
261 pmsg
($proj, "config $item: created \"$val\"", $result) unless $quiet;
263 pmsg
($proj, "config $item: \"$oldval\" -> \"$val\"", $result) unless $quiet;
266 my $do_config_unset = sub {
267 my ($item, $msg) = @_;
268 defined($msg) or $msg = "";
269 $msg eq "" or $msg = " " . $msg;
270 my $oldval = defval
($config->{$item},"");
271 my $result = "(dryrun)";
274 system($Girocco::Config
::git_bin
, "config", "--file", "config", "--unset-all", $item) == 0 or
275 $result = "FAILED", $bad = 1;
277 pmsg
($proj, "config $item: removed$msg \"$oldval\"", $result) unless $quiet;
280 my $repovers = $config->{'core.repositoryformatversion'};
281 if (!defined($repovers)) {
283 } elsif ($repovers =~ /^[2345]$/) {
284 pmsg
($proj, "WARNING: unknown core.repositoryformatversion value left unchanged: \"$repovers\"");
285 } elsif ($repovers !~ /^[01]$/) {
286 pmsg
($proj, "WARNING: replacing invalid core.repositoryformatversion value: \"$repovers\"") unless $quiet;
289 &$do_config('core.repositoryformatversion', 0) if $repovers eq "";
290 my $hookspath = $Girocco::Config
::reporoot
. "/_global/hooks";
291 defval
($config->{'core.hookspath'},"") eq $hookspath or &$do_config('core.hookspath', $hookspath);
292 my $cmplvl = defval
($config->{'core.compression'},"");
293 if ($cmplvl !~ /^-?\d+$/ || $cmplvl < -1 || $cmplvl > 9 || "" . (0 + $cmplvl) ne "" . $cmplvl) {
294 pmsg
($proj, "WARNING: replacing invalid core.compression value: \"$cmplvl\"") unless $cmplvl eq "" || $quiet;
296 } elsif ($cmplvl != 5) {
297 pmsg
($proj, "WARNING: suboptimal core.compression value left unchanged: \"$cmplvl\"") unless $quiet;
299 $cmplvl ne "" or &$do_config('core.compression', 5);
300 my $grpshr = defval
($config->{'core.sharedrepository'},"");
301 if ($grpshr eq "" || (valid_bool
($grpshr) && !git_bool
($grpshr))) {
302 &$do_config('core.sharedrepository', 1);
303 } elsif (!(valid_bool
($grpshr) && git_bool
($grpshr))) {
304 pmsg
($proj, "WARNING: odd core.sharedrepository value left unchanged: \"$grpshr\"");
306 if (git_bool
($config->{'core.bare'})) {
308 my $laru = $config->{'core.logallrefupdates'};
309 if (defined($laru)) {
310 if (valid_bool
($laru)) {
312 if (git_bool
($laru)) {
313 pmsg
($proj, "WARNING: core.logallrefupdates is true (left unchanged)") unless $quiet;
316 pmsg
($proj, "WARNING: replacing non-boolean core.logallrefupdates value") unless $quiet;
319 !$setlaru or &$do_config('core.logallrefupdates', 'false');
321 pmsg
($proj, "WARNING: core.bare is not true (left unchanged)") unless $quiet;
323 defval
($config->{'transfer.unpacklimit'},"") eq "1" or &$do_config('transfer.unpacklimit', 1);
324 lc(defval
($config->{'receive.denydeletecurrent'},"")) eq "warn" or &$do_config('receive.denydeletecurrent', 'warn');
326 !exists($config->{$_}) || valid_bool
(defval
($config->{$_},"")) or &$do_config_unset($_, "(not a boolean)");
327 } foreach (@boolvars);
329 (valid_bool
(defval
($config->{$_},"")) && !git_bool
($config->{$_})) or &$do_config($_, "false");
330 } foreach (@falsevars);
332 (valid_bool
(defval
($config->{$_},"")) && !git_bool
($config->{$_})) or &$do_config($_, 0);
333 } foreach (@false0vars);
335 (valid_bool
(defval
($config->{$_},"")) && git_bool
($config->{$_})) or &$do_config($_, "true");
336 } foreach (@truevars);
338 if (defined($Girocco::Config
::owning_group
) && $Girocco::Config
::owning_group
ne "") {
339 $fp = openfind
(qw(. -xdev ( -type d -o -type f ) ! -group
), $Girocco::Config
::owning_group
, "-print");
342 change_group
($proj, $_) or $bad = 1;
344 close($fp) or $bad = 1;
346 foreach (@fixfpermsfiles) {
349 warn "$proj: bypassing project, exists but not file: $_\n" unless $quiet;
350 $reallybad = $bad = 1;
353 check_fperm
($proj, $_) or $bad = 1;
356 return 0 if $reallybad;
358 $fp = openfindne
(@fixfpermsdirs, qw(-xdev -type f ! -perm), $fmodeoct, "-print");
361 check_fperm
($proj, $_) or $bad = 1;
363 close($fp) or $bad = 1;
364 $fp = openfind
(qw(. -xdev -type f ! -perm -a+r -print));
367 check_fpermr
($proj, $_) or $bad = 1;
369 close($fp) or $bad = 1;
370 $fp = openfind
(qw(. -xdev -type d ( -path ./hooks -o -path ./mob/hooks ) -prune
-o
-type f
-perm
+a
+x
-print));
373 check_fpermnox
($proj, $_) or $bad = 1;
375 close($fp) or $bad = 1;
377 my $bu = defval
($config->{'gitweb.baseurl'},"");
379 $bu eq "" or pmsg
($proj, "WARNING: .nofetch exists but gitweb.baseurl is not empty ($bu)") unless $quiet;
381 $bu ne "" or pmsg
($proj, "WARNING: gitweb.baseurl is empty and .nofetch does not exist") unless $quiet;
389 my ($proj, $subdir) = @_;
392 mkdir("$subdir") && -d
"$subdir" or $result = "FAILED";
394 $result = "(dryrun)";
396 pmsg
($proj, "$subdir/: created", $result);
397 return $result ne "FAILED";
401 my ($proj, $subdir) = @_;
402 my $oldmode = (stat($subdir))[2];
403 if (!defined($oldmode) || $oldmode eq "") {
404 warn "chmod: ($proj) $subdir: No such file or directory\n" unless $quiet;
407 my $newmode = ($oldmode & ~07777) | $dmode;
408 $newmode == $oldmode and return 1;
411 if (!chmod($newmode & 07777, $subdir)) {
413 warn "chmod: ($proj) $subdir: $!\n" unless $quiet;
416 $result = "(dryrun)";
418 pmsg
($proj, "$subdir/:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
419 return $result ne "FAILED";
422 sub change_dpermrwx
{
423 my ($proj, $subdir) = @_;
424 my $oldmode = (stat($subdir))[2];
425 if (!defined($oldmode) || $oldmode eq "") {
426 warn "chmod: ($proj) $subdir: No such file or directory\n" unless $quiet;
429 my $newmode = $oldmode | ($wall ?
0777 : 0775);
430 $newmode == $oldmode and return 1;
433 if (!chmod($newmode & 07777, $subdir)) {
435 warn "chmod: ($proj) $subdir: $!\n" unless $quiet;
438 $result = "(dryrun)";
440 pmsg
($proj, "$subdir/:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
441 return $result ne "FAILED";
445 my ($proj, $subdir) = @_;
447 my $oldmode = (stat($subdir))[2];
448 if (!defined($oldmode) || $oldmode eq "") {
449 warn "chmod: ($proj) $subdir: No such file or directory\n" unless $quiet;
452 my $newmode = $oldmode | 0555;
453 $newmode == $oldmode and return 1;
456 if (!chmod($newmode & 07777, $subdir)) {
458 warn "chmod: ($proj) $subdir: $!\n" unless $quiet;
461 $result = "(dryrun)";
463 pmsg
($proj, "$subdir/:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
464 return $result ne "FAILED";
468 my ($proj, $file) = @_;
469 my $oldmode = (stat($file))[2];
470 if (!defined($oldmode) || $oldmode eq "") {
471 warn "chmod: ($proj) $file: No such file or directory\n" unless $quiet;
474 my $newmode = ($oldmode & ~07777) | $fmode;
475 $newmode == $oldmode and return 1;
478 if (!chmod($newmode & 07777, $file)) {
480 warn "chmod: ($proj) $file: $!\n" unless $quiet;
483 $result = "(dryrun)";
485 pmsg
($proj, "$file:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
486 return $result ne "FAILED";
490 my ($proj, $file) = @_;
492 my $oldmode = (stat($file))[2];
493 if (!defined($oldmode) || $oldmode eq "") {
494 warn "chmod: ($proj) $file: No such file or directory\n" unless $quiet;
497 my $newmode = $oldmode | 0444;
498 $newmode == $oldmode and return 1;
501 if (!chmod($newmode & 07777, $file)) {
503 warn "chmod: ($proj) $file: $!\n" unless $quiet;
506 $result = "(dryrun)";
508 pmsg
($proj, "$file:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
509 return $result ne "FAILED";
513 my ($proj, $file) = @_;
515 my $oldmode = (stat($file))[2];
516 if (!defined($oldmode) || $oldmode eq "") {
517 warn "chmod: ($proj) $file: No such file or directory\n" unless $quiet;
520 my $newmode = $oldmode & ~0111;
521 $newmode == $oldmode and return 1;
524 if (!chmod($newmode & 07777, $file)) {
526 warn "chmod: ($proj) $file: $!\n" unless $quiet;
529 $result = "(dryrun)";
531 pmsg
($proj, "$file:", get_mode_perm
($oldmode), '->', get_mode_perm
($newmode), $result);
532 return $result ne "FAILED";
536 my ($proj, $item) = @_;
538 my @info = stat($item);
539 if (@info < 6 || $info[2] eq "" || $info[4] eq "" || $info[5] eq "") {
540 warn "chgrp: ($proj) $item: No such file or directory\n" unless $quiet;
543 $info[5] == $owning_group_id and return 1;
546 if (!chown($info[4], $owning_group_id, $item)) {
548 warn "chgrp: ($proj) $item: $!\n" unless $quiet;
549 } elsif (!chmod($info[2] & 07777, $item)) {
551 warn "chmod: ($proj) $item: $!\n" unless $quiet;
554 $result = "(dryrun)";
556 my $isdir = ((($info[2] >> 12) & 017) == 004) ?
'/' : '';
557 pmsg
($proj, "$item$isdir: group", get_grp_nam
($info[5]), '->', $Girocco::Config
::owning_group
, $result);
558 return $result ne "FAILED";
561 my $wrote; BEGIN {$wrote = ""}
564 my $msg = join(" ", @_);
568 $prefix = $wrote . $proj . ":\n";
571 print $prefix, " ", join(' ', @_), "\n";
608 my $str = $ftypes{($mode >> 12) & 017} .
609 $fperms{($mode >> 6) & 7} .
610 $fperms{($mode >> 3) & 7} .
612 substr($str,3,1) = ($mode & 0100) ?
's' : 'S' if $mode & 04000;
613 substr($str,6,1) = ($mode & 0010) ?
's' : 'S' if $mode & 02000;
614 substr($str,9,1) = ($mode & 0001) ?
't' : 'T' if $mode & 01000;
619 my $mode = (stat($_[0]))[2];
620 defined($mode) or return '??????????';
621 return get_mode_perm
($mode);
626 defined($grpid) or return '?';
627 my $grpnm = scalar(getgrgid($grpid));
628 return defined($grpnm) && $grpnm ne "" ?
$grpnm : $grpid;
632 my $grp = (stat($_[0]))[5];
633 defined($grp) or return '?';
634 return get_grp_nam
($grp);
641 update-all-config.pl - Update all projects' config settings
645 update-all-config.pl [<options>] [<projname>]...
648 -h | --help detailed instructions
649 -V | --version show version
650 -n | --dry-run show what would be done but don't do it
651 -f | --force run without a Config.pm owning_group
652 -q | --quiet suppress change messages
654 <projname> if given, only operate on these projects
660 =item B<-h>, B<--help>
662 Print the full description of update-all-config.pl's options.
664 =item B<-V>, B<--version>
666 Print the version of update-all-config.pl.
668 =item B<-n>, B<--dry-run>
670 Do not actually make any changes, just show what would be done without
673 =item B<-q>, B<--quiet>
675 Suppress the messages about what's actually being changed. This option
676 is ignored if B<--dry-run> is in effect.
678 The warnings about missing and unknown-to-Girocco projects are also
679 suppressed by this option.
681 =item B<-f>, B<--force>
683 Allow running without a $Girocco::Config::owning_group set. This is not
684 recommended as it results in world-writable items being used (instead of
685 just world-readable).
689 If no project names are specified then I<all> projects are processed.
691 If one or more project names are specified then only those projects are
692 processed. Specifying non-existent projects produces a warning for them,
693 but the rest of the projects specified will still be processed.
695 Each B<projname> may be either a full absolute path starting with
696 $Girocco::Config::reporoot or just the project name part with or without
699 Any explicitly specified projects that do exist but are not known to
700 Girocco will be skipped (with a warning).
706 Inspect the C<config> files of Girocco projects (i.e. $GIT_DIR/config) and
707 look for anomalies and out-of-date settings.
709 Additionally check the existence and permissions on various files and
710 directories in the project.
712 If an explicity specified project is located under $Girocco::Config::reporoot
713 but is not actually known to Girocco (i.e. it's not in the etc/group file)
714 then it will be skipped.
716 By default, any anomalies or out-of-date settings will be corrected with a
717 message to that effect. However using B<--dry-run> will only show the
718 correction(s) which would be made without making them and B<--quiet> will make
719 the correction(s) without any messages.
721 Any projects that have a C<$GIT_DIR/.noconfig> file are always skipped (with a
722 message unless B<--quiet> is used).