mirror of
				https://github.com/xcat2/xcat-core.git
				synced 2025-10-31 03:12:30 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			607 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			607 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| package xCAT_plugin::litetree;
 | |
| 
 | |
| BEGIN
 | |
| {
 | |
|     $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
 | |
| }
 | |
| use lib "$::XCATROOT/lib/perl";
 | |
| 
 | |
| use xCAT::NodeRange;
 | |
| use Data::Dumper;
 | |
| use xCAT::Utils;
 | |
| use Sys::Syslog;
 | |
| use xCAT::GlobalDef;
 | |
| use xCAT::Table;
 | |
| use Getopt::Long;
 | |
| use xCAT::SvrUtils;
 | |
| Getopt::Long::Configure("bundling");
 | |
| Getopt::Long::Configure("pass_through");
 | |
| 
 | |
| use strict;
 | |
| 
 | |
| # synchonize files and directories from mulitple sources.
 | |
| 
 | |
| # object is to return a list of files to be syncronized.
 | |
| # requesting them.  By default we will do read-write files.
 | |
| 
 | |
| 
 | |
| my $syncdirTab  = "litetree";
 | |
| my $syncfileTab = "litefile";
 | |
| my $synclocTab  = "statelite";
 | |
| my $errored     = 0;
 | |
| 
 | |
| 
 | |
| sub handled_commands {
 | |
| 
 | |
|     # command is syncmount, syncdir is the perl module to use.
 | |
|     return {
 | |
|         litetree  => "litetree",
 | |
|         litefile  => "litetree",
 | |
|         ilitefile => "litetree",
 | |
|         lslite    => "litetree"
 | |
|       }
 | |
| }
 | |
| 
 | |
| sub usage {
 | |
|     my $command  = shift;
 | |
|     my $callback = shift;
 | |
|     my $error    = shift;
 | |
|     my $msg;
 | |
|     if ($command eq "ilitefile") {
 | |
|         $msg = "Usage: ilitefile <imagename>
 | |
| \texample:\n\tilitefile centos5.3-x86_64-statelite-compute"
 | |
|     } elsif ($command eq "litefile") {
 | |
|         $msg = "Usage: litefile <noderange>\nexample:\n\tlitefile node1";
 | |
|     } elsif ($command eq "litetree") {
 | |
|         $msg = "Usage: litetree <noderange>\nexample:\n\tlitetree node1";
 | |
|     } else {
 | |
|         $msg = "some general usage string";
 | |
|     }
 | |
| 
 | |
|     if ($error) {
 | |
|         $callback->({ error => [$msg], errorcode => [$error] });
 | |
|     } else {
 | |
|         $callback->({ info => [$msg] });
 | |
|     }
 | |
| }
 | |
| 
 | |
