AFS-Command

 view release on metacpan or  search on metacpan

CONFIG  view on Meta::CPAN

#
# $Id: CONFIG,v 7.1 2004/01/13 19:01:10 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

#
# The name of the AFS cell in which the tests will be performed.
#
AFS_COMMAND_CELLNAME		= your.cell.name

#
# This prefix will be used to create and remove a few volumes during
# the tests.  Anything left over after the tests complete can be
# removed.  Note that a successful test run should remove them, but a
# failed test run can leave them behind.
#
AFS_COMMAND_VOLNAME_PREFIX	= afscmd.

#
# Two fileserver:/partition names in which to create volumes.  These
# must be in the same AFS cell as specified at the top of this file.
#
AFS_COMMAND_PARTITION_LIST	= server1:/vicepa server2:/vicepb

#
# The bos tests need the name of a database server to work with.
#
AFS_COMMAND_DBSERVER		= dbserver1

#
# We need a valid pathname (directory) in AFS that we can abuse (that
# is, we will be hacking the ACL on this directory, and a few other
# intrusive things, to test the FS API).  This directory must be in a
# RW volume, and system:administratoirs must have write access to it.
#
AFS_COMMAND_PATHNAME_AFS	= /afs/your.cell.name/home/you/hackmebaby

#
# We will need to create a pts group and user, and if these names are
# already taken, then change these lines.  The code will *remove* them
# when its done.
#
AFS_COMMAND_PTS_GROUP		= afscmdgroup
AFS_COMMAND_PTS_USER		= afscmduser

#
# If the AFS client on which the tests run does NOT match that
# specified above, then the pts group/user we create will NOT be
# visible to the fs setacl command.  Therefore, we need the name of an
# existing pts user or group to use for that test.
#
# Yes, I promise the tests will NOT remove this one, 'cause I did
# all the tests using my own ID. ;-)
#
AFS_COMMAND_PTS_EXISTING	= wpm

#
# If you don't have either gzip/gunzip, of bzip2/bunzip2, then comment
# the appropriate line to disable the tests using these compression
# utilities.  Then, go download the source and compile them, because
# you shouldn't be without something so basic.
#
AFS_COMMAND_GZIP_ENABLED	= 1
AFS_COMMAND_BZIP2_ENABLED	= 1

#
# Change this, if you want to scribble temporary files elsewhere.  You
# probably don't...
#
AFS_COMMAND_TMP_ROOT		= /var/tmp

#
# This parameter is used to test the output filter mechanism for vos
# dump and restore.  Obviously, 'cat' is very boring, but we just need
# something in the pipeline that reads from stdin and writes to
# stdout, and it can't break the volume dump format that passes
# through it.
#
# Tweak this at your own peril.
#
AFS_COMMAND_DUMP_FILTER		= cat
AFS_COMMAND_RESTORE_FILTER	= cat

#
# By default, we'll get the AFS commands from your PATH, but you can
# test with explicit versions by specifying these variables.
#
#AFS_COMMAND_BINARY_VOS		= /some/path/to/vos
#AFS_COMMAND_BINARY_BOS		= /some/path/to/bos
#AFS_COMMAND_BINARY_PTS		= /some/path/to/pts
#AFS_COMMAND_BINARY_FS		= /some/path/to/fs

COPYRIGHT  view on Meta::CPAN

#
# $Id: COPYRIGHT,v 7.1 2004/01/13 19:01:10 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

Changes  view on Meta::CPAN

#-*-cperl-*-
#
# $Id: Changes,v 11.1 2004/11/18 17:24:53 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

=head1 CHanges in 1.9

=head1 Enhancements

A new argument is supported by AFS::Command::Base->new():

    my $vos = AFS::Command::VOS->new( timestamps => 1 );

This will result in ISO timestamps being prepended to each line of
output when it is collected into the $vos->errors().  This is useful
for profiling the performance of operations such as vos release:

    my $result = $vos->release
      (
       id		=> 'somevol',
       cell		=> 'somecell',
      ) || die $vos->errors();

When this works, the $vos->errors() will have the verbose output,
which can be logged even in the successful case, for diagnostics.
Here's an example for a failure:

    [2004-11-18 17:20:36] Could not lock the VLDB entry for the volume 536998569.
    [2004-11-18 17:20:36] VLDB: no permission access for call
    [2004-11-18 17:20:36] Error in vos release command.
    [2004-11-18 17:20:36] VLDB: no permission access for call

=head1 Changes in 1.8

=head1 Bug Fixes

=head2 vos examine did not pick up the LOCKED flag

The code to parse the VLDB header was missing the LOCKED flag, if it
was present, so this attribute was not being set properly.  It is now.

=head2 pts membership error checking was bogus

Well, it still is bogus, actually, since the code has to deal with the
fact that pts has never produced meaningful return codes, so a failed
pts membership command can still exit 0, and we have to figure out if
it failed by other means.  This is done by looking for the known error
messages that pts prints out, which is a good example of why parsing
the ASCII test output of commands like this is a weak architecture.

=head1 Changes in 1.7

=head1 Enhancements

=head2 Boolean flags can be turned off, as well as on

If an argument to a method (and its corresponding command line
argument) doesn't take a value, it is treated like a Boolean flag.
However, the code used to assume that the existence of a Boolean key
in the argument list implied the Boolean argument was always true.

Now, the truth of the arguments I<value> is tested to determine if the
flag should be set on or off.  This makes it easy to have subroutines
that just blindly pass certain arguments along, without haing to test
them, and allows for much cleaner code.

For example:

    my $result = $vos->release
      (
       name		=> $name,
       force		=> $force,
      );

Will work as you probably expected it to, namely if $force is zero, it
will NOT be passed to the "vos release" command.  In previous
releases, regardless of the value of $force, the mere existence of the
force key in the argument hash would have caused the -force option to
be used.

=head1 Bugs

=head2 vos examine by numeric ID did not parse Volume Headers

The code to parse the volume headers in the output from "vos examine"
was looking for a pattern match based on the "id" argument.  However,
the headers always print the name first, and we were assuming that the
id argument was the volume name, when it can also be the volume ID.

The parsing is less strict now, and works for both a volume name od a
numeric ID.

=head1 Changes in 1.6

=head1 Enhancements

=head1 AFS::Object::VolumeHeader: new attribute 'attached'

When a volume's status is "offline", there is really more than one
status the volume can be in.  A volume might be unattached, and
offline, or it might be attached, but adminitratively offline.  For
example, "vos offline" put a vlume in the latter state, and if for
some reason there are two volumes with the same ID on the same
fileserver, they will both be attached (if possible), but only the
first one encountered will be brought online.

Changing the value of status would been a non-backwards compatible
change, and caused some subtle problems for some applications.  Rather
than introduce such a change, I have chosen to represent this state
with an additional attribute "attached", which is just a Boolean,
indicating whether or not the volume is attached.

=head1 Bugs

=head1 Changes in 1.5

=head1 Enhancements

=head2 Principal names, as user supplied arguments, are lowercased

As a convenience, when PTS names are passed to methods like
getUserByName(), they will be lower-cased before looking up the user,
or group.  Since PTS is a case-insensitive database, this will avoid
the need for applications to lc() some strings, although not in all
cases.

For example, you can pass a mixed case string to pts->creategroup(),
and then use the same string to query the resulting AFS::Object::Group
object using getGroupByName().

The object's "group" attribute, however, will be in lower case, since
that will be the value returned by the pts command itself.

=head2 AFS::Command::VOS->restore arguments added

Documentation for two important new vos restore arguments was added to
the documentation (-creation and -lastupdate).  These options are only
available in a patch to vos, which should be in one the next two major
OpenAFS 1.2.X releases.

=head1 Bugs

=head2 AFS::Object::VolumeHeader 'accesses' attribute was incorrect

The actual attribute on the objects was named "access".  Oops.  The
attribute was changed to "accesses" to match the documentation, and
the string that is parsed in vos output.

=head1 Changes in 1.4

=head1 Enhancements

=head2 Test suite is disabled by default.

There is a well intentioned effort to automatically test all new
submissions to CPAN (see http://testers.cpan.org).  Unfortunately, the
AFS-Command package can't be tested automatically, since it requires a
fair amount of configuration.  You have to specify a cell name, some
fileserver names and partitions, etc.

By default, the tests will all be skipped if the configured cell name
in the CONFIG file has not been modified, thus allowing the automated
CPAN testing to continue, automatically, without the author getting a
bunch of bogus emails.

=head1 Bugs

=head2 Volume status value inconsistency

!!!!!!!! WARNING !!!!!!!!

This change is NOT backwards compatible with previous releases, so
verify that your code handles the new, consistent values correctly.

The "status" field returned from either vos->examine() or
vos->listvol(), in previous releases, had inconsistent values. When a
volume is online, the status would be one of:

    online
    On-line

Likewise, when offline, one of:

    offline
    Off-line

These values have all been normalized to: online, offline

=head2 AFS::Command::VOS->examine parses busy and offline messages

When a volume is busy or can not be attached, "vos examine" will be
unable to display the volume headers.  Instead, a one line message is
printed, such as one of the following:

    **** Volume 123456789 is busy ****
    **** Could not attach volume 123456789 ****

The examine method now parses these, and the AFS::Object::VolumeHeader
object will have only the 'id' and 'status' attributes.  Previously,
those lines were incorrectly parsed and the 'name' attribute set to
'****', which is pretty obviously wrong.




=head1 Changes in 1.3

=head1 Enhancements

=head2 AFS::Command::VOS->release support both -f and -force

Someone at some point changed vos release's force argument from -f to
-force.  API calls written with one of those will not work with a vos
binary that doesn't agree on the choice of force option.  This would
have require developers to write code like this:

   $vos->release
     (
      id		=> $id,
      cell		=> $cell,
      ( $vos->supportsArgument('release','force') ?
        ( force		=> 1 ) :
        ( f		=> 1 )
      ),
     );

OK, that's the authors twisted way of doing it, and it seems like
needless complexity.  Both f and force are now supported as options,
using a simply alias mechanism to translate from one to the other,
based on what is supported by the vos binary.

The documentation hasn't been changed, since you can use 'force' in
all cases, and since that is the newer of the two, the docs will stay
as they are.  The fact that your vos binary really wants 'f' is hidden
from you, and will stay that way.

=head1 Bugs

=head2 AFS::Command::BOS->listhosts

The returned object has a cell attribute, as well as the hosts
attribute, but the docs forgot to mention this.




=head1 Changes in 1.2

=head1 Enhancements

=head2 AFS::Command::VOS->offline() and ->online()

Both of these "hidden" vos commands are now supported by the API.
These commands are part of the vos command suite, but they do not show
up in the output of "vos help".

=head1 Bugs



=head1 Changes in 1.1

=head1 Enhancements

=head2 AFS::Command::VOS->status()

This method now parses the individual transaction stanzas, and the API
provides access to objects that encapsulate each of them.  So, instead
of just finding out how many transactions are active, you can analyze
each of them in detail.

=head2 AFS::Object::VLDB data is now keyed on volume ID, as well as name

This changes the way the AFS::Object::VLDB data can be extracted.  In
1.0, the API allowed AFS::Object::VLDBEntry object to be queried only
by name, but 3 new methods calls allow this data to be queried by
numeric ID as well as name:

    getVLDBEntryById($id)
    getVLDBEntryByName($name)
    getVolumeIds()

The API for getVLDBEntry has changed, and the single argument usage:

    getVLDBEntry($name)		# DEPRECATED!!!!

has been deprecated.  If warnings are enabled (perl -w), then the API
will carp at you.  In the next release (1.2), that usage will no
longer be supported.  Upgrade your code, or pay the price.

The new usage of getVLDBEntry is one of:

    getVLDBEntry( name => $name )
    getVLDBEntry( id => $id )

The internal method _addEntry() now manages the data by id as well as
name, but you're not supposed to know that anyway...

=head1 Bugs

=head2 AFS::Command::BOS->status

When querying a specific instance, if it wasn't there, the API would
try to add an undefined instance object to the result, and croak.
Now, you just get a result object with no instances.

=head2 AFS::Command::FS->(several methods)

If you pass a list of paths to methods such as whichcell(), then the
error handling was a bit intrusive.  The way it used to work, in 1.0,
was to return an error if we didn't see output for one or more of the
paths.  Now, the code is more forgiving, and if have no idea what
happened for one of the given paths, we'll return a Path object for
that path which has a generic error.

=head2 AFS::Command::FS->lsmount

The "File 'foo' doesn't exist" error message wasn't trapped as a
recognized error.  It is now.

=head2 AFS::Command::FS->sysname

The pattern matching was a bit greedy and the trailing single quote
was showing up in the returned sysname value.

=cut

Changes.html  view on Meta::CPAN

    <HTML> 
	<HEAD> 
	    <TITLE>distro/Changes</TITLE> 
	</HEAD>

	<BODY>

<!-- INDEX BEGIN -->

<UL>

	<LI><A HREF="#CHanges_in_1_9">CHanges in 1.9</A>
	<LI><A HREF="#Enhancements">Enhancements</A>
	<LI><A HREF="#Changes_in_1_8">Changes in 1.8</A>
	<LI><A HREF="#Bug_Fixes">Bug Fixes</A>
	<UL>

		<LI><A HREF="#vos_examine_did_not_pick_up_the_">vos examine did not pick up the LOCKED flag</A>
		<LI><A HREF="#pts_membership_error_checking_wa">pts membership error checking was bogus</A>
	</UL>

	<LI><A HREF="#Changes_in_1_7">Changes in 1.7</A>
	<LI><A HREF="#Enhancements">Enhancements</A>
	<UL>

		<LI><A HREF="#Boolean_flags_can_be_turned_off_">Boolean flags can be turned off, as well as on</A>
	</UL>

	<LI><A HREF="#Bugs">Bugs</A>
	<UL>

		<LI><A HREF="#vos_examine_by_numeric_ID_did_no">vos examine by numeric ID did not parse Volume Headers</A>
	</UL>

	<LI><A HREF="#Changes_in_1_6">Changes in 1.6</A>
	<LI><A HREF="#Enhancements">Enhancements</A>
	<LI><A HREF="#AFS_Object_VolumeHeader_new_a">AFS::Object::VolumeHeader: new attribute 'attached'</A>
	<LI><A HREF="#Bugs">Bugs</A>
	<LI><A HREF="#Changes_in_1_5">Changes in 1.5</A>
	<LI><A HREF="#Enhancements">Enhancements</A>
	<UL>

		<LI><A HREF="#Principal_names_as_user_supplie">Principal names, as user supplied arguments, are lowercased</A>
		<LI><A HREF="#AFS_Command_VOS_restore_argum">AFS::Command::VOS->restore arguments added</A>
	</UL>

	<LI><A HREF="#Bugs">Bugs</A>
	<UL>

		<LI><A HREF="#AFS_Object_VolumeHeader_acces">AFS::Object::VolumeHeader 'accesses' attribute was incorrect</A>
	</UL>

	<LI><A HREF="#Changes_in_1_4">Changes in 1.4</A>
	<LI><A HREF="#Enhancements">Enhancements</A>
	<UL>

		<LI><A HREF="#Test_suite_is_disabled_by_defaul">Test suite is disabled by default.</A>
	</UL>

	<LI><A HREF="#Bugs">Bugs</A>
	<UL>

		<LI><A HREF="#Volume_status_value_inconsistenc">Volume status value inconsistency</A>
		<LI><A HREF="#AFS_Command_VOS_examine_parse">AFS::Command::VOS->examine parses busy and offline messages</A>
	</UL>

	<LI><A HREF="#Changes_in_1_3">Changes in 1.3</A>
	<LI><A HREF="#Enhancements">Enhancements</A>
	<UL>

		<LI><A HREF="#AFS_Command_VOS_release_suppo">AFS::Command::VOS->release support both -f and -force</A>
	</UL>

	<LI><A HREF="#Bugs">Bugs</A>
	<UL>

		<LI><A HREF="#AFS_Command_BOS_listhosts">AFS::Command::BOS->listhosts</A>
	</UL>

	<LI><A HREF="#Changes_in_1_2">Changes in 1.2</A>
	<LI><A HREF="#Enhancements">Enhancements</A>
	<UL>

		<LI><A HREF="#AFS_Command_VOS_offline_and">AFS::Command::VOS->offline() and ->online()</A>
	</UL>

	<LI><A HREF="#Bugs">Bugs</A>
	<LI><A HREF="#Changes_in_1_1">Changes in 1.1</A>
	<LI><A HREF="#Enhancements">Enhancements</A>
	<UL>

		<LI><A HREF="#AFS_Command_VOS_status_">AFS::Command::VOS->status()</A>
		<LI><A HREF="#AFS_Object_VLDB_data_is_now_ke">AFS::Object::VLDB data is now keyed on volume ID, as well as name</A>
	</UL>

	<LI><A HREF="#Bugs">Bugs</A>
	<UL>

		<LI><A HREF="#AFS_Command_BOS_status">AFS::Command::BOS->status</A>
		<LI><A HREF="#AFS_Command_FS_several_metho">AFS::Command::FS->(several methods)</A>
		<LI><A HREF="#AFS_Command_FS_lsmount">AFS::Command::FS->lsmount</A>
		<LI><A HREF="#AFS_Command_FS_sysname">AFS::Command::FS->sysname</A>
	</UL>

</UL>
<!-- INDEX END -->

<HR>
<P>
<H1><A NAME="CHanges_in_1_9">CHanges in 1.9

</A></H1>
<P>
<HR>
<H1><A NAME="Enhancements">Enhancements

</A></H1>
A new argument is supported by AFS::Command::Base-&gt;new():


<P>

<PRE>    my $vos = AFS::Command::VOS-&gt;new( timestamps =&gt; 1 );
</PRE>

<P>

This will result in ISO timestamps being prepended to each line of output
when it is collected into the $vos-&gt;errors(). This is useful for
profiling the performance of operations such as vos release:


<P>

<PRE>    my $result = $vos-&gt;release
      (
       id               =&gt; 'somevol',
       cell             =&gt; 'somecell',
      ) || die $vos-&gt;errors();
</PRE>

<P>

When this works, the $vos-&gt;errors() will have the verbose output, which
can be logged even in the successful case, for diagnostics. Here's an
example for a failure:


<P>

<PRE>    [2004-11-18 17:20:36] Could not lock the VLDB entry for the volume 536998569.
    [2004-11-18 17:20:36] VLDB: no permission access for call
    [2004-11-18 17:20:36] Error in vos release command.
    [2004-11-18 17:20:36] VLDB: no permission access for call
</PRE>

<P>

<P>
<HR>
<H1><A NAME="Changes_in_1_8">Changes in 1.8

</A></H1>
<P>
<HR>
<H1><A NAME="Bug_Fixes">Bug Fixes

</A></H1>
<P>
<HR>
<H2><A NAME="vos_examine_did_not_pick_up_the_">vos examine did not pick up the LOCKED flag

</A></H2>
The code to parse the VLDB header was missing the LOCKED flag, if it was
present, so this attribute was not being set properly. It is now.


<P>

<P>
<HR>
<H2><A NAME="pts_membership_error_checking_wa">pts membership error checking was bogus

</A></H2>
Well, it still is bogus, actually, since the code has to deal with the fact
that pts has never produced meaningful return codes, so a failed pts
membership command can still exit 0, and we have to figure out if it failed
by other means. This is done by looking for the known error messages that
pts prints out, which is a good example of why parsing the ASCII test
output of commands like this is a weak architecture.


<P>

<P>
<HR>
<H1><A NAME="Changes_in_1_7">Changes in 1.7

</A></H1>
<P>
<HR>
<H1><A NAME="Enhancements">Enhancements

</A></H1>
<P>
<HR>
<H2><A NAME="Boolean_flags_can_be_turned_off_">Boolean flags can be turned off, as well as on

</A></H2>
If an argument to a method (and its corresponding command line argument)
doesn't take a value, it is treated like a Boolean flag. However, the code
used to assume that the existence of a Boolean key in the argument list
implied the Boolean argument was always true.


<P>

Now, the truth of the arguments <EM>value</EM> is tested to determine if the flag should be set on or off. This makes it
easy to have subroutines that just blindly pass certain arguments along,
without haing to test them, and allows for much cleaner code.


<P>

For example:


<P>

<PRE>    my $result = $vos-&gt;release
      (
       name             =&gt; $name,
       force            =&gt; $force,
      );
</PRE>

<P>

Will work as you probably expected it to, namely if <CODE>$force</CODE> is
zero, it will NOT be passed to the ``vos release'' command. In previous
releases, regardless of the value of $force, the mere existence of the
force key in the argument hash would have caused the -force option to be
used.


<P>

<P>
<HR>
<H1><A NAME="Bugs">Bugs

</A></H1>
<P>
<HR>
<H2><A NAME="vos_examine_by_numeric_ID_did_no">vos examine by numeric ID did not parse Volume Headers

</A></H2>
The code to parse the volume headers in the output from ``vos examine'' was
looking for a pattern match based on the ``id'' argument. However, the
headers always print the name first, and we were assuming that the id
argument was the volume name, when it can also be the volume ID.


<P>

The parsing is less strict now, and works for both a volume name od a
numeric ID.


<P>

<P>
<HR>
<H1><A NAME="Changes_in_1_6">Changes in 1.6

</A></H1>
<P>
<HR>
<H1><A NAME="Enhancements">Enhancements

</A></H1>
<P>
<HR>
<H1><A NAME="AFS_Object_VolumeHeader_new_a">AFS::Object::VolumeHeader: new attribute 'attached'

</A></H1>
When a volume's status is ``offline'', there is really more than one status
the volume can be in. A volume might be unattached, and offline, or it
might be attached, but adminitratively offline. For example, ``vos
offline'' put a vlume in the latter state, and if for some reason there are
two volumes with the same ID on the same fileserver, they will both be
attached (if possible), but only the first one encountered will be brought
online.


<P>

Changing the value of status would been a non-backwards compatible change,
and caused some subtle problems for some applications. Rather than
introduce such a change, I have chosen to represent this state with an
additional attribute ``attached'', which is just a Boolean, indicating
whether or not the volume is attached.


<P>

<P>
<HR>
<H1><A NAME="Bugs">Bugs

</A></H1>
<P>
<HR>
<H1><A NAME="Changes_in_1_5">Changes in 1.5

</A></H1>
<P>
<HR>
<H1><A NAME="Enhancements">Enhancements

</A></H1>
<P>
<HR>
<H2><A NAME="Principal_names_as_user_supplie">Principal names, as user supplied arguments, are lowercased

</A></H2>
As a convenience, when PTS names are passed to methods like
<CODE>getUserByName(),</CODE> they will be lower-cased before looking up
the user, or group. Since PTS is a case-insensitive database, this will
avoid the need for applications to <CODE>lc()</CODE> some strings, although
not in all cases.


<P>

For example, you can pass a mixed case string to pts-&gt;creategroup(), and
then use the same string to query the resulting AFS::Object::Group object
using <CODE>getGroupByName().</CODE>


<P>

The object's ``group'' attribute, however, will be in lower case, since
that will be the value returned by the pts command itself.


<P>

<P>
<HR>
<H2><A NAME="AFS_Command_VOS_restore_argum">AFS::Command::VOS->restore arguments added

</A></H2>
Documentation for two important new vos restore arguments was added to the
documentation (-creation and -lastupdate). These options are only available
in a patch to vos, which should be in one the next two major OpenAFS 1.2.X
releases.


<P>

<P>
<HR>
<H1><A NAME="Bugs">Bugs

</A></H1>
<P>
<HR>
<H2><A NAME="AFS_Object_VolumeHeader_acces">AFS::Object::VolumeHeader 'accesses' attribute was incorrect

</A></H2>
The actual attribute on the objects was named ``access''. Oops. The
attribute was changed to ``accesses'' to match the documentation, and the
string that is parsed in vos output.


<P>

<P>
<HR>
<H1><A NAME="Changes_in_1_4">Changes in 1.4

</A></H1>
<P>
<HR>
<H1><A NAME="Enhancements">Enhancements

</A></H1>
<P>
<HR>
<H2><A NAME="Test_suite_is_disabled_by_defaul">Test suite is disabled by default.

</A></H2>
There is a well intentioned effort to automatically test all new
submissions to CPAN (see <A
HREF="http://testers.cpan.org).">http://testers.cpan.org).</A>
Unfortunately, the AFS-Command package can't be tested automatically, since
it requires a fair amount of configuration. You have to specify a cell
name, some fileserver names and partitions, etc.


<P>

By default, the tests will all be skipped if the configured cell name in
the CONFIG file has not been modified, thus allowing the automated CPAN
testing to continue, automatically, without the author getting a bunch of
bogus emails.


<P>

<P>
<HR>
<H1><A NAME="Bugs">Bugs

</A></H1>
<P>
<HR>
<H2><A NAME="Volume_status_value_inconsistenc">Volume status value inconsistency

</A></H2>
!!!!!!!! WARNING !!!!!!!!


<P>

This change is NOT backwards compatible with previous releases, so verify
that your code handles the new, consistent values correctly.


<P>

The ``status'' field returned from either vos-&gt;examine() or
vos-&gt;listvol(), in previous releases, had inconsistent values. When a
volume is online, the status would be one of:


<P>

<PRE>    online
    On-line
</PRE>

<P>

Likewise, when offline, one of:


<P>

<PRE>    offline
    Off-line
</PRE>

<P>

These values have all been normalized to: online, offline


<P>

<P>
<HR>
<H2><A NAME="AFS_Command_VOS_examine_parse">AFS::Command::VOS->examine parses busy and offline messages

</A></H2>
When a volume is busy or can not be attached, ``vos examine'' will be
unable to display the volume headers. Instead, a one line message is
printed, such as one of the following:


<P>

<PRE>    **** Volume 123456789 is busy ****
    **** Could not attach volume 123456789 ****
</PRE>

<P>

The examine method now parses these, and the AFS::Object::VolumeHeader
object will have only the 'id' and 'status' attributes. Previously, those
lines were incorrectly parsed and the 'name' attribute set to '****', which
is pretty obviously wrong.


<P>

<P>
<HR>
<H1><A NAME="Changes_in_1_3">Changes in 1.3

</A></H1>
<P>
<HR>
<H1><A NAME="Enhancements">Enhancements

</A></H1>
<P>
<HR>
<H2><A NAME="AFS_Command_VOS_release_suppo">AFS::Command::VOS->release support both -f and -force

</A></H2>
Someone at some point changed vos release's force argument from -f to
-force. API calls written with one of those will not work with a vos binary
that doesn't agree on the choice of force option. This would have require
developers to write code like this:


<P>

<PRE>   $vos-&gt;release
     (
      id                =&gt; $id,
      cell              =&gt; $cell,
      ( $vos-&gt;supportsArgument('release','force') ?
        ( force         =&gt; 1 ) :
        ( f             =&gt; 1 )
      ),
     );
</PRE>

<P>

OK, that's the authors twisted way of doing it, and it seems like needless
complexity. Both f and force are now supported as options, using a simply
alias mechanism to translate from one to the other, based on what is
supported by the vos binary.


<P>

The documentation hasn't been changed, since you can use 'force' in all
cases, and since that is the newer of the two, the docs will stay as they
are. The fact that your vos binary really wants 'f' is hidden from you, and
will stay that way.


<P>

<P>
<HR>
<H1><A NAME="Bugs">Bugs

</A></H1>
<P>
<HR>
<H2><A NAME="AFS_Command_BOS_listhosts">AFS::Command::BOS->listhosts

</A></H2>
The returned object has a cell attribute, as well as the hosts attribute,
but the docs forgot to mention this.


<P>

<P>
<HR>
<H1><A NAME="Changes_in_1_2">Changes in 1.2

</A></H1>
<P>
<HR>
<H1><A NAME="Enhancements">Enhancements

</A></H1>
<P>
<HR>
<H2><A NAME="AFS_Command_VOS_offline_and">AFS::Command::VOS->offline() and ->online()

</A></H2>
Both of these ``hidden'' vos commands are now supported by the API. These
commands are part of the vos command suite, but they do not show up in the
output of ``vos help''.


<P>

<P>
<HR>
<H1><A NAME="Bugs">Bugs

</A></H1>
<P>
<HR>
<H1><A NAME="Changes_in_1_1">Changes in 1.1

</A></H1>
<P>
<HR>
<H1><A NAME="Enhancements">Enhancements

</A></H1>
<P>
<HR>
<H2><A NAME="AFS_Command_VOS_status_">AFS::Command::VOS->status()

</A></H2>
This method now parses the individual transaction stanzas, and the API
provides access to objects that encapsulate each of them. So, instead of
just finding out how many transactions are active, you can analyze each of
them in detail.


<P>

<P>
<HR>
<H2><A NAME="AFS_Object_VLDB_data_is_now_ke">AFS::Object::VLDB data is now keyed on volume ID, as well as name

</A></H2>
This changes the way the AFS::Object::VLDB data can be extracted. In 1.0,
the API allowed AFS::Object::VLDBEntry object to be queried only by name,
but 3 new methods calls allow this data to be queried by numeric ID as well
as name:


<P>

<PRE>    getVLDBEntryById($id)
    getVLDBEntryByName($name)
    getVolumeIds()
</PRE>

<P>

The API for getVLDBEntry has changed, and the single argument usage:


<P>

<PRE>    getVLDBEntry($name)         # DEPRECATED!!!!
</PRE>

<P>

has been deprecated. If warnings are enabled (perl -w), then the API will
carp at you. In the next release (1.2), that usage will no longer be
supported. Upgrade your code, or pay the price.


<P>

The new usage of getVLDBEntry is one of:


<P>

<PRE>    getVLDBEntry( name =&gt; $name )
    getVLDBEntry( id =&gt; $id )
</PRE>

<P>

The internal method <CODE>_addEntry()</CODE> now manages the data by id as
well as name, but you're not supposed to know that anyway...


<P>

<P>
<HR>
<H1><A NAME="Bugs">Bugs

</A></H1>
<P>
<HR>
<H2><A NAME="AFS_Command_BOS_status">AFS::Command::BOS->status

</A></H2>
When querying a specific instance, if it wasn't there, the API would try to
add an undefined instance object to the result, and croak. Now, you just
get a result object with no instances.


<P>

<P>
<HR>
<H2><A NAME="AFS_Command_FS_several_metho">AFS::Command::FS->(several methods)

</A></H2>
If you pass a list of paths to methods such as <CODE>whichcell(),</CODE>
then the error handling was a bit intrusive. The way it used to work, in
1.0, was to return an error if we didn't see output for one or more of the
paths. Now, the code is more forgiving, and if have no idea what happened
for one of the given paths, we'll return a Path object for that path which
has a generic error.


<P>

<P>
<HR>
<H2><A NAME="AFS_Command_FS_lsmount">AFS::Command::FS->lsmount

</A></H2>
The ``File 'foo' doesn't exist'' error message wasn't trapped as a
recognized error. It is now.


<P>

<P>
<HR>
<H2><A NAME="AFS_Command_FS_sysname">AFS::Command::FS->sysname

</A></H2>
The pattern matching was a bit greedy and the trailing single quote was
showing up in the returned sysname value.


<P>

</DL>
    </BODY>

    </HTML>

LICENSE  view on Meta::CPAN


This License Agreement states the conditions under which a Package may
be copied, such that the Copyright Holder maintains some semblance of
artistic control over the development of the package, while giving the
users of the package the right to use and distribute the Package in a
more-or-less customary fashion, plus the right to make reasonable
modifications.

Definitions:

	"Package" refers to the collection of files distributed by the
	Copyright Holder, and derivatives of that collection of files
	created through textual modification.

	"Standard Version" refers to such a Package if it has not been
	modified, or has been modified in accordance with the wishes
	of the Copyright Holder as specified below.

	"Copyright Holder" is whoever is named in the copyright or
	copyrights for the package.

	"You" is you, if you're thinking about copying or distributing
	this Package.

	"Reasonable copying fee" is whatever you can justify on the
	basis of media cost, duplication charges, time of people
	involved, and so on.  (You will not be required to justify it
	to the Copyright Holder, but only to the computing community
	at large as a market that must bear the fee.)

	"Freely Available" means that no fee is charged for the item
	itself, though there may be fees involved in handling the
	item.  It also means that recipients of the item may
	redistribute it under the same conditions they received it.

1. You may make and give away verbatim copies of the source form of
   the Standard Version of this Package without restriction, provided
   that you duplicate all of the original copyright notices and
   associated disclaimers.

2. You may apply bug fixes, portability fixes and other modifications
   derived from the Public Domain or from the Copyright Holder.  A
   Package modified in such a way shall still be considered the
   Standard Version.

3. You may otherwise modify your copy of this Package in any way,
   provided that you insert a prominent notice in each changed file
   stating how and when you changed that file, and provided that you
   do at least ONE of the following:

   a) place your modifications in the Public Domain or otherwise make
      them Freely Available, such as by posting said modifications to
      Usenet or an equivalent medium, or placing the modifications on
      a major archive site such as uunet.uu.net, or by allowing the
      Copyright Holder to include your modifications in the Standard
      Version of the Package.

   b) use the modified Package only within your corporation or
      organization.

   c) rename any non-standard executables so the names do not conflict
      with standard executables, which must also be provided, and
      provide a separate manual page for each non-standard executable
      that clearly documents how it differs from the Standard Version.

   d) make other distribution arrangements with the Copyright Holder.

