#!/usr/bin/perl


my $AFS_ADMIN_VERSION = '1.25 Aug 22, 2007';
use vars qw ( $opt_h $opt_n $opt_v $opt_d $opt_o $opt_p $opt_q $opt_S $opt_w $opt_c
	      $arcserver $arc $pts $fs $obsolete $skip_volcheck $cmdlevel $whoami %names @volsets);
(my $program = $0) =~ s,.*/,,;
################################################################################
# main program, do option processing and call subcommand 
################################################################################
use lib "/afs/cern.ch/project/afs/perlmodules";
use Vos;
use Volset;
use Cwd;
use strict;
use Fcntl;

# the name and root directories of our cell, the username
my $rootdir = homedir();
$rootdir =~ s|/\w+$||;
(my $rwrootdir = $rootdir) =~ s|^/afs/|/afs/.|;
(my $cell = $rootdir) =~ s|^/afs/||;

    
# use vos examine instead of vos listvol
exa_single_volumes(1);
#
# set ENV and PATH for safety reasons
#
$ENV{ENV} = '';
$ENV{PATH} = "/usr/sue/bin:/usr/bin:/bin:/usr/local/bin:/usr/sbin:/usr/kerberos/bin";

#
# configuration parameters with CERN specific fallbacks
#
$arcserver = (get_afsservers('arc'))[0] || 'afsarc' unless $arcserver;
$Volset::arcserver = $arcserver;
$arc = fullpath('arc', $Volset::PGMLOC);
$pts = fullpath('pts', $Volset::PGMLOC);
$fs  = fullpath('fs',  $Volset::PGMLOC);


#
# remember which R/O volumes have been affected
#
my @release_at_end = ();


#
&choose_cmdlist;            # decide user authority level & choose command list accordingly
  
use Getopt::Long;
# option bundling allowed, options have to precede arguments
Getopt::Long::Configure('bundling', 'require_order', 'no_getopt_compat', 'prefix=-');
my $rc = GetOptions ('d', 'h', 'n', 'v:i', 'S:s');
my @cmds = normalize_args($ARGV[0]) if $ARGV[0] and ! $opt_h;
$opt_h = 1, shift if $cmds[0] and $cmds[0] eq 'help';
if ( $opt_h or ! $rc or ! @ARGV ) {
  @ARGV = normalize_args(@ARGV) if $opt_h;
  usage (@ARGV);
}
shift;

# need precisely one element in @cmds, otherwise it is an error
exit 1 if $#cmds;
my $cmd = $cmds[0];
#
# parse options after the subcommand, allow also the global options from above
#
Getopt::Long::Configure('permute', 'bundling');
GetOptions('d', 'h', 'n', 'v:i', @{$names{$cmd}} )
	  or usage ($cmd);
usage ($cmd) if $opt_h;

$opt_v = 1 if defined $opt_v and ! $opt_v;
Vos::verbose($opt_v) if $opt_v;
Volset::verbose($opt_v) if $opt_v;
use_arc(1); # use arc to get privileges
noaction(1) if $opt_n;
$arcserver = $opt_S if $opt_S;

print "$program version $AFS_ADMIN_VERSION ( debug level $opt_v )\n",
      "executing $cmd\n" if defined $opt_v;


no strict;
# avoid potential conflicts with perl's delete function
$cmd = delete_all if $cmd eq 'delete';
my $GLOBAL_RC = 0;
&$cmd(@ARGV);

# post-processing
my %released;
foreach (@release_at_end) {
   my ($v,$m) = split /,/, $_;
   next if $released{$v}++;
   if ($m) {
      &vol_release($v,$m);
   } else {
      &vol_release($v);
   }
}