| sub process_request {
 | |
|     my $request  = shift;
 | |
|     my $callback = shift;
 | |
|     my $noderange;
 | |
|     my $image;
 | |
| 
 | |
|     # the request can come from node or some one running command
 | |
|     # on xCAT mgmt server
 | |
|     # argument could also be image...
 | |
| 
 | |
|     # if request comes from user:
 | |
|     if ($request->{node}) {
 | |
|         $noderange = $request->{node};
 | |
| 
 | |
|         # if request comes from node post script .awk file.
 | |
|     } elsif ($request->{'_xcat_clienthost'}) {
 | |
|         my @nodenames = noderange($request->{'_xcat_clienthost'}->[0] . "," . $request->{'_xcat_clientfqdn'}->[0]);
 | |
|         if (@nodenames) {
 | |
|             $noderange = \@nodenames;
 | |
|             $request->{node} = $noderange;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     my $command = $request->{command}->[0];
 | |
|     if ($command eq "litetree") {
 | |
|         unless ($request->{node}) {
 | |
|             usage($command, $callback, 0);
 | |
|             return 1;
 | |
|         }
 | |
|         return syncmount("dir", $request, $callback, $noderange);
 | |
|     } elsif ($command eq "litefile") {
 | |
|         unless ($request->{node}) {
 | |
|             usage($command, $callback, 0);
 | |
|             return 1;
 | |
|         }
 | |
|         return syncmount("file", $request, $callback, $noderange);
 | |
|     } elsif ($command eq "ilitefile") {
 | |
| 
 | |
|         #print Dumper($request);
 | |
|         unless ($request->{arg}) {
 | |
|             usage($command, $callback, 0);
 | |
|             return 1;
 | |
|         }
 | |
|         return syncmount("image", $request, $callback, $request->{arg});
 | |
|     } elsif ($command eq "lslite") {
 | |
|         if (defined $noderange)
 | |
|         {
 | |
|             &lslite($request, $callback, $noderange);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             &lslite($request, $callback, undef);
 | |
|         }
 | |
|         return;
 | |
|     } else {
 | |
|         $callback->({ error => ["error in code..."], errorcode => [127] });
 | |
|         $request = {};
 | |
|         return;
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| sub syncmount {
 | |
|     my $syncType = shift;
 | |
|     my $request  = shift;
 | |
|     my $callback = shift;
 | |
| 
 | |
|     # deterimine which node is calling this
 | |
|     # then find out what directories to use.
 | |
|     my $noderange = shift;
 | |
|     my @nodes     = @{$noderange};
 | |
|     my $tab;
 | |
|     if ($syncType eq 'dir') {
 | |
|         $tab = xCAT::Table->new($syncdirTab, -create => 1);
 | |
|     } elsif ($syncType =~ /file|image/) {
 | |
|         $tab = xCAT::Table->new($syncfileTab, -create => 1);
 | |
|     } elsif ($syncType =~ /location/) {
 | |
|         $tab = xCAT::Table->new($synclocTab, -create => 1);
 | |
|     } else {
 | |
|         $callback->({ error => ["error in code..."], errorcode => [127] });
 | |
|         $request = {};
 | |
|         return;
 | |
|     }
 | |
|     my $ostab;
 | |
|     my %osents;
 | |
|     unless ($syncType =~ /image/) {
 | |
|         $ostab = xCAT::Table->new('nodetype');
 | |
|         %osents = %{ $ostab->getNodesAttribs(\@nodes, [ 'profile', 'os', 'arch', 'provmethod' ]) };
 | |
|     }
 | |
|     foreach my $node (@nodes) {
 | |
| 
 | |
|         # node may be an image...
 | |
|         my $image;
 | |
|         my $ent;
 | |
|         if ($syncType !~ /image/) {
 | |
|             $ent = $osents{$node}->[0];
 | |
| 
 | |
|             if (xCAT::Utils->isAIX()) {
 | |
|                 $image = $ent->{provmethod};
 | |
|             } else {
 | |
| 
 | |
|                 unless ($ent->{os} && $ent->{arch} && $ent->{profile}) {
 | |
|                     $callback->({ error => ["$node does not have os, arch, or profile defined in nodetype table"], errorcode => [1] });
 | |
|                     $request = {};
 | |
|                     next;
 | |
|                 }
 | |
| 
 | |
|                 if ((!$ent->{provmethod}) || ($ent->{provmethod} eq 'statelite') || ($ent->{provmethod} eq 'netboot') || ($ent->{provmethod} eq 'install')) {
 | |
|                     $image = $ent->{os} . "-" . $ent->{arch} . "-statelite-" . $ent->{profile};
 | |
|                 } elsif (($ent->{provmethod} ne 'netboot') && ($ent->{provmethod} ne 'install')) {
 | |
|                     $image = $ent->{provmethod};
 | |
|                 }
 | |
|             }
 | |
|         } else {
 | |
|             $image = $node;
 | |
|         }
 | |
|         my $fData = getNodeData($syncType, $node, $image, $tab, $callback);
 | |
| 
 | |
|         # now we go through each directory and search for the file.
 | |
|         showSync($syncType, $callback, $node, $fData);
 | |
|     }
 | |
| }
 | |
| 
 | |
| # In most cases the syncdir will be on the management node so
 | |
| # want to make sure its not us before we mount things.
 | |
| sub showSync {
 | |
|     my $syncType = shift;    # dir or file
 | |
|     my $callback = shift;
 | |
|     my $node     = shift;
 | |
|     my $dirs     = shift;
 | |
|     my $mnts;
 | |
|     my $first;
 | |
| 
 | |
|     #print Dumper($dirs);
 | |
|     # go through each directory in priority order
 | |
|     #mkdir "/mnt/xcat";
 | |
|     if ($syncType eq "dir") {
 | |
| 
 | |
|         foreach my $priority (sort { $a <=> $b } keys %$dirs) {
 | |
| 
 | |
|             # split the nfs server up from the directory:
 | |
|             my $mntpnt;
 | |
|             my ($server, $dir, $mntopts) = split(/:/, $dirs->{$priority});
 | |
| 
 | |
|             # if server is blank then its the directory:
 | |
|             unless ($dir) {
 | |
|                 $dir    = $server;
 | |
|                 $server = '';
 | |
|             }
 | |
| 
 | |
|             if (grep /\$|#CMD/, $dir) {
 | |
|                 $dir = xCAT::SvrUtils->subVars($dir, $node, 'dir', $callback);
 | |
|                 $dir =~ s/\/\//\//g;
 | |
|             }
 | |
|             $first = $dir;
 | |
|             $first =~ s!\/([^/]*)\/.*!$1!;
 | |
| 
 | |
|             if ($server) {
 | |
|                 if (grep /\$/, $server) {
 | |
|                     $server = xCAT::SvrUtils->subVars($server, $node, 'server', $callback);
 | |
|                 }
 | |
| 
 | |
|                 $mntpnt = $server . ":";
 | |
| 
 | |
|                 # we have a server and need to make sure we can mount them under unique names
 | |
|                 if ($mnts->{$first} eq '') { # if the first mount point doesn't have a server then leave it.
 | |
|                     $mnts->{$first} = $server;
 | |
|                 }
 | |
|             }
 | |
|             $mntpnt .= $dir;
 | |
| 
 | |
|             # add the mount options if we have any
 | |
|             if ($mntopts) {
 | |
|                 $mntpnt .= " :$mntopts";
 | |
|             }
 | |
| 
 | |
|             # ok, now we have all the mount points.  Let's go through them all?
 | |
|             if ($::from_lslite == 1)
 | |
|             {
 | |
|                 $callback->({ info => "        $priority, $mntpnt" });
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 $callback->({ info => "$node: $mntpnt" });
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     } elsif ($syncType =~ /file|image/) {
 | |
|         foreach my $file (sort keys %$dirs) {
 | |
|             my $options = $dirs->{$file};
 | |
| 
 | |
|             # persistent,rw
 | |
|             my $out;
 | |
|             if ($::from_lslite == 1)
 | |
|             {
 | |
|                 $out = sprintf("        %-13s %s", $options, $file);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 $out = sprintf("%s: %-13s %s", $node, $options, $file);
 | |
|             }
 | |
| 
 | |
|             $callback->({ info => $out });
 | |
|         }
 | |
|     } elsif ($syncType =~ /location/) {
 | |
|         foreach my $node (sort keys %$dirs) {
 | |
|             my $location = $dirs->{$node};
 | |
| 
 | |
|             my ($server, $dir) = split(/:/, $location);
 | |
| 
 | |
|             if (grep /\$|#CMD/, $dir)
 | |
|             {
 | |
|                 $dir = xCAT::SvrUtils->subVars($dir, $node, 'dir', $callback);
 | |
|                 $dir =~ s/\/\//\//g;
 | |
|             }
 | |
| 
 | |
|             if (grep /\$/, $server)
 | |
|             {
 | |
|                 $server = xCAT::SvrUtils->subVars($server, $node, 'server', $callback);
 | |
|             }
 | |
| 
 | |
|             $location = $server . ":" . $dir;
 | |
| 
 | |
|             if ($::from_lslite == 1)
 | |
|             {
 | |
|                 $callback->({ info => "        $location" });
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 $callback->({ info => "$node: $location" });
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| # get all the directories or files for given image related to this node.
 | |
| sub getNodeData {
 | |
|     my $type  = shift;
 | |
|     my $node  = shift;
 | |
|     my $image = shift;
 | |
|     my $tab   = shift;
 | |
|     my $cb    = shift;    # callback to print messages!!
 | |
|          # the image name will be something like rhels5.4-x86_64-nfsroot
 | |
|          #my $image;
 | |
|          #unless($type =~ /image/){
 | |
|       #	$image = $ent->{os} . "-" . $ent->{arch} . "-statelite-" . $ent->{profile};
 | |
|       #}else{
 | |
|       #	$image = $node;
 | |
|       #}
 | |
| 
 | |
|     my @imageInfo;
 | |
|     my @attrs;
 | |
|     my @imagegroupsattr = ('groups');
 | |
|     if ($type eq "dir") {
 | |
|         @attrs = ('priority', 'directory', 'mntopts');
 | |
|     } elsif ($type =~ /file|image/) {
 | |
|         @attrs = ('file', 'options');
 | |
|     } elsif ($type =~ /location/) {
 | |
|         @attrs = ('node', 'statemnt');
 | |
|     } else {
 | |
|         print "Yikes! error in the code litefile;getNodeData!";
 | |
|         exit 1;
 | |
|     }
 | |
| 
 | |
|     if ($type eq "location")
 | |
|     {
 | |
|         # get locations with specific nodes
 | |
|         push @imageInfo, $tab->getNodeAttribs($node, @attrs);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         # Check if this image contains osimage.groups attribute.
 | |
|         # if so, means user wants to use specific directories to this image.
 | |
|         my $osimagetab = xCAT::Table->new("osimage", -create => 1);
 | |
|         my $imagegroups = $osimagetab->getAttribs({ imagename => $image }, @imagegroupsattr);
 | |
|         if ($imagegroups and $imagegroups->{groups}) {
 | |
| 
 | |
|             # get the directories with no names
 | |
|             push @imageInfo, $tab->getAttribs({ image => '' }, @attrs);
 | |
| 
 | |
|             # get for the image groups specific directories
 | |
|             push @imageInfo, $tab->getAttribs({ image => $imagegroups->{groups} }, @attrs);
 | |
| 
 | |
|             # get for the image specific directories
 | |
|             push @imageInfo, $tab->getAttribs({ image => $image }, @attrs);
 | |
|         } else {
 | |
| 
 | |
|             # get the directories with no names
 | |
|             push @imageInfo, $tab->getAttribs({ image => '' }, @attrs);
 | |
| 
 | |
|             # get the ALL directories
 | |
|             push @imageInfo, $tab->getAttribs({ image => 'ALL' }, @attrs);
 | |
| 
 | |
|             # get for the image specific directories
 | |
|             push @imageInfo, $tab->getAttribs({ image => $image }, @attrs);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     # pass back a reference to the directory
 | |
| 
 | |
|     # now we need to sort them
 | |
|     return mergeArrays($type, \@imageInfo, $cb);
 | |
| }
 | |
| 
 | |
| sub mergeArrays {
 | |
|     my $type = shift;    # file or dir?
 | |
|     my $arr  = shift;    # array of info from the tables.
 | |
|     my $cb   = shift;    # callback routine
 | |
|     my $attrs;
 | |
|     if ($type eq "dir") {
 | |
|         foreach (@$arr) {
 | |
|             if ($_->{directory} eq '') { next; }
 | |
|             $attrs->{ $_->{priority} } = "$_->{directory}:$_->{mntopts}";
 | |
|         }
 | |
|     } elsif ($type =~ /file|image/) {
 | |
| 
 | |
|         my $doesMtabExists = 0;
 | |
|         foreach (@$arr) {
 | |
|             next if ($_->{file} eq '');
 | |
|             my $o = $_->{options};
 | |
|             unless ($o) {
 | |
|                 if (xCAT::Utils->isAIX()) {
 | |
|                     $o = "rw";    # default option if not provided
 | |
|                 } else {
 | |
| 
 | |
|                     # for compatible reason, the default option is set to "tmpfs,rw"
 | |
|                     $o = "tmpfs,rw";
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if ($_->{file} eq "/etc/mtab") {
 | |
|                 $doesMtabExists = 1;
 | |
| 
 | |
|                 # TODO
 | |
|                 # let the user know the "link" option should be with
 | |
|                 # /etc/mtab
 | |
|                 $o = "tmpfs,rw";
 | |
|             }
 | |
| 
 | |
|             # TODO: put some logic in here to make sure that ro is alone.
 | |
|             # if type is ro and con, then this is wrong silly!
 | |
|             #if($p eq "ro" and $t eq "con"){
 | |
|             #	my $f = $_->{file};
 | |
|             #	$cb->({info => "#skipping: $f.  not allowed to be ro and con"});
 | |
|             #	next;
 | |
|             #}
 | |
|             $attrs->{ $_->{file} } = $o;
 | |
|         }
 | |
| 
 | |
|         if (xCAT::Utils->isLinux()) {
 | |
|             if ($doesMtabExists eq 0) {
 | |
|                 $attrs->{"/etc/mtab"} = "link";
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     } elsif ($type =~ /location/) {
 | |
|         foreach (@$arr)
 | |
|         {
 | |
|             if ($_->{statemnt} eq '') { next; }
 | |
|             $attrs->{ $_->{node} } = $_->{statemnt};
 | |
|         }
 | |
| 
 | |
|     } else {
 | |
|         print "Yikes!  Error in the code in mergeArrays!\n";
 | |
|         exit 1;
 | |
|     }
 | |
| 
 | |
|     #print "mergeArrays...\n";
 | |
|     #print Dumper($attrs);
 | |
|     return $attrs;
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3  lslite_usage
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-----------------------------------------------------------------------------
 | |
| 
 | |
| sub lslite_usage
 | |
| {
 | |
|     my $callback = shift;
 | |
| 
 | |
|     my $rsp;
 | |
|     push @{ $rsp->{data} },
 | |
| "\n  lslite - Display a summary of the statelite information \n\t\tthat has been defined for a noderange or an image.";
 | |
|     push @{ $rsp->{data} }, "  Usage: ";
 | |
|     push @{ $rsp->{data} }, "\tlslite [-h | --help]";
 | |
|     push @{ $rsp->{data} }, "or";
 | |
|     push @{ $rsp->{data} }, "\tlslite [-V | --verbose] [-i imagename] | [noderange]";
 | |
| 
 | |
|     xCAT::MsgUtils->message("I", $rsp, $callback);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| sub lslite {
 | |
|     my $request   = shift;
 | |
|     my $callback  = shift;
 | |
|     my $noderange = shift;
 | |
|     my @image;
 | |
|     $::from_lslite = 1;    # to control the output format
 | |
| 
 | |
|     unless ($request->{arg} || defined $noderange)
 | |
|     {
 | |
|         &lslite_usage($callback);
 | |
|         return 1;
 | |
|     }
 | |
| 
 | |
|     # parse the options
 | |
|     Getopt::Long::Configure("no_pass_through");
 | |
|     Getopt::Long::Configure("bundling");
 | |
| 
 | |
|     if ($request->{arg})
 | |
|     {
 | |
|         @ARGV = @{ $request->{arg} };
 | |
| 
 | |
|         if (
 | |
|             !GetOptions(
 | |
|                 'h|help'    => \$::HELP,
 | |
|                 'i=s'       => \$::OSIMAGE,
 | |
|                 'V|verbose' => \$::VERBOSE,
 | |
|             )
 | |
|           )
 | |
|         {
 | |
|             &lslite_usage($callback);
 | |
|             return 1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if ($::HELP)
 | |
|     {
 | |
|         &lslite_usage($callback);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     # handle "lslite -i image1"
 | |
|     # use the logic for ilitefile
 | |
|     if ($::OSIMAGE)
 | |
|     {
 | |
|         # make sure the osimage is defined
 | |
|         my @imglist = xCAT::DBobjUtils->getObjectsOfType('osimage');
 | |
|         if (!grep(/^$::OSIMAGE$/, @imglist))
 | |
|         {
 | |
|             $callback->({ error => ["The osimage named \'$::OSIMAGE\' is not defined."], errorcode => [1] });
 | |
|             return 1;
 | |
|         }
 | |
| 
 | |
|         @image = join(',', $::OSIMAGE);
 | |
|         syncmount("image", $request, $callback, \@image);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     # handle "lslite node1"
 | |
|     my @nodes;
 | |
|     if (defined $noderange)
 | |
|     {
 | |
|         @nodes = @{$noderange};
 | |
| 
 | |
|         if (scalar @nodes)
 | |
|         {
 | |
|             # get node's osimage/profile
 | |
|             my $nttab = xCAT::Table->new('nodetype');
 | |
|             my $nttabdata = $nttab->getNodesAttribs(\@nodes, [ 'node', 'profile', 'os', 'arch', 'provmethod' ]);
 | |
| 
 | |
|             foreach my $node (@nodes)
 | |
|             {
 | |
|                 my $image;
 | |
|                 my $data = $nttabdata->{$node}->[0];
 | |
| 
 | |
|                 if (xCAT::Utils->isAIX())
 | |
|                 {
 | |
|                     if (defined($data->{provmethod}))
 | |
|                     {
 | |
|                         $image = $data->{provmethod};
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         $callback->({ error => ["no provmethod defined for node $node."], errorcode => [1] });
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     if ((!$data->{provmethod}) || ($data->{provmethod} eq 'statelite') || ($data->{provmethod} eq 'netboot') || ($data->{provmethod} eq 'install'))
 | |
|                     {
 | |
|                         $image = $data->{os} . "-" . $data->{arch} . "-statelite-" . $data->{profile};
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         $image = $data->{provmethod};
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 $callback->({ info => ">>>Node: $node\n" });
 | |
|                 $callback->({ info => "Osimage: $image\n" });
 | |
| 
 | |
|                 # structure node as ARRAY
 | |
|                 my @tmpnode = join(',', $node);
 | |
| 
 | |
|                 my @types = ("location", "file", "dir");
 | |
|                 foreach my $type (@types)
 | |
|                 {
 | |
|                     if ($type eq "location")
 | |
|                     {
 | |
|                         # show statelite table
 | |
|                         $callback->({ info => "Persistent directory (statelite table):" }); }
 | |
|                     elsif ($type eq "file")
 | |
|                     {
 | |
|                         # show litefile table
 | |
|                         $callback->({ info => "Litefiles (litefile table):" });
 | |
|                     }
 | |
|                     elsif ($type eq "dir")
 | |
|                     {
 | |
|                         # show litetree table
 | |
|                         $callback->({ info => "Litetree path (litetree table):" });
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         $callback->({ error => ["Invalid type."], errorcode => [1] });
 | |
|                         return 1;
 | |
|                     }
 | |
| 
 | |
|                     syncmount($type, $request, $callback, \@tmpnode);
 | |
|                     $callback->({ info => "\n" });
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return;
 | |
| 
 | |
| }
 | |
| 
 | |
| 1;
 | |
| 
 | |
| #vim: set ts=2
 | |
| 
 |