4. You may distribute the programs of this Package in object code
   or.executable form, provided that you do at least ONE of the
   following:

   a) distribute a Standard Version of the executables and library
      files, together with instructions (in the manual page or
      equivalent) on where to get the Standard Version.

   b) accompany the distribution with the machine-readable source of
      the Package with your modifications.

   c) give non-standard executables non-standard names, and clearly
      document the differences in manual pages (or equivalent),
      together with instructions on where to get the Standard Version.

   d) make other distribution arrangements with the Copyright Holder.

5. You may charge a reasonable copying fee for any distribution of
   this Package.  You may charge any fee you choose for support of
   this Package.  You may not charge a fee for this Package itself.
   However, you may distribute this Package in aggregate with other
   (possibly commercial) programs as part of a larger (possibly
   commercial) software distribution provided that you do not
   advertise this Package as a product of your own.  You may embed
   this Package's interpreter within an executable of yours (by
   linking); this shall be construed as a mere form of aggregation,
   provided that the complete Standard Version of the interpreter is
   so embedded.

6. The scripts and library files supplied as input to or produced as
   output from the programs of this Package do not automatically fall
   under the copyright of this Package, but belong to whoever
   generated them, and may be sold commercially, and may be aggregated
   with this Package.  If such scripts or library files are aggregated
   with this Package via the so-called "undump" or "unexec" methods of
   producing a binary executable image, then distribution of such an
   image shall neither be construed as a distribution of this Package
   nor shall it fall under the restrictions of Paragraphs 3 and 4,
   provided that you do not represent such an executable image as a
   Standard Version of this Package.

7. C subroutines (or comparably compiled subroutines in other
   languages) supplied by you and linked into this Package in order to
   emulate subroutines and variables of the language defined by this
   Package shall not be considered part of this Package, but are the
   equivalent of input as in Paragraph 6, provided these subroutines
   donot change the language in any way that would cause it to fail
   the regression tests for the language.

8. Aggregation of this Package with a commercial distribution is
   always permitted provided that the use of this Package is embedded;
   that is, when no overt attempt is made to make this Package's
   interfaces visible to the end user of the commercial distribution.
   Such use shall not be construed as a distribution of this Package.

9. The name of the Copyright Holder may not be used to endorse or
   promote products derived from this software without specific prior
   written permission.

10. THIS PACKAGE IS PROVIDED ON AN "AS IS" BASIS.  NO REPRESENTATION
    OR WARRANTY OF ANY KIND IS MADE, EITHER EXPRESS OR IMPLIED, WITH
    RESPECT TO THE PACKAGE (OR THE RESULTS TO BE OBTAINED BY THE USE
    THEREOF), INCLUDING, BUT NOT LIMITED TO, ANY AND ALL IMPLIED
    WARRANTIES OF ORIGINALITY, ACCURACY, COMPLETENESS, MERCHANTABILITY
    AND FITNESS FOR ANY PARTICULAR PURPOSE.  YOU ASSUME THE ENTIRE
    RISK OF ANY USE YOU MAY MAKE OF THE PACKAGE.  IN NO EVENT SHALL
    MORGAN STANLEY OR ANY OTHER PARTY, BE LIABLE TO YOU OR ANY OTHER
    PARTY FOR ANY DIRECT OR INDIRECT DAMAGES, INCLUDING, WITHOUT
    LIMITATION, ANY LOST PROFITS, LOST SAVINGS OR OTHER INCIDENTAL OR
    CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT OR YOUR
    INABILITY TO USE THE PACKAGE, REGARDLESS OF THE FORM OF ACTION,
    EVEN IF MORGAN STANLEY HAS BEEN ADVISED OF OR OTHERWISE MIGHT HAVE
    ANTICIPATED THE POSSIBILITY OF SUCH DAMAGES.  SOME STATES DO NOT
    ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR LIABILITIES.  IN SUCH
    EVENT, MORGAN STANLEY'S MAXIMUM LIABILITY SHALL BE LIMITED TO $10
    (TEN DOLLARS).

THE END

$Id: LICENSE,v 7.1 2004/01/13 19:01:10 wpm Exp $

(c) 2003-2004 Morgan Stanley and Co.

MANIFEST  view on Meta::CPAN

Changes
Changes.html
CONFIG
COPYRIGHT
lib/AFS/Command.pod
lib/AFS/Command/Base.pm
lib/AFS/Command/Base.pod
lib/AFS/Command/BOS.pm
lib/AFS/Command/BOS.pod
lib/AFS/Command/FS.pm
lib/AFS/Command/FS.pod
lib/AFS/Command/PTS.pm
lib/AFS/Command/PTS.pod
lib/AFS/Command/VOS.pm
lib/AFS/Command/VOS.pod
lib/AFS/Object.pm
lib/AFS/Object.pod
lib/AFS/Object/ACL.pm
lib/AFS/Object/BosServer.pm
lib/AFS/Object/CacheManager.pm
lib/AFS/Object/Cell.pm
lib/AFS/Object/FileServer.pm
lib/AFS/Object/Group.pm
lib/AFS/Object/Instance.pm
lib/AFS/Object/Partition.pm
lib/AFS/Object/Path.pm
lib/AFS/Object/Principal.pm
lib/AFS/Object/PTServer.pm
lib/AFS/Object/Server.pm
lib/AFS/Object/Transaction.pm
lib/AFS/Object/User.pm
lib/AFS/Object/VLDB.pm
lib/AFS/Object/VLDBEntry.pm
lib/AFS/Object/VLDBSite.pm
lib/AFS/Object/VolServer.pm
lib/AFS/Object/Volume.pm
lib/AFS/Object/VolumeHeader.pm
LICENSE
Makefile.PL
MANIFEST			This list of files
META.yml			Module meta-data (added by MakeMaker)
README
README.html
t/00vos_basic.t
t/01vos_dumprestore.t
t/02vos_volserver.t
t/10bos_basic.t
t/20fs_basic.t
t/30pts_basic.t
t/40fs_complex.t
t/99pts_cleanup.t
ToDo
ToDo.html
util/bin/check_copyright
util/bin/check_version
util/bin/write_exclude
util/bin/write_manifest
util/lib/parse_config

META.yml  view on Meta::CPAN

--- #YAML:1.0
name:               AFS-Command
version:            1.99
abstract:           ~
author:
    - W. Phillip Moore <Phil.Moore@MorganStanley.com>
license:            unknown
distribution_type:  module
configure_requires:
    ExtUtils::MakeMaker:  0
build_requires:
    ExtUtils::MakeMaker:  0
requires:  {}
no_index:
    directory:
        - t
        - inc
generated_by:       ExtUtils::MakeMaker version 6.56
meta-spec:
    url:      http://module-build.sourceforge.net/META-spec-v1.4.html
    version:  1.4

Makefile.PL  view on Meta::CPAN

#
# $Id: Makefile.PL,v 7.1 2004/01/13 19:01:10 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

require 5.6.0;

use ExtUtils::MakeMaker;

WriteMakefile
  (
   NAME			=> 'AFS::Command',
   VERSION_FROM		=> 'lib/AFS/Command/Base.pm', # finds $VERSION
   PREREQ_PM         	=> {},	# e.g., Module::Name => 1.1
   AUTHOR     		=> 'W. Phillip Moore <Phil.Moore@MorganStanley.com>',
  );

README  view on Meta::CPAN

#-*-cperl-*-
#
# $Id: README,v 7.1 2004/01/13 19:01:11 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

=head1 INTRODUCTION

AFS::Command -- An OO wrapper API for the AFS command line utilities (vos, bos, pts, fs)

=head1 SUMMARY

