delete_image.in 11.4 KB
Newer Older
1 2
#!/usr/bin/perl -w
#
3
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
# 
# {{{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 <http://www.gnu.org/licenses/>.
# 
# }}}
#
use English;
use strict;
use Getopt::Std;
27
use File::Basename;
28 29 30 31 32 33 34
use Data::Dumper;

#
# Delete an image (descriptor) 
#
sub usage()
{
35
    print("Usage: delete_image [[-f | -F] -p | -r] <imagename>\n".
36
	  "Options:\n".
37
	  "       -p     Purge the disk image file(s)\n".
38
	  "       -r     Rename the disk image file(s) instead (default)\n".
39
	  "       -n     Impotent mode, show what would be done.\n".
40
	  "       -f     Force deletion of system image\n".
41
	  "       -V     Delete only the version\n".
42
	  "       -F     Force deletion of global system image\n");
43 44
    exit(-1);
}
45
my $optlist     = "dFprnfV";
46 47
my $debug       = 0;
my $purge       = 0;
48
my $rename      = 1;
49
my $force       = 0;
50
my $FORCE       = 0;
51 52
my $impotent    = 0;
my $needunlock  = 0;
53
my $versonly    = 0;
54 55 56 57

#
# Configure variables
#
58 59 60 61
my $TB             = "@prefix@";
my $PROJROOT       = "@PROJROOT_DIR@";
my $TBOPS          = "@TBOPSEMAIL@";
my $friskiller     = "$TB/sbin/frisbeehelper";
62
my $PGENISUPPORT   = @PROTOGENI_SUPPORT@;
63 64
my $WITHPROVENANCE = @IMAGEPROVENANCE@;
my $doprovenance   = 0;
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89

#
# Untaint the path
#
$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/usr/bin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

#
# Turn off line buffering on output
#
$| = 1;

#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
    die("*** $0:\n".
	"    Must be setuid! Maybe its a development version?\n");
}

#
# Load the Testbed support stuff.
#
use lib "@prefix@/lib";
use EmulabConstants;
90
use EmulabFeatures;
91
use libEmulab;
92 93
use libtestbed;
use User;
94
use OSImage;
95 96 97 98 99 100
if ($PGENISUPPORT) {
    use vars qw($GENI_DBNAME);
    $GENI_DBNAME = "geni-cm";
    require GeniHRN;
    require GeniUser;
}
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

# Protos
sub fatal($);

#
# 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{"d"})) {
    $debug = 1;
}
if (defined($options{"p"})) {
117 118
    $purge  = 1;
    $rename = 0;
119
}
120 121 122 123 124 125
if (defined($options{"r"})) {
    $rename = 1;
}
if (defined($options{"n"})) {
    $impotent = 1;
}
126 127 128
if (defined($options{"V"})) {
    $versonly = 1;
}
129
if (defined($options{"F"})) {
130 131 132
    $FORCE = 1;
}
if (defined($options{"f"})) {
133 134
    $force = 1;
}
135 136
usage()
    if (@ARGV != 1);
137
usage()
138
    if (! ($purge || $rename));
139 140 141 142 143 144 145 146 147 148

my $imageid = shift(@ARGV);

#
# Map invoking user to object. 
#
my $this_user = User->ThisUser();
if (! defined($this_user)) {
    fatal("You ($UID) do not exist!");
}
149
my $image = OSImage->Lookup($imageid);
150 151 152 153 154 155
if (!defined($image)) {
    fatal("Image does not exist in the DB!");
}
if (!$image->AccessCheck($this_user, TB_IMAGEID_DESTROY())) {
    fatal("You do not have permission to delete this image!");
}
156

157
if ($image->pid() eq TBOPSPID() && $image->global() && !$FORCE) {
158 159 160 161
    fatal("Refusing to delete global system image $image. ".
	  "Use -F if you are sure.\n");
}

162 163 164
#
# Before we do anything destructive, we lock the descriptor.
#
165 166 167 168 169
if (!$impotent) {
    if ($image->Lock()) {
	fatal("Image is locked, please try again later!\n");
    }
    $needunlock = 1;
170
}
171 172 173
$imageid      = $image->imageid();
my $imagename = $image->imagename();
my $imagepid  = $image->pid();
174 175 176 177 178 179
my $imagevers = $image->version();

