chrootsetup_*: make sure pull_in_lib libraries have +/-x
[girocco.git] / toolbox / update-all-config.pl
blob2c2dbdf55b6e0267bc844e3ed3d1908aac328992
1 #!/usr/bin/perl
3 # update-all-config.pl - Update all out-of-date config
5 use strict;
6 use warnings;
7 use vars qw($VERSION);
8 BEGIN {*VERSION = \'2.0'}
9 use File::Basename;
10 use File::Spec;
11 use Cwd qw(realpath);
12 use POSIX qw();
13 use Getopt::Long;
14 use Pod::Usage;
15 BEGIN {
16 eval 'require Pod::Text::Termcap; 1;' and
17 @Pod::Usage::ISA = (qw( Pod::Text::Termcap ));
18 defined($ENV{PERLDOC}) && $ENV{PERLDOC} ne "" or
19 $ENV{PERLDOC} = "-oterm -oman";
21 use lib "__BASEDIR__";
22 use Girocco::Config;
23 use Girocco::Util;
24 use Girocco::CLIUtil;
25 use Girocco::Project;
27 my $shbin;
28 BEGIN {
29 $shbin = $Girocco::Config::posix_sh_bin;
30 defined($shbin) && $shbin ne "" or $shbin = "/bin/sh";
33 exit(&main(@ARGV)||0);
35 my ($dryrun, $force, $quiet);
37 sub die_usage {
38 pod2usage(-exitval => 2);
41 sub do_help {
42 pod2usage(-verbose => 2, -exitval => 0);
45 sub do_version {
46 print basename($0), " version ", $VERSION, "\n";
47 exit 0;
50 my ($dmode, $dperm, $drwxmode, $fmode, $fmodeoct, $fperm, $wall);
51 BEGIN {
52 $dmode=02775;
53 $dperm='drwxrwsr-x';
54 $drwxmode='ug+rwx,o+rx';
55 $fmode=0664;
56 $fmodeoct='0664';
57 $fperm='-rw-rw-r--';
58 $wall=0;
61 my $owning_group_id;
62 my $htmlcache_owning_group_id;
63 my $ctags_owning_group_id;
64 my $progress;
66 sub pwarn { $progress->warn(@_) }
68 sub main {
69 local *ARGV = \@_;
70 my ($help, $version);
72 umask 002;
73 close(DATA) if fileno(DATA);
74 Getopt::Long::Configure('bundling');
75 GetOptions(
76 'help|h' => sub {do_help},
77 'version|V' => sub {do_version},
78 'dry-run|n' => \$dryrun,
79 'quiet|q' => \$quiet,
80 'force|f' => \$force,
81 ) or die_usage;
82 $dryrun and $quiet = 0;
84 -f jailed_file("/etc/group") or
85 die "Girocco group file not found: " . jailed_file("/etc/group") . "\n";
87 if (!defined($Girocco::Config::owning_group) || $Girocco::Config::owning_group eq "") {
88 die "\$Girocco::Config::owning_group unset, refusing to run without --force\n" unless $force;
89 $dmode=02777;
90 $dperm='drwxrwsrwx';
91 $drwxmode='a+rwx';
92 $fmode=0666;
93 $fmodeoct='0666';
94 $fperm='-rw-rw-rw-';
95 $wall=1;
96 warn "Mode 666 in effect\n" unless $quiet;
97 } elsif (($owning_group_id = scalar(getgrnam($Girocco::Config::owning_group))) !~ /^\d+$/) {
98 die "\$Girocco::Config::owning_group invalid ($Girocco::Config::owning_group), refusing to run\n";
100 if (defined($owning_group_id) && $Girocco::Config::htmlcache_owning_group) {
101 die "\$Girocco::Config::htmlcache_owning_group invalid ($Girocco::Config::htmlcache_owning_group), refusing to run\n"
102 unless ($htmlcache_owning_group_id = scalar(getgrnam($Girocco::Config::htmlcache_owning_group))) =~ /^\d+$/;
104 if (defined($owning_group_id) && $Girocco::Config::ctags_owning_group) {
105 die "\$Girocco::Config::ctags_owning_group invalid ($Girocco::Config::ctags_owning_group), refusing to run\n"
106 unless ($ctags_owning_group_id = scalar(getgrnam($Girocco::Config::ctags_owning_group))) =~ /^\d+$/;
109 my @allprojs = Girocco::Project::get_full_list;
110 my @projects = ();
112 my $root = $Girocco::Config::reporoot;
113 $root or die "\$Girocco::Config::reporoot is invalid\n";
114 $root =~ s,/+$,,;
115 $root ne "" or $root = "/";
116 $root = realpath($root);
117 $root = $1 if $root =~ m|^(/.+)$|;
118 if (@ARGV) {
119 my %projnames = map {($_ => 1)} @allprojs;
120 foreach (@ARGV) {
121 s,/+$,,;
122 $_ or $_ = "/";
123 -d $_ and $_ = realpath($_);
124 $_ = $1 if $_ =~ m|^(.+)$|;
125 s,^\Q$root\E/,,;
126 s,\.git$,,;
127 if (!exists($projnames{$_})) {
128 warn "$_: unknown to Girocco (not in etc/group)\n"
129 unless $quiet;
130 next;
132 push(@projects, $_);
134 } else {
135 @projects = sort {lc($a) cmp lc($b) || $a cmp $b} @allprojs;
138 nice_me(18);
139 my $bad = 0;
140 $progress = Girocco::CLIUtil::Progress->new(scalar(@projects),
141 "Updating config files");
142 foreach (@projects) {
143 my $projdir = "$root/$_.git";
144 if (! -d $projdir) {
145 pwarn "$_: does not exist -- skipping\n" unless $quiet;
146 next;
148 if (!is_git_dir($projdir)) {
149 pwarn "$_: is not a .git directory -- skipping\n" unless $quiet;
150 next;
152 if (-e "$projdir/.noconfig") {
153 pwarn "$_: found .noconfig -- skipping\n" unless $quiet;
154 next;
156 if (!chdir($projdir)) {
157 pwarn "$_: chdir to project directory failed: $!\n" unless $quiet;
158 next;
160 process_one_project($_) or $bad = 1;
161 } continue {$progress->update}
162 $progress = undef;
164 return $bad ? 1 : 0;
167 my (@mkdirs, @mkfiles);
168 my (@fixdpermsdirs, @fixdpermsrwx, @fixfpermsfiles, @fixfpermsdirs);
169 BEGIN {
170 @mkdirs = qw(refs info hooks ctags htmlcache bundles reflogs objects objects/info);
171 @mkfiles = qw(config info/lastactivity);
172 @fixdpermsdirs = qw(. refs info ctags htmlcache bundles reflogs objects objects/info);
173 @fixdpermsrwx = qw(refs objects);
174 @fixfpermsfiles = qw(HEAD config description packed-refs README.html info/lastactivity
175 info/alternates info/http-alternates info/packs);
176 @fixfpermsdirs = qw(ctags);
179 my (@boolvars, @falsevars, @false0vars, @truevars);
180 BEGIN {
181 @boolvars = qw(gitweb.statusupdates);
182 @falsevars = qw(core.ignorecase receive.denynonfastforwards);
183 @false0vars = qw(receive.autogc);
184 @truevars = qw(receive.fsckobjects receive.updateserverinfo repack.writebitmaps transfer.fsckobjects);
187 my $hdr;
189 sub defval($$) {
190 return defined($_[0]) ? $_[0] : $_[1];
193 sub openfind_ {
194 my $noe = shift;
195 my $duperr;
196 if ($noe) {{
197 open $duperr, '>&2' or last;
198 my $errfd = POSIX::open(File::Spec->devnull, &POSIX::O_RDWR);
199 defined($errfd) or close($duperr), $duperr = undef, last;
200 POSIX::dup2($errfd, 2) or close($duperr), $duperr = undef;
201 POSIX::close($errfd);
203 my $fd;
204 my $ans = open $fd, '-|', "find", @_;
205 if ($noe && defined($duperr) && defined(fileno($duperr))) {
206 POSIX::dup2(fileno($duperr), 2);
207 close($duperr);
209 $ans or die "\nfind failed: $!\n";
210 return $fd;
213 sub openfind { return openfind_(0, @_); }
214 sub openfindne { return openfind_(1, @_); }
216 sub all_remotes
218 my $config = shift;
219 return map({
220 my ($i,$r) = (index($_,"."),rindex($_,"."));
221 substr($_,$i+1,$r-$i-1);
222 } grep(/^remote\.[^.].*\.url$/i, keys(%$config)));
225 sub has_default_fetch_spec
227 my $config = shift;
228 my $default = $config->{'remotes.default'};
229 my @remotes = defined($default) ? split(' ', $default) : all_remotes($config);
230 foreach (@remotes) {
231 defval($config->{"remote.$_.url"},"") ne "" or next;
232 !defined($default) && git_bool($config->{"remote.$_.skipdefaultupdate"}) and next;
233 defval($config->{"remote.$_.fetch"},"") ne "" and return 1;
235 return 0;
238 sub is_native_git_mirror_url
240 my $bu = shift;
241 defined($bu) && $bu ne "" or return 0;
242 # All current or former natively supported by Git URLs return true:
243 # 1. rsync: (removed in 2.8.0, also recognize rsync+ and rsync::)
244 # 2. ftp:/ftps: (strongly discouraged)
245 # 3. git:
246 # 4. http:/https: (smart and non-smart)
247 # 5. ssh:
248 # 6. scp-like ssh syntax [user@]host:[^:/]
249 return $bu =~ /^(?:
250 rsync[:+] |
251 ftps?: |
252 git: |
253 https?: |
254 ssh: |
255 (?:[^\s:\@]+\@)?[^\s:\@+]+:(?!\/\/)[^\s:\\]
256 )/xi;
259 sub git_valid_shared
261 my $v = shift;
262 return lc($v) =~ /^(?:false|true|umask|group|all|world|everybody|0|1|2|0[0-7]{1,3})$/
265 sub git_get_shared
267 local $_ = lc(shift);
268 /^(?:false|umask|00*)$/ and return 0;
269 /^(?:true|group|1|00*1)$/ and return 1;
270 /^(?:all|world|everybody|2|00*2)$/ and return 2;
271 /^0[0-7]*$/ and return oct($_);
272 return undef;
275 sub process_one_project
277 my ($proj) = @_;
278 my $bad = 0;
279 my $reallybad = 0;
280 $hdr = 0;
281 do {
282 if (! -d $_) {
283 if (-e $_) {
284 pwarn "$proj: bypassing project, exists but not directory: $_\n" unless $quiet;
285 $reallybad = $bad = 1;
286 last;
287 } else {
288 my $grpid = $owning_group_id;
289 $grpid = $htmlcache_owning_group_id
290 if $htmlcache_owning_group_id && $_ eq "htmlcache";
291 $grpid = $ctags_owning_group_id
292 if $ctags_owning_group_id && $_ eq "ctags";
293 do_mkdir($proj, $_, $grpid) or $bad = 1, last;
296 } foreach (@mkdirs);
297 return 0 if $reallybad;
299 -d $_ && check_dperm($proj, $_) or $bad = 1 foreach (@fixdpermsdirs);
300 my $fp = openfindne(@fixdpermsrwx, qw(-xdev -type d ( ! -path objects/?? -o -prune ) ! -perm), "-$drwxmode", "-print");
301 while (<$fp>) {
302 chomp;
303 change_dpermrwx($proj, $_) or $bad = 1;
305 close($fp) or $bad = 1;
306 $fp = openfind(qw(. -xdev -type d ( ! -path ./objects/?? -o -prune ) ! -perm -a+rx -print));
307 while (<$fp>) {
308 chomp;
309 change_dpermrx($proj, $_) or $bad = 1;
311 close($fp) or $bad = 1;
313 do {
314 if (-e $_) {
315 if (! -f $_) {
316 pwarn "$proj: bypassing project, exists but not file: $_\n" unless $quiet;
317 $reallybad = $bad = 1;
318 last;
320 } else {
321 my $result = "(dryrun)";
322 if (!$dryrun) {
323 $result = "";
324 my $tf;
325 open($tf, '>', $_) && close ($tf) or $result = "FAILED", $bad = 1;
327 pmsg($proj, "$_: created", $result) unless $quiet;
329 } foreach(@mkfiles);
330 return 0 if $reallybad;
332 $dryrun || check_fperm($proj, "config") or $bad = 1;
333 my $config = read_config_file_hash("config", !$quiet);
334 if (!defined($config)) {
335 pwarn "$proj: could not read config file -- skipping\n" unless $quiet;
336 return 0;
339 my $do_config = sub {
340 my ($item, $val) = @_;
341 my $oldval = defval($config->{$item},"");
342 my $result = "(dryrun)";
343 if (!$dryrun) {
344 $result = "";
345 system($Girocco::Config::git_bin, "config", "--file", "config", "--replace-all", $item, $val) == 0 or
346 $result = "FAILED", $bad = 1;
348 if (!exists($config->{$item})) {
349 pmsg($proj, "config $item: created \"$val\"", $result) unless $quiet;
350 } else {
351 pmsg($proj, "config $item: \"$oldval\" -> \"$val\"", $result) unless $quiet;
354 my $do_config_unset = sub {
355 my ($item, $msg) = @_;
356 defined($msg) or $msg = "";
357 $msg eq "" or $msg = " " . $msg;
358 my $oldval = defval($config->{$item},"");
359 my $result = "(dryrun)";
360 if (!$dryrun) {
361 $result = "";
362 system($Girocco::Config::git_bin, "config", "--file", "config", "--unset-all", $item) == 0 or
363 $result = "FAILED", $bad = 1;
365 pmsg($proj, "config $item: removed$msg \"$oldval\"", $result) unless $quiet;
368 my $repovers = $config->{'core.repositoryformatversion'};
369 if (!defined($repovers)) {
370 $repovers = "";
371 } elsif ($repovers =~ /^[2345]$/) {
372 pmsg($proj, "WARNING: unknown core.repositoryformatversion value left unchanged: \"$repovers\"")
373 unless $quiet;
374 } elsif ($repovers !~ /^[01]$/) {
375 pmsg($proj, "WARNING: replacing invalid core.repositoryformatversion value: \"$repovers\"")
376 unless $quiet;
377 $repovers = "";
379 &$do_config('core.repositoryformatversion', 0) if $repovers eq "";
380 my $hookspath = $Girocco::Config::reporoot . "/_global/hooks";
381 my $cfghooks = defval($config->{'core.hookspath'},"");
382 if ($cfghooks ne $hookspath) {
383 my $updatehookspath = 1;
384 $hookspath = $Girocco::Config::reporoot . "/$proj.git/hooks" if $Girocco::Config::localhooks;
385 if ($cfghooks =~ m{^/[^/]} && -d $cfghooks && -d "hooks") {
386 # tolerate this situation provided the realpath of $cfghooks
387 # matches the realpath of the hooks subdirectory and the hooks
388 # subdirectory exists; actually making sure the correct symlinks
389 # are present remains up to update-all-hooks not us
390 if (realpath($cfghooks) eq realpath("hooks")) {
391 # we do, however, insist that it be stored exactly
392 # as $reporoot/<project_name>.git/hooks in this case because
393 # that's the only guaranteed version that works in the chroot
394 $hookspath = $Girocco::Config::reporoot . "/$proj.git/hooks";
395 $cfghooks eq $hookspath and $updatehookspath = 0;
398 &$do_config('core.hookspath', $hookspath) if $updatehookspath;
400 my $cmplvl = defval($config->{'core.compression'},"");
401 if ($cmplvl !~ /^-?\d+$/ || $cmplvl < -1 || $cmplvl > 9 || "" . (0 + $cmplvl) ne "" . $cmplvl) {
402 pmsg($proj, "WARNING: replacing invalid core.compression value: \"$cmplvl\"")
403 unless $cmplvl eq "" || $quiet;
404 $cmplvl = "";
405 } elsif ($cmplvl != 5) {
406 pmsg($proj, "WARNING: suboptimal core.compression value left unchanged: \"$cmplvl\"") unless $quiet;
408 $cmplvl ne "" or &$do_config('core.compression', 5);
409 my $grpshr = defval($config->{'core.sharedrepository'},"");
410 if ($grpshr eq "" || (git_valid_shared($grpshr) &&
411 git_get_shared($grpshr) != $Girocco::Config::git_shared_repository_setting)) {
412 &$do_config('core.sharedrepository', $Girocco::Config::git_shared_repository_setting);
413 } elsif (!git_valid_shared($grpshr)) {
414 pmsg($proj, "WARNING: odd core.sharedrepository value left unchanged: \"$grpshr\"") unless $quiet;
416 if (git_bool($config->{'core.bare'})) {
417 my $setlaru = 1;
418 my $laru = $config->{'core.logallrefupdates'};
419 if (defined($laru)) {
420 if (valid_bool($laru)) {
421 $setlaru = 0;
422 if (git_bool($laru)) {
423 pmsg($proj, "WARNING: core.logallrefupdates is true (left unchanged)")
424 unless $quiet || -d "worktrees";
426 } else {
427 pmsg($proj, "WARNING: replacing non-boolean core.logallrefupdates value") unless $quiet;
430 !$setlaru or &$do_config('core.logallrefupdates', 'false');
431 } else {
432 pmsg($proj, "WARNING: core.bare is not true (left unchanged)") unless $quiet;
434 my $precious = defval($config->{'extensions.preciousobjects'},"");
435 valid_bool($precious) && git_bool($precious) or &$do_config('extensions.preciousobjects', 'true');
436 defval($config->{'transfer.unpacklimit'},"") eq "1" or &$do_config('transfer.unpacklimit', 1);
437 lc(defval($config->{'receive.denydeletecurrent'},"")) eq "warn" or &$do_config('receive.denydeletecurrent', 'warn');
438 do {
439 !exists($config->{$_}) || valid_bool(defval($config->{$_},"")) or &$do_config_unset($_, "(not a boolean)");
440 } foreach (@boolvars);
441 do {
442 (valid_bool(defval($config->{$_},"")) && !git_bool($config->{$_})) or &$do_config($_, "false");
443 } foreach (@falsevars);
444 do {
445 (valid_bool(defval($config->{$_},"")) && !git_bool($config->{$_})) or &$do_config($_, 0);
446 } foreach (@false0vars);
447 do {
448 (valid_bool(defval($config->{$_},"")) && git_bool($config->{$_})) or &$do_config($_, "true");
449 } foreach (@truevars);
451 if (defined($Girocco::Config::owning_group) && $Girocco::Config::owning_group ne "") {
452 $fp = openfind(qw(. -xdev ( -type d -o -type f ) ! -group), $Girocco::Config::owning_group, "-print");
453 while (<$fp>) {
454 chomp;
455 my $grpid = $owning_group_id;
456 $grpid = $htmlcache_owning_group_id if $htmlcache_owning_group_id && m{^\./htmlcache(?:/|$)}i;
457 $grpid = $ctags_owning_group_id if $ctags_owning_group_id && m{^\./ctags(?:/|$)}i;
458 change_group($proj, $_, $grpid) or $bad = 1;
460 close($fp) or $bad = 1;
462 foreach (@fixfpermsfiles) {
463 if (-e $_) {
464 if (! -f $_) {
465 pwarn "$proj: bypassing project, exists but not file: $_\n" unless $quiet;
466 $reallybad = $bad = 1;
467 last;
469 check_fperm($proj, $_) or $bad = 1;
472 return 0 if $reallybad;
474 $fp = openfindne(@fixfpermsdirs, qw(-xdev -type f ! -perm), $fmodeoct, "-print");
475 while (<$fp>) {
476 chomp;
477 check_fperm($proj, $_) or $bad = 1;
479 close($fp) or $bad = 1;
480 $fp = openfind(qw(. -xdev -type f ! -perm -a+r -print));
481 while (<$fp>) {
482 chomp;
483 check_fpermr($proj, $_) or $bad = 1;
485 close($fp) or $bad = 1;
486 $fp = openfind(qw(. -xdev -type d ( -path ./hooks -o -path ./mob/hooks ) -prune -o -type f ( -perm -u=x -o -perm -g=x -o -perm -o=x ) -print));
487 while (<$fp>) {
488 chomp;
489 check_fpermnox($proj, $_) or $bad = 1;
491 close($fp) or $bad = 1;
493 my $bu = defval($config->{'gitweb.baseurl'},"");
494 if (-e ".nofetch") {
495 $bu eq "" or pmsg($proj, "WARNING: .nofetch exists but gitweb.baseurl is not empty ($bu)") unless $quiet;
496 } else {
497 if ($bu eq "") {
498 if (has_default_fetch_spec($config)) {
499 pmsg($proj, "WARNING: gitweb.baseurl is empty and .nofetch does not exist but fetch spec does") unless $quiet;
500 } else {
501 pmsg($proj, "WARNING: gitweb.baseurl is empty and .nofetch does not exist") unless $quiet;
503 } elsif (is_native_git_mirror_url($bu) && !has_default_fetch_spec($config)) {
504 pmsg($proj, "WARNING: gitweb.baseurl is not empty but fetch spec is") unless $quiet;
508 return !$bad;
511 sub do_mkdir
513 my ($proj, $subdir, $grpid) = @_;
514 my $result = "";
515 if (!$dryrun) {
516 mkdir($subdir) && -d "$subdir" or $result = "FAILED";
517 if ($grpid && $grpid != $owning_group_id) {
518 my @info = stat($subdir);
519 if (@info < 6 || $info[2] eq "" || $info[4] eq "" || $info[5] eq "") {
520 $result = "FAILED";
521 } elsif ($info[5] != $grpid) {
522 if (!chown($info[4], $grpid, $subdir)) {
523 $result = "FAILED";
524 pwarn "chgrp: ($proj) $subdir: $!\n" unless $quiet;
525 } elsif (!chmod($info[2] & 07777, $subdir)) {
526 $result = "FAILED";
527 pwarn "chmod: ($proj) $subdir: $!\n" unless $quiet;
531 } else {
532 $result = "(dryrun)";
534 pmsg($proj, "$subdir/: created", $result) unless $quiet;
535 return $result ne "FAILED";
538 sub check_dperm {
539 my ($proj, $subdir) = @_;
540 my $oldmode = (stat($subdir))[2];
541 if (!defined($oldmode) || $oldmode eq "") {
542 pwarn "chmod: ($proj) $subdir: No such file or directory\n" unless $quiet;
543 return 0;
545 my $newmode = ($oldmode & ~07777) | $dmode;
546 $newmode == $oldmode and return 1;
547 my $result = "";
548 if (!$dryrun) {
549 if (!chmod($newmode & 07777, $subdir)) {
550 $result = "FAILED";
551 pwarn "chmod: ($proj) $subdir: $!\n" unless $quiet;
553 } else {
554 $result = "(dryrun)";
556 pmsg($proj, "$subdir/:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result) unless $quiet;
557 return $result ne "FAILED";
560 sub change_dpermrwx {
561 my ($proj, $subdir) = @_;
562 my $oldmode = (stat($subdir))[2];
563 if (!defined($oldmode) || $oldmode eq "") {
564 pwarn "chmod: ($proj) $subdir: No such file or directory\n" unless $quiet;
565 return 0;
567 my $newmode = $oldmode | ($wall ? 0777 : 0775);
568 $newmode == $oldmode and return 1;
569 my $result = "";
570 if (!$dryrun) {
571 if (!chmod($newmode & 07777, $subdir)) {
572 $result = "FAILED";
573 pwarn "chmod: ($proj) $subdir: $!\n" unless $quiet;
575 } else {
576 $result = "(dryrun)";
578 pmsg($proj, "$subdir/:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result) unless $quiet;
579 return $result ne "FAILED";
582 sub change_dpermrx {
583 my ($proj, $subdir) = @_;
584 $subdir =~ s,^\./,,;
585 my $oldmode = (stat($subdir))[2];
586 if (!defined($oldmode) || $oldmode eq "") {
587 pwarn "chmod: ($proj) $subdir: No such file or directory\n" unless $quiet;
588 return 0;
590 my $newmode = $oldmode | 0555;
591 $newmode == $oldmode and return 1;
592 my $result = "";
593 if (!$dryrun) {
594 if (!chmod($newmode & 07777, $subdir)) {
595 $result = "FAILED";
596 pwarn "chmod: ($proj) $subdir: $!\n" unless $quiet;
598 } else {
599 $result = "(dryrun)";
601 pmsg($proj, "$subdir/:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result) unless $quiet;
602 return $result ne "FAILED";
605 sub check_fperm {
606 my ($proj, $file) = @_;
607 my $oldmode = (stat($file))[2];
608 if (!defined($oldmode) || $oldmode eq "") {
609 pwarn "chmod: ($proj) $file: No such file or directory\n" unless $quiet;
610 return 0;
612 my $newmode = ($oldmode & ~07777) | $fmode;
613 $newmode == $oldmode and return 1;
614 my $result = "";
615 if (!$dryrun) {
616 if (!chmod($newmode & 07777, $file)) {
617 $result = "FAILED";
618 pwarn "chmod: ($proj) $file: $!\n" unless $quiet;
620 } else {
621 $result = "(dryrun)";
623 pmsg($proj, "$file:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result) unless $quiet;
624 return $result ne "FAILED";
627 sub check_fpermr {
628 my ($proj, $file) = @_;
629 $file =~ s,^\./,,;
630 my $oldmode = (stat($file))[2];
631 if (!defined($oldmode) || $oldmode eq "") {
632 pwarn "chmod: ($proj) $file: No such file or directory\n" unless $quiet;
633 return 0;
635 my $newmode = $oldmode | 0444;
636 $newmode == $oldmode and return 1;
637 my $result = "";
638 if (!$dryrun) {
639 if (!chmod($newmode & 07777, $file)) {
640 $result = "FAILED";
641 pwarn "chmod: ($proj) $file: $!\n" unless $quiet;
643 } else {
644 $result = "(dryrun)";
646 pmsg($proj, "$file:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result) unless $quiet;
647 return $result ne "FAILED";
650 sub check_fpermnox {
651 my ($proj, $file) = @_;
652 $file =~ s,^\./,,;
653 my $oldmode = (stat($file))[2];
654 if (!defined($oldmode) || $oldmode eq "") {
655 pwarn "chmod: ($proj) $file: No such file or directory\n" unless $quiet;
656 return 0;
658 my $newmode = $oldmode & ~0111;
659 $newmode == $oldmode and return 1;
660 my $result = "";
661 if (!$dryrun) {
662 if (!chmod($newmode & 07777, $file)) {
663 $result = "FAILED";
664 pwarn "chmod: ($proj) $file: $!\n" unless $quiet;
666 } else {
667 $result = "(dryrun)";
669 pmsg($proj, "$file:", get_mode_perm($oldmode), '->', get_mode_perm($newmode), $result) unless $quiet;
670 return $result ne "FAILED";
673 sub change_group {
674 my ($proj, $item, $grpid) = @_;
675 $item =~ s,^\./,,;
676 my @info = stat($item);
677 if (@info < 6 || $info[2] eq "" || $info[4] eq "" || $info[5] eq "") {
678 pwarn "chgrp: ($proj) $item: No such file or directory\n" unless $quiet;
679 return 0;
681 $info[5] == $grpid and return 1;
682 my $result = "";
683 if (!$dryrun) {
684 if (!chown($info[4], $grpid, $item)) {
685 $result = "FAILED";
686 pwarn "chgrp: ($proj) $item: $!\n" unless $quiet;
687 } elsif (!chmod($info[2] & 07777, $item)) {
688 $result = "FAILED";
689 pwarn "chmod: ($proj) $item: $!\n" unless $quiet;
691 } else {
692 $result = "(dryrun)";
694 my $isdir = ((($info[2] >> 12) & 017) == 004) ? '/' : '';
695 pmsg($proj, "$item$isdir: group", get_grp_nam($info[5]), '->', get_grp_nam($grpid), $result) unless $quiet;
696 return $result ne "FAILED";
699 my $wrote; BEGIN {$wrote = ""}
700 sub pmsg {
701 my $proj = shift;
702 my $msg = join(" ", @_);
703 $msg =~ s/\s+$//;
704 my $prefix = "";
705 if (!$hdr) {
706 $prefix = $wrote . $proj . ":\n";
707 $hdr = 1;
709 $progress->emit($prefix . " " . join(' ', @_) . "\n");
710 $wrote = "\n";
713 my %ftypes;
714 BEGIN {%ftypes = (
715 000 => '?',
716 001 => 'p',
717 002 => 'c',
718 003 => '?',
719 004 => 'd',
720 005 => '?',
721 006 => 'b',
722 007 => '?',
723 010 => '-',
724 011 => '?',
725 012 => 'l',
726 013 => '?',
727 014 => 's',
728 015 => '?',
729 016 => 'w',
730 017 => '?'
732 my %fperms;
733 BEGIN {%fperms = (
734 0 => '---',
735 1 => '--x',
736 2 => '-w-',
737 3 => '-wx',
738 4 => 'r--',
739 5 => 'r-x',
740 6 => 'rw-',
741 7 => 'rwx'
744 sub get_mode_perm {
745 my $mode = $_[0];
746 my $str = $ftypes{($mode >> 12) & 017} .
747 $fperms{($mode >> 6) & 7} .
748 $fperms{($mode >> 3) & 7} .
749 $fperms{$mode & 7};
750 substr($str,3,1) = ($mode & 0100) ? 's' : 'S' if $mode & 04000;
751 substr($str,6,1) = ($mode & 0010) ? 's' : 'S' if $mode & 02000;
752 substr($str,9,1) = ($mode & 0001) ? 't' : 'T' if $mode & 01000;
753 return $str;
756 sub get_perm {
757 my $mode = (stat($_[0]))[2];
758 defined($mode) or return '??????????';
759 return get_mode_perm($mode);
762 sub get_grp_nam {
763 my $grpid = $_[0];
764 defined($grpid) or return '?';
765 my $grpnm = scalar(getgrgid($grpid));
766 return defined($grpnm) && $grpnm ne "" ? $grpnm : $grpid;
769 sub get_grp {
770 my $grp = (stat($_[0]))[5];
771 defined($grp) or return '?';
772 return get_grp_nam($grp);
775 __END__
777 =head1 NAME
779 update-all-config.pl - Update all projects' config settings
781 =head1 SYNOPSIS
783 update-all-config.pl [<options>] [<projname>]...
785 Options:
786 -h | --help detailed instructions
787 -V | --version show version
788 -n | --dry-run show what would be done but don't do it
789 -f | --force run without a Config.pm owning_group
790 -q | --quiet suppress change messages
792 <projname> if given, only operate on these projects
794 =head1 OPTIONS
796 =over 8
798 =item B<-h>, B<--help>
800 Print the full description of update-all-config.pl's options.
802 =item B<-V>, B<--version>
804 Print the version of update-all-config.pl.
806 =item B<-n>, B<--dry-run>
808 Do not actually make any changes, just show what would be done without
809 actually doing it.
811 =item B<-q>, B<--quiet>
813 Suppress the messages about what's actually being changed. This option
814 is ignored if B<--dry-run> is in effect.
816 The warnings about missing and unknown-to-Girocco projects are also
817 suppressed by this option.
819 =item B<-f>, B<--force>
821 Allow running without a $Girocco::Config::owning_group set. This is not
822 recommended as it results in world-writable items being used (instead of
823 just world-readable).
825 =item B<<projname>>
827 If no project names are specified then I<all> projects are processed.
829 If one or more project names are specified then only those projects are
830 processed. Specifying non-existent projects produces a warning for them,
831 but the rest of the projects specified will still be processed.
833 Each B<projname> may be either a full absolute path starting with
834 $Girocco::Config::reporoot or just the project name part with or without
835 a trailing C<.git>.
837 Any explicitly specified projects that do exist but are not known to
838 Girocco will be skipped (with a warning).
840 =back
842 =head1 DESCRIPTION
844 Inspect the C<config> files of Girocco projects (i.e. $GIT_DIR/config) and
845 look for anomalies and out-of-date settings.
847 Additionally check the existence and permissions on various files and
848 directories in the project.
850 If an explicity specified project is located under $Girocco::Config::reporoot
851 but is not actually known to Girocco (i.e. it's not in the etc/group file)
852 then it will be skipped.
854 By default, any anomalies or out-of-date settings will be corrected with a
855 message to that effect. However using B<--dry-run> will only show the
856 correction(s) which would be made without making them and B<--quiet> will make
857 the correction(s) without any messages.
859 Any projects that have a C<$GIT_DIR/.noconfig> file are always skipped (with a
860 message unless B<--quiet> is used).
862 =cut