This suite of perl modules implements an API for the command line
utilities for managing and using the AFS distributed file system.  AFS
is available as both an Open Source product (http://www.openafs.org),
as well as a commercial version from IBM
(http://www.ibm.com/software/stormgmt/afs/).

For example, AFS volumes are managed using the command line utility
'vos', for which there is currently no programmatic API, in any
language.  This API is a pure perl wrapper around the command line
utility, that implements an OO API that allows for the easy
development of systems management applications for AFS.

=head1 PREREQUISITES

This module requires perl5.6 or later, as well as an installed AFS
infrastructure to work with.  The code is pure perl, with no compiled
components, do it should work on any variant of UNIX (sorry, but this
code makes aggressive use of pipe() and fork(), so porting it to
Windows is gonna be painful...  but then, why anyone would want to
manage their AFS infrastructure from Windows escapes me).

=head1 INSTALLATION

This module builds like almost everything else on CPAN:

  perl Makefile.PL
  make
  make test
  make install

Before running 'make test', edit the CONFIG file and follow the
instructions in the comments.  Minimally, you have to specify a couple
of AFS file servers and one AFS database server against which to run
the tests, as well as a pathname in AFS where we can create mount
points.

The test suite will require AFS super-user privileges, so you must
either be a member of the system:administrators protection group, or a
member of the super users list on the servers.  If you don't know what
that means, you shouldn't be testing this module, so consult your AFS
administrators.

See the CONFIG file for more details.  Its pretty verbosely
documented.

Any failure in the test suite is a cause for concern.  For more
verbose output, run:

  make test TEST_VERBOSE=1

If you can't determine the source of the problem(s), send the verbose
test output, along with the output from "perl -V", as well as the
versions of AFS in use at your site, to the author, and I'll do my
best to figure out why things are breaking, and if you're lucky,
actually fix it.

=head1 RELEASE NOTES

In addition to the README file (which in case you hadn't noticed,
you're reading right now), the history of changes is maintained in
Changes.html.

=head1 DOCUMENTATION

Docs for these modules are split across several files, and will be
installed as man pages.

  man AFS::Command

will provide the general overview of the API, its basic design.  The
details of the API calls, their arguments, and return values, are
found in the following documents:

  man AFS::Command::Base
  man AFS::Command::VOS
  man AFS::Command::BOS
  man AFS::Command::PTS
  man AFS::Command::FS
  man AFS::Object

=head1 AUTHOR

This code is a product of the demented mind of:

    W. Phillip Moore <Phil.Moore@MorganStanley.com>

Feedback, patches, recommendations, adulations, and even flames are
all welcome.   Feed my ego (or try to shoot it down :-), please...

=cut

README.html  view on Meta::CPAN

    <HTML> 
	<HEAD> 
	    <TITLE>distro/README</TITLE> 
	</HEAD>

	<BODY>

<!-- INDEX BEGIN -->

<UL>

	<LI><A HREF="#INTRODUCTION">INTRODUCTION</A>
	<LI><A HREF="#SUMMARY">SUMMARY</A>
	<LI><A HREF="#PREREQUISITES">PREREQUISITES</A>
	<LI><A HREF="#INSTALLATION">INSTALLATION</A>
	<LI><A HREF="#RELEASE_NOTES">RELEASE NOTES</A>
	<LI><A HREF="#DOCUMENTATION">DOCUMENTATION</A>
	<LI><A HREF="#AUTHOR">AUTHOR</A>
</UL>
<!-- INDEX END -->

<HR>
<P>
<H1><A NAME="INTRODUCTION">INTRODUCTION

</A></H1>
AFS::Command -- An OO wrapper API for the AFS command line utilities (vos,
bos, pts, fs)


<P>

<P>
<HR>
<H1><A NAME="SUMMARY">SUMMARY

</A></H1>
This suite of perl modules implements an API for the command line utilities
for managing and using the AFS distributed file system. AFS is available as
both an Open Source product (http://www.openafs.org), as well as a
commercial version from IBM (http://www.ibm.com/software/stormgmt/afs/).


<P>

For example, AFS volumes are managed using the command line utility 'vos',
for which there is currently no programmatic API, in any language. This API
is a pure perl wrapper around the command line utility, that implements an
OO API that allows for the easy development of systems management
applications for AFS.


<P>

<P>
<HR>
<H1><A NAME="PREREQUISITES">PREREQUISITES

</A></H1>
This module requires perl5.6 or later, as well as an installed AFS
infrastructure to work with. The code is pure perl, with no compiled
components, do it should work on any variant of UNIX (sorry, but this code
makes aggressive use of <CODE>pipe()</CODE> and <CODE>fork(),</CODE> so
porting it to Windows is gonna be painful... but then, why anyone would
want to manage their AFS infrastructure from Windows escapes me).


<P>

<P>
<HR>
<H1><A NAME="INSTALLATION">INSTALLATION

</A></H1>
This module builds like almost everything else on CPAN:


<P>

<PRE>  perl Makefile.PL
  make
  make test
  make install
</PRE>

<P>

Before running 'make test', edit the CONFIG file and follow the
instructions in the comments. Minimally, you have to specify a couple of
AFS file servers and one AFS database server against which to run the
tests, as well as a pathname in AFS where we can create mount points.


<P>

The test suite will require AFS super-user privileges, so you must either
be a member of the system:administrators protection group, or a member of
the super users list on the servers. If you don't know what that means, you
shouldn't be testing this module, so consult your AFS administrators.


<P>

See the CONFIG file for more details. Its pretty verbosely documented.


<P>

Any failure in the test suite is a cause for concern. For more verbose
output, run:


<P>

<PRE>  make test TEST_VERBOSE=1
</PRE>

<P>

If you can't determine the source of the <CODE>problem(s),</CODE> send the
verbose test output, along with the output from ``perl -V'', as well as the
versions of AFS in use at your site, to the author, and I'll do my best to
figure out why things are breaking, and if you're lucky, actually fix it.


<P>

<P>
<HR>
<H1><A NAME="RELEASE_NOTES">RELEASE NOTES

</A></H1>
In addition to the README file (which in case you hadn't noticed, you're
reading right now), the history of changes is maintained in Changes.html.


<P>

<P>
<HR>
<H1><A NAME="DOCUMENTATION">DOCUMENTATION

</A></H1>
Docs for these modules are split across several files, and will be
installed as man pages.


<P>

<PRE>  man AFS::Command
</PRE>

<P>

will provide the general overview of the API, its basic design. The details
of the API calls, their arguments, and return values, are found in the
following documents:


<P>

<PRE>  man AFS::Command::Base
  man AFS::Command::VOS
  man AFS::Command::BOS
  man AFS::Command::PTS
  man AFS::Command::FS
  man AFS::Object
</PRE>

<P>

<P>
<HR>
<H1><A NAME="AUTHOR">AUTHOR

</A></H1>
This code is a product of the demented mind of:


<P>

<PRE>    W. Phillip Moore &lt;Phil.Moore@MorganStanley.com&gt;
</PRE>

<P>

Feedback, patches, recommendations, adulations, and even flames are all
welcome. Feed my ego (or try to shoot it down :-), please...


<P>

</DL>
    </BODY>

    </HTML>

ToDo  view on Meta::CPAN

#
# $Id: ToDo,v 7.1 2004/01/13 19:01:11 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

=head1 NAME

AFS::Command ToDo List

=head1 Enhancements

=head2 Accept AFS::Object objects as arguments

Methods like $fs->listacl() return these handy little AFS::Object::ACL
objects.  It would be nice if we could manipulate them via OO method
calls, and then pass them right back to $fs->setacl(), wouldn't it?

And how about passing the AFS::Object::Path objects returned from
$fs->whichcell directly to an $fs->listquota call, so that the final
AFS::Object::Path objects have all of the attributes returned by both
calls, but in one set of objects?

Look for this in version 1.1

=head2 stderr handling

stderr processing needs to be handled in the _exec_cmds() method, and
for that matter, _reap_cmds() needs to be folded into _exec_cmds().

The problem is that each API method calls _save_stderr(), and then
later calls _restore_stderr(), and we leave stderr redirected for
longer than necessary.  The contents of the redirected output should
be *only* the output from the commands we run, and right now, some of
our own carping can creep in there.

Worse, its possible that a failure in the API can leave stderr
redirected, resulting in a lot of confusion.

Its possible we should just suck in *ALL* of the output, both
stdout/stderr, and drop that data into a couple of arrays.  Then,
method calls on the command object get gets individual rows of
stdout/stderr output.

  return unless $self->_exec_cmds();

  #
  # Process stdout
  #
  while ( defined($_ = $self->_stdout() ) ) {

  }

  #
  # Process stderr (in some cases, there's interesting data in here.
  # see the fs examine/diskfree and similar api calls)
  #
  while ( defined($_ = $self->_stderr() ) ) {

  }

Maybe something like that.  By the time _exec_cmds returns, we have
reaped the commands, and collected *ALL* of the output into arrays in
the object.

=head2 Test Suite: fs lsmount using multiple dirs

We should create several mount points and then query then all with one
lsmount method call, to verify we can parse output for multiple dirs.
We should pass in some bogus paths, too, to verify the error handling
is correct as well (that code feels dubious to me).

=head1 Bugs

=head2 stdout/stderr buffering will break the fs examine/diskfree commands

Actually, all of the commands that parse per-path output, really.
Currently the code assumes the stderr output will appear first, which
is a side effect of the buffering.  Some attempts to turn of buffering
didn't change this, and in any case, we don't want to be sensitive to
this (we currently are).

We need to process stderr first, to determine which paths had errors,
and then parse stdout.  This will require the change descibred above
for how we handle stderr.

=cut

ToDo.html  view on Meta::CPAN

    <HTML> 
	<HEAD> 
	    <TITLE>distro/ToDo</TITLE> 
	</HEAD>

	<BODY>

<!-- INDEX BEGIN -->

<UL>

	<LI><A HREF="#NAME">NAME</A>
	<LI><A HREF="#Enhancements">Enhancements</A>
	<UL>

		<LI><A HREF="#Accept_AFS_Object_objects_as_ar">Accept AFS::Object objects as arguments</A>
		<LI><A HREF="#stderr_handling">stderr handling</A>
		<LI><A HREF="#Test_Suite_fs_lsmount_using_mul">Test Suite: fs lsmount using multiple dirs</A>
	</UL>

	<LI><A HREF="#Bugs">Bugs</A>
	<UL>

		<LI><A HREF="#stdout_stderr_buffering_will_bre">stdout/stderr buffering will break the fs examine/diskfree commands</A>
	</UL>

</UL>
<!-- INDEX END -->

<HR>
<P>
<H1><A NAME="NAME">NAME

</A></H1>
AFS::Command ToDo List


<P>

<P>
<HR>
<H1><A NAME="Enhancements">Enhancements

</A></H1>
<P>
<HR>
<H2><A NAME="Accept_AFS_Object_objects_as_ar">Accept AFS::Object objects as arguments

</A></H2>
Methods like $fs-&gt;listacl() return these handy little AFS::Object::ACL
objects. It would be nice if we could manipulate them via OO method calls,
and then pass them right back to $fs-&gt;setacl(), wouldn't it?


<P>

And how about passing the AFS::Object::Path objects returned from
$fs-&gt;whichcell directly to an $fs-&gt;listquota call, so that the final
AFS::Object::Path objects have all of the attributes returned by both
calls, but in one set of objects?


<P>

Look for this in version 1.1


<P>

<P>
<HR>
<H2><A NAME="stderr_handling">stderr handling

</A></H2>
stderr processing needs to be handled in the <CODE>_exec_cmds()</CODE>
method, and for that matter, <CODE>_reap_cmds()</CODE> needs to be folded
into <CODE>_exec_cmds().</CODE>


<P>

The problem is that each API method calls <CODE>_save_stderr(),</CODE> and
then later calls <CODE>_restore_stderr(),</CODE> and we leave stderr
redirected for longer than necessary. The contents of the redirected output
should be *only* the output from the commands we run, and right now, some
of our own carping can creep in there.


<P>

Worse, its possible that a failure in the API can leave stderr redirected,
resulting in a lot of confusion.


<P>

Its possible we should just suck in *ALL* of the output, both
stdout/stderr, and drop that data into a couple of arrays. Then, method
calls on the command object get gets individual rows of stdout/stderr
output.


<P>

<PRE>  return unless $self-&gt;_exec_cmds();
</PRE>

<P>

<PRE>  #
  # Process stdout
  #
  while ( defined($_ = $self-&gt;_stdout() ) ) {
</PRE>

<P>

<PRE>  }
</PRE>

<P>

<PRE>  #
  # Process stderr (in some cases, there's interesting data in here.
  # see the fs examine/diskfree and similar api calls)
  #
  while ( defined($_ = $self-&gt;_stderr() ) ) {
</PRE>

<P>

<PRE>  }
</PRE>

<P>

Maybe something like that. By the time _exec_cmds returns, we have reaped
the commands, and collected *ALL* of the output into arrays in the object.


<P>

<P>
<HR>
<H2><A NAME="Test_Suite_fs_lsmount_using_mul">Test Suite: fs lsmount using multiple dirs

</A></H2>
We should create several mount points and then query then all with one
lsmount method call, to verify we can parse output for multiple dirs. We
should pass in some bogus paths, too, to verify the error handling is
correct as well (that code feels dubious to me).


<P>

<P>
<HR>
<H1><A NAME="Bugs">Bugs

</A></H1>
<P>
<HR>
<H2><A NAME="stdout_stderr_buffering_will_bre">stdout/stderr buffering will break the fs examine/diskfree commands

</A></H2>
Actually, all of the commands that parse per-path output, really. Currently
the code assumes the stderr output will appear first, which is a side
effect of the buffering. Some attempts to turn of buffering didn't change
this, and in any case, we don't want to be sensitive to this (we currently
are).


<P>

We need to process stderr first, to determine which paths had errors, and
then parse stdout. This will require the change descibred above for how we
handle stderr.


<P>

</DL>
    </BODY>

    </HTML>

lib/AFS/Command.pod  view on Meta::CPAN

#
# $Id: Command.pod,v 7.1 2004/01/13 19:01:11 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

=head1 NAME

AFS::Command -- Wrapper Classes for the AFS Command Line Utilities

=head1 INTRODUCTION

Welcome to the OO Perl API for the AFS Command Line Utilities.  OK,
enough with formalities....

This set of classes implements a perl API for developing systems
administration applications for AFS, and is essentially a complete
rewrite of a similar API developed for perl4 almost a decade ago.

The API is designed to be as intuitive as the underlying command
themselves.  One of the strengths of the AFS CLI is its consistent
command line parsing, since all of the utilities share a common
library, and this API leverages this fact.  The methods available, and
their available arguments, are determined dynamically by parsing the
command help strings.

This API is also designed to complement the existing perl5 AFS::*
namespace, and co-exist with it.

=head1 OBJECT ARCHITECTURE

WARNING: This is an early design phase (1.x) of this API, and it is in
its infancy, so expect it to change in future releases, and expect to
change your code to accomodate it.

The entire API is designed to be pure OO, with the following classes:

=head2 AFS::Command::(VOS|BOS|PTS|FS)

These are the primary classes used by applications directly.  All of
the other objects are used internally to encapsulate the data parsed
from the underlying commands, and returned by the primary methods.

Each of these classes has a shared constructor (new), and methods that
correspond to each of the underlying commands.  For example, the "vos"
utility has a command called "listvldb", and the AFS::Command::VOS
objects have a "listvldb" method.  Each of the methods such as
"listvldb" take a list of key/value pairs that correspond to the
command line options for the "vos listvldb" command.

These classes implement the externally supported interface to the
entire API.  If you use anything else, you're mucking with internals,
and you get what you deserver when your code implodes.

For details, see the module documentation for each of the above.

Also, the only reason there is no AFS::Command::KAS is that the
author's AFS infrastructure is an MIT Kerberos site, and since we
don't use the kas utility, there has never been a need for such a
module.

The author would welcome a contributed module to support KAS via the
same interface, but note that the AFS::KAS module exists as part of
the existing AFS module suite, although the API is very different.

=head2 AFS::Command::Base

This is the base class for the command suite, which implements the
shared constructor, and a couple of other useful class and object
methods.

=head2 AFS::Object

This is the base class for the objects returned from the command
methods that encapsulate some form of structured data.  Many of the
methods return simple boolean true/false values (either the command
worked, or it failed), with no need for any special objects, so we
don't create them.  Anything that has to return interesting data uses
this class, or one of its base classes.

The subclasses are associated with data structures such as volume
headers, a VLDB entry, or a partition on a server.  Each of these
classes has methods to retrieve the objects they "contain", such as a
method to query the list of volume names on a partition object, and a
method to get a list of VLDB entries from a VLDB site.

The data structures, and their varying relationships, are documented
in details of the methods for each of the commands, and the specific
interfaces for each object type are documented in the corresponding
class documentation.

The subclasses for encapsulating the VLDB data are:

    AFS::Object::VLDB
    AFS::Object::VLDBEntry
    AFS::Object::VLDBSite

The subclasses for encapsulating the volume headers are:

    AFS::Object::FileServer
    AFS::Object::VolServer
    AFS::Object::Partition
    AFS::Object::Volume
    AFS::Object::VolumeHeader

The subclasses for encapsulating the bosserver data are:

    AFS::Object::BosServer
    AFS::Object::Instance

Note that none of these classes are specifically documented, since the
structure of the classes is subject to change.  The API for accessin
the results of any given AFS command (eg. vos listvol) is considered
reasonably stable, but the encapsulation may change radically.

Don't get too attached to these class names, because they will likely
be rearranged in a future release.

=cut

lib/AFS/Command/BOS.pm  view on Meta::CPAN

#
# $Id$
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

package AFS::Command::BOS;

require 5.6.0;

use strict;
use English;

use AFS::Command::Base;
use AFS::Object;
use AFS::Object::BosServer;
use AFS::Object::Instance;

our @ISA = qw(AFS::Command::Base);
our $VERSION = '1.99';

sub getdate {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::BosServer->new();

    $self->{operation} = "getdate";

    my $directory = $args{dir} || '/usr/afs/bin';

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	next unless m:File $directory/(\S+) dated ([^,]+),:;

	my $file = AFS::Object->new
	  (
	   file			=> $1,
	   date			=> $2,
	  );

	if ( /\.BAK dated ([^,]+),/ ) {
	    $file->_setAttribute( bak => $1 );
	}

	if ( /\.OLD dated ([^,\.]+)/ ) {
	    $file->_setAttribute( old => $1 );
	}

	$result->_addFile($file);

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub getlog {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::BosServer->new();

    $self->{operation} = "getlog";

    my $redirect = undef;
    my $redirectname = undef;

    if ( $args{redirect} ) {
	$redirectname = delete $args{redirect};
	$redirect = IO::File->new(">$redirectname") || do {
	    $self->_Carp("Unable to write to $redirectname: $ERRNO");
	    return;
	};
    }

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    my $log = "";

    while ( defined($_ = $self->{handle}->getline()) ) {
	next if /^Fetching log file/;
	if ( $redirect ) {
	    $redirect->print($_);
	} else {
	    $log .= $_;
	}
    }

    if ( $redirect ) {
	$redirect->close()|| do {
	    $self->_Carp("Unable to close $redirectname: $ERRNO");
	    $errors++
	};
	$result->_setAttribute( log => $redirectname );
    } else {
	$result->_setAttribute( log => $log );
    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub getrestart {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::BosServer->new();

    $self->{operation} = "getrestart";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	if ( /restarts at (.*)/ || /restarts (never)/ ) {
	    $result->_setAttribute( restart => $1 );
	} elsif ( /binaries at (.*)/ || /binaries (never)/ ) {
	    $result->_setAttribute( binaries => $1 );
	}

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub listhosts {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::BosServer->new();

    $self->{operation} = "listhosts";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    my @hosts = ();

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	if ( /Cell name is (\S+)/i ) {
	    $result->_setAttribute( cell => $1 );
	}

	if ( /Host \d+ is (\S+)/i ) {
	    push(@hosts,$1);
	}

    }

    $result->_setAttribute( hosts => \@hosts );

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub listkeys {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::BosServer->new();

    $self->{operation} = "listkeys";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	if ( /key (\d+)/ ) {

	    my $key = AFS::Object->new( index => $1 );

	    if ( /has cksum (\d+)/ ) {
		$key->_setAttribute( cksum => $1 );
	    } elsif ( /is \'([^\']+)\'/ ) {
		$key->_setAttribute( value => $1 );
	    }

	    $result->_addKey($key);

	}

	if ( /last changed on (.*)\./ ) {
	    $result->_setAttribute( keyschanged => $1 );
	}

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub listusers {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::BosServer->new();

    $self->{operation} = "listusers";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	if ( /^SUsers are: (.*)/ ) {
	    $result->_setAttribute( susers => [split(/\s+/,$1)] );
	}

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

#
# XXX -- we might want to provide parsing of the bos salvage output,
# but for now, this is a non-parsed command.
#

# sub salvage {

#     my $self = shift;
#     my (%args) = @_;

#     my $result = AFS::Object::BosServer->new();

#     $self->{operation} = "salvage";

#     return unless $self->_parse_arguments(%args);

#     return unless $self->_save_stderr();

#     my $errors = 0;

#     $errors++ unless $self->_exec_cmds();

#     while ( defined($_ = $self->{handle}->getline()) ) {

	

#     }

#     $errors++ unless $self->_reap_cmds();
#     $errors++ unless $self->_restore_stderr();

#     return if $errors;
#     return $result;

# }

sub status {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::BosServer->new();

    $self->{operation} = "status";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    my $instance = undef;

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	if ( /inappropriate access/ ) {
	    $result->_setAttribute( access => 1 );
	    next;
	}

	if ( /Instance (\S+),/ ) {

	    if ( defined $instance ) {
		$result->_addInstance($instance);
	    }

	    $instance = AFS::Object::Instance->new( instance => $1 );

	    #
	    # This is ugly, since the order and number of these
	    # strings varies.
	    #
	    if ( /\(type is (\S+)\)/ ) {
		$instance->_setAttribute( type => $1 );
	    }

	    if ( /(disabled|temporarily disabled|temporarily enabled),/ ) {
		$instance->_setAttribute( state => $1 );
	    }

	    if ( /stopped for too many errors/ ) {
		$instance->_setAttribute( errorstop => 1 );
	    }

	    if ( /has core file/ ) {
		$instance->_setAttribute( core => 1 );
	    }

	    if ( /currently (.*)\.$/ ) {
		$instance->_setAttribute( status => $1 );
	    }

	}

	if ( /Auxiliary status is: (.*)\.$/ ) {
	    $instance->_setAttribute( auxiliary => $1 );
	}

	if ( /Process last started at (.*) \((\d+) proc starts\)/ ) {
	    $instance->_setAttribute
	      (
	       startdate		=> $1,
	       startcount		=> $2,
	      );
	}

	if ( /Last exit at (.*)/ ) {
	    $instance->_setAttribute( exitdate => $1 );
	}

	if ( /Last error exit at ([^,]+),/ ) {

	    $instance->_setAttribute( errorexitdate => $1 );

	    if ( /due to shutdown request/ ) {
		$instance->_setAttribute( errorexitdue => 'shutdown' );
	    }

	    if ( /due to signal (\d+)/ ) {
		$instance->_setAttribute
		  (
		   errorexitdue 	=> 'signal',
		   errorexitsignal	=> $1,
		  );
	    }

	    if ( /by exiting with code (\d+)/ ) {
		$instance->_setAttribute
		  (
		   errorexitdue 	=> 'code',
		   errorexitcode	=> $1,
		  );
	    }

	}

	if ( /Command\s+(\d+)\s+is\s+\'(.*)\'/ ) {
	    my $command = AFS::Object->new
	      (
	       index			=> $1,
	       command			=> $2,
	      );
	    $instance->_addCommand($command);
	}

	if ( /Notifier\s+is\s+\'(.*)\'/ ) {
	    $instance->_setAttribute( notifier => $1 );
	}

    }

    if ( defined $instance ) {
	$result->_addInstance($instance);
    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}


1;

lib/AFS/Command/BOS.pod  view on Meta::CPAN

#
# $Id: BOS.pod,v 7.1 2004/01/13 19:01:12 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

=head1 NAME

AFS::Command::BOS - OO API to the AFS bos command

=head1 SYNOPSIS

    use AFS::Command::BOS;

    my $bos = AFS::Command::BOS->new();

    my $bos = AFS::Command::BOS->new
      (
       command			=> $path_to_your_bos_binary,
      );

    my $bos = AFS::Command::BOS->new
      (
       localauth		=> 1,
      );

=head1 DESCRIPTION

This module implements an OO API wrapper around the AFS 'bos' command.
The supported methods depend on the version of the bos binary used,
and are determined automagically.

=head1 METHODS -- Inherited

All of the following methods are inherited from the AFS::Command::Base
class.  See that documentation for details.

=over

=item new

=item errors

=item supportsOperation

=item supportsArgument

=back

=head1 METHODS (with complex return values)

=head2 getdate

=over

=item Arguments

The bos help string is:

    bos getdate: get dates for programs
    Usage: bos getdate -server <machine name> -file <files to check>+ [-dir <destination dir>]
		       [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->getdate
      (
       # Required arguments
       server			=> $server,
       file			=> $file, # OR [ $file1, $file2, ... ]
       # Optional arguments
       dir			=> $dir,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=item Return Values

This method returns an AFS::Object::BosServer object, which
contains one or more generic AFS::Object, one for each file
specified in the arguments.

    my $result = $bos->getdate
      (
       file				=> [ 'bosserver', 'vlserver', 'ptserver' ],
       cell				=> $cell,
      ) || die $bos->errors();

    foreach my $fileobj ( $result->getFiles() ) {
	my ($file,$date) = ($fileobj->file(),$fileobj->date());
	print "File $file has date $date\n";
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::BosServer>

This object is nothing more than a container for the generic objects
for each file.  It has several methods for extracting the file objects:

    Methods			Returns
    -------			-------
    getFileNames()		a list of filenames
    getFiles()			a list of AFS::Object objects
    getFile($filename)		the AFS::Object object for $filename

B<AFS::Object>

The following attributes should always be present:

    Attributes			Values
    ----------			------
    file			fully qualified pathname to the file
    date			last modified timestamp on the file

The following attributes may be present, if there are .BAK or .OLD
versions of the file.

    Attributes			Values
    ----------			------
    bak				last modified timestamp on the .BAK file
    old				last modified timestamp on the .OLD file

=back

=head2 getlog

=over

=item Arguments

The bos help string is:

    bos getlog: examine log file
    Usage: bos getlog -server <machine name> -file <log file to examine>
		      [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->getlog
      (
       # Required arguments
       server			=> $server,
       file			=> $file,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
       # Enhanced arguments
       redirect			=> $redirect,
      );

The 'redirect' argument is the name of a file to "redirect" the log
file to.  By default, 'bos getlog' dumps the contents of the requested
file to stdout, which is not what you normally want in an application.
The value of this argument is passed directly to IO::File->open().

If this argument is not specified, then the contents of the logfile
are stashed in an attribute of the returned object.

=item Return Values

This method returns an AFS::Object::BosServer object, which
contains one attribute.

    my $result = $bos->getlog
      (
       server			=> $server,
       file			=> "/usr/afs/logs/VolserLog",
       redirect			=> "/var/tmp/VolserLog.$$",
      ) || die $bos->errors();

    my $logfile = IO::File->new("</var/tmp/VolserLog.$$") ||
      die "Unable to open logfile: $ERRNO\n";

    while ( defined($_ = $logfile->getline()) ) {
	....
    }

    # Alternately, the memory pig way:

    my $result = $bos->getlog
      (
       server			=> $server,
       file			=> "/usr/afs/logs/VolserLog",
      );

    foreach ( split(/\n+/,$result->log()) ) {
	....
    }

The object has the following attribute:

B<AFS::Object::BosServer>

    Attributes			Values
    ----------			------
    log				Contents of the logfile, or the redirect pathname

If redirect was given, then this attribute is simply same pathname.
If redirect was not given, then the value of this attribute is the
contents of the requested logfile, as a single (potentially huge)
string.

NOTE: Since this method is usually invoked to retrieve one of the AFS
logfiles, which can be enormous on heavily loaded servers that have
not been restarted in a while, use of the redirect option is strongly
encouraged.  If not used, the memory allocated to store the logfile
may be prohibitively large.  Developer beware.

=back

=head2 getrestart

=over

=item Arguments

The bos help string is:

    bos getrestart: get restart times
    Usage: bos getrestart -server <machine name> [-cell <cell name>]
			  [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->getrestart
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=item Return Values

This method returns an AFS::Object::BosServer object, which
contains two attributes.

    my $result = $bos->getrestart
      (
       server			=> $server,
       cell			=> $cell,
      ) || die $bos->errors();
    print "Binary restart time is " . $result->binaries() . "\n";
    print "Server restart time is " . $result->restart() . "\n";

The object has the following attributes:

B<AFS::Object::BosServer>

    Attributes			Values
    ----------			------
    restart			The server restart time
    binaries			The restart time when there are new, updated binaries

=back

=head2 listhosts

=over

=item Arguments

The bos help string is:

    bos listhosts: get cell host list
    Usage: bos listhosts -server <machine name> [-cell <cell name>]
			 [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->listhosts
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=item Return Values

This method returns an AFS::Object::BosServer object, which
contains one attribute.

    my $result = $bos->listhosts
      (
       server			=> $server,
       cell			=> $cell,
      ) || die $bos->errors();
    my $hosts = $result->hosts();
    print "Server $server in cell $cell has hosts:\n"
    foreach my $host ( @$hosts ) {
        print "\t$host\n";
    }

The object has the following attributes:

B<AFS::Object::BosServer>

    Attributes			Values
    ----------			------
    hosts			ARRAY reference of hostnames
    cell			Cell name

=back

=head2 listkeys

=over

=item Arguments

The bos help string is:

    bos listkeys: list keys
    Usage: bos listkeys -server <machine name> [-showkey]
			[-cell <cell name>] [-noauth] [-localauth]
    Where: -showkey    show the actual key rather than the checksum

The corresponding method invocation looks like:

    my $result = $bos->listkeys
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       showkey			=> 1,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=item Return Values

This method returns an AFS::Object::BosServer object, which
contains one or more AFS::Object objects, each of which
represents a single authentication key on the server.

    my $result = $bos->listkeys
      (
       server			=> $server,
       cell			=> $cell,
      ) || die $bos->errors();
    print "Server $server in cell $cell has the following keys:\n";
    foreach my $key ( $result->getKeys() ) {
	my ($index,$cksum) = ($key->index(),$key->cksum());
	print "\t$index => $cksum\n";
    }

    my $result = $bos->listkeys
      (
       server			=> $server,
       cell			=> $cell,
       showkey			=> 1,
      ) || die $bos->errors();
    print "Server $server in cell $cell has the following keys:\n";
    foreach my $key ( $result->getKeys() ) {
	my ($index,$value) = ($key->index(),$key->value());
	print "\t$index => $cksum\n";
    }

The objects have the following attributes and methods:

B<AFS::Object::BosServer>

    Attributes			Values
    ----------			------
    keychanged			Date the keys were last changed

    Methods			Returns
    -------			-------
    getKeyIndexes()		list of numeric key indexes
    getKeys()			list of AFS::Object objects
    getKey($index)		the AFS::Object object for the key with index $index

B<AFS::Object>

The following attribute is always present:

    Attributes			Values
    ----------			------
    index			Numeric index of the key

The following attribute is present when the 'showkey' argument is given:

    Attributes			Values
    ----------			------
    value			Value of the key, in octal, as a string

The following attribute is present when the 'showkey' argument is B<NOT> given:

    Attributes			Values
    ----------			------
    cksum			Numeric check sum of the key

=back

=head2 listusers

=over

=item Arguments

The bos help string is:

    bos listusers: list super-users
    Usage: bos listusers -server <machine name> [-cell <cell name>]
			 [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->listusers
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=item Return Values

This method returns an AFS::Object::BosServer object, which
contains one attribute.

    my $result = $bos->listusers
      (
       server			=> $server,
       cell			=> $cell,
      ) || die $bos->errors();
    my $users = $result->susers();
    print "Server $server in cell $cell has users:\n"
    foreach my $user ( @$users ) {
        print "\t$user\n";
    }

The object has the following attribute:

B<AFS::Object::BosServer>

    Attributes			Values
    ----------			------
    susers			ARRAY reference of super user names

=back

=head2 status

=over

=item Arguments

The bos help string is:

    bos status: show server instance status
    Usage: bos status -server <machine name> [-instance <server process name>+]
		      [-long] [-cell <cell name>] [-noauth] [-localauth]
    Where: -long       long status

The corresponding method invocation looks like:

    my $result = $bos->status
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       instance			=> $instance, # OR [ $inst1, $inst2, ... ],
       cell			=> $cell,
       long			=> 1,
       noauth			=> 1,
       localauth		=> 1,
      );

=item Return Values

This method returns an AFS::Object::BosServer object, which
contains one optional attribute, and one or more
AFS::Object::Instance objects, each of which represents a
single instance of a bosserver managed process on the server.

    my $result = $bos->status
      (
       server			=> $server,
       long				=> 1,
      ) || die $bos->errors();
    foreach my $instanceobj ( $result->getInstances() ) {
	my $instance		= $instanceobj->instance();
	my $status			= $instanceobj->status();
	print "Instance $instance has status $status\n";
	foreach my $commandobj ( $instance->getCommands() ) {
	    my $index		= $commandobj->index();
	    my $command		= $commandobj->command();
	    print "\tCmd $index is '$command'\n";
	}
    }

The objects have the following attributes and methods:

B<AFS::Object::BosServer>

The following attribute is only present when "bos status" reports
inappropriate access on directories:

    Attributes			Values
    ----------			------
    access			Boolean, true indicating a potential security problem

The following methods can be used to extract the instance objects:

    Methods			Returns
    -------			-------
    getInstanceNames()		list of instance names
    getInstances()		list of AFS::Object::Instance objects
    getInstance($name)		one AFS::Object::Instance object for the instance $name

B<AFS::Object::Instance>

The following attributes are always present:

    Attributes			Values
    ----------			------
    instance			Name of the instance
    status			Status string (running normally, shutdown, etc.)

The following attribute is always present is the instance is of type
'cron':

    Attributes			Values
    ----------			------
    auxiliary			Auxiliary status (date the next execution)

The following attributes are always available when the 'long' argument
is specified:

    Attributes			Values
    ----------			------
    type			"cron", "simple", or "fs"
    startdate			Date when the process was last started
    startcount			Number of times the process has started,
				since the bosserver was started
    exitdate			Date when the process last exited

The following attributes are optionally available, depending on the
state of the instance, when the 'long' argument is specified:

    Attributes			Values
    ----------			------
    notifier			Path to the notifier application for this instance
    state			"temporarily disabled", or "disabled", or "temporarily enabled"
    errorstop			Boolean, indicating the process was
				"stopped for too many errors"
    core			Boolean, indicating the instance has a core file
    errorexitdate		Date when the process last exited with an error
    errorexitdue		"shutdown", or "signal", or "code" (present only when
				"errorexitdate" attribute is present)
    errorexitsignal		Signal that cause the error exit (present only when
				"errorexitdue" eq "signal")
    errorexitcode		Exit code from last error exit (present only when
				"errorexitdue" eq "code")

The following methods can be used to extract the command objects,
which are also only present when the 'long' argument is specified.

    Methods			Returns
    -------			-------
    getCommandIndexes()		list of numeric indexes for the commands
    getCommands()		list of AFS::Object objects for all commands
    getCommand($index)		the AFS::Object object for the command with index $index

B<AFS::Object> (Commands)

The following pair of attributes are always present:

    Attributes			Values
    ----------			------
    index			Numerical index of the command
    command			Command string

=back

=head1 METHODS (with simple return values)

All of the following commands return a simple Boolean (true/false)
value, if they succeed or fail.

=head2 addhost

The bos help string is:

    bos addhost: add host to cell dbase
    Usage: bos addhost -server <machine name> -host <host name>+
		       [-clone] [-cell <cell name>] [-noauth] [-localauth]
    Where: -clone      vote doesn't count

The corresponding method invocation looks like:

    my $result = $bos->addhost
      (
       # Required arguments
       server			=> $server,
       host			=> $host, # OR [ $host1, $host2, ... ]
       # Optional arguments
       clone			=> 1,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 addkey

The bos help string is:

    bos addkey: add keys to key dbase (kvno 999 is bcrypt)
    Usage: bos addkey -server <machine name> [-key <key>] -kvno <key version number>
		      [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->addkey
      (
       # Required arguments
       server			=> $server,
       kvno			=> $kvno,
       # Optional arguments
       key			=> $key,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 adduser

The bos help string is:

    bos adduser: add users to super-user list
    Usage: bos adduser -server <machine name> -user <user names>+
		       [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->adduser
      (
       # Required arguments
       server			=> $server,
       user			=> $user, # OR [ $user1, $user2, ... ]
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 blockscanner

The bos help string is:

    bos blockscanner: block scanner daemon from making migration requests
    Usage: bos blockscanner -server <machine name> [-cell <cell name>]
			    [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->blockscanner
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 create

The bos help string is:

    bos create: create a new server instance
    Usage: bos create -server <machine name> -instance <server process name>
		      -type <server type> -cmd <command lines>+ [-notifier <Notifier program>]
		      [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->create
      (
       # Required arguments
       server			=> $server,
       instance			=> $instance,
       type			=> $type,
       cmd			=> $cmd, # OR [ $cmd1, $cmd2, ... ]
       # Optional arguments
       notifier			=> $notifier,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 delete

The bos help string is:

    bos delete: delete a server instance
    Usage: bos delete -server <machine name> -instance <server process name>+
		      [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->delete
      (
       # Required arguments
       server			=> $server,
       instance			=> $instance, # OR [ $inst1, $inst2, ... ]
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 exec

The bos help string is:

    bos exec: execute shell command on server
    Usage: bos exec -server <machine name> -cmd <command to execute>
		    [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->exec
      (
       # Required arguments
       server			=> $server,
       cmd			=> $cmd,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 install

The bos help string is:

    bos install: install program
    Usage: bos install -server <machine name> -file <files to install>+
		       [-dir <destination dir>] [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->install
      (
       # Required arguments
       server			=> $server,
       file			=> $file, # OR [ $file1, $file2, ... ]
       # Optional arguments
       dir			=> $dir,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 prune

The bos help string is:

    bos prune: prune server files
    Usage: bos prune -server <machine name> [-bak] [-old] [-core] [-all]
		     [-cell <cell name>] [-noauth] [-localauth]
    Where: -bak        delete .BAK files
	   -old        delete .OLD files
	   -core       delete core files
	   -all        delete all junk files

The corresponding method invocation looks like:

    my $result = $bos->prune
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       bak			=> 1,
       old			=> 1,
       core			=> 1,
       all			=> 1,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 removehost

The bos help string is:

    bos removehost: remove host from cell dbase
    Usage: bos removehost -server <machine name> -host <host name>+
			  [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->removehost
      (
       # Required arguments
       server			=> $server,
       host			=> $host, # OR [ $host1, $host2, ... ]
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 removekey

The bos help string is:

    bos removekey: remove keys from key dbase
    Usage: bos removekey -server <machine name> -kvno <key version number>+
			 [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->removekey
      (
       # Required arguments
       server			=> $server,
       kvno			=> $kvno, # OR [ $kvno1, $kvno2, ... ]
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 removeuser

The bos help string is:

    bos removeuser: remove users from super-user list
    Usage: bos removeuser -server <machine name> -user <user names>+
			  [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->removeuser
      (
       # Required arguments
       server			=> $server,
       user			=> $user, # OR [ $user1, $user2, ... ]
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 restart

The bos help string is:

    bos restart: restart processes
    Usage: bos restart -server <machine name> [-instance <instances>+] [-bosserver]
		       [-all] [-cell <cell name>] [-noauth] [-localauth]
    Where: -bosserver  restart bosserver
	   -all        restart all processes

The corresponding method invocation looks like:

    my $result = $bos->restart
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       instance			=> $instance, # OR [ $inst1, $inst2, ... ]
       bosserver		=> 1,
       all			=> 1,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 salvage

The bos help string is:

    bos salvage: salvage partition or volumes
    Usage: bos salvage -server <machine name> [-partition <salvage partition>]
		       [-volume <salvage volume number or volume name>]
		       [-file <salvage log output file>] [-all] [-showlog]
		       [-parallel <# of max parallel partition salvaging>]
		       [-tmpdir <directory to place tmp files>]
		       [-orphans <ignore | remove | attach>] [-debug] [-nowrite]
		       [-force] [-oktozap] [-rootfiles] [-salvagedirs] [-blockreads]
		       [-ListResidencies] [-SalvageRemote] [-SalvageArchival]
		       [-IgnoreCheck] [-ForceOnLine] [-UseRootDirACL]
		       [-TraceBadLinkCounts] [-DontAskFS] [-LogLevel <(MR-AFS) log level>]
		       [-rxdebug] [-cell <cell name>] [-noauth] [-localauth]
    Where: -all                 salvage whole server
	   -showlog             display salvage log
	   -debug               (MR-AFS) Run in Debugging mode
	   -nowrite             (MR-AFS) Run readonly/test mode
	   -force               (MR-AFS) Force full salvaging
	   -oktozap             (MR-AFS) Give permission to destroy bogus file residencies/volumes - debugging flag
	   -rootfiles           (MR-AFS) Show files owned by root - debugging flag
	   -salvagedirs         (MR-AFS) Force rebuild/salvage of all directories
	   -blockreads          (MR-AFS) Read smaller blocks to handle IO/bad blocks
	   -ListResidencies     (MR-AFS) Just list affected file residencies - debugging flag
	   -SalvageRemote       (MR-AFS) Salvage storage systems that are not directly attached
	   -SalvageArchival     (MR-AFS) Salvage HSM storage systems
	   -IgnoreCheck         (MR-AFS) Don't perform VLDB safety check when deleting unreferenced files.
					 Only a good idea in single server cell.
	   -ForceOnLine         (MR-AFS) Force the volume to come online, even if it hasn't salvaged cleanly.
	   -UseRootDirACL       (MR-AFS) Use the root directory ACL for lost+found directory if it is created.
	   -TraceBadLinkCounts  (MR-AFS) Print out lines about volume reference count changes.
	   -DontAskFS           (MR-AFS) Don't ask fileserver to take volume offline.  THIS IS VERY DANGEROUS.
	   -rxdebug             (MR-AFS) Write out rx debug information.

The corresponding method invocation looks like:

    my $result = $bos->salvage
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       partition		=> $partition,
       volume			=> $volume,
       file			=> $file,
       all			=> 1,
       showlog			=> 1,
       parallel			=> $parallel,
       tmpdir			=> $tmpdir,
       orphans			=> $orphans,
       debug			=> 1,
       nowrite			=> 1,
       force			=> 1,
       oktozap			=> 1,
       rootfiles		=> 1,
       salvagedirs		=> 1,
       blockreads		=> 1,
       ListResidencies		=> 1,
       SalvageRemote		=> 1,
       SalvageArchival		=> 1,
       IgnoreCheck		=> 1,
       ForceOnLine		=> 1,
       UseRootDirACL		=> 1,
       TraceBadLinkCounts	=> 1,
       DontAskFS		=> 1,
       LogLevel			=> $loglevel,
       rxdebug			=> 1,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 setauth

The bos help string is:

    bos setauth: set authentication required flag
    Usage: bos setauth -server <machine name>
		       -authrequired <on or off: authentication required for admin requests>
		       [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->setauth
      (
       # Required arguments
       server			=> $server,
       authrequired		=> $authrequired,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 setcellname

The bos help string is:

    bos setcellname: set cell name
    Usage: bos setcellname -server <machine name> -name <cell name>
			   [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->setcellname
      (
       # Required arguments
       server			=> $server,
       name			=> $name,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 setrestart

The bos help string is:

    bos setrestart: set restart times
    Usage: bos setrestart -server <machine name> -time <time to restart server>
			  [-general] [-newbinary] [-cell <cell name>]
			  [-noauth] [-localauth]
    Where: -general    set general restart time
	   -newbinary  set new binary restart time

The corresponding method invocation looks like:

    my $result = $bos->setrestart
      (
       # Required arguments
       server			=> $server,
       time			=> $time,
       # Optional arguments
       general			=> 1,
       newbinary		=> 1,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 shutdown

The bos help string is:

    bos shutdown: shutdown all processes
    Usage: bos shutdown -server <machine name> [-instance <instances>+]
			[-wait] [-cell <cell name>] [-noauth] [-localauth]
    Where: -wait       wait for process to stop

The corresponding method invocation looks like:

    my $result = $bos->shutdown
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       instance			=> $instance, # OR [ $inst1, $inst2, ... ]
       wait			=> 1,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 start

The bos help string is:

    bos start: start running a server
    Usage: bos start -server <machine name> -instance <server process name>+
		     [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->start
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       instance			=> $instance, # OR [ $inst1, $inst2, ... ]
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 startup

The bos help string is:

    bos startup: start all processes
    Usage: bos startup -server <machine name> [-instance <instances>+]
		       [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->startup
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       instance			=> $instance, # OR [ $inst1, $inst2, ... ]
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 stop

The bos help string is:

    bos stop: halt a server instance
    Usage: bos stop -server <machine name> -instance <server process name>+
		    [-wait] [-cell <cell name>] [-noauth] [-localauth]
    Where: -wait       wait for process to stop

The corresponding method invocation looks like:

    my $result = $bos->stop
      (
       # Required arguments
       server			=> $server,
       instance			=> $instance, # OR [ $inst1, $inst2, ... ]
       # Optional arguments
       wait			=> 1,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 unblockscanner

The bos help string is:

    bos unblockscanner: allow scanner daemon to make migration requests again
    Usage: bos unblockscanner -server <machine name>
			      [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->unblockscanner
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head2 uninstall

The bos help string is:

bos uninstall: uninstall program
Usage: bos uninstall -server <machine name> -file <files to uninstall>+
                     [-dir <destination dir>] [-cell <cell name>] [-noauth] [-localauth]

The corresponding method invocation looks like:

    my $result = $bos->uninstall
      (
       # Required arguments
       server			=> $server,
       file			=> $file, # OR [ $file1, $file2, ... ]
       # Optional arguments
       dir			=> $dir,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
      );

=head1 SEE ALSO

AFS::Command(1), AFS::Object(1)

=cut

lib/AFS/Command/Base.pm  view on Meta::CPAN

#
# $Id$
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

package AFS::Command::Base;

require 5.6.0;

use strict;
use English;
use Carp;
use File::Basename qw(basename);
use Date::Format;

use IO::File;
use IO::Pipe;

our $AUTOLOAD	= "";
our $VERSION = '1.99';

our %Carp =
  (
   carp		=> \&Carp::carp,
   croak	=> \&Carp::croak,
  );

sub setCarp {

    my $class = shift;
    my (%args) = @_;

    foreach my $key ( keys %args ) {
	unless ( $Carp{$key} ) {
	    croak("Unsupported argument: '$key'");
	}
	unless ( ref $args{$key} eq 'CODE' ) {
	    croak("Not a code reference: '$args{$key}'");
	}
	$Carp{$key} = $args{$key};
    }

    return AFS::Object->_setCarp(@_);

}

sub new {

    my $proto = shift;
    my $class = ref($proto) || $proto;
    my %args = @_;

    my $self = {};

    foreach my $key ( qw( localtime noauth localauth encrypt quiet timestamps ) ) {
	$self->{$key}++ if $args{$key};
    }

    # AFS::Command::VOS -> vos
    if ( $args{command} ) {
        my @commands = (split /\s+/,$args{command});
        push (@{$self->{command}},@commands);
    } else {
        @{$self->{command}} = lc((split(/::/,$class))[2]);
    }

    bless $self, $class;

    return $self;

}

sub errors {
    my $self = shift;
    return $self->{errors};
}

sub supportsOperation {
    my $self = shift;
    my $operation = shift;
    return $self->_operations($operation);
}

sub supportsArgument {
    my $self = shift;
    my $operation = shift;
    my $argument = shift;
    return unless $self->_operations($operation);
    return unless $self->_arguments($operation);
    return exists $self->{_arguments}->{$operation}->{$argument};
}

sub _Carp {
    my $self = shift;
    $Carp{carp}->(@_);
}

sub _Croak {
    my $self = shift;
    $Carp{croak}->(@_);
}

sub _operations {

    my $self = shift;
    my $operation = shift;

    my $class = ref $self;

    unless ( $self->{_operations} ) {

	my %operations = ();

	#
	# This hack is necessary to support the offline/online "hidden"
	# vos commands.  These won't show up in the normal help output,
	# so we have to check for them individually.  Since offline and
	# online are implemented as a pair, we can just check one of
	# them, and assume the other is there, too.
	#

	foreach my $type ( qw(default hidden) ) {

	    if ( $type eq 'hidden' ) {
		next unless $self->isa("AFS::Command::VOS");
	    }

	    my $pipe = IO::Pipe->new() || do {
		$self->_Carp("Unable to create pipe: $ERRNO\n");
		return;
	    };

	    my $pid = fork();

	    unless ( defined $pid ) {
		$self->_Carp("Unable to fork: $ERRNO\n");
		return;
	    }

	    if ( $pid == 0 ) {

		STDERR->fdopen( STDOUT->fileno(), "w" ) ||
		  $self->_Croak("Unable to redirect stderr: $ERRNO\n");
		STDOUT->fdopen( $pipe->writer()->fileno(), "w" ) ||
		  $self->_Croak("Unable to redirect stdout: $ERRNO\n");

		if ( $type eq 'default' ) {
		    exec @{$self->{command}}, 'help';
		} else {
		    exec @{$self->{command}}, 'offline', '-help';
		}
		die "Unable to exec @{$self->{command}} help: $ERRNO\n";

	    } else {

		$pipe->reader();

		while ( defined($_ = $pipe->getline()) ) {
		    if ( $type eq 'default' ) {
			next if /Commands are:/;
			my ($command) = split;
			next if $command =~ /^(apropos|help)$/;
			$operations{$command}++;
		    } else {
			if ( /^Usage:/ ) {
			    $operations{offline}++;
			    $operations{online}++;
			}
		    }
		}

	    }

	    unless ( waitpid($pid,0) ) {
		$self->_Carp("Unable to get status of child process ($pid)");
		return;
	    }

	    if ( $? ) {
		$self->_Carp("Error running @{$self->{command}} help.  Unable to configure $class");
		return;
	    }

	}

	$self->{_operations} = \%operations;

    }

    return $self->{_operations}->{$operation};

}

sub _arguments {

    my $self		= shift;
    my $operation 	= shift;

    my $arguments =
      {
       optional		=> {},
       required		=> {},
       aliases		=> {},
      };

    my @command;
    push (@command, @{$self->{command}});

    unless ( $self->_operations($operation) ) {
	$self->_Carp("Unsupported @command operation '$operation'\n");
	return;
    }

    return $self->{_arguments}->{$operation}
      if ref $self->{_arguments}->{$operation} eq 'HASH';

    my $pipe = IO::Pipe->new() || do {
	$self->_Carp("Unable to create pipe: $ERRNO");
	return;
    };

    my $pid = fork();

    my $errors = 0;

    unless ( defined $pid ) {
	$self->_Carp("Unable to fork: $ERRNO");
	return;
    }

    if ( $pid == 0 ) {

	STDERR->fdopen( STDOUT->fileno(), "w" ) ||
	  die "Unable to redirect stderr: $ERRNO\n";
	STDOUT->fdopen( $pipe->writer()->fileno(), "w" ) ||
	  die "Unable to redirect stdout: $ERRNO\n";
	exec @command, $operation, '-help';
	die "Unable to exec @command help $operation: $ERRNO\n";

    } else {

	$pipe->reader();

	while ( <$pipe> ) {

	    if ( /Unrecognized operation '$operation'/ ) {
		$self->_Carp("Unsupported @command operation '$operation'\n");
		$errors++;
		last;
	    }

	    next unless s/^Usage:.*\s+$operation\s+//;

	    while ( $_ ) {
		if ( s/^\[\s*-(\w+?)\s*\]\s*//  ) {
		    $arguments->{optional}->{$1} = 0
		      unless $1 eq 'help'; # Yeah, skip it...
		} elsif ( s/^\[\s*-(\w+?)\s+<[^>]*?>\+\s*]\s*// ) {
		    $arguments->{optional}->{$1} = [];
		} elsif ( s/^\[\s*-(\w+?)\s+<[^>]*?>\s*]\s*// ) {
		    $arguments->{optional}->{$1} = 1;
		} elsif ( s/^\s*-(\w+?)\s+<[^>]*?>\+\s*// ) {
		    $arguments->{required}->{$1} = [];
		} elsif ( s/^\s*-(\w+?)\s+<[^>]*?>\s*// ) {
		    $arguments->{required}->{$1} = 1;
		} elsif ( s/^\s*-(\w+?)\s*// ) {
		    $arguments->{required}->{$1} = 0;
		} else {
		    $self->_Carp("Unable to parse @command help for $operation\n" .
				 "Unrecognized string: '$_'");
		    $errors++;
		    last;
		}
	    }

	    last;

	}

    }

    #
    # XXX -- Hack Alert!!!
    #
    # Because some asshole decided to change the force option to vos
    # release from -f to -force, you can't use the API tranparently
    # with 2 different vos binaries that support the 2 different options.
    #
    # If we need more of these, we can add them, as this let's us
    # alias one argument to another.
    #
    if ( $self->isa("AFS::Command::VOS") && $operation eq 'release' ) {
	if ( exists $arguments->{optional}->{f} ) {
	    $arguments->{aliases}->{force} = 'f';
	} elsif ( exists $arguments->{optional}->{force} ) {
	    $arguments->{aliases}->{f} = 'force';
	}
    }

    unless ( waitpid($pid,0) ) {
	$self->_Carp("Unable to get status of child process ($pid)");
	$errors++;
    }

    if ( $? ) {
	$self->_Carp("Error running @command $operation -help.  Unable to configure @command $operation");
	$errors++;
    }

    return if $errors;
    return $self->{_arguments}->{$operation} = $arguments;

}

sub _save_stderr {

    my $self = shift;

    $self->{olderr} = IO::File->new(">&STDERR") || do {
	$self->_Carp("Unable to dup stderr: $ERRNO");
	return;
    };

    my $command = basename((split /\s+/,@{$self->{command}})[0]);

    $self->{tmpfile} = "/tmp/.$command.$self->{operation}.$$";

    my $newerr = IO::File->new(">$self->{tmpfile}") || do {
	$self->_Carp("Unable to open $self->{tmpfile}: $ERRNO");
	return;
    };

    STDERR->fdopen( $newerr->fileno(), "w" ) || do {
	$self->_Carp("Unable to reopen stderr: $ERRNO");
	return;
    };

    $newerr->close() || do {
	$self->_Carp("Unable to close $self->{tmpfile}: $ERRNO");
	return;
    };

    return 1;

}

sub _restore_stderr {

    my $self = shift;

    STDERR->fdopen( $self->{olderr}->fileno(), "w") || do {
	$self->_Carp("Unable to restore stderr: $ERRNO");
	return;
    };

    $self->{olderr}->close() || do {
	$self->_Carp("Unable to close saved stderr: $ERRNO");
	return;
    };

    delete $self->{olderr};

    my $newerr = IO::File->new($self->{tmpfile}) || do {
	$self->_Carp("Unable to reopen $self->{tmpfile}: $ERRNO");
	return;
    };

    $self->{errors} = "";

    while ( <$newerr> ) {
	$self->{errors} .= $_;
    }

    $newerr->close() || do {
	$self->_Carp("Unable to close $self->{tmpfile}: $ERRNO");
	return;
    };

    unlink($self->{tmpfile}) || do {
	$self->_Carp("Unable to unlink $self->{tmpfile}: $ERRNO");
	return;
    };

    delete $self->{tmpfile};

    return 1;

}

sub _parse_arguments {

    my $self = shift;
    my $class = ref($self);
    my (%args) = @_;

    my $arguments = $self->_arguments($self->{operation});

    unless ( defined $arguments ) {
	$self->_Carp("Unable to obtain arguments for $class->$self->{operation}");
	return;
    }

    $self->{errors} = "";

    $self->{cmds} = [];

    if ( $args{inputfile} ) {

	push( @{$self->{cmds}}, [ 'cat', $args{inputfile} ] );

    } else {

	my @argv = ( @{$self->{command}}, $self->{operation} );

	foreach my $key ( keys %args ) {
	    next unless $arguments->{aliases}->{$key};
	    $args{$arguments->{aliases}->{$key}} = delete $args{$key};
	}

	foreach my $key ( qw( noauth localauth encrypt ) ) {
	    next unless $self->{$key};
	    $args{$key}++ if exists $arguments->{required}->{$key};
	    $args{$key}++ if exists $arguments->{optional}->{$key};
	}

	unless ( $self->{quiet} ) {
	    $args{verbose}++ if exists $arguments->{optional}->{verbose};
	}

	foreach my $type ( qw( required optional ) ) {

	    foreach my $key ( keys %{$arguments->{$type}} ) {

		my $hasvalue = $arguments->{$type}->{$key};

		if ( $type eq 'required' ) {
		    unless ( exists $args{$key} ) {
			$self->_Carp("Required argument '$key' not provided");
			return;
		    }
		} else {
		    next unless exists $args{$key};
		}

		if ( $hasvalue ) {
		    if ( ref $args{$key} eq 'HASH' || ref $args{$key} eq 'ARRAY' ) {
			unless ( ref $hasvalue eq 'ARRAY' ) {
			    $self->_Carp("Invalid argument '$key': can't provide a list of values");
			    return;
			}
			push(@argv,"-$key");
			foreach my $value ( ref $args{$key} eq 'HASH' ? %{$args{$key}} : @{$args{$key}} ) {
			    push(@argv,$value);
			}
		    } else {
			push(@argv,"-$key",$args{$key});
		    }
		} else {
		    push(@argv,"-$key") if $args{$key};
		}

		delete $args{$key};

	    }

	}

	if ( %args ) {
	    $self->_Carp("Unsupported arguments: " . join(' ',sort keys %args));
	    return;
	}

	push( @{$self->{cmds}}, \@argv );

    }

    return 1;

}

sub _exec_cmds {

    my $self = shift;

    my %args = @_;

    my @cmds = @{$self->{cmds}};

    $self->{pids} = {};

    for ( my $index = 0 ; $index <= $#cmds ; $index++ ) {

	my $cmd = $cmds[$index];

	my $pipe = IO::Pipe->new() || do {
	    $self->_Carp("Unable to create pipe: $ERRNO");
	    return;
	};

	my $pid = fork();

	unless ( defined $pid ) {
	    $self->_Carp("Unable to fork: $ERRNO");
	    return;
	}

	if ( $pid == 0 ) {

	    if ( $index == $#cmds &&
		 exists $args{stdout} && $args{stdout} ne 'stdout' ) {
		my $stdout = IO::File->new(">$args{stdout}") ||
		  $self->_Croak("Unable to open $args{stdout}: $ERRNO");
		STDOUT->fdopen( $stdout->fileno(), "w" ) ||
		  $self->_Croak("Unable to redirect stdout: $ERRNO");
	    } else {
		STDOUT->fdopen( $pipe->writer()->fileno(), "w" ) ||
		  $self->_Croak("Unable to redirect stdout: $ERRNO");
	    }

	    if ( exists $args{stderr} && $args{stderr} eq 'stdout' ) {
		STDERR->fdopen( STDOUT->fileno(), "w" ) ||
		  $self->_Croak("Unable to redirect stderr: $ERRNO");
	    }

	    if ( $index == 0 ) {
		if ( exists $args{stdin} && $args{stdin} ne 'stdin' ) {
		    my $stdin = IO::File->new("<$args{stdin}") ||
		      $self->_Croak("Unable to open $args{stdin}: $ERRNO");
		    STDIN->fdopen( $stdin->fileno(), "r" ) ||
		      $self->_Croak("Unable to redirect stdin: $ERRNO");
		}
	    } else {
		STDIN->fdopen( $self->{handle}->fileno(), "r" ) ||
		  $self->_Croak("Unable to redirect stdin: $ERRNO");
	    }

	    $ENV{TZ} = 'GMT' unless $self->{localtime};

	    exec( { $cmd->[0] } @{$cmd} ) ||
	      $self->_Croak("Unable to exec @{$cmd}: $ERRNO");

	}

	$self->{handle} = $pipe->reader();

	$self->{pids}->{$pid} = $cmd;

    }

    return 1;

}

sub _parse_output {

    my $self = shift;

    $self->{errors} = "";

    while ( defined($_ = $self->{handle}->getline()) ) {
	$self->{errors} .= time2str("[%Y-%m-%d %H:%M:%S] ",time,'GMT') if $self->{timestamps};
	$self->{errors} .= $_;
    }

    return 1;

}

sub _reap_cmds {

    my $self = shift;
    my (%args) = @_;

    my $errors = 0;

    $self->{handle}->close() || do {
	$self->_Carp("Unable to close pipe handle: $ERRNO");
	$errors++;
    };

    delete $self->{handle};
    delete $self->{cmds};

    $self->{status} = {};

    my %allowstatus = ();
    if ( $args{allowstatus} ) {
	if ( ref $args{allowstatus} eq 'ARRAY' ) {
	    foreach my $status ( @{$args{allowstatus}} ) {
		$allowstatus{$status}++;
	    }
	} else {
	    $allowstatus{$args{allowstatus}}++;
	}
    }

    foreach my $pid ( keys %{$self->{pids}} ) {

	$self->{status}->{$pid}->{cmd} =
	  join(' ', @{delete $self->{pids}->{$pid}} );

	if ( waitpid($pid,0) ) {

	    $self->{status}->{$pid}->{status} = $?;
	    if ( $? ) {
		if ( %allowstatus ) {
		    $errors++ unless $allowstatus{$? >> 8};
		} else {
		    $errors++;
		}
	    }


	} else {
	    $self->{status}->{$pid}->{status} = undef;
	    $errors++;
	}

    }

    return if $errors;
    return 1;

}

sub AUTOLOAD {

    my $self = shift;
    my (%args) = @_;

    $self->{operation} = $AUTOLOAD;
    $self->{operation} =~ s/.*:://;

    return unless $self->_parse_arguments(%args);

    return unless $self->_exec_cmds( stderr => 'stdout' );

    my $errors = 0;

    $errors++ unless $self->_parse_output();
    $errors++ unless $self->_reap_cmds();

    return if $errors;
    return 1;

}

sub DESTROY {}

1;

lib/AFS/Command/Base.pod  view on Meta::CPAN

#
# $Id: Base.pod,v 10.1 2004/11/18 13:27:44 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

=head1 NAME

AFS::Command::Base -- Base OO Class for AFS::Command::* API

=head1 SYNOPSIS

This module is B<NOT> used directly by applications, but indirectly by
the various command wrapper classes:

    use AFS::Command::VOS;
    use AFS::Command::BOS;
    use AFS::Command::PTS;
    use AFS::Command::FS;

All of these classes inherit from this base class, and the inherited
methods are documented here.

=head1 DESCRIPTION

The AFS::Command suite provides an programmatic API to several of the
key AFS command line utilities, namely: vos, bos, pts and fs.  This
module is <NOT> a replacement for the AFS::* modules, which are an XS
interface to the lower level AFS C programming APIs.

This module attempts to fill a huge void in the APIs available for
developing AFS systems management applications in perl.  Norbert
Gruener's AFS module implements wrapper classes for the various C
programming APIs available for AFS, however most of the systems
management for AFS is implemented in the command line utilities
themselves, not in any of the C libraries.  Far too much of the logic
in vos, by far the most important utility, is in the application
itself, not the underlying libraries.  This makes it very difficult
for an XS-only approach to support the vos functionality.

These classes take an entirely different approach, since they are
simply wrappers around the command line executables themselves, and
implemented entirely in pure perl (no XS code at all).  This has some
advantages and disadvantages, of course.  The primary advantage is
that we are not forced to re-implement the complex logic in the vos
command using the low level primitive C API calls.  The primary
disadvantage is that we are tightly coupled with the format of the
text output of each of these commands, but this is a problem space
that perl excels in.

This document covers general programming with the API, and the details
of the specific wrapper classes are documented separately
(eg. AFS::Command::VOS).

=head1 CLASS METHODS

=head2 new

All of the AFS::Command subclasses use this inherited method as an
object constructor.  In its simplest usage, it can be called with no
arguments, but there are several which control global behavior of the
commands, simplifying subsequent coding a bit.

=over

=item command

This key has the pathname to the command to be used for execution.  By
default, this is the simple command name "vos", "bos", etc, and the
command will simply be found in your $PATH by exec().

If you want to run a specific version of vos not found in the $PATH,
then for example:

   my $vos = AFS::Command::VOS->new
     (
      command		=> "/ms/dist/openafs/PROJ/core/1.2.9/bin/vos",
     );

If the path given is invalid, then expect the API to implode on itself
when it can't be found, or it isn't an AFS vos command.

=item localauth, noauth, encrypt

All of these arguments correspond to command line arguments common
across the entire command line suite.  Typically, if an application
uses this flag once, it will be using it for B<all> subsequent calls as
well.  Therefore, the state of these flags can be set globally by
setting them when creating the command object.

    my $vos = AFS::Command::VOS->new
      (
       localauth	=> 1,
       encrypt		=> 1,
      );

NOTE: The encrypt option is only available in more recent versions of
AFS, and may be unsupported by the underlying commands.

XXX: What should the default behavior be?  Croak or carp? we can
figure out dynamically if the command supports it, and have the
constructor fail, or we can be lazy and let the first command fail.

=item quiet

The default behavior for the common -verbose flag is inverted.  By
default, all commands are run with the -verbose flag, in order to
capture maximum diagnostics when an error occurs.  Normally, the
chatty output is all trapped by the API anyway, so there is no
application visible noise, just more verbose errors.

There should be no need to disable verbosity, but for completeness,
specifying 'quiet' will turn off the default verbose output.

=item timestamps

If this argument is given, then the output collected from the commands
will be prepended with the date formatted using Date::Format with:

    %Y/%m/%d %H:%M:%S

This is primarily useful for debugging and timing of commands such as
vos release, which can be very time consuming.  Since we enable
-verbose by default, this option will let us determine the relative
time required for each step in these complex operations.

This only applies to commands that return simple return values, eg:
release, restore, etc.  Commands that return complex structures of
objects, such as listvldb, listvol, etc will not be affected.

=back

=head2 setCarp

This class method configures the carp and/or croak subroutines used
throughout the API.  By default, the obviously sensible thing is done:
the carp an croak subroutines exported by the Carp module are used.
These normally print output to stderr, and this method provides a
mechanism for trapping these errors and redirecting them elsewhere.

For example, stderr in a system daemon may be entirely ignored, and
syslog may be a more appropriate destination.  In this case, the
setCarp method may be used to configure this, globally, for the entire
API.

    AFS::Command->setCarp
      (

       carp => sub {
	   my ($lines) = @_;
	   foreach my $line ( split(/\n+/,$lines) ) {
	       syslog('warning',$line);
	   }
       },

       croak => sub {
	   my ($lines) = @_;
	   foreach my $line ( split(/\n+/,$lines) ) {
	       syslog('error',$line);
	   }
	   die $lines; # If we're dying, whine at stderr, too.
       },

      );

This method takes a list of key/value pairs, with only two supported
keys (anything else will be quietly ignored): carp an croak.  The
values are CODE references (anonymous subroutines, or references to
existing subroutines).  The carp CODE should not be fatal, however the
croak CODE should.  The API calls the croak method in very few places,
but when it does, it assumes that call will be fatal, so if you
provide a croak subroutine that doesn't die, the results will be
unpredictable, and unsupportable.

This method returns true of false, depending on whether or not the
carp and/or croak subroutines were properly configured.  If the values
are not CODE references, then this method will itself croak.

=head1 INSTANCE METHODS

=head2 errors

This method takes no arguments, and it returns a string, containing the
unparsed errors from the last command method invoked.  This string is
reset with each subsequent command method invocation.  The string is
normally the output written to stderr by the process, but in the case
of unparsed boolean commands, it contains both the stdout as well as
the stderr output.

    my $result = $vos->examine
      (
       id		=> $volname,
       cell		=> $cell,
      );
    unless ( $result ) {
        die "Unable to examine volname '$volname' in cell '$cell':" .
	  $vos->errors();
    }

=head2 supportsOperation

This class method allows the developer to test whether or not any
given operation is supported by the underlying command line utility.
For example, the "vos changeloc" operation is not supported in older
release of vos.

    unless ( $vos->supportsOperation('changeloc') {
	die "Unable to continue -- 'vos changeloc' is unsupported.\n";
    }

The return value is simply true or false.

=head2 supportsArgument

Similar to supportsOperation, supportsArgument will test whether or
not a given argument is a support command line argument for the
specified operation.  For example, the -encrypt argument is only
supported in more recent versions of vos, so that support can be
tested for easily.

    unless ( $vos->supportsArgument('listvldb','encrypt') ) {
	warn "Encryption is not support by your version of vos.\n";
    }

The return value is simply true or false.

=head1 SEE ALSO

AFS::Command::VOS(1), AFS::Command::BOS(1), AFS::Command::PTS(1),
AFS::Command::FS(1), AFS::Command::Base(1), AFS::Command(1)

=cut

lib/AFS/Command/FS.pm  view on Meta::CPAN

#
# $Id$
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

package AFS::Command::FS;

require 5.6.0;

use strict;
use English;

use AFS::Command::Base;
use AFS::Object;
use AFS::Object::CacheManager;
use AFS::Object::Path;
use AFS::Object::Cell;
use AFS::Object::Server;
use AFS::Object::ACL;

our @ISA = qw(AFS::Command::Base);
our $VERSION = '1.99';

sub checkservers {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = "checkservers";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    my @servers = ();

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	if ( /The current down server probe interval is (\d+) secs/ ) {
	    $result->_setAttribute( interval => $1 );
	}

	if ( /These servers are still down:/ ) {
	    while ( defined($_ = $self->{handle}->getline()) ) {
		s/^\s+//g;
		s/\s+$//g;
		push(@servers,$_);
	    }
	}
    }

    $result->_setAttribute( servers => \@servers );

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub diskfree {
    my $self = shift;
    return $self->_paths_method('diskfree',@_);
}

sub examine {
    my $self = shift;
    return $self->_paths_method('examine',@_);
}

sub listquota {
    my $self = shift;
    return $self->_paths_method('listquota',@_);
}

sub quota {
    my $self = shift;
    return $self->_paths_method('quota',@_);
}

sub storebehind {
    my $self = shift;
    return $self->_paths_method('storebehind',@_);
}

sub whereis {
    my $self = shift;
    return $self->_paths_method('whereis',@_);
}

sub whichcell {
    my $self = shift;
    return $self->_paths_method('whichcell',@_);
}

sub listacl {
    my $self = shift;
    return $self->_paths_method('listacl',@_);
}

sub _paths_method {

    my $self = shift;
    my $operation = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = $operation;

    my $pathkey = $operation eq 'storebehind' ? 'files' : 'path';

    return unless $self->_parse_arguments(%args);

    my $errors = 0;

    $errors++ unless $self->_exec_cmds( stderr => 'stdout' );

    my @paths = ref $args{$pathkey} eq 'ARRAY' ? @{$args{$pathkey}} : ($args{$pathkey});
    my %paths = map { $_ => 1 } @paths;

    my $default = undef; # Used by storebehind

    while ( defined($_ = $self->{handle}->getline()) ) {

	next if /^Volume Name/;

	my $path = AFS::Object::Path->new();

	if ( /fs: Invalid argument; it is possible that (.*) is not in AFS./ ||
	     /fs: no such cell as \'(.*)\'/ ||
	     /fs: File \'(.*)\' doesn\'t exist/ ||
	     /fs: You don\'t have the required access rights on \'(.*)\'/ ) {

	    $path->_setAttribute
	      (
	       path 		=> $1,
	       error		=> $_,
	      );

	    delete $paths{$1};
	    @paths = grep($_ ne $1,@paths);

	} else {

	    if ( $operation eq 'listacl' ) {

		if ( /^Access list for (.*) is/ ) {

		    $path->_setAttribute( path => $1 );
		    delete $paths{$1};

		    my $normal 		= AFS::Object::ACL->new();
		    my $negative 	= AFS::Object::ACL->new();

		    my $type = 0;

		    while ( defined($_ = $self->{handle}->getline()) ) {

			s/^\s+//g;
			s/\s+$//g;
			last if /^\s*$/;

			$type = 1, next if /^Normal rights:/;
			$type = -1, next if /^Negative rights:/;

			my ($principal,$rights) 	= split;

			if ( $type == 1 ) {
			    $normal->_addEntry( $principal => $rights );
			} elsif ( $type == -1 ) {
			    $negative->_addEntry( $principal => $rights );
			}

		    }

		    $path->_setACLNormal($normal);
		    $path->_setACLNegative($negative);

		}

	    }

	    if ( $operation eq 'whichcell' ) {

		if ( /^File (\S+) lives in cell \'([^\']+)\'/ ) {

		    $path->_setAttribute
		      (
		       path	=> $1,
		       cell 	=> $2,
		      );
		    delete $paths{$1};

		}

	    }

	    if ( $operation eq 'whereis' ) {

		if ( /^File (.*) is on hosts? (.*)$/ ) {

		    $path->_setAttribute
		      (
		       path 			=> $1,
		       hosts			=> [split(/\s+/,$2)],
		      );
		    delete $paths{$1};

		}

	    }

	    if ( $operation eq 'storebehind' ) {

		if ( /Default store asynchrony is (\d+) kbytes/ ) {

		    $default = $1;
		    next;

		} elsif ( /Will store (.*?) according to default./ ) {

		    $path->_setAttribute
		      (
		       path 			=> $1,
		       asynchrony 		=> 'default',
		      );

		    delete $paths{$1};
		    @paths = grep($_ ne $1,@paths);

		} elsif ( /Will store up to (\d+) kbytes of (.*?) asynchronously/ ) {

		    $path->_setAttribute
		      (
		       path 			=> $2,
		       asynchrony 		=> $1,
		      );

		    delete $paths{$2};
		    @paths = grep($_ ne $2,@paths);

		}

	    }

	    if ( $operation eq 'quota' ) {

		if ( /^\s*(\d{1,2})%/ ) {

		    $path->_setAttribute
		      (
		       path 			=> $paths[0],
		       percent			=> $1,
		      );
		    delete $paths{$paths[0]};
		    shift @paths;

		}

	    }

	    if ( $operation eq 'listquota' ) {

		#
		# This is a bit lame.  We want to be lazy and split on white
		# space, so we get rid of this one annoying instance.
		#
		s/no limit/nolimit/g;

		my ($volname,$quota,$used,$percent,$partition) = split;

		$quota = 0 if $quota eq "nolimit";
		$percent =~ s/\D//g; # want numeric result
		$partition =~ s/\D//g; # want numeric result

		$path->_setAttribute
		  (
		   path				=> $paths[0],
		   volname			=> $volname,
		   quota			=> $quota,
		   used				=> $used,
		   percent			=> $percent,
		   partition			=> $partition,
		  );
		delete $paths{$paths[0]};
		shift @paths;

	    }

	    if ( $operation eq 'diskfree' ) {

		my ($volname,$total,$used,$avail,$percent) = split;
		$percent =~ s/%//g; # Don't need it -- want numeric result

		$path->_setAttribute
		  (
		   path				=> $paths[0],
		   volname			=> $volname,
		   total			=> $total,
		   used				=> $used,
		   avail			=> $avail,
		   percent			=> $percent,
		  );
		delete $paths{$paths[0]};
		shift @paths;

	    }

	    if ( $operation eq 'examine' ) {

		if ( /Volume status for vid = (\d+) named (\S+)/ ) {

		    $path->_setAttribute
		      (
		       path			=> $paths[0],
		       id			=> $1,
		       volname			=> $2,
		      );

		    #
		    # Looking at Transarc's code, we can safely assume we'll
		    # get this output in the order shown. Note we ignore the
		    # "Message of the day" and "Offline reason" output for
		    # now.  Read until we hit a blank line.
		    #
		    while ( defined($_ = $self->{handle}->getline()) ) {

			last if /^\s*$/;

			if ( /Current disk quota is (\d+|unlimited)/ ) {
			    $path->_setAttribute
			      (
			       quota		=>  $1 eq "unlimited" ? 0 : $1,
			      );
			}

			if ( /Current blocks used are (\d+)/ ) {
			    $path->_setAttribute( used => $1 );
			}

			if ( /The partition has (\d+) blocks available out of (\d+)/ ) {
			    $path->_setAttribute
			      (
			       avail		=> $1,
			       total		=> $2,
			      );
			}
		    }

		    delete $paths{$paths[0]};
		    shift @paths;

		}

	    }

	}

	$result->_addPath($path);

    }

    if ( $operation eq 'storebehind' ) {

	$result->_setAttribute( asynchrony => $default );

	#
	# This is ugly, but we get the default last, and it would be nice
	# to put this value into the Path objects as well, rather than the
	# string 'default'.
	#
	foreach my $path ( $result->getPaths() ) {
	    if ( defined($path->asynchrony()) && $path->asynchrony() eq 'default' ) {
		$path->_setAttribute( asynchrony => $default );
	    }
	}
    }

    foreach my $pathname ( keys %paths ) {

	my $path = AFS::Object::Path->new
	  (
	   path			=> $pathname,
	   error		=> "Unable to determine results",
	  );

	$result->_addPath($path);

    }

    $errors++ unless $self->_reap_cmds( allowstatus => 1 );

    return if $errors;
    return $result;

}

sub exportafs {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object->new();

    $self->{operation} = "exportafs";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	/translator is (currently )?enabled/ && do {
	    $result->_setAttribute( enabled => 1 );
	};

	/translator is disabled/ && do {
	    $result->_setAttribute( enabled => 0 );
	};

	/convert owner mode bits/ && do {
	    $result->_setAttribute( convert => 1 );
	};

	/strict unix/ && do {
	    $result->_setAttribute( convert => 0 );
	};

	/strict \'?passwd sync\'?/ && do {
	    $result->_setAttribute( uidcheck => 1 );
	};

	/no \'?passwd sync\'?/ && do {
	    $result->_setAttribute( uidcheck => 0 );
	};

	/allow mounts/i && do {
	    $result->_setAttribute( submounts => 1 );
	};

	/Only mounts/i && do {
	    $result->_setAttribute( submounts => 0 );
	};

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub getcacheparms {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = "getcacheparms";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {
	if ( /using (\d+) of the cache.s available (\d+) 1K/ ) {
	    $result->_setAttribute
	      (
	       used			=> $1,
	       avail			=> $2,
	      );
	}
    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub getcellstatus {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = "getcellstatus";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	if ( /Cell (\S+) status: (no )?setuid allowed/ ) {
	    my $cell = AFS::Object::Cell->new
	      (
	       cell			=> $1,
	       status			=> $2 ? 0 : 1,
	      );
	    $result->_addCell($cell);
	}

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub getclientaddrs {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = "getclientaddrs";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    my @addresses = ();

    while ( defined($_ = $self->{handle}->getline()) ) {
	chomp;
	s/^\s+//;
	s/\s+$//;
	push(@addresses,$_);
    }

    $result->_setAttribute( addresses => \@addresses );

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub getcrypt {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = "getcrypt";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	if ( /Security level is currently (crypt|clear)/ ) {
	    $result->_setAttribute( crypt => ($1 eq 'crypt' ? 1 : 0) );
	}

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub getserverprefs {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = "getserverprefs";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	s/^\s+//g;
	s/\s+$//g;

	my ($name,$preference) = split;

	my $server = AFS::Object::Server->new
	  (
	   server		=> $name,
	   preference		=> $preference,
	  );

	$result->_addServer($server);

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub listaliases {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = "listaliases";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	if ( /Alias (.*) for cell (.*)/ ) {
	    my $cell = AFS::Object::Cell->new
	      (
	       cell			=> $2,
	       alias			=> $1,
	      );
	    $result->_addCell($cell);
	}

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub listcells {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = "listcells";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	if ( /^Cell (\S+) on hosts (.*)\.$/ ) {
	    my $cell = AFS::Object::Cell->new
	      (
	       cell			=> $1,
	       servers			=> [split(/\s+/,$2)],
	      );
	    $result->_addCell($cell);
	}

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub lsmount {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = "lsmount";

    return unless $self->_parse_arguments(%args);

    my $errors = 0;

    $errors++ unless $self->_exec_cmds( stderr => 'stdout' );

    my @dirs = ref $args{dir} eq 'ARRAY' ? @{$args{dir}} : ($args{dir});
    my %dirs = map { $_ => 1 } @dirs;

    while ( defined($_ = $self->{handle}->getline()) ) {

	my $current = shift @dirs;
	delete $dirs{$current};

	my $path = AFS::Object::Path->new( path => $current );

	if ( /fs: Can.t read target name/ ) {
	    $path->_setAttribute( error => $_ );
	} elsif ( /fs: File '.*' doesn't exist/ ) {
	    $path->_setAttribute( error => $_ );
	} elsif ( /fs: you may not use \'.\'/ ) {
	    $_ .= $self->{handle}->getline();
	    $path->_setAttribute( error => $_ );
	} elsif ( /\'(.*?)\' is not a mount point/ ) {
	    $path->_setAttribute( error => $_ );
	} elsif ( /^\'(.*?)\'.*?\'(.*?)\'$/ ) {

	    my ($dir,$mount) = ($1,$2);

	    $path->_setAttribute( symlink => 1 ) if /symbolic link/;
	    $path->_setAttribute( readwrite => 1 ) if $mount =~ /^%/;
	    $mount =~ s/^(%|\#)//;

	    my ($volname,$cell) = reverse split(/:/,$mount);

	    $path->_setAttribute( volname => $volname );
	    $path->_setAttribute( cell => $cell) if $cell;

	} else {

	    $self->_Carp("fs lsmount: Unrecognized output: '$_'");
	    $errors++;
	    next;

	}

	$result->_addPath($path);

    }

    foreach my $dir ( keys %dirs ) {
        my $path = AFS::Object::Path->new
          (
           path			=> $dir,
           error		=> "Unable to determine results",
          );
        $result->_addPath($path);
    }

    $errors++ unless $self->_reap_cmds( allowstatus => 1 );

    return if $errors;
    return $result;

}

#
# This is deprecated in newer versions of OpenAFS
#
sub monitor {
    my $self = shift;
    $self->_Carp("fs monitor: This operation is deprecated and no longer supported");
    return;
}

sub sysname {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = "sysname";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    my @sysname = ();

    while ( defined($_ = $self->{handle}->getline()) ) {

	if ( /Current sysname is \'?([^\']+)\'?/ ) {
	    $result->_setAttribute( sysname => $1 );
	} elsif ( s/Current sysname list is // ) {
	    while ( s/\'([^\']+)\'\s*// ) {
		push(@sysname,$1);
	    }
	    $result->_setAttribute( sysnames => \@sysname );
	    $result->_setAttribute( sysname => $sysname[0] );
	}

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub wscell {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::CacheManager->new();

    $self->{operation} = "wscell";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {
	next unless /belongs to cell\s+\'(.*)\'/;
	$result->_setAttribute( cell => $1 );
    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

1;

lib/AFS/Command/FS.pod  view on Meta::CPAN

#
# $Id: FS.pod,v 8.1 2004/05/17 13:05:47 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

=head1 NAME

AFS::Command::FS - OO API to the AFS fs command

=head1 SYNOPSIS

    use AFS::Command::FS;

    my $fs = AFS::Command::FS->new();

    my $fs = AFS::Command::FS->new
      (
       command			=> $path_to_your_fs_binary,
      );

=head1 DESCRIPTION

This module implements an OO API wrapper around the AFS 'fs' command.
The supported methods depend on the version of the fs binary used,
and are determined automagically.

=head1 METHODS -- Inherited

All of the following methods are inherited from the AFS::Command::Base
class.  See that documentation for details.

=over

=item new

=item errors

=item supportsOperation

=item supportsArgument

=back

=head1 METHODS (with complex return values)

=head2 NOTE: Error checking for commands that accept a list of paths

A number of these methods accept a list of paths, and will return
information for each path, individually.  If you specify a
non-existent path, or one which is not in AFS, then the fs command
returns a non-zero exist status, which normally would mean the command
failed.

If you specify a list of paths to this API, and one or more of them
result in errors, the API call is still considered to succeed, as long
as we can determine the error for each path specified.  The API will
still return an AFS::Object::CacheManager object, which contains a set
of AFS::Object::Path object, for each path specified in the arguments,
as long as we saw some kind of output from the fs commands for each
path.

Each AFS::Object::Path object must be examined to determine the
success of failure for that individual path.  When errors were
encountered for any given path, then the objects will have an "error"
attribute, and nothing else (no other data attributes, except the path
itself).

This holds true for the following API methods: diskfree, examine,
listquota, quota, storebehind, whereis, whichcell, and listacl.

=head2 checkservers

=over

=item Arguments

The fs help string is:

    fs checkservers: check local cell's servers
    Usage: fs checkservers [-cell <cell to check>] [-all] [-fast]
			   [-interval <seconds between probes>]
    Where: -all   check all cells
	   -fast  just list, don't check

The corresponding method invocation looks like:

    my $result = $fs->checkservers
      (
       # Optional arguments
       cell			=> $cell,
       interval			=> $interval,
       all			=> 1,
       fast			=> 1,
      );

=item Return Values

This method returns an AFS::Object::CacheManager object, which
contains one or more attributes.

    my $result = $fs->checkservers() || die $fs->errors();
    my @servers = $result->servers();
    foreach my $server ( @servers ) {
        print "Server $server appears to be down\n";
    }

The object has the following attributes:

    Attributes			Values
    ----------			------
    servers			ARRAY reference of strings, each of which is
				the hostname of a server which is down
    interval			The value of the probe interval, in seconds

Note that the interval attribute is only present of the internal
argument was specified, and the servers list will be empty if nothing
was down.

=back

=head2 diskfree

=over

=item Arguments

The fs help string is:

    fs diskfree: show server disk space usage
    Usage: fs diskfree [-path <dir/file path>+]

The corresponding method invocation looks like:

    my $result = $fs->diskfree
      (
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
      );

=item Return Values

This method returns an AFS::Object::CacheManager object, which
contains one or more AFS::Object::Path objects, one for each path
specified in the arguments.

    my $result = $fs->diskfree
      (
       path		=> [ $afspath, $ufspath, $boguspath ],
      ) || die $fs->errors();
    foreach my $pathobj ( $result->getPaths() ) {
	my $path = $pathobj->path();
	if ( $pathobj->hasAttribute('error') ) {
	    print "Path '$path' has errors '" . $pathobj->error() . "'\n";
	} else {
	    foreach my $attr ( qw( volname used total avail percent ) ) {
		print "Path '$path' has '$attr' of '" . $pathobj->$attr() . "'\n";
	    }
	}
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getPathNames()		list of strings, each of which is a single pathname
    getPaths()			list of AFS::Object::Path objects, one for each path
    getPath($pathname)		a single AFS::Object::Path object, for the pathname $pathname

B<AFS::Object::Path>

If errors were encountered for any given path, then its object will
have the following attributes:

    Attributes			Values
    ----------			------
    path			The pathname
    error			The error string for that path

If no errors were encountered, then the following attributes will be present:

    Attributes			Values
    ----------			------
    path			The pathname
    volname			The AFS volume name that contains the pathname
    total			The size (in KB) of the partition that contains 'volname'
    used			The amount of space (in KB) used on that partition
    avail			The amount of space (in KB) available on that partition
    percent			The amount of space used, as a percentage

=back

=head2 examine

=over

=item Arguments

The fs help string is:

    fs examine: display volume status
    Usage: fs examine [-path <dir/file path>+]

The corresponding method invocation looks like:

    my $result = $fs->examine
      (
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
      );

=item Return Values

This method returns an AFS::Object::CacheManager object, which
contains one or more AFS::Object::Path objects, one for each path
specified in the arguments.

    my $result = $fs->examine
      (
       path		=> [ $afspath, $ufspath, $boguspath ],
      ) || die $fs->errors();
    foreach my $pathobj ( $result->getPaths() ) {
	my $path = $pathobj->path();
	if ( $pathobj->hasAttribute('error') ) {
	    print "Path '$path' has errors '" . $pathobj->error() . "'\n";
	} else {
	    foreach my $attr ( qw( id volname quota used avail total ) ) {
		print "Path '$path' has '$attr' of '" . $pathobj->$attr() . "'\n";
	    }
	}
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getPathNames()		list of strings, each of which is a single pathname
    getPaths()			list of AFS::Object::Path objects, one for each path
    getPath($pathname)		a single AFS::Object::Path object, for the pathname $pathname

B<AFS::Object::Path>

If errors were encountered for any given path, then its object will
have the following attributes:

    Attributes			Values
    ----------			------
    path			The pathname
    error			The error string for that path

If no errors were encountered, then the following attributes will be present:

    Attributes			Values
    ----------			------
    path			The pathname
    volname			The AFS volume name that contains the pathname
    id				The numerical volume ID of the above volume
    total			The size (in KB) of the partition that contains 'volname'
    used			The amount of space (in KB) used on that partition
    avail			The amount of space (in KB) available on that partition
    quota			The quota of the volume (in KB), or 0 if set to "unlimited"

=back

=head2 exportafs

=over

=item Arguments

The fs help string is:

    fs exportafs: enable/disable translators to AFS
    Usage: fs exportafs -type <exporter name> [-start <start/stop translator (on | off)>]
		       [-convert <convert from afs to unix mode (on | off)>]
		       [-uidcheck <run on strict 'uid check' mode (on | off)>]
		       [-submounts <allow nfs mounts to subdirs of /afs/.. (on  | off)>]

The corresponding method invocation looks like:

    my $result = $fs->exportafs
      (
       # Required arguments
       type			=> $type,	# 'nfs' is the only supported value
       # Optional arguments
       start			=> $start,	# 'on' or 'off'
       convert			=> $convert,	# 'on' or 'off'
       uidcheck			=> $uidcheck,	# 'on' or 'off'
       submounts		=> $submounts,	# 'on' or 'off'
      );

NOTE: In a future release, the 4 optional arguments will probably take
boolean values, with "off" being a special case that means false, in
order to simply the interface (and be backwards compatible).

=item Return Values

This method returns an AFS::Object::CacheManager object with one or
more attributes.

    my $result = $fs->exportafs
      (
       type			=> 'nfs',
       start			=> 'on',
      ) || die $fs->errors();
    foreach my $attr ( qw( convert uidcheck submounts ) ) {
	print "Translator has '$attr' set to '" . $result->$attr() . "'\n";
    }

The object has the following attribute:

B<AFS::Object::CacheManager>

    Attributes			Values
    ----------			------
    enabled			Boolean, true means the translator is on, false means off
    convert			Boolean, true means mode bits are converted from AFS to UNIX, false means off
    uidcheck			Boolean, true means strict uid checking mode is on, false means off
    submounts			Boolean, true means mounts of subdirs are allowed, false means disallowed

=back

=head2 getcacheparms

=over

=item Arguments

The fs help string is:

    fs getcacheparms: get cache usage info
    Usage: fs getcacheparms

The corresponding method invocation looks like:

    my $result = $fs->getcacheparms();

=item Return Values

This method returns an AFS::Object::CacheManager object with one or
more attributes.

    my $result = $fs->getcacheparms() || die $fs->errors();
    my $used = $result->used();
    my $avail = $result->avail();
    print "Cache is using $used KB of $availa KB available\n";

The object has the following attributes:

B<AFS::Object::CacheManager>

    Attributes			Values
    ----------			------
    used			Number of KB of the AFS cache in use
    avail			Size of the AFS cache, in KB

=back

=head2 getcellstatus

=over

=item Arguments

The fs help string is:

    fs getcellstatus: get cell status
    Usage: fs getcellstatus -cell <cell name>+

The corresponding method invocation looks like:

    my $result = $fs->getcellstatus
      (
       # Required arguments
       cell			-> $cell, # OR [ $cell1, $cell2, ... ]
      );

=item Return Values

This method returns an AFS::Object::CacheManager object which contains
one or more AFS::Object::Cell objects.

    my $result = $fs->getcellstatus
      (
       cell			=> [ $cell1 , $cell2 ],
      ) || die $fs->errors();
    foreach my $cellobj ( $result->getCells() ) {
	my $cell = $cellobj->cell();
	if ( $cellobj->status() ) {
	    print("This client allows setuid binaries from cell '$cell'\n";
	} else {
	    print("This client does NOT allow setuid binaries from cell '$cell'\n";
	}
    }

The objects have the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getCellNames()		list of cell names
    getCells()			list of AFS::Object::Cell objects
    getCell($cell)		the AFS::Object::Cell object for cell $cell

B<AFS::Object::Cell>

    Attributes			Values
    ----------			------
    cell			AFS cell name
    status			Boolean, true indicating setuid/gid bits are allowed,
				false indicating they are not

=back

=head2 getclientaddrs

=over

=item Arguments

The fs help string is:

    fs getclientaddrs: get client network interface addresses
    Usage: fs getclientaddrs

The corresponding method invocation looks like:

    my $result = $fs->getclientaddrs();

=item Return Values

This method returns an AFS::Object::CacheManager object with one attribute.

    my $result = $fs->getclientaddrs() || die $fs->errors();
    print "This client has the following addressed configured for AFS:\n";
    foreach my $address ( @{$result->addresses()} ) {
	print "\t$address\n";
    }

The object has the following attribute:

B<AFS::Object::CacheManager>

    Attributes			Values
    ----------			------
    addresses			ARRAY reference of IP addresses

=back

=head2 getcrypt

=over

=item Arguments

The fs help string is:

    fs getcrypt: set cache manager encryption flag
    Usage: fs getcrypt

The corresponding method invocation looks like:

    my $result = $fs->getcrypt();

=item Return Values

This method returns an AFS::Object::CacheManager object with one attribute.

    my $result = $fs->getcrypt() || die $fs->errors();
    print "This client has encryption turned " . ( $result->crypt() ? "on" : "off" ) . "\n";

The object has the following attribute:

B<AFS::Object::CacheManager>

    Attributes			Values
    ----------			------
    crypt			Boolean, indicating whether or not encryption is enabled

=back

=head2 getserverprefs

=over

=item Arguments

The fs help string is:

    fs getserverprefs: get server ranks
    Usage: fs getserverprefs [-file <output to named file>] [-numeric] [-vlservers]
    Where: -numeric    addresses only
	   -vlservers  VL servers

The corresponding method invocation looks like:

    my $result = $fs->getserverprefs
      (
       # Optional arguments
       file			=> $file,
       numeric			=> 1,
       vlservers		=> 1,
      );

=item Return Values

This method returns an AFS::Object::CacheManager object which contains
one or more AFS::Object::Server objects.

    my $result = $fs->getserverprefs() || die $fs->errors();
    foreach my $serverobj ( $result->getServers() ) {
	my $server = $serverobj->server();
	my $pref = $serverobj->preference();
	print "Server '$server' has preference '$preference'\n";
    }

The objects have the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getServerNames()		list of server hostnames (or addresses)
    getServers()		list of AFS::Object::Server objects
    getServer($server)		the AFS::Object::Server object for server $server

B<AFS::Object::Server>

    Attributes			Values
    ----------			------
    server			Hostname or IP address of the server
    preference			Numeric preference value

=back

=head2 listacl

=over

=item Arguments

The fs help string is:

    fs listacl: list access control list
    Usage: fs listacl [-path <dir/file path>+] [-id] [-if]
    Where: -id  initial directory acl
	   -if  initial file acl

The corresponding method invocation looks like:

    my $result = $fs->listacl
      (
       # Required arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
       # Optional arguments
       id			=> 1,
       if			=> 1,
      );

=item Return Values

This method returns an AFS::Object::CacheManager object, which
contains one or more AFS::Object::Path objects, one for each path
specified in the arguments.  Each AFS::Object::Path object contains
one or two AFS::Object::ACL objects (one for normal, and one for
negative).

    my $result = $fs->listacl
      (
       path		=> [ $afspath, $ufspath, $boguspath ],
      ) || die $fs->errors();
    foreach my $pathobj ( $result->getPaths() ) {
	my $path = $pathobj->path();
	if ( $pathobj->hasAttribute('error') ) {
	    print "Path '$path' has errors '" . $pathobj->error() . "'\n";
	} else {
	    foreach my $type ( qw( normal negative ) ) {
		my $acl = $pathobj->getACL($type);
		my %entries = $acl->getEntries();
		foreach my $principal ( keys %entries ) {
		    my $rights = $acl->getRights($principal);
		    print "$type rights for $principal are $rights\n";
		}
	    }
	}
    }

The objects have the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getPathNames()		list of strings, each of which is a single pathname
    getPaths()			list of AFS::Object::Path objects, one for each path
    getPath($pathname)		a single AFS::Object::Path object, for the pathname $pathname

B<AFS::Object::Path>

    Methods			Returns
    -------			-------
    getACLNormal()		the AFS::Object::ACL object for the normal rights
    getACLNegative()		the AFS::Object::ACL object for the negative rights
    getACL($type)		the AFS::Object::ACL object for rights of type $type,
				where $type is either 'normal' or 'negative'

B<AFS::Object::ACL>

    Methods			Returns
    -------			-------
    getPrincipals()		a list of the principals (users, groups) on the ACL
    getRights($principal)	the rights (permissions) of the specified $principal
    getEntries()		a list of key/value pairs, where the keys are the principals,
				and the values are the rights for that principal

=back

=head2 listaliases

=over

=item Arguments

The fs help string is:

    fs listaliases: list configured cell aliases
    Usage: fs listaliases

The corresponding method invocation looks like:

    my $result = $fs->listaliases();

=item Return Values

This method returns an AFS::Object::CacheManager object, which
contains one or more AFS::Object::Cell objects.

    my $result = $fs->listaliases() || die $fs->errors();
    foreach my $cellobj ( $result->getCells() ) {
	my $cell = $cellobj->cell();
	my $alias = $cellobj->alias();
	print "Cell '$cell' has alias '$alias'\n";
    }

The objects have the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getCellNames()		list of cell names
    getCells()			list of AFS::Object::Cell objects
    getCell($cell)		the AFS::Object::Cell object for cell $cell

B<AFS::Object::Cell>

    Attributes			Values
    ----------			------
    cell			AFS cell name
    alias			Alias name for this cell

=back

=head2 listcells

=over

=item Arguments

The fs help string is:

    fs listcells: list configured cells
    Usage: fs listcells [-numeric]
    Where: -numeric  addresses only

The corresponding method invocation looks like:

    my $result = $fs->listcells
      (
       # Optional arguments
       numeric			=> 1,
      );

=item Return Values

This method returns an AFS::Object::CacheManager object, which
contains one or more AFS::Object::Cell objects.

    my $result = $fs->listcells() || die $fs->errors();
    foreach my $cellobj ( $result->getCells() ) {
	my $servers = $cellobj->servers();
	print "Cell $cell has servers " . join(" ",@$servers) . "\n";
    }

The objects have the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getCellNames()		list of cell names
    getCells()			list of AFS::Object::Cell objects
    getCell($cell)		the AFS::Object::Cell object for cell $cell

B<AFS::Object::Cell>

    Attributes			Values
    ----------			------
    cell			AFS cell name
    servers			ARRAY reference of strings, each of which is
				the hostname of a server

=back

=head2 listquota

=over

=item Arguments

The fs help string is:

    fs listquota: list volume quota
    Usage: fs listquota [-path <dir/file path>+]

The corresponding method invocation looks like:

    my $result = $fs->listquota
      (
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
      );

=item Return Values

=back

This method returns an AFS::Object::CacheManager object, which
contains one or more AFS::Object::Path objects, one for each path
specified in the arguments.

    my $result = $fs->listquota
      (
       path		=> [ $afspath, $ufspath, $boguspath ],
      ) || die $fs->errors();
    foreach my $pathobj ( $result->getPaths() ) {
	my $path = $pathobj->path();
	if ( $pathobj->hasAttribute('error') ) {
	    print "Path '$path' has errors '" . $pathobj->error() . "'\n";
	} else {
	    foreach my $attr ( qw( volname quota used percent partition ) ) {
		print "Path '$path' has '$attr' of '" . $pathobj->$attr() . "'\n";
	    }
	}
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getPathNames()		list of strings, each of which is a single pathname
    getPaths()			list of AFS::Object::Path objects, one for each path
    getPath($pathname)		a single AFS::Object::Path object, for the pathname $pathname

B<AFS::Object::Path>

If errors were encountered for any given path, then its object will
have the following attributes:

    Attributes			Values
    ----------			------
    path			The pathname
    error			The error string for that path

If no errors were encountered, then the following attributes will be present:

    Attributes			Values
    ----------			------
    path			The pathname
    volname			The AFS volume name that contains the pathname
    quota			Volume quota, in KB
    used			The amount of space (in KB) used in that volume
    percent			The percentage of the allocated quota in use
    partition			The percentage of space used on the partition where the volume resides

=head2 lsmount

=over

=item Arguments

The fs help string is:

    fs lsmount: list mount point
    Usage: fs lsmount -dir <directory>+

The corresponding method invocation looks like:

    my $result = $fs->lsmount
      (
       # Required arguments
       dir			=> $dir, # OR [ $dir1, $dir2, ... ]
      );

=item Return Values

This method returns an AFS::Object::CacheManager object, which
contains one or more AFS::Object::Path objects, one for each path
specified in the arguments.

    my $result = $fs->lsmount
      (
       dir			=> [ $dir1, $dir2 ],
      ) || die $fs->errors();
    foreach my $pathobj ( $result->getPaths() ) {
	my $path = $pathobj->path();
	if ( $pathobj->hasAttribute('error') ) {
	    print "Path '$path' has errors '" . $pathobj->error() . "'\n";
	} else {
            my $volname = $pathobj->volname();
            my $cell = $pathobj->cell();
            print("Path '$path' is a mtpt for volume $volname" .
                  ( $cell ? ", in cell '$cell'\n" : "\n" ));
	}
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getPathNames()		list of strings, each of which is a single pathname
    getPaths()			list of AFS::Object::Path objects, one for each path
    getPath($pathname)		a single AFS::Object::Path object, for the pathname $pathname

B<AFS::Object::Path>

If errors were encountered for any given path, then its object will
have the following attributes:

    Attributes			Values
    ----------			------
    path			The pathname
    error			The error string for that path

If no errors were encountered, then the following attributes will
always be present:

    Attributes			Values
    ----------			------
    path			The pathname
    volname			AFS volname in the mount point

The following attributes may or may not be present:

    Attributes			Values
    ----------			------
    symlink			Boolean, true if the pathname is a symlink to a mount point
    readwrite			Boolean, true if the mount point is explicitly readwrite
    cell			AFS cell name in the mount point

=back

=head2 quota

=over

=item Arguments

The fs help string is:

    fs quota: show volume quota usage
    Usage: fs quota [-path <dir/file path>+]

The corresponding method invocation looks like:

    my $result = $fs->quota
      (
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
      );

=item Return Values

This method returns an AFS::Object::CacheManager object, which
contains one or more AFS::Object::Path objects, one for each path
specified in the arguments.

    my $result = $fs->quota
      (
       path		=> [ $afspath, $ufspath, $boguspath ],
      ) || die $fs->errors();
    foreach my $pathobj ( $result->getPaths() ) {
	my $path = $pathobj->path();
	if ( $pathobj->hasAttribute('error') ) {
	    print "Path '$path' has errors '" . $pathobj->error() . "'\n";
	} else {
            print "Path '$path' has quota '" . $pathobj->quota() . "'\n";
	}
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getPathNames()		list of strings, each of which is a single pathname
    getPaths()			list of AFS::Object::Path objects, one for each path
    getPath($pathname)		a single AFS::Object::Path object, for the pathname $pathname

B<AFS::Object::Path>

If errors were encountered for any given path, then its object will
have the following attributes:

    Attributes			Values
    ----------			------
    path			The pathname
    error			The error string for that path

If no errors were encountered, then the following attributes will be present:

    Attributes			Values
    ----------			------
    path			The pathname
    quota			The percentage of the allocated quota in use

=back

=head2 storebehind

=over

=item Arguments

The fs help string is:

    fs storebehind: store to server after file close
    Usage: fs storebehind [-kbytes <asynchrony for specified names>]
			  [-files <specific pathnames>+] [-allfiles <new default (KB)>]
			  [-verbose]
    Where: -verbose  show status

The corresponding method invocation looks like:

    my $result = $fs->storebehind
      (
       # Optional arguments
       kbytes			=> $kbytes,
       files			=> $file, # OR [ $file1, $file2, ... ]
       allfiles			=> $default,
       verbose			=> 1,
      );

=item Return Values

This method returns an AFS::Object::CacheManager object, which
contains one or more AFS::Object::Path objects, one for each path
specified in the arguments.

    my $result = $fs->quota
      (
       path		=> [ $afspath, $ufspath, $boguspath ],
      ) || die $fs->errors();
    foreach my $pathobj ( $result->getPaths() ) {
	my $path = $pathobj->path();
	if ( $pathobj->hasAttribute('error') ) {
	    print "Path '$path' has errors '" . $pathobj->error() . "'\n";
	} else {
	    foreach my $attr ( qw( volname quota used percent partition ) ) {
		print "Path '$path' has '$attr' of '" . $pathobj->$attr() . "'\n";
	    }
	}
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::CacheManager>

    Attributes			Values
    ----------			------
    asynchrony			Default value (in KB) of asynchronous writes

    Methods			Returns
    -------			-------
    getPathNames()		list of strings, each of which is a single pathname
    getPaths()			list of AFS::Object::Path objects, one for each path
    getPath($pathname)		a single AFS::Object::Path object, for the pathname $pathname

B<AFS::Object::Path>

If errors were encountered for any given path, then its object will
have the following attributes:

    Attributes			Values
    ----------			------
    path			The pathname
    error			The error string for that path

If no errors were encountered, then the following attributes will be present:

    Attributes			Values
    ----------			------
    path			The pathname
    asynchrony			The number of KB of asynchronous writes for this file

=back

=head2 sysname

=over

=item Arguments

The fs help string is:

    fs sysname: get/set sysname (i.e. @sys) value
    Usage: fs sysname [-newsys <new sysname>+]

The corresponding method invocation looks like:

    my $result = $fs->sysname
      (
       # Optional arguments
       newsys			=> $sysname, # OR [ $sysname1, $sysname2, ... ]
      );

=item Return Values

This method returns an AFS::Object::CacheManager object which has one
of two possible attributes.

    my $result = $fs->sysname() || die $fs->errors();
    my $sysname = $result->sysname();
    my $sysnames = $result->sysnames();
    print "This client has a primary sysname of '$sysname'\n";
    if ( ref $sysnames eq 'ARRAY' ) {
	print "This client has a list of sysnames: " . join(" ,",@$sysnames) . "\n";
    }

The object has the following attributes:

B<AFS::Object::CacheManager>

    Attributes			Values
    ----------			------
    sysname			The primary sysname of the client
    sysnames			An ARRAY reference of sysnames

NOTE: When a list of sysnames has been configured on the client, then
the 'sysname' attribute is simnply the first one in the list.

=back

=head2 whereis

=over

=item Arguments

The fs help string is:

    fs whereis: list file's location
    Usage: fs whereis [-path <dir/file path>+]

The corresponding method invocation looks like:

    my $result = $fs->whereis
      (
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
      );

=item Return Values

This method returns an AFS::Object::CacheManager object, which
contains one or more AFS::Object::Path objects, one for each path
specified in the arguments.

    my $result = $fs->whereis
      (
       path		=> [ $afspath, $ufspath, $boguspath ],
      ) || die $fs->errors();
    foreach my $pathobj ( $result->getPaths() ) {
	my $path = $pathobj->path();
	if ( $pathobj->hasAttribute('error') ) {
	    print "Path '$path' has errors '" . $pathobj->error() . "'\n";
	} else {
            print "Path '$path' is on hosts " . join(" ,",@{pathobj->hosts()}) . "\n";
	}
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getPathNames()		list of strings, each of which is a single pathname
    getPaths()			list of AFS::Object::Path objects, one for each path
    getPath($pathname)		a single AFS::Object::Path object, for the pathname $pathname

B<AFS::Object::Path>

If errors were encountered for any given path, then its object will
have the following attributes:

    Attributes			Values
    ----------			------
    path			The pathname
    error			The error string for that path

If no errors were encountered, then the following attributes will be present:

    Attributes			Values
    ----------			------
    path			The pathname
    hosts			An ARRAY reference of hostnames

=back

=head2 whichcell

=over

=item Arguments

The fs help string is:

    fs whichcell: list file's cell
    Usage: fs whichcell [-path <dir/file path>+]

The corresponding method invocation looks like:

    my $result = $fs->whichcell
      (
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
      );

=item Return Values

This method returns an AFS::Object::CacheManager object, which
contains one or more AFS::Object::Path objects, one for each path
specified in the arguments.

    my $result = $fs->whichcell
      (
       path		=> [ $afspath, $ufspath, $boguspath ],
      ) || die $fs->errors();
    foreach my $pathobj ( $result->getPaths() ) {
	my $path = $pathobj->path();
	if ( $pathobj->hasAttribute('error') ) {
	    print "Path '$path' has errors '" . $pathobj->error() . "'\n";
	} else {
            print "Path '$path' is in cell '" . $pathobj->cell() . "'\n";
	}
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::CacheManager>

    Methods			Returns
    -------			-------
    getPathNames()		list of strings, each of which is a single pathname
    getPaths()			list of AFS::Object::Path objects, one for each path
    getPath($pathname)		a single AFS::Object::Path object, for the pathname $pathname

B<AFS::Object::Path>

If errors were encountered for any given path, then its object will
have the following attributes:

    Attributes			Values
    ----------			------
    path			The pathname
    error			The error string for that path

If no errors were encountered, then the following attributes will be present:

    Attributes			Values
    ----------			------
    path			The pathname
    cell			Cell in which the pathname lives

=back

=head2 wscell

=over

=item Arguments

The fs help string is:

    fs wscell: list workstation's cell
    Usage: fs wscell

The corresponding method invocation looks like:

    my $result = $fs->wscell();

=item Return Values

This method returns an AFS::Object::CacheManager object which has one
attribute.

    my $result = $fs->wscell() || die $fs->errors();
    print "This client lives in cell '" . $result->cell() . "'\n";

The object has the following attribute:

    Attributes			Values
    ----------			------
    cell			The AFS cell of the client

=back

=head1 METHODS (with simple return values)

=head2 checkvolumes

The fs help string is:

    fs checkvolumes: check volumeID/name mappings
    Usage: fs checkvolumes

The corresponding method invocation looks like:

    my $result = $fs->checkvolumes();

=head2 cleanacl

The fs help string is:

    fs cleanacl: clean up access control list
    Usage: fs cleanacl [-path <dir/file path>+]

The corresponding method invocation looks like:

    my $result = $fs->cleanacl
      (
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
      );

=head2 copyacl

The fs help string is:

    fs copyacl: copy access control list
    Usage: fs copyacl -fromdir <source directory (or DFS file)>
		      -todir <destination directory (or DFS file)>+
		      [-clear] [-id] [-if]
    Where: -clear  first clear dest access list
	   -id     initial directory acl
	   -if     initial file acl

The corresponding method invocation looks like:

    my $result = $fs->copyacl
      (
       # Required arguments
       fromdir			=> $fromdir,
       todir			=> $todir, # OR [ $todir1, $todir2, ... ]
       # Optional arguments
       clear			=> 1,
       id			=> 1,
       if			=> 1,
      );

=head2 flush

The fs help string is:

    fs flush: flush file from cache
    Usage: fs flush [-path <dir/file path>+]

The corresponding method invocation looks like:

    my $result = $fs->flush
      (
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
      );

=head2 flushmount

The fs help string is:

    fs flushmount: flush mount symlink from cache
    Usage: fs flushmount [-path <dir/file path>+]

The corresponding method invocation looks like:

    my $result = $fs->flushmount
      (
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
      );

=head2 flushvolume

The fs help string is:

    fs flushvolume: flush all data in volume
    Usage: fs flushvolume [-path <dir/file path>+]

The corresponding method invocation looks like:

    my $result = $fs->flushvolume
      (
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
      );

=head2 messages

The fs help string is:

    fs messages: control Cache Manager messages
    Usage: fs messages [-show <[user|console|all|none]>]

The corresponding method invocation looks like:

    my $result = $fs->messages
      (
       # Optional arguments
       show			=> $show,
      );

=head2 mkmount

The fs help string is:

    fs mkmount: make mount point
    Usage: fs mkmount -dir <directory> -vol <volume name> [-cell <cell name>] [-rw] [-fast]
    Where: -rw    force r/w volume
	   -fast  don't check name with VLDB

The corresponding method invocation looks like:

    my $result = $fs->mkmount
      (
       # Required arguments
       dir			=> $dir,
       vol			=> $vol,
       # Optional arguments
       cell			=> $cell,
       rw			=> 1,
       fast			=> 1,
      );

=head2 newalias

The fs help string is:

    fs newalias: configure new cell alias
    Usage: fs newalias -alias <alias name> -name <real name of cell>

The corresponding method invocation looks like:

    my $result = $fs->newalias
      (
       # Required arguments
       alias			=> $alias,
       name			=> $name,
      );

=head2 newcell

The fs help string is:

    fs newcell: configure new cell
    Usage: fs newcell -name <cell name> -servers <primary servers>+
		      [-linkedcell <linked cell name>]

The corresponding method invocation looks like:

    my $result = $fs->newcell
      (
       # Required arguments
       name			=> $name,
       servers			=> $server, # OR [ $server1, $server2, ... ]
       # Optional arguments
       linkedcell		=> $linkedcell,
      );

=head2 rmmount

The fs help string is:

    fs rmmount: remove mount point
    Usage: fs rmmount -dir <directory>+

The corresponding method invocation looks like:

    my $result = $fs->rmmount
      (
       # Required arguments
       dir			=> $dir, # OR [ $dir1, $dir2, ... ]
      );

=head2 rxstatpeer

The fs help string is:

    fs rxstatpeer: Manage per peer RX statistics
    Usage: fs rxstatpeer [-enable] [-disable] [-clear]
    Where: -enable   Enable RX stats
	   -disable  Disable RX stats
	   -clear    Clear RX stats

The corresponding method invocation looks like:

    my $result = $fs->rxstatpeer
      (
       # Optional arguments
       enable			=> 1,
       disable			=> 1,
       clear			=> 1,
      );

=head2 rxstatproc

The fs help string is:

    fs rxstatproc: Manage per process RX statistics
    Usage: fs rxstatproc [-enable] [-disable] [-clear]
    Where: -enable   Enable RX stats
	   -disable  Disable RX stats
	   -clear    Clear RX stats

The corresponding method invocation looks like:

    my $result = $fs->rxstatproc
      (
       # Optional arguments
       enable			=> 1,
       disable			=> 1,
       clear			=> 1,
      );

=head2 setacl

The fs help string is:

    fs setacl: set access control list
    Usage: fs setacl -dir <directory>+ -acl <access list entries>+
		     [-clear] [-negative] [-id] [-if]
    Where: -clear     clear access list
	   -negative  apply to negative rights
	   -id        initial directory acl (DFS only)
	   -if        initial file acl (DFS only)

The corresponding method invocation looks like:

    my $result = $fs->setacl
      (
       # Required arguments
       dir			=> $dir, # OR [ $dir1, $dir2, ... ]
       acl			=> [ <<see below>> ],
       # Optional arguments
       clear			=> 1,
       negative			=> 1,
       id			=> 1,
       if			=> 1,
      );

NOTE: The values passed to the 'acl' argument has to be constructed
with care.  Unlike many of the other arguments, this has to be a seen
by the 'fs' command as an even number of additional command line
arguments immediately after the -acl flag.

If you construct a single string, such as "user read group write",
then the method will fail.  There is no shell involved in exec'ing fs,
so there will be no splitting of this string on whitespace before we
construct the arguments to fs, so it will look like a single argument,
not four distinct arguments.

Therefore, there are two ways to construct an ACL to pass to setacl():

    my @acl = ( $user, 'read', $group, 'write' );
    my $result = $fs->setacl
      (
       dir			=> $dir,
       acl			=> \@acl,
      );

    my %acl =
      (
       $user			=> 'read',
       $group			=> 'write',
      );
    my $result = $fs->setacl
      (
       dir			=> $dir,
       acl			=> \%acl,
      );

In a future release of the API, maybe even 1.1, it will be possible to
pass AFS::Object::ACL objects as arguments to these API
calls, but not yet...

=head2 setcachesize

The fs help string is:

    fs setcachesize: set cache size
    Usage: fs setcachesize [-blocks <size in 1K byte blocks (0 => reset)>] [-reset]
    Where: -reset  reset size back to boot value

The corresponding method invocation looks like:

    my $result = $fs->setcachesize
      (
       # Optional arguments
       blocks			=> $blocks,
       reset			=> 1,
      );

=head2 setcell

The fs help string is:

    fs setcell: set cell status
    Usage: fs setcell -cell <cell name>+ [-suid] [-nosuid]
    Where: -suid    allow setuid programs
	   -nosuid  disallow setuid programs

The corresponding method invocation looks like:

    my $result = $fs->setcell
      (
       # Required arguments
       cell			=> $cell, # OR [ $cell1, $cell2, ... ]
       # Optional arguments
       suid			=> 1,
       nosuid			=> 1,
      );

=head2 setclientaddrs

The fs help string is:

    fs setclientaddrs: set client network interface addresses
    Usage: fs setclientaddrs [-address <client network interfaces>+]

The corresponding method invocation looks like:

    my $result = $fs->setclientaddrs
      (
       # Required arguments
       address			=> $address, # OR [ $address1, $address2, ... ]
      );

=head2 setcrypt

The fs help string is:

    fs setcrypt: set cache manager encryption flag
    Usage: fs setcrypt -crypt <on or off>

The corresponding method invocation looks like:

    my $result = $fs->setcrypt
      (
       # Required arguments
       crypt			=> 1,
      );

=head2 setquota

The fs help string is:

    fs setquota: set volume quota
    Usage: fs setquota [-path <dir/file path>] -max <max quota in kbytes>

The corresponding method invocation looks like:

    my $result = $fs->setquota
      (
       # Required arguments
       max			=> $max,
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
      );

=head2 setserverprefs

The fs help string is:

    fs setserverprefs: set server ranks
    Usage: fs setserverprefs [-servers <fileserver names and ranks>+]
			     [-vlservers <VL server names and ranks>+]
			     [-file <input from named file>] [-stdin]
    Where: -stdin  input from stdin

The corresponding method invocation looks like:

    my $result = $fs->setserverprefs
      (
       # Optional arguments
       servers			=> $server, # OR [ $server1, $server2, ... ]
       vlservers		=> $vlserver, # OR [ $vlserver1, $vlserver2, ... ]
       file			=> $file,
       stdin			=> 1,
      );

=head2 setvol

The fs help string is:

    fs setvol: set volume status
    Usage: fs setvol [-path <dir/file path>+] [-max <disk space quota in 1K units>]
		     [-offlinemsg <offline message>]

The corresponding method invocation looks like:

    my $result = $fs->setvol
      (
       # Optional arguments
       path			=> $path, # OR [ $path1, $path2, ... ]
       max			=> $max,
       offlinemsg   		=> $offlinemsg,
      );

=head1 SEE ALSO

AFS::Command(1), AFS::Object(1)

=cut

lib/AFS/Command/PTS.pm  view on Meta::CPAN

#
# $Id$
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

package AFS::Command::PTS;

require 5.6.0;

use strict;
use English;

use AFS::Command::Base;
use AFS::Object;
use AFS::Object::PTServer;
use AFS::Object::Principal;
use AFS::Object::Group;
use AFS::Object::User;

our @ISA = qw(AFS::Command::Base);
our $VERSION = '1.99';

sub creategroup {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::PTServer->new();

    $self->{operation} = "creategroup";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {
	next unless /group (\S+) has id (-\d+)/;
	my $group = AFS::Object::Group->new
	  (
	   name 		=> $1,
	   id			=> $2,
	  );
	$result->_addGroup($group);
    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub createuser {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::PTServer->new();

    $self->{operation} = "createuser";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {
	next unless /User (\S+) has id (\d+)/;
	my $user = AFS::Object::User->new
	  (
	   name 		=> $1,
	   id			=> $2,
	  );
	$result->_addUser($user);
    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub examine {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::PTServer->new();

    $self->{operation} = "examine";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	while ( /,\s*$/ ) {
	    $_ .= $self->{handle}->getline();
	    chomp;
	}

	my %data = ();

	foreach my $field ( split(/,\s*/) ) {

	    my ($key,$value) = split(/:\s+/,$field,2);

	    $key =~ tr/A-Z/a-z/;
	    $key =~ s/\s+//g;	# group quota -> groupquota
	    $value =~ s/\.$//;

	    $data{$key} = $value;

	}

	unless ( $data{id} ) {
	    $self->_Carp("pts examine: Unrecognized output: '$_'");
	    $errors++;
	    next;
	}

	if ( $data{id} > 0 ) {
	    $result->_addUser( AFS::Object::User->new(%data) );
	} else {
	    $result->_addGroup( AFS::Object::Group->new(%data) );
	}

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub listentries {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::PTServer->new();

    $self->{operation} = "listentries";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	next if /^Name/;

	my ($name,$id,$owner,$creator) = split;

	#
	# We seem to be getting this one bogus line of data, with no
	# name, and 0's for the IDs.  Probably a bug in pts...
	#
	next if ( ! $name && ! $id && ! $owner && ! $creator );

	if ( $id > 0 ) {
	    my $user = AFS::Object::User->new
	      (
	       name 			=> $name,
	       id			=> $id,
	       owner			=> $owner,
	       creator			=> $creator,
	      );
	    $result->_addUser($user);
	} else {
	    my $group = AFS::Object::Group->new
	      (
	       name 			=> $name,
	       id			=> $id,
	       owner			=> $owner,
	       creator			=> $creator,
	      );
	    $result->_addGroup($group);
	}

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub listmax {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::PTServer->new();

    $self->{operation} = "listmax";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {
	next unless /Max user id is (\d+) and max group id is (-\d+)/;
	$result->_setAttribute
	  (
	   maxuserid		=> $1,
	   maxgroupid		=> $2,
	  );
    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub listowned {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::PTServer->new();

    $self->{operation} = "listowned";

    return unless $self->_parse_arguments(%args);

    my $errors = 0;

    $errors++ unless $self->_exec_cmds( stderr => 'stdout' );

    my $user = undef;
    my $group = undef;

    while ( defined($_ = $self->{handle}->getline()) ) {

	if ( /Groups owned by (\S+) \(id: (-?\d+)\)/ ) {

	    $result->_addUser($user) if $user;
	    $result->_addGroup($group) if $group;

	    my ($name,$id) = ($1,$2);

	    if ( $id > 0 ) {
		$user = AFS::Object::User->new
		  (
		   name 		=> $name,
		   id			=> $id,
		  );
		$group = undef;
	    } else {
		$group = AFS::Object::Group->new
		  (
		   name 		=> $name,
		   id			=> $id,
		  );
		$user = undef;
	    }

	} elsif ( /^\s+(\S+)\s*/ ) {

	    if ( $user ) {
		$user->_addOwned($1);
	    } else {
		$group->_addOwned($2);
	    }

	} elsif ( /unable to get owner list/ ) {

	    #
	    # pts still (as of OpenAFS 1.2.8) doesn't have proper exit codes.
	    # If we see this string, then let the command fail, even
	    # though we might have partial data.
	    #
	    $self->{errors} .= $_;
	    $errors++;

	}

    }

    $result->_addUser($user) if $user;
    $result->_addGroup($group) if $group;

    $errors++ unless $self->_reap_cmds();

    return if $errors;
    return $result;

}

sub membership {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::PTServer->new();

    $self->{operation} = "membership";

    return unless $self->_parse_arguments(%args);

    my $errors = 0;

    $errors++ unless $self->_exec_cmds( stderr => 'stdout' );

    my $user = undef;
    my $group = undef;

    while ( defined($_ = $self->{handle}->getline()) ) {

	if ( /(\S+) \(id: (-?\d+)\)/ ) {

	    $result->_addUser($user) if $user;
	    $result->_addGroup($group) if $group;

	    my ($name,$id) = ($1,$2);

	    if ( $id > 0 ) {
		$user = AFS::Object::User->new
		  (
		   name 		=> $name,
		   id			=> $id,
		  );
		$group = undef;
	    } else {
		$group = AFS::Object::Group->new
		  (
		   name 		=> $name,
		   id			=> $id,
		  );
		$user = undef;
	    }

	} elsif ( /^\s+(\S+)\s*/ ) {

	    if ( $user ) {
		$user->_addMembership($1);
	    } else {
		$group->_addMembership($1);
	    }

	} elsif ( /unable to get membership/ ||
		  /User or group doesn't exist/ ||
		  /membership list for id \d+ exceeds display limit/ ) {

	    #
	    # pts still (as of OpenAFS 1.2.8) doesn't have proper exit codes.
	    # If we see this string, then let the command fail, even
	    # though we might have partial data.
	    #
	    $self->{errors} .= $_;
	    $errors++;

	}

    }

    $result->_addUser($user) if $user;
    $result->_addGroup($group) if $group;

    $errors++ unless $self->_reap_cmds();

    return if $errors;
    return $result;

}

1;

lib/AFS/Command/PTS.pod  view on Meta::CPAN

#
# $Id: PTS.pod,v 7.1 2004/01/13 19:01:13 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

=head1 NAME

AFS::Command::PTS - OO API to the AFS pts command

=head1 SYNOPSIS

    use AFS::Command::PTS;

    my $pts = AFS::Command::PTS->new();

    my $pts = AFS::Command::PTS->new
      (
       command			=> $path_to_your_pts_binary,
      );

    my $pts = AFS::Command::PTS->new
      (
       noauth			=> 1,
       force			=> 1,
      );

=head1 DESCRIPTION

This module implements an OO API wrapper around the AFS 'pts' command.
The supported methods depend on the version of the pts binary used,
and are determined automagically.

=head1 METHODS -- Inherited

All of the following methods are inherited from the AFS::Command::Base
class.  See that documentation for details.

=over

=item new

=item errors

=item supportsOperation

=item supportsArgument

=back

=head1 METHODS (with complex return values)

=head2 creategroup

=over

=item Arguments

The pts help string is:

    pts creategroup: create a new group
    Usage: pts creategroup -name <group name>+ [-owner <owner of the group>]
			   [-id <id (negated) for the group>+]
			   [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->creategroup
      (
       # Required arguments
       name			=> $name, # OR [ $name1, $name2, ... ]
       # Optional arguments
       owner			=> $owner,
       id			=> $id, # OR [ $id1, $id2, ... ]
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=item Return Values

This method returns an AFS::Object::PTServer object, which
contains one AFS::Object::Group for each group created.

    my $result = $pts->creategroup
      (
       name			=> $name,
       owner		=> $owner,
      ) || die $pts->errors();
    foreach my $group ( $result->getGroups() ) {
	my ($grname,$grid) = ($group->name(),$group->id());
	print "New group $grname has id $grid\n";
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::PTServer>

    Methods			Returns
    -------			-------
    getGroupNames()		list of group names
    getGroupIds()		list of group ids
    getGroups()			list of AFS::Object::Group objects
    getGroupByName($name)	the AFS::Object::Group object for group $name
    getGroupById($id)		the AFS::Object::Group object for id $id
    getGroup( name => $name )	the AFS::Object::Group object for group $name
    getGroup( id => $id )	the AFS::Object::Group object for id $id

B<AFS::Object::Group>

    Attributes			Values
    ----------			------
    name			Group name
    id				Group id

=back

=head2 createuser

=over

=item Arguments

The pts help string is:

    pts createuser: create a new user
    Usage: pts createuser -name <user name>+ [-id <user id>+]
			  [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->createuser
      (
       # Required arguments
       name			=> $name, # OR [ $name1, $name2, ... ]
       # Optional arguments
       owner			=> $owner,
       id			=> $id, # OR [ $id1, $id2, ... ]
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=item Return Values

This method returns an AFS::Object::PTServer object, which
contains one AFS::Object::User for each user created.

    my $result = $pts->createuser
      (
       name			=> $name,
       owner		=> $owner,
      ) || die $pts->errors();
    foreach my $user ( $result->getUsers() ) {
	my ($username,$userid) = ($user->name(),$user->id());
	print "New user $username has id $userid\n";
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::PTServer>

    Methods			Returns
    -------			-------
    getUserNames()		list of user names
    getUserIds()		list of user ids
    getUsers()			list of AFS::Object::User objects
    getUserByName($name)	the AFS::Object::User object for user $name
    getUserById($id)		the AFS::Object::User object for id $id
    getUser( name => $name )	the AFS::Object::User object for user $name
    getUser( id => $id )	the AFS::Object::User object for id $id

B<AFS::Object::User>

    Attributes			Values
    ----------			------
    name			User name
    id				User id

=back

=head2 examine

=over

=item Arguments

The pts help string is:

    pts examine: examine an entry
    Usage: pts examine -nameorid <user or group name or id>+
		       [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->examine
      (
       # Required arguments
       nameorid			=> $nameorid, # OR [ $nameorid1, $nameorid2, ... ]
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=item Return Values

This method returns an AFS::Object::PTServer object, which
contains one AFS::Object::User or AFS::Object::Group
object for each user/group examined.

    my $result = $pts->examine
      (
       nameorid			=> [ $name1, $name2 ],
       cell				=> 1,
      ) || die $pts->errors();
    foreach my $userobj ( $result->getUser() ) {
	my ($name,$id) = ($userobj->name(),$userobj->id());
	print "User $name has id $id\n";
    }
    foreach my $groupobj ( $result->getGroups() ) {
	my ($name,$id) = ($groupobj->name(),$groupobj->id());
	print "Group $name has id $id\n";
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::PTServer>

    Methods			Returns
    -------			-------
    getGroupNames()		list of group names
    getGroupIds()		list of group ids
    getGroups()			list of AFS::Object::Group objects
    getGroupByName($name)	the AFS::Object::Group object for group $name
    getGroupById($id)		the AFS::Object::Group object for id $id
    getGroup( name => $name )	the AFS::Object::Group object for group $name
    getGroup( id => $id )	the AFS::Object::Group object for id $id
    getUserNames()		list of user names
    getUserIds()		list of user ids
    getUsers()			list of AFS::Object::User objects
    getUserByName($name)	the AFS::Object::User object for user $name
    getUserById($id)		the AFS::Object::User object for id $id
    getUser( name => $name )	the AFS::Object::User object for user $name
    getUser( id => $id )	the AFS::Object::User object for id $id

B<AFS::Object::User>, B<AFS::Object::Group>

    Attributes			Values
    ----------			------
    name			User or group name
    id				User or group id
    owner			Owner of the entry
    creator			Creator of the entry
    membership			Number of groups the user is a member of, or..
				Number of members of the group
    groupquota			Group creation quota

=back

=head2 listentries

=over

=item Arguments

The pts help string is:

    pts listentries: list users/groups in the protection database
    Usage: pts listentries [-users] [-groups] [-cell <cell name>]
			   [-noauth] [-force]
    Where: -users   list user entries
	   -groups  list group entries

The corresponding method invocation looks like:

    my $result = $pts->listentries
      (
       # Optional arguments
       users			=> 1,
       groups			=> 1,
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=item Return Values

This method returns an AFS::Object::PTServer object, which
contains one AFS::Object::User or AFS::Object::Group
object for each user/group listed.

    my $result = $pts->listentries
      (
       users			=> 1,
       groups			=> 1,
       cell				=> $cell,
      ) || die $pts->errors();
    # Starting to see a pattern?  The result is parsed in almost the
    # same way as shown for examine

Each of these objects has the following attributes and methods:

B<AFS::Object::PTServer>

    Methods			Returns
    -------			-------
    getGroupNames()		list of group names
    getGroupIds()		list of group ids
    getGroups()			list of AFS::Object::Group objects
    getGroupByName($name)	the AFS::Object::Group object for group $name
    getGroupById($id)		the AFS::Object::Group object for id $id
    getGroup( name => $name )	the AFS::Object::Group object for group $name
    getGroup( id => $id )	the AFS::Object::Group object for id $id
    getUserNames()		list of user names
    getUserIds()		list of user ids
    getUsers()			list of AFS::Object::User objects
    getUserByName($name)	the AFS::Object::User object for user $name
    getUserById($id)		the AFS::Object::User object for id $id
    getUser( name => $name )	the AFS::Object::User object for user $name
    getUser( id => $id )	the AFS::Object::User object for id $id

B<AFS::Object::User>, B<AFS::Object::Group>

    Attributes			Values
    ----------			------
    name			User or group name
    id				User or group id
    owner			Numeric id of the owner of the entry
    creator			Numeric id of the creator of the entry

=back

=head2 listmax

=over

=item Arguments

The pts help string is:

    pts listmax: list max id
    Usage: pts listmax [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->listmax
      (
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=item Return Values

This method returns an AFS::Object::PTServer object, which
contains two attributes:

    my $result = $pts->listmax
      (
       cell				=> $cell,
      ) || die $pts->errors();
    print "Maximum group ID is " . $result->maxgroupid() . "\n";
    print "Maximum user ID is " . $result->maxuserid() . "\n";

This object has the following attributes, which are always present:

    Attributes			Values
    ----------			------
    maxgroupid			Numeric value of the highest group ID
    maxuserid			Numeric value of the highest user ID

=back

=head2 listowned

=over

=item Arguments

The pts help string is:

    pts listowned: list groups owned by an entry or zero id gets orphaned groups
    Usage: pts listowned -nameorid <user or group name or id>+
			 [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->listowned
      (
       # Required arguments
       nameorid			=> $nameorid, # OR [ $nameorid1, $nameorid2, ... ]
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=item Return Values

This method returns an AFS::Object::PTServer object, which
contains one AFS::Object::User or AFS::Object::Group
object for each user/group specified.

    my $result = $pts->listowned
      (
       nameorid			=> $user,
       cell				=> $cell,
      ) || die $pts->errors();
    my $userobj = $result->getUserbyName($user);
    print "User $user owns the following groups:\n";
    foreach my $owned ( $userobj->getOwned() ) {
	print "\t$owned\n";
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::PTServer>

    Methods			Returns
    -------			-------
    getGroupNames()		list of group names
    getGroupIds()		list of group ids
    getGroups()			list of AFS::Object::Group objects
    getGroupByName($name)	the AFS::Object::Group object for group $name
    getGroupById($id)		the AFS::Object::Group object for id $id
    getGroup( name => $name )	the AFS::Object::Group object for group $name
    getGroup( id => $id )	the AFS::Object::Group object for id $id
    getUserNames()		list of user names
    getUserIds()		list of user ids
    getUsers()			list of AFS::Object::User objects
    getUserByName($name)	the AFS::Object::User object for user $name
    getUserById($id)		the AFS::Object::User object for id $id
    getUser( name => $name )	the AFS::Object::User object for user $name
    getUser( id => $id )	the AFS::Object::User object for id $id

B<AFS::Object::User>, B<AFS::Object::Group>

    Attributes			Values
    ----------			------
    name			User or group name
    id				User or group id

    Methods			Returns
    -------			-------
    getOwned()			list of group names owned by the user or group

=back

=head2 membership

=over

=item Arguments

The pts help string is:

    pts membership: list membership of a user or group
    Usage: pts membership -nameorid <user or group name or id>+
			  [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->membership
      (
       # Required arguments
       nameorid			=> $nameorid, # OR [ $nameorid1, $nameorid2, ... ]
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=item Return Values

This method returns an AFS::Object::PTServer object, which
contains one AFS::Object::User or AFS::Object::Group
object for each user/group specified.

    my $result = $pts->membership
      (
       nameorid			=> $user,
       cell			=> $cell,
      ) || die $pts->errors();
    my $userobj = $result->getUserbyName($user);
    print "User $user is a member of these groups:\n";
    foreach my $group ( $userobj->getMembership() ) {
	print "\t$group\n";
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::PTServer>

    Methods			Returns
    -------			-------
    getGroupNames()		list of group names
    getGroupIds()		list of group ids
    getGroups()			list of AFS::Object::Group objects
    getGroupByName($name)	the AFS::Object::Group object for group $name
    getGroupById($id)		the AFS::Object::Group object for id $id
    getGroup( name => $name )	the AFS::Object::Group object for group $name
    getGroup( id => $id )	the AFS::Object::Group object for id $id
    getUserNames()		list of user names
    getUserIds()		list of user ids
    getUsers()			list of AFS::Object::User objects
    getUserByName($name)	the AFS::Object::User object for user $name
    getUserById($id)		the AFS::Object::User object for id $id
    getUser( name => $name )	the AFS::Object::User object for user $name
    getUser( id => $id )	the AFS::Object::User object for id $id

B<AFS::Object::User>, B<AFS::Object::Group>

    Attributes			Values
    ----------			------
    name			User or group name
    id				User or group id

    Methods			Returns
    -------			-------
    getMembership()		For a user, the list of group to which the user belongs,
				for a group, the members of the group

=back

=head1 METHODS (with simple return values)

All of the following commands return a simple Boolean (true/false)
value, if they succeed or fail.

=head2 adduser

The pts help string is:

    pts adduser: add a user to a group
    Usage: pts adduser -user <user name>+ -group <group name>+
		       [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->adduser
      (
       # Required arguments
       user			=> $user, # OR [ $user1, $user2, ... ]
       group			=> $group, # OR [ $group1, $group2, ... ]
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=head2 chown

The pts help string is:

    pts chown: change ownership of a group
    Usage: pts chown -name <group name> -owner <new owner>
		     [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->chown
      (
       # Required arguments
       name			=> $name,
       owner			=> $owner,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=head2 delete

The pts help string is:

    pts delete: delete a user or group from database
    Usage: pts delete -nameorid <user or group name or id>+
		      [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->delete
      (
       # Required arguments
       nameorid			=> $nameorid, # OR [ $nameorid1, $nameorid2, ... ]
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=head2 removeuser

The pts help string is:

    pts removeuser: remove a user from a group
    Usage: pts removeuser -user <user name>+ -group <group name>+
			  [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->removeuser
      (
       # Required arguments
       user			=> $user, # OR [ $user1, $user2, ... ]
       group			=> $group, # OR [ $group1, $group2, ... ]
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=head2 rename

The pts help string is:

    pts rename: rename user or group
    Usage: pts rename -oldname <old name> -newname <new name>
		      [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->rename
      (
       # Required arguments
       oldname			=> $oldname,
       newname			=> $newname,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=head2 setfields

The pts help string is:

pts setfields: set fields for an entry
Usage: pts setfields -nameorid <user or group name or id>+ [-access <set privacy flags>]
                     [-groupquota <set limit on group creation>]
                     [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->setfields
      (
       # Required arguments
       nameorid			=> $nameorid, # OR [ $nameorid1, $nameorid2, ... ]
       # Optional arguments
       access			=> $access,
       groupquota		=> $groupquota,
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=head2 setmax

The pts help string is:

    pts setmax: set max id
    Usage: pts setmax [-group <group max>] [-user <user max>]
		      [-cell <cell name>] [-noauth] [-force]

The corresponding method invocation looks like:

    my $result = $pts->setmax
      (
       # Optional arguments
       user			=> $user,
       group			=> $group,
       cell			=> $cell,
       noauth			=> 1,
       force			=> 1,
      );

=head1 SEE ALSO

AFS::Command(1), AFS::Object(1)

=cut

lib/AFS/Command/VOS.pm  view on Meta::CPAN

#
# $Id$
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

package AFS::Command::VOS;

use strict;
use English;

use AFS::Command::Base;
use AFS::Object;
use AFS::Object::VLDB;
use AFS::Object::VLDBEntry;
use AFS::Object::VLDBSite;
use AFS::Object::Volume;
use AFS::Object::VolumeHeader;
use AFS::Object::VolServer;
use AFS::Object::FileServer;
use AFS::Object::Partition;
use AFS::Object::Transaction;

our @ISA = qw(AFS::Command::Base);
our $VERSION = '1.99';

sub examine {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::Volume->new();
    my $entry = AFS::Object::VLDBEntry->new( locked => 0 );

    $self->{operation} = "examine";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	#
	# These two lines are part of the verbose output
	#
	next if /Fetching VLDB entry/;
	next if /Getting volume listing/;

	#
	# This code parses the volume header information.  If we match
	# this line, then we go after the information we expect to be
	# right after it.  We also test for this first, because we
	# might very well have several of these chunks of data for RO
	# volumes.
	#
	if ( /^\*{4}/ ) {

	    my $header = AFS::Object::VolumeHeader->new();

	    if ( /Volume (\d+) is busy/ ) {
		$header->_setAttribute
		  (
		   id			=> $1,
		   status		=> 'busy',
		   attached		=> 1,
		  );
	    } elsif ( /Could not attach volume (\d+)/ ) {
		$header->_setAttribute
		  (
		   id			=> $1,
		   status		=> 'offline',
		   attached		=> 0,
		  );
	    }

	    $result->_addVolumeHeader($header);

	    next;

	} elsif ( /^(\S+)\s+(\d+)\s+(RW|RO|BK)\s+(\d+)\s+K/ ) {

	    my $header = AFS::Object::VolumeHeader->new();

	    if ( /^(\S+)\s+(\d+)\s+(RW|RO|BK)\s+(\d+)\s+K\s+([\w-]+)/ ) {

		$header->_setAttribute
		  (
		   name			=> $1,
		   id 			=> $2,
		   type 		=> $3,
		   size 		=> $4,
		  );
		$header->_setAttribute( rwrite	=> $2 ) if $3 eq 'RW';
		$header->_setAttribute( ronly	=> $2 ) if $3 eq 'RO';
		$header->_setAttribute( backup	=> $2 ) if $3 eq 'BK';

		my $status = $5;
		$status = 'offline' if $status eq 'Off-line';
		$status = 'online' if $status eq 'On-line';
		$header->_setAttribute
		  (
		   status 		=> $status,
		   attached		=> 1,
		  );

	    } elsif ( /^(\S+)\s+(\d+)\s+(RW|RO|BK)\s+(\d+)\s+K\s+used\s+(\d+)\s+files\s+([\w-]+)/ ) {

		$header->_setAttribute
		  (
		   name			=> $1,
		   id			=> $2,
		   type 		=> $3,
		   size 		=> $4,
		   files 		=> $5,
		  );
		$header->_setAttribute( rwrite	=> $2 ) if $3 eq 'RW';
		$header->_setAttribute( ronly	=> $2 ) if $3 eq 'RO';
		$header->_setAttribute( backup	=> $2 ) if $3 eq 'BK';

		my $status = $6;
		$status = 'offline' if $status eq 'Off-line';
		$status = 'online' if $status eq 'On-line';
		$header->_setAttribute
		  (
		   status 		=> $status,
		   attached		=> 1,
		  );

	    } else {

		$self->_Carp("Unable to parse volume header: '$_'");

	    }

	    #
	    # We are interested in the next 6 lines as they are also
	    # from the same volume headers as the one we just matched.
	    # Suck data until we get to a blank line.
	    #
	    while ( defined($_ = $self->{handle}->getline()) ) {

		chomp;

		last if /^\s*$/; # Stop when we hit the blank line

		if ( m:^\s+(\S+)\s+(/vicep\w+)\s*$: ) {
		    $header->_setAttribute
		      (
		       server		=> $1,
		       partition	=> $2,
		      );
		    next;
		}

		#
		# Next we get ALL the volume IDs we can off this next
		# line.
		#
		# Q: Do we want to check that the id already found
		# matches one of these??  Not yet...
		#
		if ( /^\s+RWrite\s+(\d+)\s+ROnly\s+(\d+)\s+Backup\s+(\d+)/ ) {

		    $header->_setAttribute
		      (
		       rwrite		=> $1,
		       ronly		=> $2,
		       backup		=> $3,
		      );

		    if ( /RClone\s+(\d+)/ ) {
			$header->_setAttribute( rclone	=> $1 );
		    }
		    next;

		}

		if ( /^\s+MaxQuota\s+(\d+)/ ) {
		    $header->_setAttribute( maxquota	=> $1 );
		    next;
		}

		if ( /^\s+Creation\s+(.*)\s*$/ ) {
		    $header->_setAttribute( creation	=> $1 );
		    next;
		}

		if ( /^\s+Copy\s+(.*)\s*$/ ) {
		    $header->_setAttribute( copyTime	=> $1 );
		    next;
		}

		if ( /^\s+Backup\s+(.*)\s*$/ ) {
		    $header->_setAttribute( backupTime	=> $1 );
		    next;
		}

		if ( /^\s+Last Access\s+(.*)\s*$/ ) {
		    $header->_setAttribute( access	=> $1 );
		    next;
		}

		if ( /^\s+Last Update\s+(.*)\s*$/ ) {
		    $header->_setAttribute( update	=> $1 );
		    next;
		}

		if ( /^\s+(\d+) accesses/ ) {
		    $header->_setAttribute( accesses	=> $1 );
		    next;
		}

		#
		# If we get this far, then we have an unrecognized
		# line of vos examine output.  Complain.
		#
		$self->_Carp("Unrecognized output format:\n" . $_);

	    }

	    #
	    # Are we looking for extended data??
	    #
	    if ( $args{extended} ) {

		my $raw = AFS::Object->new();
		my $author = AFS::Object->new();

		my $boundary = 0;

		while ( defined($_ = $self->{handle}->getline()) ) {

		    chomp;

		    $boundary++ if /^\s+\|-+\|\s*$/;

		    last if /^\s*$/ && $boundary == 4;

		    next unless /\s+(\d+)\s+\|\s+(\d+)\s+\|\s+(\d+)\s+\|\s+(\d+)\s+\|/;

		    my @column = ( $1, $2, $3, $4 );

		    my $class			= "";
		    my $int			= "";

		    $class = 'reads' 		if /^Reads/;
		    $class = 'writes' 		if /^Writes/;

		    if ( $class ) {

			my $same = AFS::Object->new
			  (
			   total		=> $column[0],
			   auth			=> $column[1],
			  );

			my $diff = AFS::Object->new
			  (
			   total		=> $column[2],
			   auth			=> $column[3],
			  );

			my $stats = AFS::Object->new
			  (
			   same			=> $same,
			   diff			=> $diff,
			  );

			$raw->_setAttribute( $class	=> $stats );

		    }

		    $int = '0sec' 		if /^0-60 sec/;
		    $int = '1min' 		if /^1-10 min/;
		    $int = '10min' 		if /^10min-1hr/;
		    $int = '1hr' 		if /^1hr-1day/;
		    $int = '1day' 		if /^1day-1wk/;
		    $int = '1wk' 		if /^> 1wk/;

		    if ( $int ) {

			my $file = AFS::Object->new
			  (
			   same			=> $column[0],
			   diff			=> $column[1],
			  );

			my $dir = AFS::Object->new
			  (
			   same			=> $column[2],
			   diff			=> $column[3],
			  );

			my $stats = AFS::Object->new
			  (
			   file			=> $file,
			   dir			=> $dir,
			  );

			$author->_setAttribute( $int	=>  $stats );

		    }

		}

		$header->_setAttribute
		  (
		   raw				=> $raw,
		   author			=> $author,
		  );

	    }

	    $result->_addVolumeHeader($header);

	    next;

	}

	#
	# The rest of the information we get will be from the
	# VLDB. This will start with the volume ids, which we DO want
	# to check against those found above, since they are from a
	# different source, and a conflict is cause for concern.
	#
	if ( /^\s+RWrite:\s+(\d+)/ ) {

	    if ( /RWrite:\s+(\d+)/ ) { $entry->_setAttribute( rwrite	=> $1 ); }
	    if ( /ROnly:\s+(\d+)/ )  { $entry->_setAttribute( ronly	=> $1 ); }
	    if ( /Backup:\s+(\d+)/ ) { $entry->_setAttribute( backup	=> $1 ); }
	    if ( /RClone:\s+(\d+)/ ) { $entry->_setAttribute( rclone	=> $1 ); }

	    next;

	}			# if ( /^\s+RWrite:....

	#
	# Next we are looking for the number of sites, and then we'll
	# suck that data in as well.
	#
	# NOTE: Because there is more interesting data after the
	# locations, we fall through to the next test once we are done
	# parsing them.
	#
	if ( /^\s+number of sites ->\s+(\d+)/ ) {

	    while ( defined($_ = $self->{handle}->getline()) ) {

		chomp;

		last unless m:^\s+server\s+(\S+)\s+partition\s+(/vicep\w+)\s+([A-Z]{2})\s+Site\s*(--\s+)?(.*)?:;

		my $site = AFS::Object::VLDBSite->new
		  (
		   server		=> $1,
		   partition		=> $2,
		   type			=> $3,
		   status		=> $5,
		  );

		$entry->_addVLDBSite($site);

	    }

	}

	#
	# Last possibility (that we know of) -- volume might be
	# locked.
	#
	if ( /LOCKED/ ) {
	    $entry->_setAttribute( locked => 1 );
	    next;
	}

	#
	# Actually, this is the last possibility...  The volume name
	# leading the VLDB entry stanza.
	#
	if ( /^(\S+)/ ) {
	    $entry->_setAttribute( name => $1 );
	}

    }

    $result->_addVLDBEntry($entry);

    $errors++ unless $self->_reap_cmds();

    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub listaddrs {

    my $self = shift;
    my (%args) = @_;

    my @result = ();

    $self->{operation} = "listaddrs";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    if ( $args{printuuid} ) {

	while ( defined($_ = $self->{handle}->getline()) ) {

	    chomp;

	    if ( /^UUID:\s+(\S+)/ ) {

		my $fileserver = AFS::Object::FileServer->new( uuid => $1 );

		my @addresses = ();
		my $hostname = "";

		while ( defined($_ = $self->{handle}->getline()) ) {
		    s/^\s*//g;
		    s/\s*$//g;
		    last if /^\s*$/;
		    chomp;
		    if ( /^\d+\.\d+\.\d+\.\d+$/ ) {
			push(@addresses,$_);
		    } else {
			$hostname = $_;
		    }
		}

		$fileserver->_setAttribute( addresses => \@addresses ) if @addresses;
		$fileserver->_setAttribute( hostname => $hostname ) if $hostname;

		push(@result,$fileserver);

	    }

	}

    } elsif ( $args{uuid} ) {

	my @addresses = ();
	my $hostname = "";

	while ( defined($_ = $self->{handle}->getline()) ) {
	    chomp;
	    s/^\s*//g;
	    s/\s*$//g;
	    if ( /^\d+\.\d+\.\d+\.\d+$/ ) {
		push(@addresses,$_);
	    } else {
		$hostname = $_;
	    }
	}

	if ( $hostname || @addresses ) {
	    my $fileserver = AFS::Object::FileServer->new();
	    $fileserver->_setAttribute( addresses => \@addresses ) if @addresses;
	    $fileserver->_setAttribute( hostname => $hostname ) if $hostname;
	    push(@result,$fileserver);
	}

    } else {

	while ( defined($_ = $self->{handle}->getline()) ) {
	    chomp;
	    s/^\s*//g;
	    s/\s*$//g;
	    if ( /^\d+\.\d+\.\d+\.\d+$/ ) {
		push(@result,AFS::Object::FileServer->new( addresses => [$_] ));
	    } else {
		push(@result,AFS::Object::FileServer->new( hostname => $_ ));
	    }
	}	

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return @result;

}

sub listpart {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::FileServer->new();

    $self->{operation} = "listpart";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	next unless m:/vice:;

	s/^\s+//g;
	s/\s+$//g;

	foreach my $partname ( split ) {
	    my $partition = AFS::Object::Partition->new( partition => $partname );
	    $result->_addPartition($partition);
	}

    }

    $errors++ unless $self->_reap_cmds();
    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub listvldb {

    my $self = shift;
    my (%args) = @_;

    $self->{operation} = "listvldb";

    my $locked = 0;

    my $result = AFS::Object::VLDB->new();

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	next if /^\s*$/;	# If it starts with a blank line, then
				# its not a volume name.
	#
	# Skip the introductory lines of the form:
	# "VLDB entries for all servers"
	# "VLDB entries for server ny91af01"
	# "VLDB entries for server ny91af01 partition /vicepa"
	#
	next if /^VLDB entries for /;

	s/\s+$//g;		# Might be trailing whitespace...

	#
	# We either get the total number of volumes, or we assume the
	# line is a volume name.
	#
	if ( /Total entries:\s+(\d+)/ ) {
	    $result->_setAttribute( total => $1 );
	    next;
	}

	my $name = $_;

	my $entry = AFS::Object::VLDBEntry->new( name => $name );

	while ( defined($_ = $self->{handle}->getline()) ) {

	    chomp;

	    last if /^\s*$/;	# Volume info ends with a blank line

	    #
	    # Code to parse this output lives in examine.pl.  This
	    # will need to be made generic and used here to parse and
	    # return the full vldb entry.
	    #

	    if ( /RWrite:\s+(\d+)/ ) { $entry->_setAttribute( rwrite 	=> $1 ); }
	    if ( /ROnly:\s+(\d+)/ )  { $entry->_setAttribute( ronly 	=> $1 ); }
	    if ( /Backup:\s+(\d+)/ ) { $entry->_setAttribute( backup	=> $1 ); }
	    if ( /RClone:\s+(\d+)/ ) { $entry->_setAttribute( rclone	=> $1 ); }

	    if ( /^\s+number of sites ->\s+(\d+)/ ) {

		my $sites = $1;

		while ( defined($_ = $self->{handle}->getline()) ) {

		    chomp;

		    next unless m:^\s+server\s+(\S+)\s+partition\s+(/vicep\w+)\s+([A-Z]{2})\s+Site\s*(--\s+)?(.*)?:;

		    $sites--;

		    my $site = AFS::Object::VLDBSite->new
		      (
		       server		=> $1,
		       partition	=> $2,
		       type		=> $3,
		       status		=> $5,
		      );

		    $entry->_addVLDBSite( $site );

		    last if $sites == 0;

		}

	    }

	    #
	    # Last possibility (that we know of) -- volume might be
	    # locked.
	    #
	    if ( /LOCKED/ ) {
		$entry->_setAttribute( locked => 1 );
		$locked++;
	    }

	}

	$result->_addVLDBEntry( $entry );

    }

    $result->_setAttribute( locked => $locked );

    $errors++ unless $self->_reap_cmds();

    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}


sub listvol {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::VolServer->new();

    $self->{operation} = "listvol";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    if ( delete $args{extended} ) {
	$self->_Carp("vos listvol: -extended is not supported by this version of the API");
    }

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	next if /^\s*$/;	# Blank lines are not interesting

	next unless /^Total number of volumes on server \S+ partition (\/vice[\w]+): (\d+)/;

	my $partition = AFS::Object::Partition->new
	  (
	   partition			=> $1,
	   total			=> $2,
	  );

	while ( defined($_ = $self->{handle}->getline()) ) {

	    chomp;

	    last if /^\s*$/ && $args{fast};

	    next if /^\s*$/;

	    s/\s+$//;

	    if ( /^Total volumes onLine (\d+) ; Total volumes offLine (\d+) ; Total busy (\d+)/ ) {
		$partition->_setAttribute
		  (
		   online		=> $1,
		   offline		=> $2,
		   busy			=> $3,
		  );
		last;		# Done with this partition
	    }

	    if ( /Volume (\d+) is busy/ ) {
		my $volume = AFS::Object::VolumeHeader->new
		  (
		   id			=> $1,
		   status		=> 'busy',
		   attached		=> 1,
		  );
		$partition->_addVolumeHeader($volume);
		next;
	    } elsif ( /Could not attach volume (\d+)/ ) {
		my $volume = AFS::Object::VolumeHeader->new
		  (
		   id			=> $1,
		   status		=> 'offline',
		   attached		=> 0,
		  );
		$partition->_addVolumeHeader($volume);
		next;
	    }

	    #
	    # We have to handle multiple formats here.  For
	    # now, just parse the "fast" and normal output.
	    # Extended is not yet supported.
	    #

	    my (@array) = split;
	    my ($name,$id,$type,$size,$status) = ();

	    my $volume = AFS::Object::VolumeHeader->new();

	    if ( @array == 6 ) {
		($name,$id,$type,$size,$status) = @array[0..3,5];
		$status = 'offline' if $status eq 'Off-line';
		$status = 'online' if $status eq 'On-line';
		$volume->_setAttribute
		  (
		   id			=> $id,
		   name			=> $name,
		   type			=> $type,
		   size			=> $size,
		   status		=> $status,
		   attached		=> 1,
		  );
	    } elsif ( @array == 1 ) {
		$volume->_setAttribute
		  (
		   id			=> $_,
		   status		=> 'online',
		   attached		=> 1,
		  );
	    } else {
		$self->_Carp("Unable to parse header summary line:\n" . $_);
		$errors++;
		next;
	    }

	    #
	    # If the output is long, then we have some more
	    # interesting information to parse.  See vos/examine.pl
	    # for notes.  This code was stolen from there...
	    #

	    if ( $args{long} || $args{extended} ) {

		while ( defined($_ = $self->{handle}->getline()) ) {

		    last if /^\s*$/;

		    if ( /^\s+RWrite\s+(\d+)\s+ROnly\s+(\d+)\s+Backup\s+(\d+)/ ) {
			$volume->_setAttribute
			  (
			   rwrite		=> $1,
			   ronly		=> $2,
			   backup		=> $3,
			  );
			if ( /RClone\s+(\d+)/ ) {
			    $volume->_setAttribute( rclone => $1 );
			}
			next;
		    }

		    if ( /^\s+MaxQuota\s+(\d+)/ ) {
			$volume->_setAttribute( maxquota => $1 );
			next;
		    }

		    if ( /^\s+Creation\s+(.*)\s*$/ ) {
			$volume->_setAttribute( creation => $1 );
			next;
		    }

		if ( /^\s+Copy\s+(.*)\s*$/ ) {
		    $volume->_setAttribute( copyTime	=> $1 );
		    next;
		}

		if ( /^\s+Backup\s+(.*)\s*$/ ) {
		    $volume->_setAttribute( backupTime	=> $1 );
		    next;
		}

		if ( /^\s+Last Access\s+(.*)\s*$/ ) {
		    $volume->_setAttribute( access	=> $1 );
		    next;
		}

		    if ( /^\s+Last Update\s+(.*)\s*$/ ) {
			$volume->_setAttribute( update => $1 );
			next;
		    }

		    if ( /^\s+(\d+) accesses/ ) {
			$volume->_setAttribute( accesses => $1 );
			next;
		    }
		}		# while(defined($_ = $self->{handle}->getline())) {

	    }

	    $partition->_addVolumeHeader($volume);

	}

	$result->_addPartition($partition);

    }

    $errors++ unless $self->_reap_cmds();

    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub partinfo {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::FileServer->new();

    $self->{operation} = "partinfo";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    while ( defined($_ = $self->{handle}->getline()) ) {

	next unless m|partition (/vice\w+): (-?\d+)\D+(\d+)$|;

	my $partition = AFS::Object::Partition->new
	  (
	   partition 		=> $1,
	   available		=> $2,
	   total		=> $3,
	  );

	$result->_addPartition($partition);

    }

    $errors++ unless $self->_reap_cmds();

    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub status {

    my $self = shift;
    my (%args) = @_;

    my $result = AFS::Object::VolServer->new();

    $self->{operation} = "status";

    return unless $self->_parse_arguments(%args);

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds();

    my $transaction = undef;

    while ( defined($_ = $self->{handle}->getline()) ) {

	chomp;

	if ( /No active transactions/ ) {
	    $result->_setAttribute( transactions => 0 );
	    last;
	}

	if ( /Total transactions: (\d+)/ ) {
	    $result->_setAttribute( transactions => $1 );
	    next;
	}

	if ( /^-+\s*$/ ) {

	    if ( $transaction ) {
		$result->_addTransaction($transaction);
		$transaction = undef;
	    } else {
		$transaction = AFS::Object::Transaction->new();
	    }

	}

	next unless $transaction;

	if ( /transaction:\s+(\d+)/ ) {
	    $transaction->_setAttribute( transaction => $1 );
	}

	if ( /created:\s+(.*)$/ ) {
	    $transaction->_setAttribute( created => $1 );
	}

	if ( /attachFlags:\s+(.*)$/ ) {
	    $transaction->_setAttribute( attachFlags => $1 );
	}

	if ( /volume:\s+(\d+)/ ) {
	    $transaction->_setAttribute( volume => $1 );
	}

	if ( /partition:\s+(\S+)/ ) {
	    $transaction->_setAttribute( partition => $1 );
	}

	if ( /procedure:\s+(\S+)/ ) {
	    $transaction->_setAttribute( procedure => $1 );
	}

	if ( /packetRead:\s+(\d+)/ ) {
	    $transaction->_setAttribute( packetRead => $1 );
	}

	if ( /lastReceiveTime:\s+(\d+)/ ) {
	    $transaction->_setAttribute( lastReceiveTime => $1 );
	}

	if ( /packetSend:\s+(\d+)/ ) {
	    $transaction->_setAttribute( packetSend => $1 );
	}

	if ( /lastSendTime:\s+(\d+)/ ) {
	    $transaction->_setAttribute( lastSendTime => $1 );
	}

    }

    $errors++ unless $self->_reap_cmds();

    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return $result;

}

sub dump {

    my $self = shift;
    my (%args) = @_;

    $self->{operation} = 'dump';

    my $file = delete $args{file} || do {
	$self->_Carp("Missing required argument: 'file'");
	return;
    };

    my $gzip_default = 6;
    my $bzip2_default = 6;

    my $nocompress = delete $args{nocompress} || undef;
    my $gzip = delete $args{gzip} || undef;
    my $bzip2 = delete $args{bzip2} || undef;
    my $filterout = delete $args{filterout} || undef;

    if ( $gzip && $bzip2 && $nocompress ) {
	$self->_Carp("Invalid argument combination: only one of 'gzip' or 'bzip2' or 'nocompress' may be specified");
	return;
    }

    if ( $file eq 'stdin' ) {
	$self->_Carp("Invalid argument 'stdin': you can't write output to stdin");
	return;
    }

    if ( $file ne 'stdout' ) {

	if ( $file =~ /\.gz$/ && not defined $gzip and not defined $nocompress ) {
	    $gzip = $gzip_default;
	} elsif ( $file =~ /\.bz2$/ && not defined $bzip2 and not defined $nocompress ) {
	    $bzip2 = $bzip2_default;
	}

	if ( $gzip && $file !~ /\.gz$/ ) {
	    $file .= ".gz";
	} elsif ( $bzip2 && $file !~ /\.bz2/ ) {
	    $file .= ".bz2";
	}

	unless ( $gzip || $bzip2 || $filterout ) {
	    $args{file} = $file;
	}

    }

    return unless $self->_parse_arguments(%args);

    if ( $filterout ) {

	unless ( ref $filterout eq 'ARRAY' ) {
	    $self->_Carp("Invalid argument 'filterout': must be an ARRAY reference");
	    return;
	}

	if ( ref($filterout->[0]) eq 'ARRAY' ) {
	    foreach my $filter ( @$filterout ) {
		unless ( ref $filter eq 'ARRAY' ) {
		    $self->_Carp("Invalid argument 'filterout': must be an ARRAY of ARRAY references, \n" .
				    "OR an ARRAY of strings.  See the documentation for details");
		    return;
		}
		push( @{$self->{cmds}}, $filter );
	    }
	} else {
	    push( @{$self->{cmds}}, $filterout );
	}

    };

    if ( $gzip ) {
	push( @{$self->{cmds}}, [ 'gzip', "-$gzip", '-c' ] );
    } elsif ( $bzip2 ) {
	push( @{$self->{cmds}}, [ 'bzip2', "-$bzip2", '-c' ] );
    }

    return unless $self->_save_stderr();

    my $errors = 0;

    $errors++ unless $self->_exec_cmds
      (
       stdout 			=> ( $args{file} ? "/dev/null" : $file ),
      );

    $errors++ unless $self->_reap_cmds();

    $errors++ unless $self->_restore_stderr();

    return if $errors;
    return 1;

}

sub restore {

    my $self = shift;
    my (%args) = @_;

    $self->{operation} = "restore";

    my $file = delete $args{file} || do {
	$self->_Carp("Missing required argument: 'file'");
	return;
    };

    my $nocompress = delete $args{nocompress} || undef;
    my $gunzip = delete $args{gunzip} || undef;
    my $bunzip2 = delete $args{bunzip2} || undef;
    my $filterin = delete $args{filterin} || undef;;

    if ( $gunzip && $bunzip2 && $nocompress ) {
	$self->_Carp("Invalid argument combination: only one of 'gunzip' or 'bunzip2' or 'nocompress' may be specified");
	return;
    }

    if ( $file eq 'stdout' ) {
	$self->_Carp("Invalid argument 'stdout': you can't read input from stdout");
	return;
    }

    if ( $file ne 'stdin' ) {

	if ( $file =~ /\.gz$/ && not defined $gunzip and not defined $nocompress ) {
	    $gunzip = 1;
	} elsif ( $file =~ /\.bz2$/ && not defined $bunzip2 and not defined $nocompress ) {
	    $bunzip2 = 1;
	}

	unless ( $gunzip || $bunzip2 || $filterin ) {
	    $args{file} = $file;
	}

    }

    return unless $self->_parse_arguments(%args);

    if ( $filterin ) {

	unless ( ref $filterin eq 'ARRAY' ) {
	    $self->_Carp("Invalid argument 'filterin': must be an ARRAY reference");
	    return;
	}

	if ( ref($filterin->[0]) eq 'ARRAY' ) {
	    foreach my $filter ( @$filterin ) {
		unless ( ref $filter eq 'ARRAY' ) {
		    $self->_Carp("Invalid argument 'filterin': must be an ARRAY of ARRAY references, \n" .
				"OR an ARRAY of strings.  See the documentation for details");
		    return;
		}
		unshift( @{$self->{cmds}}, $filter );
	    }
	} else {
	    unshift( @{$self->{cmds}}, $filterin );
	}

    };

    if ( $gunzip ) {
	unshift( @{$self->{cmds}}, [ 'gunzip', '-c' ] );
    } elsif ( $bunzip2 ) {
	unshift( @{$self->{cmds}}, [ 'bunzip2', '-c' ] );
    }

    my $errors = 0;

    $errors++ unless $self->_exec_cmds
      (
       stderr 			=> 'stdout',
       stdin			=> ( $args{file} ? "/dev/null" : $file ),
      );

    $errors++ unless $self->_parse_output();
    $errors++ unless $self->_reap_cmds();

    return if $errors;
    return 1;

}

1;

lib/AFS/Command/VOS.pod  view on Meta::CPAN

#
# $Id: VOS.pod,v 7.2 2004/05/11 15:55:17 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

=head1 NAME

AFS::Command::VOS - OO API to the AFS vos command

=head1 SYNOPSIS

    use AFS::Command::VOS;

    my $vos = AFS::Command::VOS->new();

    my $vos = AFS::Command::VOS->new
      (
       command			=> $path_to_your_vos_binary,
      );

    my $vos = AFS::Command::VOS->new
      (
       localauth			=> 1,
       encrypt			=> 1,
      );

=head1 DESCRIPTION

This module implements an OO API wrapper around the AFS 'vos' command.
The supported methods depend on the version of the vos binary used,
and are determined automagically.

=head1 METHODS -- Inherited

All of the following methods are inherited from the AFS::Command::Base
class.  See that documentation for details.

=over

=item new

=item errors

=item supportsOperation

=item supportsArgument

=back

=head1 METHODS (dump, restore)

Both the 'dump' and 'restore' methods are special, since this API
supports compression to and from the filesystem when dumping or
restoring the volume.  Normally, "vos dump -file" will just wrote the
uncompressed volume dump to the file, but this API can compress it.
This is a huge cost savings in disk space, assuming you can afford the
CPU time to perform the compression (this is the 21st century -- you
probably can).

Both of these commands return simply boolean true/false values, but
they have some special case handling for the -file argument, and
support several special arguments that are extensions implemented in
this API only.

=head2 dump

The vos help string is:

    vos dump: dump a volume
    Usage: vos dump -id <volume name or ID> [-time <dump from time>] [-file <dump file>]
		    [-server <server>] [-partition <partition>] [-cell <cell name>]
		    [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->dump
      (
       # Required arguments
       id			=> $id,
       file			=> $file, # SPECIAL CASE!!! (see below)
       # Optional arguments
       time			=> $time,
       server			=> $server,
       partition		=> $partition,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
       # Enhanced arguments
       gzip			=> $gzip,
       bzip2			=> $bzip2,
       filterout		=> [ @command ], # OR [ [ @cmd1 ], [ @cmd2 ], ... ]
      );

The first thing to notice is that 'file' is optional to the vos dump
command itself, but required in this API.  The second thing to notice
is the addition of three new arguments: gzip, bzip2 and filterout.

=over

=item file

This argument specifies the file to which the vos dump output should
be written.  If this file ends in '.gz' or '.bz2', then gzip or bzip2
will be used to compress the output before it is written to the
filesystem.  This is accomplished using a pipe, so there is no
intermediate file written to disk first.

By default, 'vos dump' will write the volume dump to stdout, which is
not what you want in most applications.  If you really want the volume
to be written to stdout, then you have to explicitly say so:

   my $result = $vos->dump
     (
      ...
      file			=> 'stdout',
      ...
     );

=item gzip, bzip2

Both of these arguments will turn on compression explicitly, and if
the file specified doesn't end in the appropriate extension already
('.gz' for gzip, and '.bz2' for bzip2), then the extension is appended
to the filename.

The value of these arguments specifies the degree of compression used,
an should be a single numeric digit, from 0 to 9.  See the gzip and
bzip2 man pages for more information.

These arguments are also mutually exclusive.

=item filterout

This is an advanced feature, and one that allows the volume dump to be
filtered through any arbitrary number of commands before it is
compressed (optionally) and written to the filesystem.  The value of
this argument is either an ARRAY reference to a list of command line
arguments, suitable for passing to exec(), or an ARRAY or such ARRAYS,
when more than one filter command is being used.

For example, the author has a requirement to pass all volume dumps
through a simple filter called 'newversion', which reads a volume
dump, changes the directory version numbers to the current utime
value, and writes the volume dump to stdout.  Trust me, you really
don't want to know why.

    my $result = $vos->dump
      (
       ...
       filterout		=> [ 'newversion' ],
       ...
      );

If there were command line arguments for this command, then they must
be given as follows:

    my $result = $vos->dump
      (
       ...
       filterout		=> [ 'newversion', '-arg1', '-value1' ],
       ...
      );

These args are passed directly to exec, with no shell involved.  When
more than one command is given, then an ARRAY or ARRAYs must be
specified as follows:

    my $result = $vos->dump
      (
       ...
       filterout		=> [
                                    [ 'command1', '-arg1', '-value1' ],
                                    [ 'command2', '-arg2', '-value2' ],
                                    [ 'command3', '-arg3', '-value3' ],
                                   ],
       ...
      );

If B<ANY> of the filterout commands exits with a non-zero status, then
the entire dump method invocation is considered to fail.  You may or
may not get a valid volume dump file, compressed or otherwise,
depending on the behavior of the commands you specify.

=back

=head2 restore

The vos help string is:

    vos restore: restore a volume
    Usage: vos restore -server <machine name> -partition <partition name>
		       -name <name of volume to be restored> [-file <dump file>]
		       [-id <volume ID>] [-overwrite <abort | full | incremental>]
		       [-offline] [-readonly]
                       [-creation <dump | keep | new>] [-lastupdate <dump | keep | new>]
                       [-cell <cell name>] [-noauth]
		       [-localauth] [-verbose] [-encrypt]
    Where: -offline    leave restored volume offline
	   -readonly   make restored volume read-only

The corresponding method invocation looks like:

    my $result = $vos->restore
      (
       # Required arguments
       server			=> $server,
       partition		=> $partition,
       name			=> $name,
       file			=> $file, # SPECIAL CASE!!! (see below)
       # Optional arguments
       id			=> $id,
       overwrite		=> 'abort' | 'full' | 'incremental',
       offline			=> 1,
       readonly			=> 1,
       creation			=> 'dump' | 'keep' | 'new',
       lastupdate		=> 'dump' | 'keep' | 'new',
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
       # Enhanced arguments
       gunzip			=> 1,
       bunzip2			=> 1,
       filterin			=> [ @command ], # OR [ [ @cmd1 ], [ @cmd2 ], ... ]
      );

NOTE: The 'creation' and 'lastupdate' options are available only in a
very recent patch to the vos command, which should be available in the
OpenAFS 1.2.11 or 1.2.12 releases.  These options control how the
Creation and LastUpdate timestamps are set on the restored volume.

The 3 values these options can take, and their meanings, are:

=over

=item dump

Use the timestamp from the volume dump file being restored to the
volume.  This is the default behavior for the LastUpdate timestamp.

=item keep

Preserve the existing timestamp on the volume.

=item new

Set the timestamp to the current time.  This is the default behavior
for the Creation timestamp.

=back

Note that the default behavior creates the condition where the
Creation time is newer than the LastUpdate time, and when this is
true, "vos examine" (or any command that display the timestamps in the
volume header, really) will show the Creation time as the LastUpdate
time, presumably because it would be confusing to show the volume as
having been updated before it was created.

Similar to 'vos dump', the 'file' argument is optional to 'vos
restore', but required in this API.  There are also three new
arguments: gunzip, bunzip2, and filterin.  The analogy with 'vos dump'
is by design entirely symmetrical.

=over

=item file

This argument specifies the file from which the vos restore input
should be read.  If the file ends in '.gz' or '.bz2', then gunzip or
bunzip2 will be used to uncompress the input before it is read by vos
restore.  This is accomplished using a pipe, so there is no
intermediate file written to disk first.

By default, 'vos restore' will read the volume dump from stdin, which
is not what you want in most applications.  If you really want the
volume to be read from stdin, then you have to explicitly say so:

   my $result = $vos->restore
     (
      ...
      file			=> 'stdin',
      ...
     );

=item gunzip, bunzip2

Both of these arguments will turn on uncompression explicitly,
although they only need to be specified if the need for uncompression
can not be determined dynamically from the filename.  If the files are
compressed, but lack the proper extension ('.gz' or '.bz2'), or if the
compressed input is being read from stdin, then uncompression must be
specified explicitly.

These arguments have boolean values, since uncompression is either on
or off.  They are mutually exclusive as well.

=item filterin

This is an advanced feature, and one that allows the volume dump to be
filtered through any arbitrary number of commands after it is
uncompressed (optionally) and read by vos restore.  The value of this
argument is either an ARRAY reference to a list of command line
arguments, suitable for passing to exec(), or an ARRAY or such ARRAYS,
when more than one filter command is being used.

Lacking a better example, let's assume the author's 'newversion'
utility is being applied to the restore process, rather than the dump.

    my $result = $vos->restore
      (
       ...
       filterin			=> [ 'newversion' ],
       ...
      );

If there were command line arguments for this command, then they must
be given as follows:

    my $result = $vos->restore
      (
       ...
       filterin			=> [ 'newversion', '-arg1', '-value1' ],
       ...
      );

These args are passed directly to exec, with no shell involved.  When
more than one command is given, then an ARRAY or ARRAYs must be
specified as follows:

    my $result = $vos->restore
      (
       ...
       filterin			=> [
                                    [ 'command1', '-arg1', '-value1' ],
                                    [ 'command2', '-arg2', '-value2' ],
                                    [ 'command3', '-arg3', '-value3' ],
                                   ],
       ...
      );

If B<ANY> of the filterin commands exits with a non-zero status, then
the entire restore method invocation is considered to fail.  You may
or may not get a valid volume restored to your fileserver, depending
on the behavior of the commands you specify.

=back

=head1 METHODS (with complex return values)

=head2 examine

=over

=item Arguments

The vos help string is:

    vos examine: everything about the volume
    Usage: vos examine -id <volume name or ID> [-extended] [-cell <cell name>]
                       [-noauth] [-localauth] [-verbose] [-encrypt]
    Where: -extended   list extended volume fields

The corresponding method invocation looks like:

    my $result = $vos->examine
      (
       # Required arguments
       id			=> $id,
       # Optional arguments
       cell			=> $cell,
       extended			=> 1,
       noauth			=> 1,
       localauth		=> 1,
       verbose			=> 1,
       encrypt			=> 1,
      );

=item Return Values

This method returns an AFS::Object::Volume object, which in
turn contains one or more AFS::Object::VolumeHeader objects,
as well as an AFS::Object::VLDBEntry, which contains one or
more AFS::Object::VLDBSite objects.

    my $result = $vos->examine
      (
       id			=> $volname,
       cell			=> $cell,
      ) || die $vos->errors();

    foreach my $header ( $result->getVolumeHeaders() ) {
	my ($server,$partition) = ($header->server(),$header->partition());
	print "[header] server = $server, partition = $partition\n";
    }

    my $vldbentry = $result->getVLDBEntry();

    foreach my $vldbsite ( $vldbentry->getVLDBSites() ) {
	my ($server,$partition) = ($vldbsite->server(),$vldbsite->partition());
	print "[vldbsite] server = $server, partition = $partition\n";
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::Volume>

This object is nothing more than a container for the VolumeHeader and
VLDBEntry objects, and has no attributes of its own.  It has two
methods for extracting the objects it contains.

    Methods			Returns
    -------			-------
    getVLDBEntry()		a single AFS::Object::VLDBEntry object
    getVolumeHeaders()		list of AFS:Command::Result::VolumeHeader objects

B<AFS::Object::VLDBEntry>

This object is created by parsing this stanza of output:

    root.afs
	RWrite: 536918445     ROnly: 536918450
	number of sites -> 6
	   server pasafq3 partition /vicepc RW Site
	   server pasafq3 partition /vicepc RO Site
	   server pasafq1 partition /viceph RO Site
	   server pasafq2 partition /vicepg RO Site
	   server pasafq4 partition /vicepc RO Site
	   server pasafq5 partition /vicepg RO Site

The object attributes are taken from the first two lines of output:

    root.afs
	RWrite: 536918445     ROnly: 536918450

The following attributes should always be present:

    Attributes			Values
    ----------			------
    name			Volume name
    rwrite			Numeric Volume ID for the RW volume
    locked			Boolean value, indicating the VLDB entry is locked or not

The following attributes may be present, if there are volumes of the
associated type in the VLDB entry:

    Attributes			Values
    ----------			------
    ronly			Numeric Volume ID for the RO volume
    backup			Numeric Volume ID for the BK volume
    rclone			Numeric Volume ID for the RClone volume, if present

Note that the 'rclone' attribute is only present if the volume was
actively being cloned while being examined.  This is true when a 'vos
release' command is actively updating the RO volumes.

The following methods are available:

    Methods			Returns
    -------			-------
    getVLDBSites()		list of AFS::Object::VLDBSite objects

B<AFS::Object::VLDBSite>

This object is created by parsing the individual VLDB sites in the
VLDB entry, namely the lines such as:

	   server pasafq5 partition /vicepg RO Site

The following attributes are always available:

    Attributes			Values
    ----------			------
    server			Fileserver hostname
    partition			Fileserver /vice partition name
    type			"RO" | "RW" | "BK"
    status			Site status.

Note that the status is the field indicating the state of the volume
during a 'vos release' command, and this will be an empty string for
VLDB entries which are completely in sync.

B<AFS::Object::VolumeHeader>

This object is created by parsing the volume header stanza, such as:

    root.afs                          536908042 RW         23 K  Off-line
        npiafa3 /viceph
        RWrite  536908042 ROnly  536908046 Backup          0
        MaxQuota          0 K
        Creation    Sat Sep 23 03:41:50 2006
        Copy        Fri Aug 31 01:12:21 2007
        Backup      Fri Oct 17 20:59:02 2003
        Last Update Sat Nov  7 15:12:40 1998
        0 accesses in the past day (i.e., vnode references)

Note that there may very well be more than one of these, if a
.readonly is examined, since the volume headers for all of the RO
volumes will be queried.

The attributes available in this object depend on the method
arguments, as well as the state of the volume (less information can be
obtained when a volume is busy, for example).

The following attributes should always be present.

    Attributes			Values
    ----------			------
    id				Numeric Volume ID
    status			online | offline | busy
    attached			Boolean

The 'attached' attribute is a Boolean that indicates whether or not
the volume is attached by the volserver.  A volume which can not be
brought online due to volume header problems will be offline, and
unattached (attached == 0), but a volume can be offline for other
reasons, (eg. vos offline, or more than one volume with the same ID on
the same server), and still be attached (attached == 1).

The following attributes are present only if the volume's status is
'online':

    Attributes			Values
    ----------			------
    name			Volume Name
    type			"RO" | "RW" | "BK"
    size			Numeric size in KB
    server			Fileserver hostname
    partition			Fileserver /vice partition
    maxquota			Volume quota in KB
    creation			Volume creation date (ctime format, eg: Sat Oct  6 04:39:50 2001)
    copyTime			Volume copy date   (also in ctime format)
    backupTime			Volume backup date (also in ctime format)
    access      Volume Last Access date (also in ctime format)
    update			Volume update date (also in ctime format)
    accesses			Number of volume accesses since the last reset
    rwrite			Numeric Volume ID for the RW volume
    ronly			Numeric Volume ID for the RO volume
    backup			Numeric Volume ID for the BK volume
    rclone			Numeric Volume ID for the RClone volume, if present

Note that the 'rclone' attribute is only present if the volume was
actively being cloned while being examined.  This is true when a 'vos
release' command is actively updating the RO volumes.

The following attributes are only present if the 'extended' argument
was specified (see below for details on access the raw and author
stats):

    Attributes			Values
    ----------			------
    files			Number of files in the volume
    raw				Generic AFS::Object object
    author			Generic AFS::Object object

The 'raw' and 'author' stats are implemented as a hierarchy of simple,
generic AFS::Object objects, which have nothing but a couple
of attributes, and no special methods associated with them.

The 'raw' object has the following attributes:

    Attributes			Values
    ----------			------
    reads			Generic AFS::Object object
    writes			Generic AFS::Object object

Both of the 'reads' and 'writes' objects have the following
attributes:

    Attributes			Values
    ----------			------
    same			Generic AFS::Object object
    diff			Generic AFS::Object object

Both of the 'same' and 'diff' objects have the following attributes:

    Attributes			Values
    ----------			------
    total			Numeric value
    auth			Numeric value

The 'author' object has the following attributes:

    Attributes			Values
    ----------			------
    0sec			Generic AFS::Object object
    1min			Generic AFS::Object object
    10min			Generic AFS::Object object
    1hr				Generic AFS::Object object
    1day			Generic AFS::Object object
    1wk				Generic AFS::Object object

Each of the above interval value objects has the following attributes:

    Attributes			Values
    ----------			------
    file			Generic AFS::Object object
    dir				Generic AFS::Object object

Both the 'file' and 'dir' objects have the following attributes:

    Attributes			Values
    ----------			------
    same			Numeric value
    diff			Numeric value

At this point, any sane individual is probably hopelessly confused how
to make sense of the statistics, so let's make this clear with an
example.

First of all, the 'raw' and 'author' statistics are parsed from this
noise, generated when -extended is given:

			  Raw Read/Write Stats
	      |-------------------------------------------|
	      |    Same Network     |    Diff Network     |
	      |----------|----------|----------|----------|
	      |  Total   |   Auth   |   Total  |   Auth   |
	      |----------|----------|----------|----------|
    Reads     |      162 |      162 |        6 |        6 |
    Writes    |     1815 |     1815 |        0 |        0 |
	      |-------------------------------------------|
		       Writes Affecting Authorship
	      |-------------------------------------------|
	      |   File Authorship   | Directory Authorship|
	      |----------|----------|----------|----------|
	      |   Same   |   Diff   |    Same  |   Diff   |
	      |----------|----------|----------|----------|
    0-60 sec  |      226 |        0 |      621 |        0 |
    1-10 min  |       87 |        0 |      105 |        0 |
    10min-1hr |       42 |        0 |       44 |        0 |
    1hr-1day  |       18 |        0 |        6 |        0 |
    1day-1wk  |        0 |        0 |        0 |        0 |
    > 1wk     |        1 |        0 |        0 |        0 |
	      |-------------------------------------------|

Since attributes can most easily be accessed by calling the method of
the same name, one can easily dig into the hierarchy as follows:

    my $result = $vos->examine
      (
       id			=> 'user.wpm',
       cell			=> 'q.ny.ms.com',
       extended			=> 1,
      );

    print $result->raw()->reads()->same()->total();  # 162, in the above output.
    print $result->author()->10min()->dir()->same(); # 44, in the above output.

See?  It's not as ugly as the pedantic description above implies.

=back

=head2 listaddrs

=over

=item Arguments

The vos help string is:

    vos listaddrs: list the IP address of all file servers registered in the VLDB
    Usage: vos listaddrs [-uuid <uuid of server>] [-host <address of host>]
			 [-noresolve] [-printuuid] [-cell <cell name>] [-noauth]
			 [-localauth] [-verbose] [-encrypt] 
    Where: -noresolve  don't resolve addresses
	   -printuuid  print uuid of hosts

The corresponding method invocation looks like:

    my $result = $vos->listaddrs
      (
       # Optional arguments
       uuid			=> $uuid,
       host			=> $host,
       noresolve		=> 1,
       printuuid		=> 1,
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
       verbose			=> 1,
       encrypt			=> 1,
      );

=item Return Values

This method returns a list of AFS::Object::FileServer
objects, each of which has attributes that vary depending on the
arguments to the method call.

In particular, vos will try to map IP addresses into hostnames, and
this may or may not succeed, depending on the stability and
correctness of the hostname resolution mechanism (usually DNS, of
course, but that is outside of vos' control).

    my @result = $vos->listaddrs
      (
       cell				=> $cell,
      );

    foreach my $result ( @result ) {
	if ( $result->hasAttribute('hostname') {
	    print "Hostname: " . $result-hostname() . "\n";
	} elsif ( $result->hasAttribute('addresses') {
	    my $addresses = $result->addresses();
	    foreach my $address ( @addresses ) {
		print "IP Address: $address\n";
	    }
	}
    }

If a specific 'host' or 'uuid' is specified, then only one object will
be returned (assuming the specified host or uuid is valid, of course,
otherwise, you get nothing).

B<AFS::Object::FileServer>

This object will have one or more of the following attributes,
depending on the choice of arguments to the method, as well as the
ability of vos to map the IP addresses back into hostnames.

    Attributes			Values
    ----------			------
    hostname			Server's hostname (duh)
    addresses			ARRAY reference of IP addresses
    uuid			Servers's UUID (duh)

The 'uuid' will be present if the 'printuuid' or 'uuid' arguments were
passed to the method call.  The 'addresses' will be present either
'noresolve' was specified, or vos has problems with hostname
resolution.

=back

=head2 listpart

=over

=item Arguments

The vos help string is:

    vos listpart: list partitions
    Usage: vos listpart -server <machine name> [-cell <cell name>] [-noauth]
			[-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->listpart
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       cell			=> $cell,
       noauth			=> 1,
       localauth		=> 1,
       verbose			=> 1,
       encrypt			=> 1,
      );

=item Return Values

This method returns an AFS::Object::FileServer object, which
contains one or more AFS::Object::Partition objects.  Because
'listpart' returns nothing other than the partition names, the
underlying Partition objects have only one attribute ('partition'), so
the API for access this data is trivial:

    my $result = $vos->listpart
      (
       server			=> 'fs1.ms.com',
      ) || die $vos->errors();

    foreach my $partition ( $result->getPartitionNames() ) {
	print "Server '$server' has partition '$partition'\n";
    }

The FileServer object has no attributes at all, it merely contains the
Partition objects.  Since the Partition objects are indexed by name,
there's no need to extract the partition objects and query their
attributes, since once you have the names, you have all the information
already.

Compare this with 'vos partinfo', which provides a lot more
information.  For pedantic completeness (the author is kinda
anal-retentive in that way), here's the description of the complete
interface.

B<AFS::Object::FileServer>

This object has no attributes, and is merely a container for the
AFS::Object::Partition objects.  It has the following methods
for extracting the objects is contains.

    Methods			Returns
    -------			-------
    getPartitionNames()		list of strings (partition names)
    getPartitions()		list of AFS::Object::Partition objects
    getPartition($partname)	a single AFS::Object::Partition object,
				for the partition named $partname

B<AFS::Object::Partition>

This object has one boring attribute:

    Attributes			Values
    ----------			------
    partition			Fileserver /vice partition name

When used to encapsulate 'vos listpart' output, this object has no
relevant methods.  Note, however, that this version of the API reuses
this object to represent other partition-related data (see 'vos
listvol' method documentation, for example), but they are not relevant
in this usage of the object.  This multiple personality of the objects
may be changed in a future release, so don't get too attached to the
specific class names.

See the AFS::Object documentation for a discussion of the
planned evolution of the API.

=back

=head2 listvldb

=over

=item Arguments

The vos help string is:

    vos listvldb: list volumes in the VLDB
    Usage: vos listvldb [-name <volume name or ID>] [-server <machine name>]
			[-partition <partition name>] [-locked] [-quiet] [-nosort]
			[-cell <cell name>] [-noauth] [-localauth] [-verbose] [-encrypt] 
    Where: -locked     locked volumes only
	   -quiet      generate minimal information
	   -nosort     do not alphabetically sort the volume names

The corresponding method invocation looks like:

    my $result = $vos->listvldb
      (
       # Optional arguments
       name			=> $name,
       server			=> $server,
       partition		=> $partition,
       locked			=> 1,
       quiet			=> 1,
       nosort			=> 1,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=item Return Values

This method returns an AFS::Object::VLDB object, which has a
few attributes, and contains one or more
AFS::Object::VLDBEntry objects, which in turn contain
AFS::Object::VLDBSite objects, as well as their own
attributes.

NOTE: the VLDBEntry and VLDBSite objects are the same as those used by
the 'examine' method, since that command also queries the VLDB for
part of its return values.  See that discussion above for some
relevant details on the parsing of those objects, which will no be
repeated here.

    my $result = $vos->listvldb
      (
       cell				=> $cell,
      ) || die $vos->errors();

    print("VLDB contains " . $result->total() " volumes, " .
	  $result->locked() . " of which are locked\n");

    foreach my $entry ( $result->getVLDBEntries() ) {
	my $name = $entry->name();
	foreach my $attr ( $entry->listAttributes() ) {
	    print "Volume $name has attribute $attr => " . $entry->$attr() . "\n";
	}
	foreach my $site ( $entry->getVLDBSites() ) {
	    my %attrs = $site->getAttributes();
	    while ( my($attr,$value) = each %attrs ) {
		print "Site has attribute $attr => $value\n";
	    }
	}
    }

Another way to slice and dice this data:

    foreach my $name ( $result->getVolumeNames() ) {
	my $entry = $result->getVLDBEntry( name => $name );
	....
    }

Each of these objects has the following attributes and methods:

B<AFS::Object::VLDB>

This object has two attributes, and several methods:

    Attributes			Values
    ----------			------
    total			Number of VLDBEntries in the results
    locked			Number of locked volumes in the results

    Methods			Returns
    -------			-------
    getVolumeNames()		list of volume names in the results
    getVolumeIds()		list of numeric volume IDs
    getVLDBEntry(name => $name)	the AFS::Object::VLDBEntry for name $name
    getVLDBEntry(id => $id)	the AFS::Object::VLDBEntry for id $id
    getVLDBEntries()		list of AFS::Object::VLDBEntry objects
    getVLDBEntryByName($name)   the AFS::Object::VLDBEntry for $name
    getVLDBEntryById($id)   	the AFS::Object::VLDBEntry for $id

NOTE: name to volume mappings are one to one, but id to volume
mappings are many to one, since a single logical VLDB entry can have
several IDs associated with it (RW, RO, BK, and/or RC).

B<AFS::Object::VLDBEntry>

This object also has a few attributes, and a few methods.  The 'name'
attribute is always present, but the others vary, depending on the
volume (again, see the 'examine' documentation for more verbosity).

    Attributes			Values
    ----------			------
    name			Volume name
    rwrite			Numeric Volume ID for the RW volume
    ronly			Numeric Volume ID for the RO volume
    backup			Numeric Volume ID for the BK volume
    rclone			Numeric Volume ID for the RClone volume, if present
    locked			Boolean, indicating whether or not the VLDB entry is locked

    Methods			Returns
    -------			-------
    getVLDBSites()		list of AFS::Object::VLDBSite objects

B<AFS::Object::VLDBSite>

The following attributes are always available:

    Attributes			Values
    ----------			------
    server			Fileserver hostname
    partition			Fileserver /vice partition name
    type			"RO" | "RW" | "BK"
    status			Site status.

This object has no special methods.

=back

=head2 listvol

=over

=item Arguments

The vos help string is:

    vos listvol: list volumes on server (bypass VLDB)
    Usage: vos listvol -server <machine name> [-partition <partition name>]
		       [-fast] [-long] [-quiet] [-extended] [-cell <cell name>]
		       [-noauth] [-localauth] [-verbose] [-encrypt] 
    Where: -fast       minimal listing
	   -long       list all normal volume fields
	   -quiet      generate minimal information
	   -extended   list extended volume fields

The corresponding method invocation looks like:

    my $result = $vos->listvol
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       partition		=> $partition,
       fast			=> 1,
       long			=> 1,
       quiet			=> 1,
       extended			=> 1, # Not really... see below
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

NOTE: 'extended' is not supported in this version of the API, and
specifying it will result in a warning, but not an error.  However,
'vos examine' does parse the extended output, so if you really want
that data you can get it on a volume by volume basis.  Adding support
for 'extended' to 'vos listvol' is on the todo list.

=item Return Values

This method returns an AFS::Object::VolServer object, which
merely contains one or more AFS::Object::Partition objects,
which in turn have a few attributes and contain one or more
AFS::Object::VolumeHeader objects.

    my $result = $vos->listvol
      (
       server			=> $server,,
       cell				=> $cell,
      ) || die $vos->errors();
    foreach my $partition ( $result->getPartitions() ) {
	my $partname 		= $partition->partition();
	my $total			= $partition->total();
	my $online			= $partition->online();
	my $offline			= $partition->offline();
	my $busy			= $partition->busy();
	print("Partition $partname has $total total volumes, of which " .
	      "$online are online, $offline are offline, and $busy are busy.\n");
	foreach my $header ( $partition->getVolumeHeaders() ) {
	    # Do something interesting with $header.
	}
    }

There are several other ways to get at the headers, of course.

	foreach my $name ( $partition->getVolumeNames() ) {
	    my $header = $partition->getVolumeHeaderByName($name)
	    # Do something interesting with $header.
	}

	foreach my $id ( $partition->getVolumeIds() ) {
	    my $header = $partition->getVolumeHeaderById($id);
	    # Do something interesting with $header.
	}

And there is yet one more method to extract the headers (don't say the
author doesn't pander to lots of different programming styles,
provided of course they are one of his own).


	foreach my $name ( $partition->getVolumeNames() ) {
	    my $header = $partition->getVolumeHeader( name => $name );
	    # Do something interesting with $header.
	}

	foreach my $id ( $partition->getVolumeIds() ) {
	    my $header = $partition->getVolumeHeader( id => $id );
	    # Do something interesting with $header.
	}

Each of these objects has the following attributes and methods:

B<AFS::Object::VolServer>

This object has no attributes, and has several methods for extracting
the partition objects.

    Methods			Returns
    -------			-------
    getPartitionNames()		list of partition names
    getPartitions()		list of AFS::Object::Partition objects
    getPartition($name)		the AFS::Object::Partition for partition $name

B<AFS::Object::Partition>

This objects has several attributes, and several methods for
extracting the VolumeHeader objects.

    Attributes				Values
    ----------				------
    partition				Partition name
    total				Total number of volumes on the partition
    online				Total number of online volumes
    offline				Total number of offline volumes
    busy				Total number of busy volumes

    Methods				Returns
    -------				-------
    getVolumeIds()			List of volume ids
    getVolumeNames()			List of volume names
    getVolumeHeaderById($id)		the AFS::Object::VolumeHeader object for $id
    getVolumeHeaderByName($name)	the AFS::Object::VolumeHeader object for $name
    getVolumeHeaders()			list of AFS::Object::VolumeHeader objects
    getVolumeHeader( id => $id )	the AFS::Object::VolumeHeader object for $id
    getVolumeHeader( name => $name )	the AFS::Object::VolumeHeader object for $name

Note that both of the following are equivalent, but merely differ in style:

    getVolumeHeaderById($id)
    getVolumeHeader( id => $id )

And it should be obvious, but these are also equivalent we well:

    getVolumeHeaderByName($name)
    getVolumeHeader( name => $name )

B<AFS::Object::VolumeHeader>

The following attributes should always be present.

    Attributes			Values
    ----------			------
    id				Numeric Volume ID
    status			online | offline | busy
    attached			Boolean

The 'attached' attribute is a Boolean that indicates whether or not
the volume is attached by the volserver.  A volume which can not be
brought online due to volume header problems will be offline, and
unattached (attached == 0), but a volume can be offline for other
reasons, (eg. vos offline, or more than one volume with the same ID on
the same server), and still be attached (attached == 1).

If the 'fast' argument was specified, then none of the other
attributes will be present.

The following attributes are present only if the volume's status is
'online':

    Attributes			Values
    ----------			------
    name			Volume Name
    type			"RO" | "RW" | "BK"
    size			Numeric size in KB

The following attributes are present only if the 'long' argument was
specified:

    Attributes			Values
    ----------			------
    server			Fileserver hostname
    partition			Fileserver /vice partition
    maxquota			Volume quota in KB
    creation			Volume creation date (ctime format, eg: Sat Oct  6 04:39:50 2001)
    copyTime			Volume copy date   (also in ctime format)
    backupTime			Volume backup date (also in ctime format)
    access			Volume Last Access date (also in ctime format)
    update			Volume update date (also in ctime format)
    accesses			Number of volume accesses since the last reset
    rwrite			Numeric Volume ID for the RW volume
    ronly			Numeric Volume ID for the RO volume
    backup			Numeric Volume ID for the BK volume
    rclone			Numeric Volume ID for the RClone volume, if present

=back

=head2 partinfo

=over

=item Arguments

The vos help string is:

    vos partinfo: list partition information
    Usage: vos partinfo -server <machine name> [-partition <partition name>]
			[-cell <cell name>] [-noauth] [-localauth]
			[-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->partinfo
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       partition		=> $partition,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=item Return Values

This method returns an AFS::Object::FileServer object, which
contains one or more AFS::Object::Partition objects, which
have more interesting attributes than those returned by 'vos
partinfo'.

    my $result = $vos->partinfo
      (
       server			=> $server,
       cell			=> $cell,
      ) || die $vos->errors();
    foreach my $partition ( $result->getPartitions() ) {
	my $partname		= $partition->partition();
	my $available		= $partition->available();
	my $total		= $partition->total();
	print("Partition $partname has $available KB of " .
              "space available out of $total KB total\n");
    }

B<AFS::Object::FileServer>

This object has no attributes, and is merely a container for the
AFS::Object::Partition objects.  It has the following methods
for extracting the objects is contains.

    Methods			Returns
    -------			-------
    getPartitionNames()		list of strings (partition names)
    getPartitions()		list of AFS::Object::Partition objects
    getPartition($partname)	a single AFS::Object::Partition object,
				for the partition named $partname

B<AFS::Object::Partition>

This object has three attributes:

    Attributes			Values
    ----------			------
    partition			Fileserver /vice partition name
    available			Space available, in KB
    total			Total space, in KB

=back

=head2 status

=over

=item Arguments

The vos help string is:

    vos status: report on volser status
    Usage: vos status -server <machine name> [-cell <cell name>] [-noauth]
		      [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->status
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=item Return Values

This method returns an AFS::Object::VolServer object with one
attribute, which also may or may not contain one or more
AFS::Object::Transaction objects.

    my $result = $vos->status
      (
       server			=> $server,
      ) || die $vos->errors();
    print "Server has " . $result->transactions() . "active transactions\n";

    foreach my $transaction ( $result->getTransactions() ) {
	print("There are active transactions for volume ID " .
	      $transaction->volume() . "\n");
    }

B<AFS::Object::VolServer>

This object has exactly one attribute, and several methods:

    Attributes				Values
    ----------				------
    transactions			Number of active transactions on the volserver

    Methods				Returns
    -------				-------
    getTransactions			list of AFS::Object::Transaction objects
    getVolumes				list of volume IDs for which there are transactions
    getTransactionByVolume($volume)	a single AFS::Object::Transaction object for the volume $volume

B<AFS::Object::Transaction>

This object has several attributes:

    Attributes				Values
    ----------				------
    transaction				Numeric transaction ID
    created				Creation date (in ctime format)
    attachFlags				String (exact meaning unclear)
    volume				Numeric volume ID
    partition				Vice partition on whcih the volume resides
    procedure				What is being done to the volume
    packetRead				Numeric value
    lastReceiveTime			Time value (utime format)
    packetSend				Numeric value
    lastSendTime			Time value (utime format)

NOTE: These attributes just come from a straight parsing of output like this:

    --------------------------------------
    transaction: 170423  created: Wed Oct  8 15:59:12 2003
    attachFlags:  offline
    volume: 536963097  partition: /vicepf  procedure: Restore
    packetRead: 222  lastReceiveTime: 1065643165  packetSend: 1  lastSendTime: 1065643165
    --------------------------------------

To understand the meaning of these various fields (which to the
author's knowledge are not documented anywhere), see the OpenAFS
source code.  Some of these values are obvious, or intuitive, but
others are not.

=back

=head1 METHODS (with simple return values)

All of the following commands return a simple Boolean (true/false)
value, if they succeed or fail.

=head2 addsite

The vos help string is:

    vos addsite: add a replication site
    Usage: vos addsite -server <machine name for new site> -partition <partition name for new site>
		       -id <volume name or ID> [-cell <cell name>]
		       [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->addsite
      (
       # Required arguments
       server			=> $server,
       partition		=> $partition,
       id			=> $id,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 backup

The vos help string is:

    vos backup: make backup of a volume
    Usage: vos backup -id <volume name or ID> [-cell <cell name>]
		      [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->backup
      (
       # Required arguments
       id			=> $id,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 backupsys

The vos help string is:

    vos backupsys: en masse backups
    Usage: vos backupsys [-prefix <common prefix on volume(s)>+] [-server <machine name>]
			 [-partition <partition name>] [-exclude]
			 [-xprefix <negative prefix on volume(s)>+] [-dryrun]
			 [-cell <cell name>] [-noauth] [-localauth] [-verbose] [-encrypt]
    Where: -exclude    exclude common prefix volumes
	   -dryrun     no action

The corresponding method invocation looks like:

    my $result = $vos->backupsys
      (
       # Optional arguments
       prefix			=> $prefix, # OR [ $prefix1, $prefix2, ... ]
       server			=> $server,
       partition		=> $partition,
       exclude			=> 1,
       prefix			=> $xprefix, # OR [ $xprefix1, $xprefix2, ... ]
       dryrun			=> 1,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 changeaddr

The vos help string is:

    vos changeaddr: change the IP address of a file server
    Usage: vos changeaddr -oldaddr <original IP address> [-newaddr <new IP address>]
			  [-remove] [-cell <cell name>] [-noauth]
			  [-localauth] [-verbose] [-encrypt]
    Where: -remove     remove the IP address from the VLDB

The corresponding method invocation looks like:

    my $result = $vos->changeaddr
      (
       # Required arguments
       oldaddr			=> $oldaddr,
       # Optional arguments
       newaddr			=> $newaddr,
       remove			=> 1,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 changeloc

The vos help string is:

    vos changeloc: change an RW volume's location in the VLDB
    Usage: vos changeloc -server <machine name for new location>
			 -partition <partition name for new location>
			 -id <volume name or ID> [-cell <cell name>] [-noauth]
			 [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->changeloc
      (
       # Required arguments
       server			=> $server,
       partition		=> $partition,
       id			=> $id,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 clone

The vos help string is:

    vos clone: make clone of a volume
    Usage: vos clone -id <volume name or ID> 
      [-server <server>] [-partition <partition>] 
      [-toname <volume name on destination>] [-toid <volume ID on destination>] 
      [-offline] [-readonly] [-cell <cell name>] [-noauth] 
      [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->clone
      (
       # Required arguments
       id           => $id,
       # Optional arguments
       server       => $server,
       partition    => $partition,
       toname       => $newname,
       toid         => $newid,
       cell         => $cell,
       noauth       => 1,
       localauth    => 1,
       verbose      => 1,
       encrypt      => 1,
      );

=head2 convertROtoRW

The vos help string is:

    vos convertROtoRW: convert a RO volume into a RW volume (after loss of old RW volume)
    Usage: vos convertROtoRW -server <machine name> -partition <partition name> 
       -id <volume name or ID> [-force] [-cell <cell name>] [-noauth]
       [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->convertROtoRW
      (
       # Required arguments
       server     => $server,
       partition  => $partition,
       id             => $id,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 copy

The vos help string is:

    vos copy: copy a volume
    Usage: vos copy -id <volume name or ID on source> -fromserver <machine name on source> 
       -frompartition <partition name on source> -toname <volume name on destination> 
       -toserver <machine name on destination> -topartition <partition name on destination> 
       [-offline] [-readonly] [-live] [-cell <cell name>] [-noauth] 
       [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->copy
      (
       # Required arguments
       id             => $id,
       fromserver     => $server,
       frompartition  => $partition,
       toname         => $name
       toserver       => $newserver,
       topartition    => $newpartition,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );


=head2 create

The vos help string is:

    vos create: create a new volume
    Usage: vos create -server <machine name> -partition <partition name>
		      -name <volume name> [-maxquota <initial quota (KB)>]
		      [-cell <cell name>] [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->create
      (
       # Required arguments
       server			=> $server,
       partition		=> $partition,
       name			=> $name,
       # Optional arguments
       maxquota			=> $maxquota,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 delentry

The vos help string is:

    vos delentry: delete VLDB entry for a volume
    Usage: vos delentry [-id <volume name or ID>+]
			[-prefix <prefix of the volume whose VLDB entry is to be deleted>]
			[-server <machine name>] [-partition <partition name>]
			[-cell <cell name>] [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->delentry
      (
       # Optional arguments
       id			=> $id, # OR [ $id1, $id2, ... ]
       prefix			=> $prefix,
       server			=> $server,
       partition		=> $partition,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 lock

The vos help string is:

    vos lock: lock VLDB entry for a volume
    Usage: vos lock -id <volume name or ID> [-cell <cell name>]
		    [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->lock
      (
       # Required arguments
       id			=> $id,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 move

The vos help string is:

    vos move: move a volume
    Usage: vos move -id <volume name or ID> -fromserver <machine name on source>
		    -frompartition <partition name on source>
		    -toserver <machine name on destination>
		    -topartition <partition name on destination> [-cell <cell name>]
		    [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->move
      (
       # Required arguments
       id			=> $id,
       fromserver		=> $fromserver,
       frompartition		=> $frompartition,
       toserver			=> $toserver,
       topartition		=> $topartition,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 offline

The vos help string is:

    Usage: vos offline -server <server name> -partition <partition name>
 		       -id <volume name or ID> [-sleep <seconds to sleep>]
                       [-busy] [-cell <cell name>]
 		       [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->offline
      (
       # Required arguments
       id			=> $id,
       server			=> $server,
       partition		=> $partition,
       # Optional arguments
       sleep			=> $sleep,
       busy			=> 1,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 online

The vos help string is:

    Usage: vos online -server <server name> -partition <partition name>
		      -id <volume name or ID> [-cell <cell name>]
		      [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->online
      (
       # Required arguments
       id			=> $id,
       server			=> $server,
       partition		=> $partition,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 release

The vos help string is:

    vos release: release a volume
    Usage: vos release -id <volume name or ID> [-force] [-cell <cell name>]
		       [-noauth] [-localauth] [-verbose] [-encrypt]
    Where: -force      force a complete release

The corresponding method invocation looks like:

    my $result = $vos->release
      (
       # Required arguments
       id			=> $id,
       # Optional arguments
       force			=> 1,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 remove

The vos help string is:

    vos remove: delete a volume
    Usage: vos remove [-server <machine name>] [-partition <partition name>]
		      -id <volume name or ID> [-cell <cell name>] [-noauth]
		      [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->remove
      (
       # Required arguments
       id			=> $id,
       # Optional arguments
       server			=> $server,
       partition		=> $partition,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 remsite

The vos help string is:

    vos remsite: remove a replication site
    Usage: vos remsite -server <machine name> -partition <partition name>
		       -id <volume name or ID> [-cell <cell name>] [-noauth]
		       [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->remsite
      (
       # Required arguments
       id			=> $id,
       server			=> $server,
       partition		=> $partition,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 rename

The vos help string is:

    vos rename: rename a volume
    Usage: vos rename -oldname <old volume name > -newname <new volume name >
		      [-cell <cell name>] [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->rename
      (
       # Required arguments
       oldname			=> $oldname,
       newname			=> $newname,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 setfields

The vos help string is:

    vos setfields: change volume info fields
    Usage: vos setfields -id <volume name or ID> [-maxquota <quota (KB)>]
			 [-clearuse] [-cell <cell name>] [-noauth]
			 [-localauth] [-verbose] [-encrypt]
    Where: -clearuse   clear dayUse

The corresponding method invocation looks like:

    my $result = $vos->setfields
      (
       # Required arguments
       id			=> $id,
       # Optional arguments
       maxquota			=> $maxquota,
       clearuse			=> 1,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 shadow

The vos help string is:

    vos shadow: make or update a shadow volume
    Usage: vos shadow -id <volume name or ID on source> -fromserver <machine name on source> 
       -frompartition <partition name on source> -toserver <machine name on destination> 
       -topartition <partition name on destination> [-toname <volume name on destination>] 
       [-toid <volume ID on destination>] [-offline] [-readonly] [-live] [-incremental] 
       [-cell <cell name>] [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->shadow
      (
       # Required arguments
       id             => $id,
       fromserver     => $server,
       frompartition  => $partition,
       toserver       => $newserver,
       topartition    => $newpartition,
       # Optional arguments
       toname                   => $newname
       toid                     => $newid
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );


=head2 syncserv

The vos help string is:

    vos syncserv: synchronize server with VLDB
    Usage: vos syncserv -server <machine name> [-partition <partition name>]
			[-cell <cell name>] [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->syncserv
      (
       # Required arguments
       server			=> $server,
       # Optional arguments
       partition		=> $partition,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 syncvldb

The vos help string is:

    vos syncvldb: synchronize VLDB with server
    Usage: vos syncvldb [-server <machine name>] [-partition <partition name>]
			[-volume <volume name or ID>] [-cell <cell name>]
			[-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->syncvldb
      (
       # Optional arguments
       server			=> $server,
       partition		=> $partition,
       volume			=> $volume,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 unlock

The vos help string is:

    vos unlock: release lock on VLDB entry for a volume
    Usage: vos unlock -id <volume name or ID> [-cell <cell name>] [-noauth]
		      [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->unlock
      (
       # Required arguments
       id			=> $id,
       # Optional arguments
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 unlockvldb

The vos help string is:

    vos unlockvldb: unlock all the locked entries in the VLDB
    Usage: vos unlockvldb [-server <machine name>] [-partition <partition name>]
			  [-cell <cell name>] [-noauth] [-localauth] [-verbose] [-encrypt]

The corresponding method invocation looks like:

    my $result = $vos->unlockvldb
      (
       # Optional arguments
       server			=> $server,
       partition		=> $partition,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head2 zap

The vos help string is:

    vos zap: delete the volume, don't bother with VLDB
    Usage: vos zap -server <machine name> -partition <partition name> -id <volume ID>
		   [-force] [-backup] [-cell <cell name>] [-noauth]
		   [-localauth] [-verbose] [-encrypt]
    Where: -force      force deletion of bad volumes
	   -backup     also delete backup volume if one is found

The corresponding method invocation looks like:

    my $result = $vos->zap
      (
       # Required arguments
       server			=> $server,
       partition		=> $partition,
       id			=> $id,
       # Optional arguments
       force			=> 1,
       backup			=> 1,
       cell                     => $cell,
       noauth                   => 1,
       localauth                => 1,
       verbose                  => 1,
       encrypt                  => 1,
      );

=head1 SEE ALSO

AFS::Command(1), AFS::Object(1)

=cut

lib/AFS/Object.pm  view on Meta::CPAN

#
# $Id$
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

package AFS::Object;

use strict;
use Carp;

our $AUTOLOAD = "";
our $VERSION = '1.99';

our %Carp =
  (
   carp		=> \&Carp::carp,
   croak	=> \&Carp::croak,
  );

sub _setCarp {
    my $class = shift;
    my (%args) = @_;
    foreach my $key ( keys %args ) {
	$Carp{$key} = $args{$key};
    }
    return 1;
}

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = { _attrs	=> { @_ } };
    return bless $self, $class;
}

sub listAttributes {
    my $self = shift;
    return keys %{$self->{_attrs}};
}

sub getAttribute {
    my $self = shift;
    my $attr = shift;
    return $self->{_attrs}->{$attr};
}

sub getAttributes {
    my $self = shift;
    my $attr = shift;
    return %{$self->{_attrs}};
}

sub hasAttribute {
    my $self = shift;
    my $attr = shift;
    return exists $self->{_attrs}->{$attr};
}

sub _Carp {
    my $self = shift;
    $Carp{carp}->(@_);
}

sub _Croak {
    my $self = shift;
    $Carp{croak}->(@_);
}

sub _setAttribute {
    my $self = shift;
    my (%attrs) = @_;
    foreach my $attr ( keys %attrs ) {
	$self->{_attrs}->{$attr} = $attrs{$attr};
    }
    return 1;
}

sub DESTROY {}

sub AUTOLOAD {
    my $self = shift;
    my $attr = $AUTOLOAD;
    $attr =~ s/.*:://;
    return $self->{_attrs}->{$attr};
}

1;

lib/AFS/Object.pod  view on Meta::CPAN

#
# $Id: Object.pod,v 7.1 2004/01/13 19:01:14 wpm Exp $
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

=head1 NAME

AFS::Object - Base class for encapsulating AFS::Command results

=head1 SYNOPSIS

This module is not used directly by applications, only indirectly
through the return values of the various AFS::Command::* methods.

=head1 DESCRIPTION

This class is used by the various AFS::Command classes to encapsulate
data returned from any command that has structured return values.  In
the simplest case, this object just encapsulates a simple list of
key/value pairs.  Each distinct key is represented as an object
attribute, and is query-able via one of several methods documented
below.

There are numerous subclasses of this class, which are used when
objects must contains other objects, and these subclasses just
implement special methods for querying the embedded objects.

=head1 METHODS

In all of the example code snippets below, $result is assumed to be an
AFS::Object object, or an object derived from it.

=head2 listAttributes

This method takes no arguments, and returns a list of the attribute
names available in the object.

    my @attrs = $result->listAttributes();
    foreach my $attr ( @attrs ) {
	my $value = $result->getAttribute($attr);
	print "Key '$attr' has value '$value\n";
    }

=head2 getAttribute

This methods takes a single argument, the name of an attribute, and
returns the value of the attribute, if it exists in the object.

    my $name = $result->getAttribute('name');

NOTE: Attributes may also be queried by calling the method of the same
name.  If the attribute doesn't exist, then the method will just
return a false value.  The above example is the same as:

    my $name = $result->name();

BEWARE: It is impossible to tell the difference between a non-existent
attribute, and one with a false value using this method.  If attribute
existence is interesting to you (or to your code, I suppose), use the
hasAttribute method.

=head2 getAttributes

This method takes no arguments, and returns the entire list of
attributes as list of key/value pairs.

    my %attrs = $result->getAttributes();
    while ( my ($key,$value) = each %attrs ) {
	print "Key '$key' has value '$value'\n";
    }

=head2 hasAttribute

This method takes a single argument, the name of a potentially
available attribute, and returns a boolean true/false value if the
attribute exists in the object.

    if ( $result->hasAttribute('name') ) {
       # Well, then it has a name attribute...
    }

=head1 SEE ALSO

AFS::Command(1), AFS::Command::Base(1)

=cut

lib/AFS/Object/ACL.pm  view on Meta::CPAN

#
# $Id$
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

package AFS::Object::ACL;

use strict;

our @ISA = qw(AFS::Object);
our $VERSION = '1.99';

sub getPrincipals {
    my $self = shift;
    return unless ref $self->{_principals};
    return keys %{$self->{_principals}};
}

sub getRights {
    my $self = shift;
    my $principal = shift;
    return unless ref $self->{_principals};
    return $self->{_principals}->{lc($principal)};
}

sub getEntries {
    my $self = shift;
    return unless ref $self->{_principals};
    return %{$self->{_principals}};
}

sub _addEntry {
    my $self = shift;
    my $principal = shift;
    my $rights = shift;
    return $self->{_principals}->{$principal} = $rights;
}

1;

lib/AFS/Object/BosServer.pm  view on Meta::CPAN

#
# $Id$
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

package AFS::Object::BosServer;

use strict;

our @ISA = qw(AFS::Object);
our $VERSION = '1.99';

sub getInstanceNames {
    my $self = shift;
    return unless ref $self->{_instances};
    return keys %{$self->{_instances}};
}

sub getInstance {
    my $self = shift;
    my $name = shift;
    return unless ref $self->{_instances};
    return $self->{_instances}->{$name};
}

sub getInstances {
    my $self = shift;
    return unless ref $self->{_instances};
    return values %{$self->{_instances}};
}

sub _addInstance {
    my $self = shift;
    my $instance = shift;
    unless ( ref $instance && $instance->isa("AFS::Object::Instance") ) {
	$self->_Croak("Invalid argument: must be an AFS::Object::Instance object");
    }
    return $self->{_instances}->{$instance->instance()} = $instance;
}

sub getFileNames {
    my $self = shift;
    return unless ref $self->{_files};
    return keys %{$self->{_files}};
}

sub getFile {
    my $self = shift;
    my $filename = shift;
    return unless ref $self->{_files};
    return $self->{_files}->{$filename};
}

sub getFiles {
    my $self = shift;
    return unless ref $self->{_files};
    return values %{$self->{_files}};
}

sub _addFile {
    my $self = shift;
    my $file = shift;
    unless ( ref $file && $file->isa("AFS::Object") ) {
	$self->_Croak("Invalid argument: must be an AFS::Object object");
    }
    return $self->{_files}->{$file->file()} = $file;
}

sub getKeyIndexes {
    my $self = shift;
    return unless ref $self->{_keys};
    return keys %{$self->{_keys}};
}

sub getKey {
    my $self = shift;
    my $index = shift;
    return unless ref $self->{_keys};
    return $self->{_keys}->{$index};
}

sub getKeys {
    my $self = shift;
    return unless ref $self->{_keys};
    return values %{$self->{_keys}};
}

sub _addKey {
    my $self = shift;
    my $key = shift;
    unless ( ref $key && $key->isa("AFS::Object") ) {
	$self->_Croak("Invalid argument: must be an AFS::Object object");
    }
    return $self->{_keys}->{$key->index()} = $key;
}

1;

lib/AFS/Object/CacheManager.pm  view on Meta::CPAN

#
# $Id$
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

package AFS::Object::CacheManager;

use strict;

our @ISA = qw(AFS::Object);
our $VERSION = '1.99';

sub getPathNames {
    my $self = shift;
    return unless ref $self->{_pathnames};
    return keys %{$self->{_pathnames}};
}

sub getPaths {
    my $self = shift;
    return unless ref $self->{_pathnames};
    return values %{$self->{_pathnames}};
}

sub getPath {
    my $self = shift;
    my $path = shift;
    return unless ref $self->{_pathnames};
    return $self->{_pathnames}->{$path};
}

sub _addPath {
    my $self = shift;
    my $path = shift;
    unless ( ref $path && $path->isa("AFS::Object::Path") ) {
	$self->_Croak("Invalid argument: must be an AFS::Object::Path object");
    }
    return $self->{_pathnames}->{$path->path()} = $path;
}

sub getCellNames {
    my $self = shift;
    return unless ref $self->{_cells};
    return keys %{$self->{_cells}};
}

sub getCells {
    my $self = shift;
    return unless ref $self->{_cells};
    return values %{$self->{_cells}};
}

sub getCell {
    my $self = shift;
    my $cell = shift;
    return unless ref $self->{_cells};
    return $self->{_cells}->{$cell};
}

sub _addCell {
    my $self = shift;
    my $cell = shift;
    unless ( ref $cell && $cell->isa("AFS::Object::Cell") ) {
	$self->_Croak("Invalid argument: must be an AFS::Object::Cell object");
    }
    return $self->{_cells}->{$cell->cell()} = $cell;
}

sub getServerNames {
    my $self = shift;
    return unless ref $self->{_servers};
    return keys %{$self->{_servers}};
}

sub getServers {
    my $self = shift;
    return unless ref $self->{_servers};
    return values %{$self->{_servers}};
}

sub getServer {
    my $self = shift;
    my $server = shift;
    return unless ref $self->{_servers};
    return $self->{_servers}->{$server};
}

sub _addServer {
    my $self = shift;
    my $server = shift;
    unless ( ref $server && $server->isa("AFS::Object::Server") ) {
	$self->_Croak("Invalid argument: must be an AFS::Object::Server object");
    }
    return $self->{_servers}->{$server->server()} = $server;
}

1;

lib/AFS/Object/Cell.pm  view on Meta::CPAN

#
# $Id$
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

package AFS::Object::Cell;

use strict;

our @ISA = qw(AFS::Object);
our $VERSION = '1.99';

1;

lib/AFS/Object/FileServer.pm  view on Meta::CPAN

#
# $Id$
#
# (c) 2003-2004 Morgan Stanley and Co.
# See ..../src/LICENSE for terms of distribution.
#

package AFS::Object::FileServer;

use strict;

our @ISA = qw(AFS::Object);
our $VERSION = '1.99';

sub getPartitionNames {
    my $self = shift;
    return unless ref $self->{_partitions};
    return keys %{$self->{_partitions}};
}

sub getPartitions {
    my $self = shift;
    return unless ref $self->{_partitions};
    return values %{$self->{_partitions}};
}

sub getPartition {
    my $self = shift;
    my $partname = shift;
    return unless ref $self->{_partitions};
    return $self->{_partitions}->{$partname};
}

sub _addPartition {
    my $self = shift;
    my $partition = shift;
    unless ( ref $partition && $partition->isa("AFS::Object::Partition") ) {
	$self->_Croak("Invalid argument: must be an AFS::Object::Partition object");
    }
    return $self->{_partitions}->{$partition->partition()} = $partition;
}

1;

 view all matches for this distribution
 view release on metacpan -  search on metacpan

( run in 3.225 seconds using v1.00-cache-2.02-grep-82fe00e-cpan-72ae3ad1e6da )