# Sanity check; cannot delete a deleted version.
if ($versonly && defined($image->deleted())) {
    fatal("Image version is already deleted");
}
180

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
#
# We want to send email to the creator. Also, watch for an image created
# on the Geni path; the creator urn tells us who the creator is, rather
# then who is calling the script. When PROTOGENI_LOCALUSER=0 there is no
# local creator, but when set there is a local shadow user we can use.
#
my $notifyuser = $image->GetCreator();
if (!defined($notifyuser)) {
    $notifyuser = $this_user;
}
if (defined($image->creator_urn())) {
    my $geniuser = GeniUser->Lookup($image->creator_urn(), 1);
    if (defined($geniuser) && $geniuser->IsLocal()) {
	$notifyuser = $geniuser->emulab_user();
    }
    else {
	# This is okay, it is just for email below.
	$notifyuser = $geniuser;
    }
}
if ($debug) {
    print STDERR "Will send email to $notifyuser\n";
}

205 206 207 208 209 210 211 212 213
#
# Need root to delete the image file later.
#
$EUID = $UID;

#
# Be sure to kill off running frisbee. If a node is trying to load that
# image, well tough. 
#
214 215 216 217 218
if (!$impotent) {
    system("$friskiller -k $imageid");
    if ($?) {
	fatal("Could not kill running frisbee for $imageid!");
    }
219 220
}

221 222 223 224
#
# When IMAGEPROVENANCE is on, we never delete system images, we
# rename them. 
#
225
if ($image->pid() eq TBOPSPID() && !$force) {
226 227 228 229 230 231 232 233
    if ($purge) {
	$purge  = 0;
	print STDERR "Ignoring purge option for system image. \n";
    }
    if ($WITHPROVENANCE) {
	print STDERR "Turning on rename option for system image. \n";
	$rename = 1;
    }
234 235
}

