#!/usr/bin/perl

# Script to do cvsupdate on ports, while displaying progress, sorted
# from the directories with the fewest ports to the directories with
# the most ports.
#
# Without arguments, it does all. Option -i means check the important
# stuff. Option -u means don't check the important stuff.
# Option -q means don't print progress info.
#
# ToDo: An option to only check individual ports that are installed.

# Written 1 February 2024 by Jim Lippard. version 1.0
# Modified 3 February 2024 by Jim Lippard to use pledge/unveil. version 1.1

use strict;
use Getopt::Std;
use if $^O eq "openbsd", "OpenBSD::Pledge";
use if $^O eq "openbsd", "OpenBSD::Unveil";

my @IMPORTANT = (
    'databases',
    'devel',
    'lang',
    'mail',
    'net',
    'security',
    'www'
    );

my @EXCLUDE = (
    'CVS',
    'distfiles',
    'mystuff',
    'packages',
    'plist',
    'pobj'
    );

my $ANONCVS = 'anoncvs@anoncvs.usa.openbsd.org';

my $CVS = '/usr/bin/cvs';
my $PORTS_DIR = '/usr/ports';
my $START_WD = '/usr';
my $UNAME = '/usr/bin/uname';

my (%opts, $quiet_mode, $arg, @portcats, @files, $file, $portcat,
    $version, $openbsd_version, $cvs_command,
    %port_counts, $chars_printed);

getopts ('iuq', \%opts) || die "Usage: portcheck.pl [-i|-u] [portcats|ports]\n";

$quiet_mode = $opts{'q'};

die "-i and -u are mutually exclusive options.\n" if ($opts{'i'} && $opts{'u'});

if ($#ARGV >= 0) {
    die "-i cannot be used if port categories or individual ports are specified.\n" if ($opts{'i'});
    die "-u cannot be used if port categories or individual ports are specified.\n" if ($opts{'u'});    
    foreach $arg (@ARGV) {
	die "No such port category $arg.\n" if (!-d "$PORTS_DIR/$arg");
	push (@portcats, $arg);
    }
}
elsif ($opts{'i'}) {
    @portcats = @IMPORTANT;
}
else {
    opendir (DIR, $PORTS_DIR) || die "Cannot open $PORTS_DIR. $!\n";
    @files = grep (!/^\.{1,2}$/, readdir (DIR));
    closedir (DIR);
    foreach $file (@files) {
	next if ($opts{'u'} && grep (/^$file$/, @IMPORTANT));
	next if (grep /^$file$/, @EXCLUDE);
	push (@portcats, $file) if (-d "$PORTS_DIR/$file");
    }
}

# Get current system version.
$version = `$UNAME -a`;
chop ($version);
if ($version =~ /OpenBSD [\w\.]+ (\d+)\.(\d+)/) {
    $openbsd_version = 'OPENBSD_' . $1 . '_' . $2;
    $cvs_command = "$CVS -qd $ANONCVS:/cvs up -Pd -r$openbsd_version ports";
}
else {
    die "Cannot identify system version. $version\n";
}

chdir ($START_WD);

# Pledge and unveil.
# Can't touch anything except ports and cvs command.
if ($^O eq 'openbsd') {
    pledge ('rpath', 'wpath', 'cpath', 'exec', 'proc', 'unveil');
    unveil ($PORTS_DIR, 'rwc');
    unveil ($CVS, 'rx');
    unveil ();
 }

# Count ports per category.
foreach $portcat (@portcats) {
    opendir (DIR, "$PORTS_DIR/$portcat") || die "Cannot open $PORTS_DIR/$portcat. $!\n";
    @files = grep (!/^\.{1,2}$/, readdir (DIR));   
    closedir (DIR);
    $port_counts{$portcat} = $#files;
}

print "Updating ports tree:\n" unless ($quiet_mode);
foreach $portcat (sort numerically keys (%port_counts)) {
    print " " if ($chars_printed > 0 && !$quiet_mode);
    print "$portcat" unless ($quiet_mode);
    $chars_printed += length ($portcat) + 1;
    if ($chars_printed > 75 && !$quiet_mode) {
	$chars_printed = 0;
	print "\n";
    }
    system "$cvs_command/$portcat";
}
print ".\n" unless ($quiet_mode);

# Sort numerically.
sub numerically { $port_counts{$a} <=> $port_counts{$b}; }