exit $GLOBAL_RC;
#
### end of main program ###
#
################################################################################
#
# usage - print usage information, be more verbose if $opt_v is set
#	  print selective usage info if names of subcommands given as arguments
#	  generate all information from the appended pod data
#
sub usage {

  if ( defined $opt_v and ! @_ ) {
    system "perldoc $0";
    exit;
  } 

  my $cont = 0;
  print "This is $program version $AFS_ADMIN_VERSION\n","="x80,"\n" if ! $opt_h and ! @_;
  my $skip = 1;
  while ( <DATA> ) {
    if ( /^afs_admin \[B/ ) {
      s/B<|>//g;
      print "Usage:  ${_}Options:\n" if ! defined $opt_v and ! @_;
    }
    print if s/^=item B<(.*)>/\t$1/ and ! defined $opt_v and ! @_;
    next if $skip and ! /^=head1 SUBCOMMANDS/;

    print <<SUB if /^=head1 SUBCOMMANDS/ and ! defined $opt_v and ! @_;
Subcommands:	(can be abbreviated, e.g. set_a or s_a means set_acl)
SUB
    last if /^=head1 WARNINGS/;
    $skip = 0;

    if ( /^=for cmdlevel (\d)/ ) {
       $valid = ($1 <= $cmdlevel);
       next;
    }
    next unless $valid;

    if ( @_ ) {
      $cont = 0 if /^$/;
      print if $cont and (defined $opt_v or ! $#_);
      my $x = $_;
      $cont = 1, print "$x" if grep { $x =~ /^ $_\b/ } @_;
    } else {
      print if s/^ (\w)/\t$1/;
    }
  }
  exit;
}
################################################################################
#
# choose_cmdlist - get the right list of valid commands depending on the caller's authority
#
sub choose_cmdlist {
#
# all the valid subcommands 
# there are 3 levels of commands:  lambda user, project administrator & super-admin
#
  my %names_l = (  'help' , [ ] ,
                   'recover' , [ ] ,
                   'salvage' , [ "o:s" , "w:i" ] ,
                   'list_acl' , [ ] ,
                   'list_occupancy' , [ "q:s" ] ,
                   'list_project' , [ "q:s" ] ,
                   'list_quota' , [ ] ,
                   'set_owner' , [ "r" , "f" ] ,
                   'set_acl' , [ "r" , "f" , "c" ] ,
                   'clean_acl' , [ "r" ] ,
                   'check_acl' , [ "r" ] ,
                   'vos_release' , [ ]
                  );
  my %names_a = (  'clear_parameters' , [ ] , 
                   'create' , [ "q:s" , "u:s" , "p:s" , "a:s" , "r:s" ] ,
                   'create_mount' , [ ] , 
                   'create_replica' , [ "p:s" ] ,
                   'create_scratch' , [ "q:s" , "u:s" , "p:s" , "w:i" ] ,
                   'create_volume' , [ "p:s" ] ,
                   'create_workspace' , [ "q:s" , "u:s" , "p:s" , "w:i" ] ,
                   'delete' , [ ] , 
                   'delete_mount' , [ ] , 
                   'delete_replica' , [ "p:s" ] ,
                   'delete_scratch' , [ "u:s" , "w:i" ] ,
                   'delete_volume' , [ "p:s" ] ,
                   'delete_workspace' , [ "u:s" , "w:i" ] ,
		   'list_parameters' , [ ] , 
                   'move_volume' , [ "p:s" ] ,
                   'rename' , [ ] , 
                   'rename_mount' , [ ] , 
                   'rename_volume' , [ ] ,
		   'set_parameter' , [ "a:s" , "r:s", "s" ] ,
                   'set_quota' , [ ]
                  );
  my %names_s = (  'create_project' , [ "q:s" , "u:s" ] ,
                   'delete_project' , [ ]
                  );
  
# decide which command set to use
  my $admined;
  ($whoami) = &whoami(); 
  if ($whoami) {
    $admined = `$pts members $whoami 2>/dev/null`;
  } else {
    $admined = "";
  }
  unless ($admined) {
    $admined = $whoami = "unknown";
    $arc .= " -n";
  }
  if ( $admined =~ /\s+system:administrators\n/
    || $admined =~ /\s+afs:afs_admin\n/
     )  {    # super-admin
    %names = ( %names_l , %names_a, %names_s );
    $cmdlevel = 3;
  } 
  elsif ($admined =~ /\s_\S+_\n/) {  # some sort of administrator: _<proj>_   
    %names = ( %names_l , %names_a );
    $cmdlevel = 2;
  } 
  else {
    %names = %names_l;                  # at least that
    $cmdlevel = 1;
  }
}


################################################################################
#
# normalize_args - get the unabbreviated command names
# returns 0 (invalid cmd) 1 (expanded cmd) or more (ambig) names per input arg
#
sub normalize_args {
  my %abbrev = (  la => 'list_acl',
                  sa => 'set_acl',
                  lq => 'list_quota',
                  sq => 'set_quota',
               );
  my %prefix = map {$_,1} map { (/(^.*)_/) } keys %names;
  my @args;
  for my $pat (@_) {
    my @hits;
    my ($pre, $post) = split(/_/, $pat, 2);
    if ( $pat ne $pre ) {
      my @prehits = grep (/^$pre/, keys %prefix);
      for my $ph ( @prehits ) {
        my $p = $ph."_$post";
        push @hits, grep { /^$p/ } keys %names;
      }
    } elsif ( defined $abbrev{$pat} ) {
      @hits = ( $abbrev{$pat} );
    } else {
      @hits = grep { $_ !~ /_/ } grep { /^$pat/ } keys %names;
    }
    _print 'E', "unknown command $pat" unless @hits;
    _print 'W', "ambiguous command $pat: @hits" if $#hits > 0;
    push @args, @hits;
  }
  @_ = @args;
}
######################## start of functions for subcommands ####################
#
# create - choose a partition and create a volume and a mount point
#
#  either called directly ($program create ... ). or from create_scratch or create_workspace
#                                                 in which case $owner is provided
sub create {
  usage("create") unless ($#_ == 1 or $#_ == 2);
  my ($mp, $vol, $owner) = (@_, 0);
  _print "arguments \$mp=$mp, \$vol=$vol, \$owner=$owner, \$opt_q=$opt_q, \$opt_u=$opt_u, \$opt_a=$opt_a, \$opt_r=$opt_r" if $opt_v;

# try to catch common errors early, so we do not even create the volume
# 1 -  path & volume parameters
  return unless check_path(\$mp);                       # to catch invalid characters
  return unless check_volume($vol,0);    # error if name invalid or volume exists
  return unless check_vol2($vol);        # more checks for volume create
  my @p0 = vol2set($vol);
  my @p1 = check_mount(\$mp) or return undef;
  my $pvol = parentvol($mp);
  @volsets = grep { my $a=$_; grep {$_ eq $a} @p0} @p1;
  _print "create volume and mountpoint for project $volsets[-1]"
        if $opt_v and $volsets[-1];
# 2 - '-u' option
  my @admins = ();
  if ( ! defined $owner && defined $opt_u) {
     my @u = split (/,/, $opt_u);
     $opt_u = "";
     foreach (@u) {
        my ($o) = ckown($_,1);            # valid a pts entry
        push @admins, $o if $o;
     }
  }
# 3 - '-q' option
  if($opt_q) {
    my $d_q= decode_quota($opt_q);
    unless ($d_q) {
       _print 'E', "invalid quota '$opt_q' defined";
       $opt_q = 0;
    } elsif ($d_q <= 0) {
       _print 'E', "quota must be a positive number";
       $opt_q = 0;
    } else {
       $opt_q = encode_quota($d_q);
    }
  }
  # $skip_volcheck is used only for create_project
  if ( ! @volsets and ! $skip_volcheck ) {
    _print 'E', "volume name and mount point not in same project",
          "\n\tvolume $vol in @p0",
          "\n\tmount point $mp in @p1";
  } elsif ( -e $mp ) {
    _print 'E', "mountpoint $mp already existing";
  } elsif ( ! $pvol ) {
    _print 'E', "no parentvol for mount point $mp found";
  } elsif ( blocks($pvol) >= quota($pvol) ) {
    _print 'E', "parentvol $pvol over quota, mount would fail";
  } elsif ( create_volume($vol) ) {
    if ( create_mount($mp, $vol) ) {
      unless ($opt_n) { 
        my $i = 5; my $j = 1;
        while ( $i-- and ! -e $mp ) { sleep($j); $j *= 2; }
      }
      if (-e $mp or $opt_n) {
        # set_quota($mp, $quota);            # done in create_volume
        set_owner($owner, $mp) if $owner;
        my $acls = "";
        $acls .= "$opt_u all " if $opt_u;
        $acls .= "_${volsets[-1]}_ all "
          if ($volsets[-1]) and ! (system "$pts exa _${volsets[-1]}_ >/dev/null 2>&1");
        foreach (@admins) {
          $acls .= "$_ all ";
        }
        arc_execute(qw(pv fs setacl), $mp, $acls) if $acls;
        chmod 0755, $mp;                   # UNIX perms might be wonky otherwise; RQF0044285
      }
      _print "Please check the ACL settings below:" if ! $opt_n;
      arc_execute(qw(pv fs listacl), $mp) if ! $opt_n;
      print "You might have to set further ACL's using $program set_acl\n";
    }
  } else {
    _print 'W', "mount point $mp not created, as creation of $vol failed";
  }
}

sub parentvol {
  my $mnt = shift;
  my $vol;
  $mnt =~ s/\/$//;
  while ( $mnt =~ s/\/([^\/]*)$// ) {
    if ( -r $mnt ) {
      $vol = path2vol($mnt);
      last;
    }
  }
  return $vol;
}
################################################################################
#
# create_mount - create a mount point for an existing volume
#
# arguments: mount_point volume [mount_point volume]+
#
sub create_mount {
  usage('create_mount') unless @_;
  my $rc = 1;
  while ( @_ ) {
    my $mount = shift;
    my $vol = shift;
    if ( $mount !~ /\// and $vol =~ /\// ) {
      ($mount, $vol) = ($vol, $mount);
      _print 'W', "interchanged parameters $mount and $vol";
    }
    my @p0 = check_mount( \$mount);
    unless (@p0) {
      $rc = 0;
      next;
    }
    if ( -e $mount ) {
      _print 'E', "mount point $mount already existing";
      $rc = 0;
      next;
    }
    unless(check_volume($vol,1) or $opt_n) {
      $rc = 0;
      next;
    }
    my @p1 = vol2set($vol);
    unless ( grep { my $a=$_; grep {$_ eq $a} @p0} @p1 ) {
      _print 'E', "path & volume names do not belong to same project",
            "\n\t$mount in @p0\n\t$vol in @p1\n";
      $rc = 0;
      next;
    }
    my $parentvol = prepare_mount($mount);
    next unless $parentvol;
    _print "mounting $vol at $mount" if ! $opt_n;
    arc_execute(qw(pv fs mkmount), $mount, $vol) if $mount and $vol;
    if ( get_volattrib($parentvol, 'ROVol') ) {
      push @release_at_end, "$parentvol" if -e $mount or $opt_n;
    } else {
      $rc = 0 if ! $opt_n and ! -e _;
    }
  }
  $GLOBAL_RC = 1 if $rc == 0;
  return $rc;
}
################################################################################
#
# create_project - create a project definition, a root volume, mount point and
#                  a file containing the Kerberos principals to administer it
#
# arguments: -q <quota> [-u <user>] <project> [<mountpoint_pattern>]
#
sub create_project {
  $skip_volcheck = 1;
  my $proj = $_[0];
  my $veto = 2;
  my $quota;
  if (is_volset($proj)) {
    _print 'E', "project $proj already existing";
  } elsif (Vol_Do::get_acl(lc($proj))) {
      _print 'E', "project $proj only differs by case from an already existing project";
  } else {
    $veto--;
  }
  unless ($opt_q) {
    _print 'E', "quota for project $proj not set";
  } else {
    my $d_q= decode_quota($opt_q);
    unless ($d_q) {
       _print 'E', "invalid quota '$opt_q' for project $proj";
    }
    $quota = encode_quota($d_q);
    if ( $opt_q <= 0 ) {
      _print 'E', "quota must be a positive number";
    } else {
      $veto--;
    }
  }
  usage('create_project') if $#_ < 0 or $#_ > 1 or $veto;
  my $w = $opt_n ? " would" : "";
  _print "$w create project $proj in afsadmin.cf";
  open CF, $Volset::CFLOC or die "Could not open $Volset::CFLOC:$!\n";
  my @new_file = ();
  my $in_volset = 0;
  local $_;
  my $mp = $_[1] || "project/$proj";
  while (<CF>) {
    push @new_file, $_;
    $in_volset = 1, next if /^\[AFSVOLSET\]/;
    next unless $in_volset;
    next if /^#/;
    $_ = sprintf "%-10s %6s %s\n", $proj, $quota, $mp;
    _print "$w add in afsadmin.cf section [AFSVOLSET] the line\n$_"
          if $opt_v;
    push @new_file, $_;
    $in_volset = 0;
  }
  close CF;
  my $comment = "added project $proj";
  update_file($Volset::CFLOC, \@new_file, $comment) if ! $opt_n;
  undef $Volset::Init_done;                      # force re-read of afsadmin.cf

  _print "W", "\n\noption -o now obsolete, use -u instead\n" if $opt_o;
    my @admins;
  unless ( $opt_u ) {
     _print "W", "no -u option, ACLs will remain empty" ;
  } else {
     my @u = split (/,/, $opt_u);
     $opt_u = 0;
     foreach (@u) {
        my ($o) = ckown($_,1);            # valid a pts entry
        push @admins, $o if $o;
     }
     $opt_u = "";
  }
  arc_execute(qw(pv acl add), $proj, @admins);
  $opt_q = undef;
  $mp = "$rwrootdir/$mp";
  my $vol = "p.$proj.root";
# now do the real work
  create($mp, $vol);
# arc_execute(qw(pv fs setacl), $mp, _${proj}_, all);
}
################################################################################
#
# create_replica - choose a partition and create replica for the volume
#
# arguments: [-p server] volume+
#        or: volume server/partition [volume server/partition]+
#
sub create_replica {
my $msg = <<EOM;
### to maintain backward compatibility: ####################################
# without specifying server and/or partition only a vos release is done
# to create a replication site on any suitable partition please use the
# option -p ' ' for the moment
############################################################################
EOM
  usage("create_replica") unless @_;
  while ( @_ ) {
    my $vol = shift;
    $vol =~ s/\.readonly$//;
    my $to;
    if ( $_[0] and $_[0] =~ /\// ) {
      $to = shift;
    } else {
      $to = $opt_p;
    }

    next unless check_volume($vol,1);


    &vol_create_replica($vol, $to);
  }
}
################################################################################
#
# create_volume - choose a partition and create a volume
#
# arguments: [-p pool|server[/part]] volume+
#        or: volume server/partition [volume server/partition]+
#
sub create_volume {
  usage('create_volume') unless @_;
  my $rc = 0;      # 0=bad, 1=success
  while ( @_ ) {
    my $vol = shift;
    $vol =~ s/\.readonly$//;
    local @volsets = vol2set($vol);      # outside @volsets too restrictive
    my $to;
    if ( defined $_[0] and $_[0] =~ /\// ) {
      $to = shift;
    } else {
      $to = $opt_p;
    }
    last unless check_volume($vol,0);    # error if name invalid or volume exists
    last unless check_vol2($vol);        # more checks for volume create
    if ($volsets[0] eq 'unknown') {
      _print 'W', "illegal volume name, no match in [VOLUMEPATTERNS]";
      next;
    }
    _print "$vol " , ($opt_n) ? "would" : "will" , " belong to project(s) @volsets" if $opt_v;
    my $quota = $opt_q || volsetopts('newquota', reverse @volsets);

    # make the -a and -r settings permanent
    if ($opt_a) {
       my @A = ();
       push @A, "with";
       push @A, "$opt_a";
       push @A, "--server" if $opt_s;
       arc_execute(qw(pv set_parameter),
                $vol,
                @A);
    } 
    if ($opt_r) {
       my @A = ();
       push @A, "not_with";
       push @A, "$opt_r";
       push @A, "--server" if $opt_s;
       arc_execute(qw(pv set_parameter),
                $vol,
                @A);
    } 
 
    if (&vol_create($vol,$to,$quota)) {         # if rc != 0

      _print 'W', "Command creating volume failed (see below for errors)" unless $opt_n;
    }
    my $lrc = $opt_n || ( vos_listvldb($vol)  );
    _print 'W', "volume $vol NOT created" unless $lrc;
    $rc += $lrc;             #  add '1' only if everything ok so far
  }
  $GLOBAL_RC = 1 unless $rc > 0;
  return $rc;
}
################################################################################
#
# create_scratch - choose a partition and create a personal scratch workspace
# create_workspace - choose a partition and create a personal workspace
#
sub create_scratch {
  my $num = $opt_w || 0;
  usage("create_scratch") if $num !~ /^\d$/ or $#_ != 0;
  create_workspace_or_scratch ('scratch', 's', @_);
}
sub create_workspace {
  my $num = $opt_w || 0;
  usage("create_workspace") if $num !~ /^\d$/ or $#_ != 0;
  create_workspace_or_scratch ('w', 'u', @_);
}
sub create_workspace_or_scratch {
  my $num = 0;
  my ($mntprefix, $volprefix, $what) = @_;
  if ( ! is_volset($what) ) {
    _print 'E', "$what is not a valid project";
    return undef;
  }
  if (defined $opt_w) {
    unless (($num) = $opt_w =~ /^(\d+)$/) {
      _print 'E', "$opt_w invalid. Must be a number";
      return undef;
    }
  }
  my ($_user, $owner) = ckown($opt_u) if defined $opt_u;        # expect user[:group]
  unless (defined $_user) {
      _print 'E', "-u option not specified or invalid";
      return undef;
  }
  $opt_u = $_user;

  (my $w2 = $what) =~ s/_\w$//;
  if ( $w2 ne $what ) {
    my $res = arc_execute2(qw(pv acl list), $what);
    unless( $res) {
      _print 'W', "no ACL for project $what, using $w2 instead";
      $what = $w2;
    }
  }
  my $vol = "$volprefix.$what.$_user.$num";
  my $mp = homedir($_user);
  if ( ! -e $mp ) {
    _print 'E', "home directory $mp not existing";
    return undef;
  }
  $mp .= "/$mntprefix$num";
  if ( defined $opt_w ) {
# number given, verify its correctness;
    if ( -e $mp ) {
      _print 'E', "mountpoint $mp already existing";
      return undef;
    } elsif ( vos_listvldb($vol)  ) {
      _print 'E', "volume $vol already existing";
      return undef;
    }
  } else {
# find out which number we could use
    while ( $num < 10 ) {
      if ( -e $mp or vos_listvldb($vol)  ) {
        $num++;
        $vol = "$volprefix.$what.$_user.$num";
        $mp = homedir($_user) . "/$mntprefix$num";
      } else {
        last;
      }
    }
  }
  create($mp, $vol, $owner);
}
################################################################################
#
# delete - delete both mount point and volume
# arguments: directory+
#
sub delete_all {
  usage ('delete') unless @_;
  for my $dir ( @_ ) {
    if ( -e $dir ) {
      my $volname = path2vol($dir);
      if ( $volname ) {
        my $rc = delete_mount($dir);
        delete_volume($volname) if $rc;
      }
    } else {
      _print 'E', "mount point $dir not existing or unreadable",
             "\n\tUse $program delete_mount to delete mountpoints leading to unexisting volumes";
    }
  }
}
################################################################################
#
# delete_mount - delete a mount point
# arguments: directory+
#
sub delete_mount {
  usage('delete_mount') unless @_;
  my $rc = 1;
  for my $m ( @_ ) {
    if ( ! -e $m ) {
      _print 'W', "mount point $m not existing or unreadable";
    }
    unless ( check_mount(\$m) ) {
      $rc = undef;
    } else {
      my $mnt = arc_execute2(qw(pv fs lsmount), $m);
      if ( $mnt =~ /is a mount point/ ) {
        _print "removing mountpoint $m for ", path2vol($m), "" 
              if $opt_v or ! $opt_n;
# as the volume could be missing, or invalid, try a local rmm, 
# which only relies on current ACLs in the parent directory.
        system("$fs rmmount $m 2>/dev/null") unless $opt_n;
# then try it the long way
        arc_execute(qw(pv fs rmmount), $m) if $? or $opt_n;
# get rid of trailing slash, if any
        $m =~ s/\/$//;
        my $path = substr $m, 0, rindex($m, '/');
        if ( -r $path ) {
          my $pvol = path2vol($path);
          $pvol =~ s/\.readonly$//;
          if ( get_volattrib($pvol, 'ROVol') ) {
            push @release_at_end, "$pvol,$path";
          }
        }
      } else {
        _print 'E', "removing $m failed:\n\t$mnt";
        $rc = undef;
      }
    }
  }
  return $rc;
}
################################################################################
#
# delete_project - delete a project definition (needs administrator privs) 
# arguments: project
#
# A project cannot be deleted by the project admins themselves
#
sub delete_project {
  _print 'E', "project $_[0] not existing" if ! is_volset($_[0]);
  usage('delete_project') if $#_ != 0 or ! is_volset($_[0]);
  my $w = $opt_n ? " would" : "";
  _print "$w delete project $_[0] from afsadmin.cf";
  open CF, $Volset::CFLOC or die "Could not open $Volset::CFLOC:$!\n";
  my @new_file = ();
  my $in_volset = 0;
  local $_;
  while (<CF>) {
    push @new_file, $_;
    _print 'E', "$_[0] not found in $Volset::CFLOC"
          if /^\[/ and $in_volset;
    $in_volset = 1 if /^\[AFSVOLSET\]/;
    next unless $in_volset;
    next unless /^$_[0]\b/;
    pop @new_file;
    _print "$w delete in afsadmin.cf section [AFSVOLSET] the line\n$_"
          if $opt_v;
    $in_volset = 0;
  }
  close CF;
  my $comment = "deleted project $_[0]";
  update_file($Volset::CFLOC, \@new_file, $comment) if ! $opt_n;

  arc_execute(qw(pv acl delete), $_[0]);
  _print "delete_project will NOT remove any mount points, volumes ",
        "etc.\n\t   belonging to the project $_[0], this can be done using",
        "\n\t   other $program commands\n"; 
}
################################################################################
#
# delete_replica - delete one of the replicas
# arguments: volume+
#        or: volume server/partition [volume server/partition]+
#
sub delete_replica {
  usage('delete_replica') unless @_;
  while ( @_ ) {
    my $vol = shift;
    $vol =~ s/\.readonly$//;
    my $to;
    if ( defined $_[0] and $_[0] =~ /\// ) {
      $to = shift;
    } else {
      $to = $opt_p;
    }
    $part = get_volattrib($vol, 'Partition');
    my $sites = get_volattrib($vol, 'Sites');
    next unless $sites > 1;
    if ( (! defined $to) or ($to eq "") or ($to !~ /\//) ) {
# just get the partition list
      if ( (defined $to) and ($to) ) {
        ($to) = grep { $_ =~ /^$to\// } @$part;
      } else {
# delete first RO sites living on partitions different from the RW site
        $sites-- if $sites > 2 and $part->[$sites-1] eq $part->[0];
        $to = $part->[$sites-1];
# prefer partitions to be drained
        my %drain = drain_partition();
        my @drain = grep { defined $drain{$_} } @$part;
        $to = $drain[0] if $drain[0];
      }
    } elsif ( ! grep { $_ eq $to } get_pooldisks() ) {
      _print 'E', "partition $to not found in the config file";
      next;
    } elsif ( ! grep { $_ eq $to } @$part ) {
      _print 'W', "$to is not a RO site for $vol";
    }
    if ( ($part->[0] eq $to or $part->[0] =~ /^$to\//) and $sites != 2) {
      _print 'E', "ignored request to remove replica from RW site";
      next;
    }
    $vol .= '.readonly' unless $vol =~ /\.readonly$/;
    _print 'W', "no suitable replica for $vol to delete" unless $to;
    &vol_delete($vol, $to) if $to;
  }
}
################################################################################
#
# delete_volume - delete volume
# arguments: volume server/partition [volume server/partition]+
#        or: volume+
#
sub delete_volume {
  usage('delete_volume') unless @_;
  while ( @_ ) {
    my $volorig = shift;
    (my $vol = $volorig) =~ s/\.readonly$//;
    my $to;
    if ( defined $_[0] and $_[0] =~ /\// ) {
      $to = shift;
    } else {
      $to = $opt_p;
    }
    check_volume($vol,1);         # send error message if no such volume;
# just get the partition list
    my $part = get_volattrib($vol, 'Partition');
    if ( ! $part ) {
# volume seems not to be in the VLDB, try to remove the volume anyway
      next unless $to =~ /\//;
      &vol_delete($volorig,$to);
    } elsif ( $to ) {
# server or server/partition specified, interpret that as delete_replica
      delete_replica($vol, $to);
    } else {
# the RW volume is the first in the list
      my $rw = shift @$part;
# do we have to delete a backup volume ?
      @$part = () if $vol =~ /\.backup$/;
# start removing readonly volumes first
      _print 'W', "volume $volorig not found in VLDB"
            if $vol ne $volorig and ! @$part;
      for my $p ( @$part ) {
        &vol_delete("$vol.readonly", $p);
      }
      &vol_delete($vol, $rw) if $vol eq $volorig;
    }
  }
}
################################################################################
#
# delete_scratch - delete a personal scratch space
# delete_workspace - delete a workspace
# arguments: directory+
#        or: -u user -w{0..9} volset
#
sub delete_scratch {
  usage('delete_scratch') unless @_;
  delete_workspace_or_scratch ('scratch', @_);
}
sub delete_workspace {
  usage('delete_workspace') unless @_;
  delete_workspace_or_scratch ('w', @_);
}
sub delete_workspace_or_scratch {
  my $mntprefix = shift;
  my $rc = 1;
  my $num = 0;
  if (defined $opt_w) {
    unless (($num) = $opt_w =~ /^(\d+)$/) {
      _print 'E', "$opt_w invalid. Must be a number";
      return undef;
    }
  }
  for my $dir ( @_ ) {
    if ( $dir !~ /\// ) {
      my ($_user) = ckown($opt_u) if defined($opt_u);           # expect user
      if ( ! defined $_user ) {
        _print 'E', "both -u user and -w{0..9} have to be given";
        $rc = undef;
        next; 
      }
      $dir = homedir($_user) . "/$mntprefix$num";
    }
    delete_all($dir);
  }
  return $rc;
}
################################################################################
#
# list_acl - list acls for an AFSVolset (or a directory if arg contains slash)
# arguments: [directory|volset]+
#
sub list_acl {
  for ( @_ ) {
    if ( -e $_ and ! is_volset($_) ) {
      next unless check_path(\$_);
      arc_execute(qw(pv fs listacl), $_);
    } else {
      if ( ! is_volset($_) ) {
        _print 'E', "$_ is not a valid path or project";
      } else {
        print "Administrators for $_: ", (arc_execute2(qw(pv acl list), $_) || "** none **"), "\n";
      }
    }
  }
  return if @_;
  print "AFS Project    Mount point pattern (without leading $rootdir)\n";
  my %h = get_volsets();
  for (sort keys %h) {
    printf "%-15s%s\n", $_, $h{$_};
  }

}
################################################################################
#
# list_occupancy - show used / committed space for volumes belonging to project
# arguments: [project]+
#
sub list_occupancy {
  if ( $opt_q ) {
    die "option -q should specify a percentage\n" unless $opt_q =~ /^\d+$/ and $opt_q <= 100;
    arc_execute(qw(pv_list -v -d -q), $opt_q, "-a", $_[0]);
  } else {
    arc_execute(qw(pv_list -v -d -a), $_[0]);
  }
}
################################################################################
#
# list_quota - list quota for an AFSVolset or a volume
# arguments: [directory|volset]+
#
sub list_quota {
# for all args in turn
  for ( @_ ) {
    if ( -e $_ and ! is_volset($_) ) {
      next unless check_path(\$_);
      arc_execute(qw(pv fs listquota), $_);
    } else {
      if ( ! is_volset($_) ) {
        _print 'E', "$_ is not a valid path or project";
      }
      arc_execute(qw(pv_list -a), $_);
    }
  }
# no args given
  arc_execute(qw(pv_list)) unless @_;
}
################################################################################
#
# list_project - show used and committed space for volumes belonging to project
# arguments: [project]+
#
sub list_project {
  $| = 1;
  if ( $opt_q ) {
    die "option -q should specify a percentage\n" unless $opt_q =~ /^\d+$/ and $opt_q <= 100;
  }
  for ( @_ ) {
    if ( ! is_volset($_) ) {
      _print 'W', "$_ is not a valid path or registered project";
    }
    arc_execute(qw(pv_list -a), $_);
    printf "\n%-10s %s", "Project", "administrators";
    printf "\n%-10s %s", "_______", "______________";
    printf "\n%-10s %s\n", $_, (arc_execute2(qw(pv acl list), $_) || "** none **");
    print "\n";
    arc_execute(qw(pv_list -s -a), $_);
    list_occupancy($_);
  }
  arc_execute(qw(pv_list -s)) unless @_;
}
################################################################################
#
# move_volume - move volumes to other partitions 
# arguments: [-p server[/part]] volume+
#        or: volume server/partition [volume server/partition]+
#
sub move_volume {
  usage('move_volume') unless @_;
  while ( @_ ) {
    my $vol = shift;
    my $to;
    if ( defined $_[0] and $_[0] =~ /\// ) {
      $to = shift;
    } else {
      $to = $opt_p;
    }
    volmove($vol, $to);
  }
  return;
}
################################################################################
#
# rename - rename a volume that is mounted
# arguments: oldpath newname
#
sub rename {
  usage('rename') unless $#_ == 1;
  my ($mp, $nvol) = @_;
  check_mount(\$mp) or return;
  my $ovol = path2vol($mp);
  my @p0 = vol2set($ovol);
  my @p1 = vol2set($nvol);
  unless ( grep { my $a=$_; grep {$_ eq $a} @p0} @p1 ) {
    _print 'E', "volume names do not belong to same project",
          "\n\t$ovol in @p0\n\t$nvol in @p1\n";
    return undef;
  }
  return undef unless $ovol;
  return undef unless delete_mount($mp);
  return undef unless rename_volume($ovol, $nvol);
  create_mount ($mp, $nvol) unless $opt_n;
  arc_execute(qw(pv fs mkmount), $mp, $nvol) if $opt_n;
}
################################################################################
#
# rename_mount - move volume from one location to another
# arguments: frompath topath
#
sub rename_mount {
  usage('rename_mount') unless $#_ == 1;
# count the number of mount patterns, which do match all mount points
  my @p0 = check_mount(\$_[0]) or return;
  my @p1 = check_mount(\$_[1]) or return;
  if ( grep { my $a=$_; grep {$_ eq $a} @p0} @p1 ) {
    arc_execute(qw(pv mv), @_);
    my $p1 = parentvol($_[1]);
    my $p0 = parentvol($_[0]);
    push @release_at_end, "$p1,$_[1]" if get_volattrib($p1, 'ROVol') ;
    push @release_at_end, "$p0,$_[0]" if get_volattrib($p0, 'ROVol') ;
  } else {
    _print 'E', "mountpoints do not belong to same project",
          "\n\t$_[0] in @p0\n\t$_[1] in @p1\n";
  }
  `$fs checkv`;
}
################################################################################
#
# rename_volume - rename volume
# arguments: oldname newname
#
sub rename_volume {
  usage('rename_volume') unless $#_ == 1;
  my @p0 = vol2set($_[0]);
  my @p1 = vol2set($_[1]);
  unless ( my @c = examine($_[0])) {
    _print 'E', "volume $_[0] is not existing in the VLDB";
  } elsif ( grep { my $a=$_; grep {$_ eq $a} @p0} @p1 ) {
    print <<EOF;
$program: (WARN) This operation will invalidate all mount points associated
        with volume $_[0]. If you did not remove all the mount points
        then for still existing ones please issue the command
                $program delete_mount <mount_point_path>
EOF
    &vol_rename(@_);
    return 1;
  } else {
    _print 'E', "volume names do not belong to same project",
          "\n\t$_[0] in @p0\n\t$_[1] in @p1\n";
  }
  return undef;
}
################################################################################
#
# clean_acl - clean acls for a directory (& maybe its subdirectories)
# check_acl - verifies ACLs follow the "official" rules
# arguments: directory acl_filter(s)  
#
sub clean_acl {
    usage('clean_acl') unless $#_ >= 0;
    my ($path, @acls) = @_;
    return unless check_path(\$path);
    usage('clean_acl') unless $#acls%2;      # we must have an even number of arguments
    arc_execute(qw(pv clean_acl), ($opt_r? "-r ":"" ), (($opt_d)? "-d ":""), $path, @acls);
}
sub check_acl {
    usage('check_acl') unless $#_ >= 0;
    my ($path) = @_;
    return unless check_path(\$path);
    arc_execute(qw(pv check_acl), ($opt_r? "-r ":"" ), (($opt_d)? "-d ":""), $path);
}
################################################################################
#
# set_acl - set acls for an AFSVolset
# arguments: directory acl (or the full fs sa syntax, see fs help sa)
#
sub set_acl {
  if ( is_volset($_[0]) ) {
    usage('set_acl') unless $#_ > 0;
    arc_execute(qw(pv acl add), @_);
  } else {
    usage('set_acl') unless $#_ > 1;
    my ($path, @acls) = @_;
    return unless check_path(\$path);
    usage('set_acl') unless $#acls%2;      # we must have an even number of arguments
    arc_execute(qw(pv set_acl), ($opt_r? "-r ":"" ), ($opt_d? "-d ":""),
                                ($opt_c? "-c ":"" ), ($opt_f? "-f ":""), $path, @acls );
  }
}
################################################################################
#
# set_owner - set the ownership for given directories
# arguments: owner directory+
#
sub set_owner {
  usage('set_owner') unless $#_ > 0;
  my $owner = shift;
  (my $_user, $owner) = ckown($owner);                          # expect user[:group]
  for my $dir ( @_ ) {
    next unless check_path(\$dir);
    _print "will execute a ", ($opt_r? "recursive ":""), "chown $owner $dir";
    arc_execute(qw(pv chown), ($opt_r? "-r ":"" ), ($opt_d? "-d ":""), 
                              ($opt_f? "-f ":""), $owner, $dir);
  }
}
################################################################################
#
# set_quota - set quota for a volume or a project (needs administrator privs) 
# arguments: directory maxquota
# or         project maxquota
#
# The project quota cannot be changed by the project admins 
#
sub set_quota {
  my ($path, $qstr) = @_;
  if ( ! $qstr and $opt_q ) { $qstr = $opt_q; }
  if ( $qstr !~ /^[\+\-]?\d+[kMGT]?B?$/i ) 
    { _print 'E', "invalid Quota: $qstr"; $qstr = ""; }
  usage('set_quota') if $#_ > 1 or !$qstr ;
  my $newq  = decode_quota($qstr);
  my $quota = encode_quota($newq);

  if ( is_volset($path) ) {
    open CF, $Volset::CFLOC or die "Could not open $Volset::CFLOC:$!\n";
    my @new_file = ();
    my $in_volset = 0;
    local $_;
    while (<CF>) {
      push @new_file, $_;
      _print 'E', "$_[0] not found in $Volset::CFLOC"
            if /^\[/ and $in_volset;
      $in_volset = 1 if /^\[AFSVOLSET\]/;
      next unless $in_volset;
      next unless /^$_[0]\b/;
      chomp $_[0];
      my @items = split(' ', $_, 3);
      if ( $items[1] !~ /=/ ) {
        if ( $newq =~ /^[\+\-]/ ) {
          $newq += decode_quota($items[1]);
          $quota = encode_quota($newq);
        }
        my $w = ' set_quota (afsadmin.cf):' . ($opt_n ? ' would' : '');
        _print "$w change quota for $_[0] from $items[1] to $quota";
        $items[1] = $quota;
      } else {
        $quota =~ s/^\+//;
        splice @items, 1, 1, $quota;
        _print "setting quota for $_[0] to $quota";
      }
      if ( $quota =~ /^\-/ )
        { _print 'E', "quota must be a positive number"; return undef; }
      $new_file[-1] = sprintf "%-10s %6s %s\n", @items;
      _print "update in afsadmin.cf section [AFSVOLSET] the line",
            "$new_file[-1]" if $opt_v;
      $in_volset = 0;
    }
    close CF;
    my $comment = "updated quota for project $_[0]";
    update_file($Volset::CFLOC, \@new_file, $comment) if ! $opt_n;

  } elsif ($opt_n or -e $path) {
    _print 'W', "$_[0] is not an existing path" unless -e $path;
    return unless check_path(\$path);
    my $vol = path2vol($path);
    $vol =~ s/.readonly$//;
# in no execute mode vol may not yet exist
    my $blks = $quota = 0;
    $blks = blocks($vol) if $vol;
    my $qvol = quota($vol) if $vol;
    $newq += $qvol if $newq =~ /^[\+\-]/;
    if ( $newq < $blks ) {
      _print 'E', "new quota ($newq) is smaller than blocks used ($blks) !!!";
    } else {
      my $how = $qvol < $newq ? 'increasing' : 'shrinking';
      if ( $qvol ) {
        _print "$how quota for $path from $qvol to $newq blocks";
      } else {
        _print "setting quota for $path to $newq blocks";
      }
      &vol_quota($vol,$newq);
    }
  } else {
    _print 'E', "$_[0] is not a valid path or project";
  }
}
################################################################################
#
# vos_release - release a volume
# arguments: volume+
sub vos_release {
  usage('vos_release') unless @_;
  for my $vol ( @_ ) {
    $vol = path2vol($vol) if -e $vol;
    $vol =~ s/\.readonly$//;
    if ( $vol =~ /\// ) {
      _print 'E', "no such directory: $vol";
      next;
    } elsif ( ! check_volume($vol,1) ) {
      next;
    }
    my $sites = get_volattrib($vol, 'Sites');
    if ( $sites > 1 ) {
      &vol_release("$vol");
    } else {
      _print 'E', "No RO sites for $vol defined";
    }
  }
}
################################################################################
#
# set_parameter - set a persistent volume parameter 
# arguments: attractive/repulsive volume patterns, server flag
#
sub set_parameter {
    if ((!defined $opt_a) && (!defined $opt_r)) {
       usage('set_parameter');
    }
    my $volume = shift;    
    if ($opt_a) {
       my @A = ();
       push @A, "with";
       push @A, "$opt_a";
       push @A, "--server" if $opt_s;
       arc_execute(qw(pv set_parameter),
                $volume,
                @A);
    } 
    if ($opt_r) {
       my @A = ();
       push @A, "not_with";
       push @A, "$opt_r";
       push @A, "--server" if $opt_s;
       arc_execute(qw(pv set_parameter),
                $volume,
                @A);
    } 
}
################################################################################
#
# list_parameters - list the persistent parameters of a volume 
# arguments: volume name
#
sub list_parameters {
    if (scalar @_ != 1) {
       usage('list_parameters');
    }
    my $volume = shift;
    arc_execute(qw(pv list_parameters),
                $volume)    
}
################################################################################
#
# clear_parameters - clear the list of persistent volume parameters 
# arguments: volume name
#
sub clear_parameters {
    if (scalar @_ != 1) {
       usage('clear_parameters');
    }
    my $volume = shift;
    arc_execute(qw(pv clear_parameters),
+                $volume)        
}
################################################################################
#
# recover -  recover a volume from backups 
#
# arguments: file or directory name
#        or: volumename
#
sub recover {
  my ($path) = shift;
  my ($vol,$path1) = follow($path);
  my $cmd = fullpath("afs_recover", ('/afs/cern.ch/project/afs/ABS/ABS/'))  || _print 'D', "Cannot locate afs_recover";
  exec( "$cmd -v $vol");
}
################################################################################
#
# salvage -  salvage the volume containing the argument path
#
# arguments: file or directory name
#        or: volumename
#
sub salvage {
  my ($timestmp);
  my ($path) = shift;
  my ($when) = shift;
  my $nowait = (defined $opt_w and $opt_w == 0) ? "-nowait" : "";

  if ($opt_o) {
      $valid = 0;
      foreach (qw(ignore remove attach)) {
          if ( $_ =~ /^$opt_o/ ) {  $opt_o= $_; $valid = 1; last;}
      }
      die "invalid value for -o option. Valid options are \"ignore\", \"remove\" or \"attach\"\n"
          unless $valid;
  }
  else {
      $opt_o = "ignore";
  }
  my $orphan = "-orphan $opt_o";

  (my $vol, $path) = follow($path);
  my ($server,$part) = split /\// , rw_site($vol);
  die "Cannot locate host for R/W volume\n" unless $server;

  use Date::Parse;
  use POSIX "strftime";
  my $now = time;
  if (! defined $when) { $timestmp = $now; }
  elsif ($when eq "now" ) { $timestmp = $now; }
  elsif ($when =~ /(\d\d?):(\d\d?)/) {  $timestmp = &timeconv($1,$2); }
  else { $timestmp = str2time($when); }
  die "cannot decode $when as a date\n" unless $timestmp;
  my $whan = strftime("%A %e %B %Y, %H:%M", localtime($timestmp));
  die "Parameter $when, interpreted as $whan, is in the past\n" if $timestmp < $now;
  if ($timestmp - $now > 86400 and -t STDIN) {
    print <<SLV;
Parameter $when, interpreted as $whan, looks a bit far in the future
Are you sure you want to wait that long?  (reply yes or no}
SLV
    my $ans = <STDIN>; chomp $ans;
    die "OK, re-type the command with a better chosen time stamp\n" unless $ans =~ /^y(es?)?$/i;
  }

  _print "we will try to salvage volume $vol on $server /vicep$part on $whan";
  unless ($nowait) {
    print <<SLV;

The $program command will be kept pending until salvage completes. Then it will print
the result of the operation.

If you do not want to wait, hit Ctrl-C in a second or two.  
Then the results will be sent to you by mail.
Next time, you can use the -w 0 option of afs_admin to avoid waiting.

SLV
  }

  arc_execute("salvage", $path, $timestmp, $orphan, $nowait);
  my $caution = `$fs checkvolumes`;
     $caution = `$fs checkservers`;

  sub timeconv {
    my ($HH,$MM) = @_;
    my $now = time;     # reference
    die "Invalid value $HH for <HH>. Should be between 0 and 23\n" if $HH > 23;
    die "Invalid value $MM for <MM>. Should be between 0 and 59\n" if $MM > 59;
    my(@now) = localtime($now);
    if ($HH < $now[2] or ($HH == $now[2] and $MM < $now[1] )) {    # must go into future
       @now = localtime(86400+$now);
    }
    $now[2] = $HH;    # HH
    $now[1] = $MM;    # MM
    
    my $date =  str2time(strftime("%D %T",@now));
    return $date;
  }
}


######################### auxiliary functions ##################################
#
#       check_vol2 verifies the length of the volume name
#
sub check_vol2 {
  my $vol = shift;
  @volsets = vol2set($vol) unless @volsets;
  my $back = volsetopts('backup', reverse @volsets);
  if ( length($vol) > 20 && $back ) {
    _print 'E', "volume name too long. " ,
      "It would be impossible to restore the volume.\n",
      "limit is 20 characters for backed-up volumes.\n";
    return undef;
  }
  if ( length($vol) > 22 ) {
    _print 'E', "volume name too long; volume names are limited to 22 characters.";
    return undef;
  }
  return $vol;
}
#################################################################################
# update_file - rewrite the contents of a file (using RCS)
#
sub update_file {
  my ($f, $lines, $comment) = @_;
  my $p = '/bin:/usr/bin:/usr/local/bin:/usr/local/bin/gnu:/usr/local/gnu/bin';
  my $co = fullpath('co', $p);
  my $ci = fullpath('ci', $p);
  die "$program: commands ci and/or co not found in $ENV{PATH}\n"
      unless -x $co and -x $ci;
  system("$co -f -l $f") && die "$program: cannot check out $f:$!\n";
  open F, ">$f" or die "$program: cannot open $f: $!\n";
  for my $l ( @$lines ) {
    print F $l;
  }
  close F;
  system("$ci -u -m\"$comment\" $f") && die "$program: cannot check in $f:$!\n";
}
################################################################################
#
# path2vol - convert directory path to volume name
# argument: directory path
#
sub path2vol {
  my $dir = shift;
  return undef unless check_path(\$dir);
  my $vol = arc_execute2(qw(pv path2vol), $dir);
  chomp $vol;
  $vol = undef if $vol =~ /Error/;
  if ( $vol ) {
    return $vol;
  } else {
    _print 'W', "directory $dir not existing or unreadable";
    return undef;
  }
}
################################################################################
#
# prepare_mount - get the volume name for the parent volume and create
# missing path components
# return value: volume name, undef on failure
#
sub prepare_mount {
  my $mnt = shift;
  my ($vol, $ind, $path);
  my $rc = 1;
# get rid of trailing slash, if any
  $mnt =~ s/\/$//;
  $ind = length $mnt;
  while ( $ind = rindex $mnt, '/', $ind ) {
    $path = substr $mnt, 0, $ind;
    if ( -r $path ) {
      $vol = path2vol($path);
      last;
    } else {
# make the missing path component
      arc_execute(qw(pv mkdir), $path);
    }
    $ind--;
    last if $path eq /$rwrootdir/;
  } 
  $vol = '' unless $rc;
  return $vol;
}

sub arc_execute {
  if ($opt_v && $_[$[] eq "pv") { splice(@_, 1, 0 , ("-v" , $opt_v) ); }
  my @cmd = ($arc, "-h", $arcserver, @_);
  _print $opt_n ? "would " : "", "execute @cmd" if $opt_v or $opt_n;
  return 0 if $opt_n;
  ## we do not want to expose STDIN. We do not want to start a shell either (</dev/null)
  open (SAVEIN,"<&STDIN");
  open (STDIN, "</dev/null");
  system(@cmd);
  open (STDIN,"<&SAVEIN");
  my $signal = $? & 127 ? "Signal ".($? & 127) : '';
  my $core = ' (dumped core)' if $? & 128;
  my $error = $?>>8 ? "ERROR(".($?>>8).") " : '';
  _print 'W', "got $error$signal$core\nwhile executing $cmd" if $?;
  die "\n" if $signal;
  return $?;
}

sub arc_execute2 {
  # similar to arc_execute, but returns a value and is immune from $opt_n
  local $opt_n = 0;
  my @cmd = ($arc, "-h", $arcserver, @_);
  _print "execute @cmd" if $opt_v;
  ## we do not want to expose STDIN. We do not want to start a shell either (</dev/null)
  open (SAVEIN,"<&STDIN");
  open (STDIN, "</dev/null");
  my $res = `@cmd`;
  open (STDIN,"<&SAVEIN");
  my $signal = $? & 127 ? "Signal ".($? & 127) : '';
  my $core = ' (dumped core)' if $? & 128;
  my $error = $?>>8 ? "ERROR(".($?>>8).") " : '';
  _print 'W', "got $error$signal$core\nwhile executing $cmd" if $?;
  die "\n" if $signal;
  return $res;
}

sub ckown {
# analyze -u option , or arglist for set_owner
# argument can be a pts name, including a:b , but also user:group, a-la unix
  my ($arg, $what) = (@_, 0);
  my ($owner, $group);
  if ($_[0] =~ /:/) {
    ($owner, $group) = $_[0] =~  /([^:]*)(?::([^:]*))?/;
  } else {
    $owner = $_[0]; $group = 0;
  }

  #
  # first validate arguments
  #
  if ( $owner !~ /^$legal_username_pattern$/ and $owner !~ /^\-?\d+$/ ) {   # doesnot looks like a valid userid
    _print 'E', "Invalid character detected in string '$owner'.";
    return (undef,undef);
  }
  if ( $group and $group !~ /^$legal_username_pattern$/ and $group !~ /^\-?\d+$/ ) {   # doesnot looks like a valid userid
    _print 'E', "Invalid character detected in string '$group'.";
    return (undef,undef);
  }

  if ($owner eq 'root' or $owner eq 0) {
     _print 'W', "\nYou specified root as owner. This will not change the AFS access rights, based on ACLs\n";
     return (0, "0:0");
  }

  # what do we expect ?
  if ($what) {                          # expects a pts entry, with or without ':'
     my $a = `$pts exam $arg`;
     if ($? != 0) {         # valid pts name
        _print 'E', "$arg is not a valid user or protection group";
        return (undef);
     }
     return ($arg, "");
  } else {                              # expects user[:group]
     my $a = `$pts exam $owner`;
     if ($? != 0) {
        _print 'E', "$owner is not a valid user";
        return (undef);
     }
     my ($id) = $a =~ /id: (\-?\d+),/;
     if ($id <0) {
        _print 'E', "'$owner' must be a user entry";
        return (undef);
     }
     if ($group) {
        $group = ckgrp($group);
        return ($owner, "$owner$group") if $group;
     }
     return ($owner, $owner);
  }

}
sub ckgrp {
  my ($grp) = @_;
  if ($grp eq 'root' or $grp eq 0) {
    _print 'W', "\nYou specified root as group. This will not change the AFS access rights, based on ACLs\n";
    return ":0";
  }
  elsif ( $grp =~ /^\d+$/ ) {                         # looks like a numeric group
    return ":$1" if `$pts examine -$grp 2>&1` =~ /Name: $Volset::extra_pts_pfx(\w+),/;
  }
  elsif ( $grp =~ /^$legal_username_pattern$/ ) {   # looks like a valid group
    return ":$grp" if `$pts examine $Volset::extra_pts_pfx$grp 2>&1` =~ /id: -\d+,/;
  }
  _print 'W', "cannot convert $grp to a groupid, using nullstring instead";
  return "";

}

sub AUTOLOAD {
  my $function = $AUTOLOAD;
  $function =~ s/.*:://;
  _print 'E', "Function $function not yet implemented, exiting...";
  exit 2;
}


sub check_path {
  my ($path) = @_;
#convert relative path into an absolute path
  $$path = cwd() . '/' . $$path if $$path !~ /^\//;
# check against invalid characters
  if ($$path =~ /[^A-Za-z_0-9\/\:\.\+\-\@\(\)]/) {
    _print 'E', "there are strange characters in the path '$$path', which could provoke subsequent problems.\n";
    $$path = undef;
    return 0;
  }
# resolve @sys locally before it goes to a server through arc
  if ($$path =~ /\W\@sys\b/) {
    my ($sys) = `$fs sysname` =~ /'(.*)'/;
    $$path =~ s/(\W)\@sys\b/$1$sys/g;
  }
  return 1;
}
sub follow {
  my ($path) = @_;
  my $suff = "";  
  my $vol = "";
  
  if ( $path !~ m|/| && (my @c = examine($path)) ) {
    $vol = $path;
  } else {
    # get rid of trailing slash, if any
    $path =~ s|/$||;
    $path = cwd() . '/' . $path if $path !~ m|^/|;
    while($path) {
       print "path now: $path\n" if $opt_d;
       if ( `$fs exam $path 2>/dev/null` =~ /Volume status .* named (.*)\n/ ) {
         $vol = $1; 
         last;
       }
       elsif ( -l $path ) {
         my ($pref) = $path =~ m|^(.*)/([^/]+)$|;
         my ($link) = `ls -l $path 2>/dev/null` =~ m| -> (.*)$|;
         if ($link =~ m|^/|) { $path = $link; }
         else { ($path = "$pref/$link") =~ s|/\./|/|g; }
         next;
       }
       elsif ( -r $path ) {
         last;
       }
       else {
         last unless $path =~ m|^(.*)/([^/]+)$|;
         $path = $1;  ($suff = "$2/$suff") =~ s|/$||;
       }
    }
  }
  ##if ($suff) {  print "lost on the way: $suff\n"; }
  unless ($vol) {
    if ($path =~ /\W\@sys\b/) {
      my ($sys) = `$fs sysname` =~ m|'(.*)'|;
      $path =~ s/(\W)\@sys\b/$1$sys/g;
    }
    $vol = path2vol($path);
  }
  print "vol found: $vol\n" if $opt_d;
  print "Resulting path $path is/was on volume $vol\n" if $opt_v;
  print "switching to R/W volume $vol\n" if $vol =~ s/\.readonly$|\.backup$//;
  return ($vol, $path);
}

__DATA__
=head1 NAME

afs_admin - a tool to manage AFS project space.

=head1 SYNOPSIS

afs_admin [B<-d>] [B<-h>] [B<-n>] [B<-v [n]>] [B<-S arcserver>] subcommand [suboptions] [subarguments]

where subcommand is one of

 --commands valid for any user:
 help [command]+
 clean_acl [-r] <dir_path> <access_list_filters>+
 check_acl [-r] <dir_path>
 list_acl [<project>+|<dir path>+]
 list_quota [<project>+|<dir path>+]
 list_occupancy [-q <max_percentage_quota>] [project|srv[/part]]
 list_project [-q <max_percentage_quota>] [<project>]
 recover <dir_path> | <volume>
 salvage <dir_path> [<timestamp>]   [ -o attach | ignore | remove ]  [ -w 0 ]
 set_acl [-r] [-f] <dir_path> <access_list_entries>+ [-c]
 set_owner [-r] [-f] <owner[:group]> <dir_path>+

 --commands valid for project administrators:
 clear_parameters <volume>
 create [-q <quota>] [-u <user>] [-p <pool|srv[/part]>] [-a <volume pattern>] [-r <volume pattern>] <mnt> <volume>
 create_mount (<mount_point> <volume_name>)+
 create_replica [-p <pool|srv[/part]>] (<volume> [<srv/part>])+
 create_scratch [-q <quota>] [-u <user>] [-p pool] [-w{0..9}] <project>
 create_volume [-p <pool|srv[/part]>] (<volume> [<srv/part>])+
 create_workspace [-q<quota>] [-u<user>] [-p pool] [-w{0..9}] <project>
 delete <dir_path>+
 delete_mount <dir_path>+
 delete_replica [-p <srv[/part]>] (<vol_name_or_ID> [<srv/part>])+
 delete_scratch -u <user> -w{0...9} <project>
 delete_volume [-p <srv[/part]>] (<vol_name_or_ID> [<srv/part>])+
 delete_workspace -u <user> -w{0...9} <project>
 list_parameters <volume>
 move_volume [-p <pool|srv[/part]>] (<volume> [<srv/part>])+
 rename <dir_path> <new_volume_name>
 rename_mount <old_dir_path> <new_dir_path>
 rename_volume <old_volume_name> <new_volume_name>
 set_acl <project> <account[ none]>+
 set_quota <dir_path> [+|-]<max_quota_in_kbytes>
 set_parameter (-a <volume pattern>)|(-r <volume pattern>) [-s] <volume>
 vos_release (<volume name>|<dir path>)+

 --commands valid only for system administrators:
 create_project -q <quota> [-u <user>,<user>...] <project> [<mount_point_pattern>]
 delete_project <project>
 set_quota <project> [+|-]<max_quota_in_kbytes>

=head1 DESCRIPTION

afs_admin is a tool to help AFS space administrators perform
their administration tasks. afs_admin consists of several
groups of subcommands: 

=over 4

=item C<create, delete, list, move, rename, set>

=item C<misc commands> (help, vos_release)

=back

With the exception of the misc commands the subcommand name is formed
concatenating the group name (which is an action) and the object (which
may vary depending on the type of the action) with an underscore.

The subcommands B<create>, B<delete> and B<rename> without an object perform
simultaneous actions on the objects B<volume> and B<mount>.

For example, the subcommand B<create_volume> creates an AFS volume, the
subcommand B<create_mount> creates a mount point to a volume but B<create>
creates an AFS volume and a mount point to it.

The I<options> and I<arguments> fields vary from one subcommand to another.
Help on the syntax of one subcommand can be obtained with:

B<afs_admin help> I<subcommand>

The subcommand names can be abbreviated as much as desired as long as the
action and the object parts can be unambiguously identified. For example,
B<rename_volume> can be abbreviated to B<ren_vol> or even to B<r_v>.

Before actually performing an operation using afs_admin it is useful to 
have a dry run using the C<-n> option. That will usually try to figure
out what would be done and give already some warnings or even error
messages if the operation would not succeed.

=head1 OPTIONS

=over 5

=item B<-d>      switch on debug output

=item B<-h>      get help, get even more detailed help with -v

=item B<-n>      do not take actions, show only what would be done

=item B<-v [n]>  verbose output, currently levels 0, 1, 2 are used

=item B<-S HOST> talk to a different ARC server

=back

=head1 VOLUME POOLS AND PROJECTS

afs_admin has the concept of volumes belonging to projects and pools
to be able to delegate the administration of AFS space. The definition
of pools and projects is done in a configuration file afsadmin.cf.
A project definition contains rules for the naming of volumes and for
the creation of mount points. The subcommand list_project (described
below) displays the pattern used for checking the mount point of a
volume and the pattern(s) to associate a volume with a project.
The subcommand list_acl without further args lists all known projects.
Projects can have subprojects usually denoted by an underscore and a
suffix in the name (e.g. atlas and atlas_s).

A pool is a project or a collection of projects that are associated
with one or more disk partitions on an AFS file server. This concept
allows to keep volumes for different projects on different partitions
or e.g to have volumes with a certain backup strategy on a well defined
set of file servers. Usually administrators should not use the
C<-p> <pool|srv[/part]> option found in some subcommands and not use
the optional target specification [<srv/part>] for commands like create,
create_replica or move_volume, as afs_admin will then select the most
appropriate pool and/or disk.

=head1 ADMINISTRATORS AND ACCESS CONTROL

Except of the help and list commands the commands in afs_admin require
privileges reserved to AFS project administrators. The easiest way to
get information who is having these privileges is to use the afs_admin
commands. All of the list subcommands have a special functionality when
called without arguments.

A list of all projects for which project administrators are defined can
be obtained by

  afs_admin list_acl

Subprojects (e.g. project_s and project_u) will not be shown in this listing.
Then for any given project the administrators of that project (to be more
precise: the Kerberos principals, that is accountname.@CERN.CH) can be 
listed using 

  afs_admin list_acl <project>

The AFS projects that a given user is able to administer using afs_admin
can be listed (with further information) by the command

  afs_admin list_project

A quick overview of the related project quota can similarly be obtained:

  afs_admin list_quota

Finally a list of all the volumes with quota and other information a user
is able to manage by afs_admin is printed using

  afs_admin list_occupancy

=head1 SUBCOMMANDS

For some of the subcommands the syntax used with the previous version of 
afs_admin is still valid, you will however get a warning message.

=for cmdlevel 1

 clean_acl [-r] <dir_path> <access_list_filters>+
        clean access control list
        filters are a list of what is to be kept from the actiual ACLs, i.e. 
        any extra letters get removed.
        e.g. system:anyuser rl on a directory which currently has system:anyuser lidw will
        leave only "l", cince "r" was not initially present.
        the -r option allows the command to run recursively across all directories
        INSIDE the same volume.  It doesnot follow links nor mountpoints.

=for cmdlevel 2

 clear_parameters <volume>
        clear volume parameters
        Clear the list of persistent parameters stored for a volume.

=for cmdlevel 1

 check_acl [-r] <dir_path>
        verifies that the ACLs of the specified path (and of its subdirectories if -r is specified)
        do not allow too wide write access.

=for cmdlevel 2

 create [-q <quota>] [-u <user>] [-p <pool|srv[/part]>] [-a <volume pattern>] [-r <volume pattern>] <mnt> <volume>
        create a volume, mount point, set quota,...
        The option -p can be used to restrict the creation of volumes to
        a certain pool or partition (given as srv/part or srv); however,
        there is also some pool information extracted from the volume
        name which might clash with the specified pool.
        If there is no exact location given, the volume will be created
        on a suitable partition taking into account the poolname, free
        space on the partition and the volume allocation policy (e.g.
        clustering).
        For initial volume placement, the option -a can be used to define 
        volumes which are "attractive" to the new volume. This can be used
        as a hint for volume colocation. 
        Equally, the option -r can be used to define volumes which are 
        "repulsive" to the new volume. This can be used as a hint for 
        distributing volumes.
	<volume pattern> is either a full volume name or a string starting like a volume
        name and ending with a '*' in order to denote all volumes that match that 
        pattern. Examples fo valid patterns include 'p.xyz.root' or 's.abc.run*'.

 create_mount (<mount_point> <volume name>)+
        create one or more mount points

=for cmdlevel 3

 create_project -q <quota> [-u <list>] <project> [<mount_point_pattern>]
        create a new project. This command is requiring special
        privileges normally not granted to project admins. If possible
        the default for the mount_point_pattern should not be changed.
        With the -u switch a comma separated (no blanks allowed) list
        of project admins can be given. Each user has to correspond
        to a valid AFS userid.
        The mount_point_pattern, when specified, is that part of the project's top
        directory which FOLLOWS the Cell's root, i.e. /afs/cern.ch/ .
        When not specified, it defaults to "project/<project>", NOT just
        to <project>!

=for cmdlevel 2

 create_replica [-p <pool>|<srv>[/<part>]] (<volume> [<srv>[/<part>]])+
        add a replication site and release the volume
        The option -p can be used to restrict the creation of volumes to
        a certain pool or partition (given as srv/part or srv).
        To do just a vos release use the new command vos_release. To
        create a replication site on any suitable volumes please use
        create_replica -p ' ' <vol> for now as the command without the
        -p option was used to just do a vos release.

 create_scratch [-q <quota>] [-u <user>] [-p <pool>] [-w{0..9}] <project>
        create a personal scratch workspace (not backed up)
        This is the same as create workspace, volumes are named
        s.project.user.N, the mount point is ~user/scratchN.

 create_volume [-p <pool>|<srv>[/<part>]] (<volume> [<srv>[/<part>]])+
        create one or more volumes
        The option -p can be used to restrict the creation of volumes to
        a certain pool or partition (given as srv/part or srv).

 create_workspace [-q<quota>] [-u<user>] [-p <pool>] [-w{0..9}] <project>
        create a personal workspace (backed up)
        The user (-u <user>) can be omitted, if the workspace should be
        created for the calling user. The option -p is used to restrict
        the creation of the volume to a certain pool or partition
        ([-p <pool>|<srv>[/<part>]]).

 delete <dir_path>+
        delete one or more mount points and the mounted volumes

 delete_mount <dir_path>+
        delete one or more mount points

=for cmdlevel 3

 delete_project <project>
        delete a project. This command is requiring special privileges
        normally not granted to project admins.

=for cmdlevel 2

 delete_replica [-p <srv>[/<part>]] (<vol_name_or_ID> [<srv>[/<part>]])+
        Delete replica of a volume, the RO replica that lives on the
        partition where the RW volume is stored is deleted last. A
        request to remove such a partition if there are still other RO
        clones is denied.

 delete_scratch -u <user> -w{0...9} <project>
        delete a personal scratch workspace
        This command is the same as delete_workspace below, but for
        personal scratch space.

 delete_volume [-p <srv>[/<part>]] (<vol_name_or_ID> [<srv>[/<part>]])+
        delete one or more volumes (including all replicas, if any)
        There is no longer a need to specify the pool. To give the exact
        location of a volume to be deleted both the -p <srv>[/<part>]]>
        and the <vol_name_or_ID> <srv>[/<part>] notation can be used.

 delete_workspace -u <user> -w{0...9} <project>
        delete a personal workspace
        For safety reasons the -w switch is not optional.

=for cmdlevel 1

 help [command]+
        get a list of available commands or help on one or more commands
        More than one command may be specified, help -v gives more
        detailed help.

 list_acl [<project>+|<dir path>+]
        list acls on projects or the AFS directory acls
        The distinction between projects and directories is made checking
        for a slash. If no slash is present and there is no project known
        with that name, the argument is treated as a relative dir path.
        More than one argument can be supplied. The AFS abbreviation la
        for list_acl is also accepted. The subcommand list_acl without
        further arguments displays all the projects and mount point
        patterns currently defined in afsadmin.cf.

 list_quota [<project>+|<dir path>+]
        list quota on projects or the AFS volume quota. If a project is
        given, allocated space and quota are printed. Without arguments
        it lists allocated space and quota for projects that the calling
        user (based on the Kerberos Ticket) is allowed to manage.
        If a dir path is given, the equivalent of a fs lq with the
        privileges of the effective user will be executed.
        More than one argument can be supplied. The AFS
        abbreviation lq for list_quota is also understood.

 list_occupancy [-q <max_percentage_quota>] [<project>|<srv>[/<part>]]
        checks the occupancy of volumes (listing only volumes with
        more than max_percentage_quota, if -q given). If no project
        name is given all volumes that the calling user is allowed to
        manage are printed together with quota, allocation etc.

 list_project [-q <max_percentage_quota>] [<project>]
        display project definition and usage. The quota and space
        allocation for the project are displayed first. Then the mount
        point pattern and volume patterns that are valid for a project
        are printed, followed by the list of volumes with used and
        allocated space. If option -q is given, only volumes with
        more than max_percentage_quota are displayed. If no project
        name is given, the project definitions are displayed, for
        which the calling user has administrative rights.

=for cmdlevel 2

 list_parameters <volume>
        list volume parameters
        Get the persistent parameters set for a volume. 

 move_volume [-p <pool>|<srv>[/<part>]] (<volume> [<srv>[/<part>]])+
        move one or more volumes to a new location
        The name was changed to avoid two commands starting with c
        (create and change_location).  More than one volume can be moved
        now. Option -p can be used to restrict the move of volumes to a
        certain server or partition or to move the volume to a different
        pool. Alternatively you can use <volume> <srv>[/<part>] pairs.

=for cmdlevel 1

 recover <dir_path> | <volume>
        recover a volume from backup tapes
        A list of available backups is printed, then the caller is invited
        to choose one, which is then recovered. The recovered volume is then
        mounted in a specific system path, and a shell is started with the
        current directory set to this system path. The mountpoint is removed
        when the shell exits.

=for cmdlevel 2

 rename <dir_path> <new_volume_name>
        rename a volume which is mounted at dir_path
        This command will perform a rmmount, then rename the volume, then
        perform a mkmount again.

 rename_mount <old_dir_path> <new_dir_path>
        rename a mount point

 rename_volume <old_volume_name> <new_volume_name>
        rename a volume

=for cmdlevel 1

 salvage <dir_path> [<timestamp>]   [ -o attach | ignore | remove ]  [ -w 0 ]
        salvage a corrupted volume containing the argument path
        A vos salvage operation is performed on the target volume, with some
        serialization on the machine hosting the R/W volume.

        without any timestamp specified, or if the timestamp represents some time 
        in the past, the action is performed as soon as possible. Else, the command 
        is kept waiting, until the time has come.

        timestamp can be specified as:
            HH:MM       where HH & MM are two 2-digits numbers. The action is performed
                        at the next occurence of HH:MM.
            mm/dd/yy    where mm, dd & yy are numeric values for month, day, year.  MONTH FIRST!
            "dd MMM yy" (watch the quotes) where dd & yy are numeric values for day and year,
                        and MMM the abbreviated month name
            "dd MMM yy HH:MM"   same thing with hour:minute appended

        -o attach | remove | ignore    specifies how orphan files should be treated.
                        default is ignore

        -w 0            specifies that afs_admin should not wait for the salvage to be completed
                        in which case, result is sent back by mail

=for cmdlevel 1

 set_acl [-r] [-f] <dir_path> <access_list_entries>+ [-c]
        set access control list
        The AFS abbreviation sa for set_acl is also understood. The name
        was changed to closer resemble the AFS command and to avoid two
        commands starting with c (create and change_acl).
        the -r option allows the command to run recursively across all directories
        INSIDE the same volume.  It doesnot follow links , nor mountpoints.
        the -f option extends recursivity to follows mountpoint
        leading to volumes in the same pool.
         The "-c" option can be added to  mean -clear: the ACLs will get cleared first,
        then the ACLS passed as argument will be set.

=for cmdlevel 2

 set_acl <project> <account [none]>+
        set administrators for a project
        The AFS abbreviation sa for set_acl is also understood. 
        Lets a project administrator to add new administrators  ( <account> )
        or to remove existing ones ( <account none> ), including oneself.

=for cmdlevel 1

 set_owner [-r] [-f] <owner>[:<group>]  <dir_path>+
        change the ownership of the directory & files to <owner> or <owner>:<group>
        the -r option allows the command to run recursively across all directories & files
        INSIDE the same volume.  It doesnot follow links , nor mountpoints.
        the -f option extends recursivity to follows mountpoint
        leading to volumes in the same pool.

=for cmdlevel 2

 set_parameter (-a <volume pattern>)|(-r <volume pattern>) [-s] <volume>
        set a volume parameter
        Set persistent parameters for volumes. The following parameters are available:
          -a <volume pattern> try to colocate the volume to volumes matching the pattern
                               on the same server and partition                               
          -r <volume pattern> try to avoid the same partition for the volume and volumes
                               matching the pattern
          -s                  apply the preferences not only to the partition, but to the 
                              whole server 
        <volume pattern> is either a full volume name or a string starting like a volume
        name and ending with a '*' in order to denote all volumes that match that 
        pattern. Examples fo valid patterns include 'p.xyz.root' or 's.abc.run*'.  
        Please note that colocate/avoid don't make much sense for readonly replicas.

 set_quota <dir_path> [+|-]<max_quota>[kMGT[b]]
        set volume quota
        The AFS abbreviation sq for set_quota is also understood. The
        name was changed to closer resemble the AFS command and to avoid
        two commands starting with c (create and change_quota).
        The max_quota can be postfixed with the units abbreviations k, M, 
        G, and T respectively.
        Prefixing the max_quota with a plus sign will increase the
        quota by that amount.  Similarily, prefixing the max_quota with 
        a minus sign will decrease the quota by that amount. 
        It is an error specifying a quota smaller than the real allocation.
        NOTE: since the minus sign introduces options, one has to type
        a double minus first, to stop interpreting minus that way.

=for cmdlevel 3

 set_quota <project_name> [+|-]<max_quota>[kMGT[b]]
        set project quota
        the syntax is similar to the project administrator's set_quota, but it applies to
        a complete project.
        Instead of a dir_path a project_name must be given. Then the
        total project quota is modified. This command is requiring
        special privileges normally not granted to project admins.

=for cmdlevel 2

 vos_release (<volume name>|<dir path>)+
        release one or more volumes
        This replaces some functionality of the create replica command.

=head1 WARNINGS

afs_admin calls 'arc' which uses kerberos authentication so that
kerberos must be installed in both the local and the server machines.

=head1 EXAMPLES

Warning: Trying blindly these examples might have undesired effects. Therefore
it is always a good idea to make first a dry run using the B<-n> option of
afs_admin, i.e.
  C<afs_admin -n delete_volume p.test> instead of
  C<afs_admin delete_volume p.test>

Get help on the command 'list_acl':

            afs_admin help list_acl


Create a volume called 'p.test' and mount it on
'/afs/cern.ch/project/afs/test.vol':

            afs_admin create /afs/cern.ch/project/afs/test.vol p.test

Create a replica of the volume 'p.test' on the disk afs1/f:

            afs_admin create_replica -p afs1/f p.test

List the volumes which belong to the project 'test':

            afs_admin list_project test 


List for project 'oracle' all disks running over 75% full:

            afs_admin list_occupancy oracle -d 75


Change the quota of the volume 'p.test', which is mounted on
'/afs/cern.ch/project/afs/test.vol', to 10000 Kb: 

           afs_admin set_quota /afs/cern.ch/project/afs/test.vol 10000

Delete a readonly copy of the volume 'p.oracle':

           afs_admin delete_replica p.oracle

Delete the mount point '/afs/cern.ch/project/afs/test.vol' and the
volume which is mounted on it:

           afs_admin delete /afs/cern.ch/project/afs/test.vol


=head1 NOTES

The backup volumes share disk blocks with their corresponding
non-backup volumes, so that the addition of the blocks used by volumes
in a disk can be greater than the number of blocks of the disk.
Same thing can occur with percentages. This is because some blocks
are added twice.

=head1 SEE ALSO

/afs/cern.ch/system/@sys/usr/local/etc/perldoc <modulename> where <modulename>
is one of AFS, Vos, Volset. Especially the Volset perl module, section
"THE CONFIGURATION FILE afsadmin.cf" should be consulted for configuring
afs_admin.

=head1 BUGS

Many of the good features were copied from the original version. For the bugs
however I would like to get credit alone.
Although bja is now also adding his own.

=head1 AUTHORS

Originally written by Rainer Toebbicke, Francisco Lozano, Olivier le Moigne.
Redesigned by Wolfgang Friebel C<Wolfgang.Friebel@cern.ch>.
And afterwards hacked by bja (aka C<Bernard.Antoine@cern.ch>.

=cut