236 237 238 239
#
# Since admins will often delete image descriptors for users, we are
# setuid root. Flip for deleting the image file.
#
240
if ($purge || $rename) {
241 242
    my $isdirpath = $image->IsDirPath();
    
243 244
    #
    # When doing image provenance, we have to deal with all versions
245
    # of the image. This will not return deleted versions.
246 247 248 249
    #
    my @images = ();
    if ($image->AllVersions(\@images)) {
	fatal("Could not get list of image (versions)");
250
    }
251 252 253 254 255 256 257 258 259 260 261
    #
    # When deleting just a single version, if this is the last or only
    # version, then turn off version only. Makes no sense to have a
    # descriptor with no non-deleted versions.
    #
    if ($versonly && scalar(@images) == 1) {
	$versonly = 0;
    }
    if ($versonly) {
	@images = ($image);
    }
262 263 264

    #
    # If the path is a directory, we can just do a rename on it.
265
    # But not if deleting just a single image version.
266
    #
267
    if ($isdirpath && !$versonly) {
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
	my $dirname = $image->path();
	
	if ($purge) {
	    if ($impotent) {
		print "Would remove directory $dirname\n" if (-e $dirname);
	    }
	    else {
		$EUID = 0;
		system("/bin/rm -rf $dirname");
		if ($?) {
		    fatal("Could not remove $dirname");
		}
		$EUID = $UID;
	    }
	}
	else {
Leigh B Stoller's avatar
Leigh B Stoller committed
284
	    my $newname = dirname($dirname) . "/" . basename($dirname) .
285
		"," . $image->imageid();
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

	    if ($impotent) {
		print "Would rename $dirname to $newname\n" if (-e $dirname);
	    }
	    else {
		if (-e $dirname) {
		    $EUID = 0;
		    system("/bin/mv -fv $dirname $newname");
		    if ($?) {
			fatal("Could not rename $dirname to $newname");
		    }
		    $EUID = $UID;
		}
		# Hmm, need an update all versions method.
		foreach my $imageversion (@images) {
301 302
		    # Need trailing slash!
		    $imageversion->Update({"path" => $newname . "/"});
303 304 305 306 307 308 309 310
		}
	    }
	}
	#
	# Fall into the loop below to clean up stale image versions and
	# backup files.
	#
    }
311 312 313
    foreach my $imageversion (@images) {
	my @todelete = ();
	my @torename = ();
314
	my $filename = $imageversion->FullImageFile();
315 316 317

	push(@torename, $filename);
	push(@todelete, "$filename.bak");
318 319 320 321 322 323 324 325 326 327 328
	push(@todelete, "$filename.tmp");
	push(@torename, $imageversion->FullImageSHA1File());
	push(@torename, $imageversion->FullImageSigFile());

	# Backwards compat with non-directory image paths.
	if ($filename ne $imageversion->DeltaImageFile()) {
	    $filename = $imageversion->DeltaImageFile();
	    push(@torename, $filename);
	    push(@todelete, "$filename.bak");
	    push(@todelete, "$filename.tmp");
	    push(@torename, $imageversion->DeltaImageSHA1File());
329 330 331 332 333 334 335 336 337
	}

	# We throw away versions that never came ready or released.
	if ($purge ||
	    !($imageversion->ready() && $imageversion->released())) {
	    @todelete = (@todelete, @torename);
	    @torename = ();
	}
	# Throw away the slot if it never came ready or released.
Leigh B Stoller's avatar
Leigh B Stoller committed
338 339 340
	# Only if the highest numbered version, no holes please.
	if ($imageversion->IsNewest() &&
	    !($imageversion->ready() && $imageversion->released())) {
341 342
	    if ($impotent) {
		my $vers = $imageversion->version();
Leigh B Stoller's avatar
Leigh B Stoller committed
343 344 345 346 347
		print "Would kill version $vers DB state since it was ".
		    "not ready/released\n";
	    }
	    else {
		$imageversion->PurgeVersion();
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
	    }
	}

	$EUID = 0;
	foreach my $file (@todelete) {
	    if (-e $file) {
		if ($impotent) {
		    print "Would delete $file\n";
		    next;
		}
		if (! unlink($file)) {
		    SENDMAIL($TBOPS,
			     "delete_image: Could not remove image file",
			     "Could not remove $file\n".
			     "Someone will need to do this by hand.\n");
		}
364 365
	    }
	}
366 367 368
	$EUID = $UID;
	
	#
369 370 371 372
	# Skip renames for directory based images. 
	# Note that when deleting a single version in an image directory,
	# we do not want to do a rename. That would be confusing if some
	# versions were deleted and then the entire image deleted later.
373 374 375 376
	#
	next
	    if ($isdirpath);
	
377 378 379 380 381
	#
	# Delete with rename; move the current files out of the way
	# so that they do not conflict with a later image of the same name.
	# We do this by creating a subdir for the files.
	#
382
	$EUID = 0;
383 384
	if (@torename) {
	    my $dirname = dirname($imageversion->path()) .
385
		"/" . $image->imagename() . "," . $image->imageid();
386

387 388 389 390 391
	    if (! -e $dirname) {
		if ($impotent) {
		    print "Would mkdir($dirname)\n";
		}
		elsif (! mkdir("$dirname", 0775)) {
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
		    fatal("Could not mkdir $dirname");
		}
	    }
	    foreach my $file (@torename) {
		my $newname = $dirname . "/" . basename($file);
		
		if ($impotent) {
		    print "Would rename $file to $newname\n" if (-e $file);
		    next;
		}
		if (-e $file) {
		    system("/bin/mv -fv $file $newname");
		    if ($?) {
			fatal("Could not rename $file to $dirname");
		    }
		}
		if ($file eq $filename &&
		    $imageversion->Update({"path" => $newname})) {
		    fatal("Could not update path for $imageversion");
		}
	    }
	}
	$EUID = $UID;
415 416
    }
}
417 418 419
exit(0)
    if ($impotent);

420 421 422
#
# If using the image tracker, have to notify the IMS.
#
423 424 425
if (!$versonly) {
    # Do this before delete().
    if (GetSiteVar("protogeni/use_imagetracker")) {
426
	$image->SchedIMSDeletion(1) == 0
427 428 429 430 431
	    or fatal("Could not schedule IMS deletion");
    }
    if ($image->Delete() != 0) {
	fatal("Could not delete image!");
    }
432 433 434
    $notifyuser->SendEmail("delete_image: Image has been deleted",
			   "Image $imagepid,$imagename ($imageid) has ".
			   "been deleted by $this_user\n");
435
}
436
else {
437 438 439 440 441
    # Do this before delete().
    if (GetSiteVar("protogeni/use_imagetracker")) {
	$image->SchedIMSDeletion(0) == 0
	    or fatal("Could not schedule IMS deletion");
    }
442 443 444 445 446
    if ($image->DeleteVersion() != 0) {
	fatal("Could not delete image version!");
    }
    # I know, we are unlocking something we just deleted. Its okay, relax.
    $image->Unlock();
447 448 449
    $notifyuser->SendEmail("delete_image: Image Version has been deleted",
			   "Version $imagevers of image $imagepid,$imagename".
			   "($imageid)\nhas been deleted by $this_user\n");
450 451
}
exit(0);
452

453 454 455 456
sub fatal($)
{
    my ($mesg) = @_;

457 458 459
    $image->Unlock()
	if ($needunlock);

460 461 462
    die("*** $0:\n".
	"    $mesg\n");
}