1# lvm-lib.pl 2# Common functions for managing VGs, PVs and LVs 3 4BEGIN { push(@INC, ".."); }; 5use WebminCore; 6&init_config(); 7&foreign_require("mount"); 8if (&foreign_check("raid")) { 9 &foreign_require("raid"); 10 $has_raid++; 11 } 12&foreign_require("fdisk"); 13 14$lvm_proc = "/proc/lvm"; 15$lvm_tab = "/etc/lvmtab"; 16 17# list_physical_volumes(vg) 18# Returns a list of all physical volumes for some volume group 19sub list_physical_volumes 20{ 21local @rv; 22if (-d $lvm_proc) { 23 # Get list from /proc/lvm 24 opendir(DIR, "$lvm_proc/VGs/$_[0]/PVs"); 25 foreach $f (readdir(DIR)) { 26 next if ($f eq '.' || $f eq '..'); 27 local $pv = { 'name' => $f, 28 'vg' => $_[0] }; 29 local %p = &parse_colon_file("$lvm_proc/VGs/$_[0]/PVs/$f"); 30 $pv->{'device'} = $p{'name'}; 31 $pv->{'number'} = $p{'number'}; 32 $pv->{'size'} = $p{'size'}/2; 33 $pv->{'status'} = $p{'status'}; 34 $pv->{'number'} = $p{'number'}; 35 $pv->{'pe_size'} = $p{'PE size'}; 36 $pv->{'pe_total'} = $p{'PE total'}; 37 $pv->{'pe_alloc'} = $p{'PE allocated'}; 38 $pv->{'alloc'} = $p{'allocatable'} == 2 ? 'y' : 'n'; 39 push(@rv, $pv); 40 } 41 closedir(DIR); 42 } 43else { 44 # Use pvdisplay command 45 local $pv; 46 local $_; 47 open(DISPLAY, "pvdisplay 2>/dev/null |"); 48 while(<DISPLAY>) { 49 s/\r|\n//g; 50 if (/PV\s+Name\s+(.*)/i) { 51 $pv = { 'name' => $1, 52 'device' => $1, 53 'number' => scalar(@rv) }; 54 $pv->{'name'} =~ s/^\/dev\///; 55 push(@rv, $pv); 56 } 57 elsif (/VG\s+Name\s+(.*)/i) { 58 $pv->{'vg'} = $1; 59 $pv->{'vg'} =~ s/\s+\(.*\)//; 60 } 61 elsif (/PV\s+Size\s+<?(\S+)\s+(\S+)/i) { 62 $pv->{'size'} = &mult_units($1, $2); 63 } 64 elsif (/PE\s+Size\s+\(\S+\)\s+(\S+)/i) { 65 $pv->{'pe_size'} = $1; 66 } 67 elsif (/PE\s+Size\s+(\S+)\s+(\S+)/i) { 68 $pv->{'pe_size'} = &mult_units($1, $2); 69 } 70 elsif (/Total\s+PE\s+(\S+)/i) { 71 $pv->{'pe_total'} = $1; 72 } 73 elsif (/Allocated\s+PE\s+(\S+)/i) { 74 $pv->{'pe_alloc'} = $1; 75 } 76 elsif (/Allocatable\s+(\S+)/i) { 77 $pv->{'alloc'} = lc($1) eq 'yes' ? 'y' : 'n'; 78 } 79 } 80 close(DISPLAY); 81 @rv = grep { $_->{'vg'} eq $_[0] } @rv; 82 @rv = grep { $_->{'name'} ne 'unknown device' } @rv; 83 } 84return @rv; 85} 86 87# get_physical_volume_usage(&lv) 88# Returns a list of LVs and blocks used on this physical volume 89sub get_physical_volume_usage 90{ 91local @rv; 92open(DISPLAY, "pvdisplay -m ".quotemeta($_[0]->{'device'})." 2>/dev/null |"); 93local $lastlen; 94while(<DISPLAY>) { 95 if (/Physical\s+extent\s+(\d+)\s+to\s+(\d+)/) { 96 $lastlen = $2 - $1 + 1; 97 } 98 elsif (/Logical\s+volume\s+\/dev\/(\S+)\/(\S+)/) { 99 push(@rv, [ $2, $lastlen ]); 100 } 101 } 102close(DISPLAY); 103return @rv; 104} 105 106# create_physical_volume(&pv, [force]) 107# Add a new physical volume to a volume group 108sub create_physical_volume 109{ 110local $cmd = "pvcreate -y ".($_[1] ? "-ff " : "-f "); 111$cmd .= quotemeta($_[0]->{'device'}); 112local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 113return $out if ($?); 114$cmd = "vgextend ".quotemeta($_[0]->{'vg'})." ".quotemeta($_[0]->{'device'}); 115local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 116return $? ? $out : undef; 117} 118 119# change_physical_volume(&pv) 120# Change the allocation flag for a physical volume 121sub change_physical_volume 122{ 123local $cmd = "pvchange -x ".quotemeta($_[0]->{'alloc'}). 124 " ".quotemeta($_[0]->{'device'}); 125local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 126return $? ? $out : undef; 127} 128 129# delete_physical_volume(&pv) 130# Remove a physical volume from a volume group 131sub delete_physical_volume 132{ 133if ($_[0]->{'pe_alloc'}) { 134 local $cmd; 135 if (&get_lvm_version() >= 2) { 136 $cmd = "yes | pvmove ".quotemeta($_[0]->{'device'}); 137 } 138 else { 139 $cmd = "pvmove -f ".quotemeta($_[0]->{'device'}); 140 } 141 local $out = &backquote_logged("$cmd 2>&1"); 142 return $out if ($? && $out !~ /\-\-\s+f/); 143 } 144local $cmd = "vgreduce ".quotemeta($_[0]->{'vg'})." ". 145 quotemeta($_[0]->{'device'}); 146local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 147return $? ? $out : undef; 148} 149 150# resize_physical_volume(&pv) 151# Set the size of a physical volume to match the underlying device 152sub resize_physical_volume 153{ 154local $cmd = "pvresize ".quotemeta($_[0]->{'device'}); 155local $out = &backquote_logged("$cmd 2>&1"); 156return $? ? $out : undef; 157} 158 159# list_volume_groups() 160# Returns a list of all volume groups 161sub list_volume_groups 162{ 163local (@rv, $f); 164if (-d $lvm_proc) { 165 # Can scan /proc/lvm 166 opendir(DIR, "$lvm_proc/VGs"); 167 foreach $f (readdir(DIR)) { 168 next if ($f eq '.' || $f eq '..'); 169 local $vg = { 'name' => $f }; 170 local %g = &parse_colon_file("$lvm_proc/VGs/$f/group"); 171 $vg->{'number'} = $g{'number'}; 172 $vg->{'size'} = $g{'size'}; 173 $vg->{'pe_size'} = $g{'PE size'}; 174 $vg->{'pe_total'} = $g{'PE total'}; 175 $vg->{'pe_alloc'} = $g{'PE allocated'}; 176 push(@rv, $vg); 177 } 178 closedir(DIR); 179 } 180else { 181 # Parse output of vgdisplay 182 local $vg; 183 open(DISPLAY, "vgdisplay 2>/dev/null |"); 184 while(<DISPLAY>) { 185 s/\r|\n//g; 186 if (/VG\s+Name\s+(.*)/i) { 187 $vg = { 'name' => $1 }; 188 push(@rv, $vg); 189 } 190 elsif (/VG\s+Size\s+<?(\S+)\s+(\S+)/i) { 191 $vg->{'size'} = &mult_units($1, $2); 192 } 193 elsif (/PE\s+Size\s+(\S+)\s+(\S+)/i) { 194 $vg->{'pe_size'} = &mult_units($1, $2); 195 } 196 elsif (/Total\s+PE\s+(\d+)/i) { 197 $vg->{'pe_total'} = $1; 198 } 199 elsif (/Alloc\s+PE\s+\/\s+Size\s+(\d+)/i) { 200 $vg->{'pe_alloc'} = $1; 201 } 202 } 203 close(DISPLAY); 204 } 205return @rv; 206} 207 208sub mult_units 209{ 210local ($n, $u) = @_; 211return $n*(uc($u) eq "KB" || uc($u) eq "KIB" ? 1 : 212 uc($u) eq "MB" || uc($u) eq "MIB" ? 1024 : 213 uc($u) eq "GB" || uc($u) eq "GIB" ? 1024*1024 : 214 uc($u) eq "TB" || uc($u) eq "TIB" ? 1024*1024*1024 : 215 uc($u) eq "PB" || uc($u) eq "PIB" ? 1024*1024*1024 : 1); 216} 217 218# delete_volume_group(&vg) 219sub delete_volume_group 220{ 221local $cmd = "vgchange -a n ".quotemeta($_[0]->{'name'}); 222local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 223return $out if ($?); 224$cmd = "vgremove ".quotemeta($_[0]->{'name'}); 225local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 226return $? ? $out : undef; 227} 228 229# create_volume_group(&vg, device) 230sub create_volume_group 231{ 232&system_logged("vgscan >/dev/null 2>&1 </dev/null"); 233local $cmd = "pvcreate -f -y ".quotemeta($_[1]); 234local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 235return $out if ($?); 236$cmd = "vgcreate"; 237$cmd .= " -s ".quotemeta($_[0]->{'pe_size'})."k" if ($_[0]->{'pe_size'}); 238$cmd .= " ".quotemeta($_[0]->{'name'})." ".quotemeta($_[1]); 239$out = &backquote_logged("$cmd 2>&1 </dev/null"); 240return $? ? $out : undef; 241} 242 243# rename_volume_group(&vg, name) 244sub rename_volume_group 245{ 246local $cmd = "vgrename ".quotemeta($_[0]->{'name'})." ".quotemeta($_[1]); 247local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 248return $out if ($?); 249$cmd = "vgchange -a n ".quotemeta($_[1]); 250$out = &backquote_logged("$cmd 2>&1 </dev/null"); 251return $out if ($?); 252$cmd = "vgchange -a y ".quotemeta($_[1]); 253$out = &backquote_logged("$cmd 2>&1 </dev/null"); 254return $? ? $out : undef; 255} 256 257 258# list_logical_volumes(vg) 259sub list_logical_volumes 260{ 261local @rv; 262if (-d $lvm_proc) { 263 # Get LVs from /proc/lvm 264 opendir(DIR, "$lvm_proc/VGs/$_[0]/LVs"); 265 foreach $f (readdir(DIR)) { 266 next if ($f eq '.' || $f eq '..'); 267 local $lv = { 'name' => $f, 268 'vg' => $_[0] }; 269 local %p = &parse_colon_file("$lvm_proc/VGs/$_[0]/LVs/$f"); 270 $lv->{'device'} = $p{'name'}; 271 $lv->{'number'} = $p{'number'}; 272 $lv->{'size'} = $p{'size'}/2; 273 $lv->{'perm'} = $p{'access'} == 3 ? 'rw' : 'r'; 274 $lv->{'alloc'} = $p{'allocation'} == 2 ? 'y' : 'n'; 275 $lv->{'has_snap'} = $p{'access'} == 11; 276 $lv->{'is_snap'} = $p{'access'} == 5; 277 $lv->{'stripes'} = $p{'stripes'}; 278 push(@rv, $lv); 279 280 # For snapshots, use the lvdisplay command to get usage 281 if ($lv->{'is_snap'}) { 282 local $out = &backquote_command( 283 "lvdisplay ".quotemeta($lv->{'device'}). 284 " 2>/dev/null"); 285 if ($out =~/Allocated\s+to\s+snapshot\s+([0-9\.]+)%/i) { 286 $lv->{'snapusage'} = $1; 287 } 288 } 289 } 290 closedir(DIR); 291 } 292else { 293 # Use the lvdisplay command 294 local $lv; 295 local $_; 296 local ($vg) = grep { $_->{'name'} eq $_[0] } &list_volume_groups(); 297 open(DISPLAY, "lvdisplay -m 2>/dev/null |"); 298 while(<DISPLAY>) { 299 s/\r|\n//g; 300 if (/LV\s+(Name|Path)\s+(.*\/(\S+))/i) { 301 $lv = { 'name' => $3, 302 'device' => $2, 303 'number' => scalar(@rv) }; 304 push(@rv, $lv); 305 } 306 elsif (/LV\s+Name\s+([^\/ ]+)/) { 307 # Ignore this, as we got the name from LV Path line. 308 # UNLESS it doesn't match the current LV, in which 309 # case it is the start of a new thinpool LV. 310 if (!@rv || $rv[$#rv]->{'name'} ne $1) { 311 $lv = { 'name' => $1, 312 'device' => $1, 313 'thin' => $1, 314 'number' => scalar(@rv) }; 315 push(@rv, $lv); 316 } 317 } 318 elsif (/VG\s+Name\s+(.*)/) { 319 $lv->{'vg'} = $1; 320 } 321 elsif (/LV\s+Size\s+<?(\S+)\s+(\S+)/i) { 322 $lv->{'size'} = &mult_units($1, $2); 323 } 324 elsif (/Current\s+LE\s+(\d+)/ && $vg) { 325 $lv->{'size'} = $1 * $vg->{'pe_size'}; 326 } 327 elsif (/COW-table\s+LE\s+(\d+)/ && $vg) { 328 $lv->{'cow_size'} = $1 * $vg->{'pe_size'}; 329 } 330 elsif (/LV\s+Write\s+Access\s+(\S+)/i) { 331 $lv->{'perm'} = $1 eq 'read/write' ? 'rw' : 'r'; 332 } 333 elsif (/Allocation\s+(.*)/i) { 334 $lv->{'alloc'} = $1 eq 'contiguous' ? 'y' : 'n'; 335 } 336 elsif (/LV\s+snapshot\s+status\s+(.*)/i) { 337 if ($1 =~ /source/) { 338 $lv->{'has_snap'} = 1; 339 } 340 else { 341 $lv->{'is_snap'} = 1; 342 } 343 if (/destination\s+for\s+\/dev\/[^\/]+\/(\S+)/) { 344 $lv->{'snap_of'} = $1; 345 } 346 if (/active\s+destination/i) { 347 $lv->{'snap_active'} = 1; 348 } 349 elsif (/INACTIVE\s+destination/i) { 350 $lv->{'snap_active'} = 0; 351 } 352 } 353 elsif (/LV\s+Thin\s+origin\s+name\s+(\S+)/) { 354 # Snapshot in a thin pool 355 $lv->{'is_snap'} = 1; 356 $lv->{'snap_of'} = $1; 357 } 358 elsif (/Read ahead sectors\s+(\d+|auto)/) { 359 $lv->{'readahead'} = $1; 360 } 361 elsif (/Stripes\s+(\d+)/) { 362 $lv->{'stripes'} = $1; 363 } 364 elsif (/Stripe\s+size\s+(\S+)\s+(\S+)/) { 365 $lv->{'stripesize'} = &mult_units($1, $2); 366 } 367 elsif (/Allocated\s+to\s+snapshot\s+([0-9\.]+)%/i) { 368 $lv->{'snapusage'} = $1; 369 } 370 elsif (/LV\s+Pool\s+name\s+(\S+)/) { 371 $lv->{'thin_in'} = $1; 372 } 373 elsif (/Allocated\s+pool\s+data\s+(\S+)%/) { 374 $lv->{'thin_percent'} = $1; 375 $lv->{'thin_used'} = $1 / 100.0 * $lv->{'size'}; 376 } 377 } 378 close(DISPLAY); 379 @rv = grep { $_->{'vg'} eq $_[0] } @rv; 380 } 381return @rv; 382} 383 384# get_logical_volume_usage(&lv) 385# Returns a list of PVs and blocks used by this logical volume. Each is an 386# array ref of : device physical-blocks reads writes 387sub get_logical_volume_usage 388{ 389local ($lv) = @_; 390local @rv; 391if (&get_lvm_version() >= 2) { 392 # LVdisplay has new format in version 2 393 open(DISPLAY, "lvdisplay -m ".quotemeta($lv->{'device'})." 2>/dev/null |"); 394 while(<DISPLAY>) { 395 if (/\s+Physical\s+volume\s+\/dev\/(\S+)/) { 396 push(@rv, [ $1, undef ]); 397 } 398 elsif (/\s+Physical\s+extents\s+(\d+)\s+to\s+(\d+)/ && @rv) { 399 $rv[$#rv]->[1] = $2-$1+1; 400 } 401 } 402 close(DISPLAY); 403 } 404else { 405 # Old version 1 format 406 open(DISPLAY, "lvdisplay -v ".quotemeta($lv->{'device'})." 2>/dev/null |"); 407 local $started; 408 while(<DISPLAY>) { 409 if (/^\s*PV\s+Name/i) { 410 $started = 1; 411 } 412 elsif ($started && /^\s*\/dev\/(\S+)\s+(\d+)\s+(\d+)\s+(\d+)/) { 413 push(@rv, [ $1, $2, $3, $4 ]); 414 } 415 elsif ($started) { 416 last; 417 } 418 } 419 close(DISPLAY); 420 } 421return @rv; 422} 423 424# create_logical_volume(&lv) 425# Creates a new LV, and returns undef on success or an error message on failure 426sub create_logical_volume 427{ 428local ($lv) = @_; 429local $cmd = "lvcreate -y -n".quotemeta($lv->{'name'})." "; 430local $suffix; 431if ($lv->{'size_of'} eq 'VG' || $lv->{'size_of'} eq 'FREE' || 432 $lv->{'size_of'} eq 'ORIGIN') { 433 $cmd .= "-l ".quotemeta("$lv->{'size'}%$lv->{'size_of'}"); 434 } 435elsif ($lv->{'size_of'}) { 436 $cmd .= "-l $lv->{'size'}%PVS"; 437 $suffix = " ".quotemeta("/dev/".$lv->{'size_of'}); 438 } 439elsif ($lv->{'thin_in'} && $lv->{'is_snap'}) { 440 # For a snapshot inside a thin pool, no size is needed 441 } 442else { 443 $cmd .= ($lv->{'thin_in'} ? "-V" : "-L").$lv->{'size'}."k"; 444 } 445if ($lv->{'is_snap'}) { 446 $cmd .= " -s ".quotemeta("/dev/$lv->{'vg'}/$lv->{'snapof'}"); 447 } 448else { 449 $cmd .= " -p ".quotemeta($lv->{'perm'}); 450 if (!$lv->{'thin_in'}) { 451 $cmd .= " -C ".quotemeta($lv->{'alloc'}); 452 } 453 $cmd .= " -r ".quotemeta($lv->{'readahead'}) 454 if ($lv->{'readahead'} && $lv->{'readahead'} ne "auto"); 455 $cmd .= " -i ".quotemeta($lv->{'stripe'}) 456 if ($lv->{'stripe'}); 457 $cmd .= " -I ".quotemeta($lv->{'stripesize'}) 458 if ($lv->{'stripesize'} && $lv->{'stripe'}); 459 if ($lv->{'thin_in'}) { 460 $cmd .= " --thinpool ".quotemeta($lv->{'vg'})."/". 461 quotemeta($lv->{'thin_in'}); 462 } 463 else { 464 $cmd .= " ".quotemeta($lv->{'vg'}); 465 } 466 } 467$cmd .= $suffix; 468local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 469return $? ? $out : undef; 470} 471 472# delete_logical_volume(&lv) 473# Deletes an existing LV, and returns undef on success or an error message 474sub delete_logical_volume 475{ 476local ($lv) = @_; 477 478# First delete any /dev/mapper entries for the LV 479my $mapper = $lv->{'vg'}."-".$lv->{'lv'}; 480my $dashvg = $lv->{'vg'}; 481$dashvg =~ s/\-/\-\-/g; 482my $dashmapper = $dashvg."-".$lv->{'lv'}; 483opendir(MAPPER, "/dev/mapper"); 484my @files = readdir(MAPPER); 485closedir(MAPPER); 486my @delmaps; 487foreach my $f (@files) { 488 if ($f =~ /^\Q$mapper\Ep?\d+$/ || 489 $f =~ /^\Q$dashmapper\Ep?\d+$/) { 490 push(@delmaps, $f); 491 } 492 } 493foreach my $f (@files) { 494 if ($f eq $mapper || $f eq $dashmapper) { 495 push(@delmaps, $f); 496 } 497 } 498foreach my $f (@delmaps) { 499 &system_logged("dmsetup remove /dev/mapper/$f >/dev/null 2>&1"); 500 } 501 502# Finally remove the LV 503local $cmd = "lvremove -f ".quotemeta($lv->{'device'}); 504local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 505return $? ? $out : undef; 506} 507 508# resize_logical_volume(&lv, size) 509# Grows or shrinks a regular LV to the new size in KB 510sub resize_logical_volume 511{ 512local ($lv, $size) = @_; 513local $cmd = $size > $lv->{'size'} ? "lvextend" : "lvreduce -f"; 514$cmd .= " -L".quotemeta($size)."k"; 515$cmd .= " ".quotemeta($lv->{'device'}); 516local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 517return $? ? $out : undef; 518} 519 520# resize_snapshot_volume(&lv, size) 521# Grows or shrinks a snapshot LV to the new size in KB 522sub resize_snapshot_volume 523{ 524local ($lv, $size) = @_; 525local $cmd = $size > $lv->{'cow_size'} ? "lvextend" : "lvreduce -f"; 526$cmd .= " -L".quotemeta($size)."k"; 527$cmd .= " ".quotemeta($lv->{'device'}); 528local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 529return $? ? $out : undef; 530} 531 532# change_logical_volume(&lv, [&old-lv]) 533# Changes various parameters of an LV< like the permissions and readahead 534sub change_logical_volume 535{ 536local ($lv, $oldlv) = @_; 537local $cmd = "lvchange "; 538$cmd .= " -p ".quotemeta($lv->{'perm'}) 539 if (!$oldlv || $lv->{'perm'} ne $oldlv->{'perm'}); 540$cmd .= " -r ".quotemeta($lv->{'readahead'}) 541 if (!$oldlv || $lv->{'readahead'} ne $oldlv->{'readahead'}); 542$cmd .= " -C ".quotemeta($lv->{'alloc'}) 543 if (!$oldlv || $lv->{'alloc'} ne $oldlv->{'alloc'}); 544$cmd .= " ".quotemeta($lv->{'device'}); 545local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 546return $? ? $out : undef; 547} 548 549# rename_logical_volume(&lv, name) 550# Renames an existing LV 551sub rename_logical_volume 552{ 553local ($lv, $name) = @_; 554local $cmd = "lvrename ".quotemeta($lv->{'device'})." ". 555 quotemeta("/dev/$lv->{'vg'}/$name"); 556local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 557return $? ? $out : undef; 558} 559 560# rollback_snapshot(&lv) 561# Returns the underlying LV to the state in the given snapshot 562sub rollback_snapshot 563{ 564local ($lv) = @_; 565local $cmd = "lvconvert --merge ".quotemeta($lv->{'device'}); 566local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 567return $? ? $out : undef; 568} 569 570# can_resize_filesystem(type) 571# 0 = no, 1 = enlarge only, 2 = enlarge or shrink 572sub can_resize_filesystem 573{ 574local ($type) = @_; 575if ($type =~ /^ext\d+$/) { 576 if (&has_command("e2fsadm")) { 577 return 2; # Can extend and reduce 578 } 579 elsif (&has_command("resize2fs")) { 580 # Only new versions can reduce a FS 581 local $out = &backquote_command("resize2fs 2>&1"); 582 return $out =~ /resize2fs\s+([0-9\.]+)/i && $1 >= 1.4 ? 2 : 1; 583 } 584 else { 585 return 0; 586 } 587 } 588elsif ($type eq "xfs") { 589 return &has_command("xfs_growfs") ? 1 : 0; 590 } 591elsif ($type eq "reiserfs") { 592 return &has_command("resize_reiserfs") ? 2 : 0; 593 } 594elsif ($type eq "jfs") { 595 return 1; 596 } 597else { 598 return 0; 599 } 600} 601 602# can_resize_lv_stat(dir, type, mounted) 603# Returns 1 if some LV can be enlarged, 2 if enlarged or shrunk, or 0 604# if neither, based on the details provided by device_status 605sub can_resize_lv_stat 606{ 607local ($dir, $type, $mounted) = @_; 608if (!$type) { 609 # No FS known, assume can resize safely 610 return 2; 611 } 612else { 613 my $can = &can_resize_filesystem($type); 614 if ($can && $mounted) { 615 # If currently mounted, check if resizing is possible 616 if ($type =~ /^ext[3-9]$/ || $type eq "xfs" || 617 $type eq "reiserfs" || $type eq "jfs") { 618 # ext*, xfs, jfs and reiserfs can be resized up 619 $can = 1; 620 } 621 else { 622 # Nothing else can 623 $can = 0; 624 } 625 } 626 return $can; 627 } 628} 629 630# resize_filesystem(&lv, type, size) 631sub resize_filesystem 632{ 633if ($_[1] =~ /^ext\d+$/) { 634 &foreign_require("proc"); 635 if (&has_command("e2fsadm")) { 636 # The e2fsadm command can re-size an LVM and filesystem together 637 local $cmd = "e2fsadm -v -L ".quotemeta($_[2])."k ". 638 quotemeta($_[0]->{'device'}); 639 local ($fh, $fpid) = &proc::pty_process_exec($cmd); 640 print $fh "yes\n"; 641 local $out; 642 while(<$fh>) { 643 $out .= $_; 644 } 645 close($fh); 646 waitpid($fpid, 0); 647 &additional_log("exec", undef, $cmd); 648 return $? ? $out : undef; 649 } 650 else { 651 if ($_[2] > $_[0]->{'size'}) { 652 # Need to enlarge LV first, then filesystem 653 local $err = &resize_logical_volume($_[0], $_[2]); 654 return $err if ($err); 655 656 local $cmd = "resize2fs -f ". 657 quotemeta($_[0]->{'device'}); 658 local $out = &backquote_logged("$cmd 2>&1"); 659 return $? ? $out : undef; 660 } 661 else { 662 # Need to shrink filesystem first, then LV 663 local $cmd = "resize2fs -f ". 664 quotemeta($_[0]->{'device'})." ". 665 quotemeta($_[2])."k"; 666 local $out = &backquote_logged("$cmd 2>&1"); 667 return $out if ($?); 668 669 local $err = &resize_logical_volume($_[0], $_[2]); 670 return $err; 671 } 672 } 673 } 674elsif ($_[1] eq "xfs") { 675 # Resize the logical volume first 676 local $err = &resize_logical_volume($_[0], $_[2]); 677 return $err if ($err); 678 679 # Resize the filesystem .. which must be mounted! 680 local @stat = &device_status($_[0]->{'device'}); 681 local ($m, $mount); 682 foreach $m (&mount::list_mounts()) { 683 if ($m->[1] eq $_[0]->{'device'}) { 684 $mount = $m; 685 } 686 } 687 if (!$stat[2]) { 688 $mount || return "Mount not found"; 689 &mount::mount_dir(@$mount); 690 } 691 local $cmd = "xfs_growfs ".quotemeta($stat[0] || $mount->[0]); 692 local $out = &backquote_logged("$cmd 2>&1"); 693 local $q = $?; 694 if (!$stat[2]) { 695 &mount::unmount_dir(@$mount); 696 } 697 return $q ? $out : undef; 698 } 699elsif ($_[1] eq "reiserfs") { 700 if ($_[2] > $_[0]->{'size'}) { 701 # Enlarge the logical volume first 702 local $err = &resize_logical_volume($_[0], $_[2]); 703 return $err if ($err); 704 705 # Now enlarge the reiserfs filesystem 706 local $cmd = "resize_reiserfs ".quotemeta($_[0]->{'device'}); 707 local $out = &backquote_logged("$cmd 2>&1"); 708 return $? ? $out : undef; 709 } 710 else { 711 # Try to shrink the filesystem 712 local $cmd = "yes | resize_reiserfs -s ". 713 quotemeta($_[2])."K ".quotemeta($_[0]->{'device'}); 714 local $out = &backquote_logged("$cmd 2>&1"); 715 return $out if ($?); 716 717 # Now shrink the logical volume 718 local $err = &resize_logical_volume($_[0], $_[2]); 719 return $err ? $err : undef; 720 } 721 } 722elsif ($_[1] eq "jfs") { 723 # Enlarge the logical volume first 724 local $err = &resize_logical_volume($_[0], $_[2]); 725 return $err if ($err); 726 727 # Now enlarge the jfs filesystem with a remount - must be mounted first 728 local @stat = &device_status($_[0]->{'device'}); 729 local ($m, $mount); 730 foreach $m (&mount::list_mounts()) { 731 if ($m->[1] eq $_[0]->{'device'}) { 732 $mount = $m; 733 } 734 } 735 if (!$stat[2]) { 736 $mount || return "Mount not found"; 737 &mount::mount_dir(@$mount); 738 } 739 local $ropts = $mount->[3]; 740 $ropts = $ropts eq "-" ? "resize,remount" : "$ropts,resize,remount"; 741 local $err = &mount::mount_dir($mount->[0], $mount->[1], 742 $mount->[2], $ropts); 743 if (!$stat[2]) { 744 &mount::unmount_dir(@$mount); 745 } 746 return $err ? $err : undef; 747 } 748else { 749 return "???"; 750 } 751} 752 753 754# parse_colon_file(file) 755sub parse_colon_file 756{ 757local %rv; 758open(FILE, "<".$_[0]); 759while(<FILE>) { 760 if (/^([^:]+):\s*(.*)/) { 761 $rv{$1} = $2; 762 } 763 } 764close(FILE); 765return %rv; 766} 767 768# device_status(device) 769# Returns an array of directory, type, mounted 770sub device_status 771{ 772local ($dev) = @_; 773return ( ) if ($dev !~ /^\//); 774local @st = &fdisk::device_status($dev); 775return @st if (@st); 776if (&foreign_check("server-manager")) { 777 # Look for Cloudmin systems using the disk, hosted on this system 778 if (!@server_manager_systems) { 779 &foreign_require("server-manager"); 780 @server_manager_systems = 781 grep { my $p = &server_manager::get_parent_server($_); 782 $p && $p->{'id'} eq '0' } 783 &server_manager::list_managed_servers(); 784 } 785 foreach my $s (@server_manager_systems) { 786 if ($s->{$s->{'manager'}.'_filesystem'} eq $_[0]) { 787 return ( $s->{'host'}, 'cloudmin', 788 $s->{'status'} ne 'down' ); 789 } 790 my $ffunc = "type_".$s->{'manager'}."_list_disk_devices"; 791 if (&foreign_defined("server-manager", $ffunc)) { 792 my @disks = &foreign_call("server-manager", $ffunc, $s); 793 if (&indexof($_[0], @disks) >= 0) { 794 return ( $s->{'host'}, 'cloudmin', 795 $s->{'status'} ne 'down' ); 796 } 797 } 798 } 799 } 800return (); 801} 802 803# device_message(stat) 804# Returns a text string about the status of an LV 805sub device_message 806{ 807my $msg; 808if ($_[1] eq 'cloudmin') { 809 # Used by Cloudmin system 810 $msg = $_[2] ? 'lv_mountcm' : 'lv_umountcm'; 811 return &text($msg, "<tt>$_[0]</tt>"); 812 } 813else { 814 # Used by filesystem or RAID or iSCSI 815 $msg = $_[2] ? 'lv_mount' : 'lv_umount'; 816 $msg .= 'vm' if ($_[1] eq 'swap'); 817 $msg .= 'raid' if ($_[1] eq 'raid'); 818 $msg .= 'iscsi' if ($_[1] eq 'iscsi'); 819 return &text($msg, "<tt>$_[0]</tt>", "<tt>$_[1]</tt>"); 820 } 821} 822 823# list_lvmtab() 824sub list_lvmtab 825{ 826local @rv; 827open(TAB, "<".$lvm_tab); 828local $/ = "\0"; 829while(<TAB>) { 830 chop; 831 push(@rv, $_) if ($_); 832 } 833close(TAB); 834return @rv; 835} 836 837# device_input() 838# Returns a selector for a free device 839sub device_input 840{ 841local (%used, $vg, $pv, $d, $p); 842 843# Find partitions that are part of an LVM 844foreach $vg (&list_volume_groups()) { 845 foreach $pv (&list_physical_volumes($vg->{'name'})) { 846 $used{$pv->{'device'}}++; 847 } 848 } 849 850# Show available partitions 851local @opts; 852foreach $d (&fdisk::list_disks_partitions()) { 853 foreach $p (@{$d->{'parts'}}) { 854 next if ($used{$p->{'device'}} || $p->{'extended'}); 855 local @ds = &device_status($p->{'device'}); 856 next if (@ds); 857 if ($p->{'type'} eq '83' || $p->{'type'} eq 'ext2') { 858 local $label = &fdisk::get_label($p->{'device'}); 859 next if ($used{"LABEL=$label"}); 860 } 861 local $tag = &fdisk::tag_name($p->{'type'}); 862 push(@opts, [ $p->{'device'}, 863 $p->{'desc'}. 864 ($tag ? " ($tag)" : ""). 865 ($d->{'cylsize'} ? " (".&nice_size($d->{'cylsize'}*($p->{'end'} - $p->{'start'} + 1)).")" : 866 " ($p->{'blocks'} $text{'blocks'})") ]); 867 } 868 } 869 870# Show available RAID devices 871local $conf = &raid::get_raidtab(); 872foreach $c (@$conf) { 873 next if ($used{$c->{'value'}}); 874 local @ds = &device_status($c->{'value'}); 875 next if (@ds); 876 push(@opts, [ $c->{'value'}, &text('pv_raid', $c->{'value'} =~ /md(\d+)$/ ? "$1" : $c->{'value'}) ]); 877 } 878 879push(@opts, [ '', $text{'pv_other'} ]); 880return &ui_select("device", $opts[0]->[0], \@opts)." ". 881 &ui_textbox("other", undef, 30)." ".&file_chooser_button("other"). 882 "<br>\n<b>$text{'pv_warn'}</b>"; 883} 884 885# get_lvm_version() 886# Returns the lvm version number and optionally output from the vgdisplay 887# command used to get it. 888sub get_lvm_version 889{ 890local $out = `vgdisplay --version 2>&1`; 891local $ver = $out =~ /\s+([0-9\.]+)/ ? $1 : undef; 892return wantarray ? ( $ver, $out ) : $ver; 893} 894 895# nice_round(number) 896# Round some number to TB, GB, MB or kB, depending on size 897sub nice_round 898{ 899local ($bytes) = @_; 900my $units; 901if ($bytes >= 10*1024*1024*1024*1024) { 902 $units = 1024*1024*1024*1024; 903 } 904elsif ($bytes >= 10*1024*1024*1024) { 905 $units = 1024*1024*1024; 906 } 907elsif ($bytes >= 10*1024*1024) { 908 $units = 1024*1024; 909 } 910elsif ($bytes >= 10*1024) { 911 $units = 1024; 912 } 913else { 914 $units = 1; 915 } 916return int($bytes / $units) * $units; 917} 918 919# move_logical_volume(&lv, from, to, [print]) 920# Moves blocks on an LV from one PV to another. Returns an error message on 921# failure, or undef on success 922sub move_logical_volume 923{ 924local ($lv, $from, $to, $print) = @_; 925local $cmd = "pvmove -n $lv->{'name'} /dev/$from /dev/$to"; 926if ($print) { 927 open(OUT, "$cmd 2>&1 </dev/null |"); 928 my $old = select(OUT); 929 $| = 1; 930 select($old); 931 while(<OUT>) { 932 print &html_escape($_); 933 } 934 my $ex = close(OUT); 935 return $? ? "Failed" : undef; 936 } 937else { 938 local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 939 return $? ? $out : undef; 940 } 941} 942 943# supports_snapshot_rollback() 944# Only newer kernels safely support this (2.6.35 and above) 945sub supports_snapshot_rollback 946{ 947my $out = &backquote_command("uname -r 2>/dev/null </dev/null"); 948return $out =~ /^(\d+)\./ && $1 >= 3 || 949 $out =~ /^(\d+)\.(\d+)/ && $1 == 2 && $2 >= 7 || 950 $out =~ /^(\d+)\.(\d+)\.(\d+)/ && $1 == 2 && $2 == 6 && $3 >= 35; 951} 952 953# create_thin_pool(&data-lv, &metadata-lv) 954# Convert two LVs into a thin pool 955sub create_thin_pool 956{ 957local ($datalv, $metadatalv) = @_; 958local $cmd = "lvconvert -y --type thin-pool --poolmetadata ". 959 quotemeta($metadatalv->{'device'})." ". 960 quotemeta($datalv->{'device'}); 961local $out = &backquote_logged("$cmd 2>&1 </dev/null"); 962return $? ? $out : undef; 963} 964 9651; 966 967