#!/usr/bin/perl -w
#
# Copyright (c) 2012-2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see .
#
# }}}
#
use English;
use strict;
use Getopt::Std;
use File::stat;
use File::Basename;
#
# Convert path based image to directory based image.
#
sub usage()
{
print("Usage: imagetodir [-nq] \n" .
" imagetodir [-nq] -P pid\n" .
" imagetodir [-nq] -a\n" .
"Options:\n".
" -P pid Do all images for a specific project\n".
" -a Do ALL images\n".
" -n Impotent mode\n".
" -q Quiet mode\n");
exit(-1);
}
my $optlist = "qnaP:";
my $impotent = 0;
my $quiet = 0;
my $doall = 0;
my $doallpid;
#
# Configure variables
#
my $TB = "@prefix@";
my $WITHZFS = @WITHZFS@;
my $ZFS_NOEXPORT = @ZFS_NOEXPORT@;
my $EXPORTSSETUP = "$TB/sbin/exports_setup";
my $PROJROOT = "@PROJROOT_DIR@";
# Protos
sub fatal($);
#
# Untaint the path
#
$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/usr/bin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# We don't want to run this script unless you are root.
#
if ($UID != 0) {
die("*** $0:\n".
" Must be root to run this script!\n");
}
#
# Turn off line buffering on output
#
$| = 1;
#
# Load the Testbed support stuff.
#
use lib "@prefix@/lib";
use emutil;
use Node;
use OSImage;
use User;
use Project;
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"n"})) {
$impotent = 1;
}
if (defined($options{"q"})) {
$quiet = 1;
}
if (defined($options{"P"})) {
if ($options{"P"} =~ /^([-\w]+)$/) {
$doallpid = $1;
} else {
fatal("Invalid project name for -P");
}
if (!Project->Lookup($doallpid)) {
fatal("No such project '$doallpid'");
}
}
if (defined($options{"a"})) {
$doall = 1;
}
usage()
if (!$doall && !$doallpid && @ARGV != 1);
sub ConvertImage($)
{
my ($image) = @_;
if ($image->IsDirPath()) {
print STDERR "$image has already been converted\n";
return 0;
}
my @images = ();
if ($image->AllVersions(\@images, 1)) {
print STDERR "Could not get list of (versions) for $image\n";
return -1;
}
my $newpath = dirname($image->path()) . "/" . $image->imagename() . "/";
if (-e $newpath && -f $newpath) {
print STDERR "$newpath already exists and is a file\n";
return -1;
}
if (! -e $newpath) {
my ($uid,$gid);
#
# Project/User info might be gone, so use stat to find out
# the owner and group of the file, use that for the directory.
#
if (-e $image->path()) {
$uid = stat($image->path())->uid;
$gid = stat($image->path())->gid;
}
else {
my $project = $image->GetProject();
if (!defined($project)) {
print STDERR "No project for image\n";
return -1;
}
$gid = $project->unix_gid();
my $user = User->Lookup($image->creator_idx());
if (!defined($user)) {
print STDERR "No creator for image\n";
return -1;
}
$uid = $user->unix_uid();
}
if (! (defined($uid) && defined($gid))) {
print STDERR "Could not get uid/gid from " . $image->path() . "\n";
return -1;
}
if ($impotent) {
print "Would create $newpath\n" if (! $quiet);
}
else {
if (! mkdir("$newpath", 0775)) {
print STDERR "Could not mkdir $newpath: $!\n";
return -1;
}
if (! chmod(0775, "$newpath")) {
print STDERR "Could not chown $newpath: $!\n";
return -1;
}
if (! chown($uid, $gid, $newpath)) {
print STDERR "Could not chown($uid,$gid) $newpath: $!\n";
return -1;
}
}
}
foreach my $imageversion (@images) {
my @todelete = ();
my %torename = ();
my $imagename= $imageversion->imagename();
my $filename = $imageversion->path();
my $basename = basename($filename);
if ($imageversion->isdelta() || $imageversion->HaveDeltaImage()) {
$basename =~ s/\.ndz/\.ddz/;
}
push(@todelete, "$filename.bak");
push(@todelete, "$filename.tmp");
$torename{$filename} = "${newpath}${basename}";
$torename{$filename . ".sig"} = "${newpath}${basename}.sig";
$torename{$filename . ".sha1"} = "${newpath}${basename}.sha1";
foreach my $file (@todelete) {
if (-e $file) {
if ($impotent) {
print "Would delete $file\n" if (! $quiet);
next;
}
if (! unlink($file)) {
print STDERR "Could not unlink $file\n";
return -1;
}
}
}
foreach my $file (keys(%torename)) {
my $newname = $torename{$file};
next
if (! -e $file);
if ($impotent) {
if (! $quiet) {
print "Would rename $file to $newname\n";
}
next;
}
if (-e $file) {
system("/bin/mv -fv $file $newname");
if ($?) {
print STDERR "Could not rename $file to $newname\n";
return -1;
}
}
}
if ($impotent) {
print "Would update $imageversion to new directory\n" if (! $quiet);
next;
}
if ($imageversion->Update({"path" => $newpath})) {
print STDERR "Could not update path for $imageversion\n";
return -1;
}
}
}
if ($doall || $doallpid) {
my @images = OSImage->ListAll(undef, $doallpid);
if ($doallpid && @images == 0) {
print "No images in project '$doallpid'\n";
exit(0);
}
my %lastactive = ();
if ($WITHZFS && $ZFS_NOEXPORT) {
#
# Have to force the new directories to be exported.
# See ZFS code in exports_setup
#
my %projs = ();
foreach my $imagename (@images) {
# XXX OSImage->Lookup blows up on old multi-partition images
my $image = Image->Lookup($imagename);
if ($image && $image->ezid()) {
$image = OSImage->Lookup($imagename);
}
my $project = $image->GetProject();
if (!exists($projs{$project->pid()})) {
$projs{$project->pid()} = $project;
}
}
foreach my $pname (keys %projs) {
if (! -d "$PROJROOT/$pname/images") {
my $project = $projs{$pname};
$lastactive{$pname}{'obj'} = $project;
$lastactive{$pname}{'last'} = $project->GetActivity();
if (!$impotent) {
$project->BumpActivity();
}
}
}
if (keys(%lastactive) > 0) {
my $activate = int(keys %lastactive);
print STDERR "Found $activate inactive projects, activating ...\n";
if (!$impotent) {
system($EXPORTSSETUP) == 0 or
fatal("$EXPORTSSETUP failed");
}
}
}
foreach my $imagename (@images) {
# XXX OSImage->Lookup blows up on old multi-partition images
my $image = Image->Lookup($imagename);
if ($image && $image->ezid()) {
$image = OSImage->Lookup($imagename);
}
next
if (!defined($image->path()));
if ($WITHZFS) {
my $project = $image->GetProject();
my $pid = $project->pid();
#
# There is some lag before the automounter can mount the new volume.
#
if (emutil::waitForMount("$PROJROOT/$pid") < 0) {
print STDERR "Could not access $PROJROOT/$pid, ignoring\n";
next;
}
}
print "--> $image\n";
ConvertImage($image);
}
# Restore last legit activity timestamp for otherwise inactive projects
if ($WITHZFS && $ZFS_NOEXPORT && keys(%lastactive) > 0) {
print STDERR "Restoring last activity stamps ...\n";
foreach my $pname (keys %lastactive) {
my $project = $lastactive{$pname}{'obj'};
my $last = $lastactive{$pname}{'last'};
if (!$impotent) {
$project->SetActivity($last);
}
}
if (!$impotent) {
system($EXPORTSSETUP) == 0 or
fatal("$EXPORTSSETUP failed");
}
}
}
else {
my $image = OSImage->Lookup($ARGV[0]);
if (!defined($image)) {
fatal("No such image exists");
}
exit(ConvertImage($image));
}
exit(0);
sub fatal($)
{
my ($mesg) = $_[0];
die("*** $0:\n".
" $mesg\n");
}