1# fdisk-lib.pl 2# Functions for disk management under linux 3 4BEGIN { push(@INC, ".."); }; 5use WebminCore; 6&init_config(); 7&foreign_require("mount", "mount-lib.pl"); 8if (&foreign_check("raid")) { 9 &foreign_require("raid"); 10 $raid_module++; 11 } 12if (&foreign_check("lvm")) { 13 &foreign_require("lvm"); 14 $lvm_module++; 15 } 16if (&foreign_check("iscsi-server")) { 17 &foreign_require("iscsi-server"); 18 $iscsi_server_module++; 19 } 20if (&foreign_check("iscsi-target")) { 21 &foreign_require("iscsi-target"); 22 $iscsi_target_module++; 23 } 24&foreign_require("proc", "proc-lib.pl"); 25%access = &get_module_acl(); 26$has_e2label = &has_command("e2label"); 27$has_xfs_db = &has_command("xfs_db"); 28$has_volid = &has_command("vol_id"); 29$has_reiserfstune = &has_command("reiserfstune"); 30$uuid_directory = "/dev/disk/by-uuid"; 31if ($config{'mode'} eq 'parted') { 32 $has_parted = 1; 33 } 34elsif ($config{'mode'} eq 'fdisk') { 35 $has_parted = 0; 36 } 37else { 38 $has_parted = !$config{'noparted'} && &has_command("parted") && 39 &get_parted_version() >= 1.8; 40 } 41$| = 1; 42 43# list_disks_partitions([include-cds]) 44# Returns a structure containing the details of all disks and partitions 45sub list_disks_partitions 46{ 47if (scalar(@list_disks_partitions_cache)) { 48 return @list_disks_partitions_cache; 49 } 50 51local (@pscsi, @dscsi, $dscsi_mode); 52if (-r "/proc/scsi/sg/devices" && -r "/proc/scsi/sg/device_strs") { 53 # Get device info from various /proc/scsi files 54 open(DEVICES, "</proc/scsi/sg/devices"); 55 while(<DEVICES>) { 56 s/\r|\n//g; 57 local @l = split(/\t+/, $_); 58 push(@dscsi, { 'host' => $l[0], 59 'bus' => $l[1], 60 'target' => $l[2], 61 'lun' => $l[3], 62 'type' => $l[4] }); 63 } 64 close(DEVICES); 65 local $i = 0; 66 open(DEVNAMES, "</proc/scsi/sg/device_strs"); 67 while(<DEVNAMES>) { 68 s/\r|\n//g; 69 local @l = split(/\t+/, $_); 70 $dscsi[$i]->{'make'} = $l[0]; 71 $dscsi[$i]->{'model'} = $l[1]; 72 $i++; 73 } 74 close(DEVNAMES); 75 $dscsi_mode = 1; 76 @dscsi = grep { $_->{'type'} == 0 } @dscsi; 77 } 78else { 79 # Check /proc/scsi/scsi for SCSI disk models 80 open(SCSI, "</proc/scsi/scsi"); 81 local @lines = <SCSI>; 82 close(SCSI); 83 if ($lines[0] =~ /^Attached\s+domains/i) { 84 # New domains format 85 local $dscsi; 86 foreach (@lines) { 87 s/\s/ /g; 88 if (/Device:\s+(.*)(sd[a-z]+)\s+usage/) { 89 $dscsi = { 'dev' => $2 }; 90 push(@dscsi, $dscsi); 91 } 92 elsif (/Device:/) { 93 $dscsi = undef; 94 } 95 elsif (/Vendor:\s+(\S+)\s+Model:\s+(\S+)/ && $dscsi) { 96 $dscsi->{'make'} = $1; 97 $dscsi->{'model'} = $2; 98 } 99 elsif (/Host:\s+scsi(\d+)\s+Channel:\s+(\d+)\s+Id:\s+(\d+)\s+Lun:\s+(\d+)/ && $dscsi) { 100 $dscsi->{'host'} = $1; 101 $dscsi->{'bus'} = $2; 102 $dscsi->{'target'} = $3; 103 $dscsi->{'lun'} = $4; 104 } 105 } 106 $dscsi_mode = 1; 107 } 108 else { 109 # Standard format 110 foreach (@lines) { 111 s/\s/ /g; 112 if (/^Host:/) { 113 push(@pscsi, $_); 114 } 115 elsif (/^\s+\S/ && @pscsi) { 116 $pscsi[$#pscsi] .= $_; 117 } 118 } 119 @pscsi = grep { /Type:\s+Direct-Access/i } @pscsi; 120 $dscsi_mode = 0; 121 } 122 } 123 124local (@disks, @devs, $d); 125if (open(PARTS, "</proc/partitions")) { 126 # The list of all disks can come from the kernel 127 local $sc = 0; 128 while(<PARTS>) { 129 if (/\d+\s+\d+\s+\d+\s+sd([a-z]+)\s/ || 130 /\d+\s+\d+\s+\d+\s+(scsi\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/disc)\s+/) { 131 # New or old style SCSI device 132 local $d = $1; 133 local ($host, $bus, $target, $lun) = ($2, $3, $4, $5); 134 if (!$dscsi_mode && $pscsi[$sc] =~ /USB-FDU/) { 135 # USB floppy with scsi emulation! 136 splice(@pscsi, $sc, 1); 137 next; 138 } 139 if ($host ne '') { 140 local $scsidev = "/dev/$d"; 141 if (!-r $scsidev) { 142 push(@devs, "/dev/". 143 &number_to_device("sd", $sc)); 144 } 145 else { 146 push(@devs, $scsidev); 147 } 148 } 149 else { 150 push(@devs, "/dev/sd$d"); 151 } 152 $sc++; 153 } 154 elsif (/\d+\s+\d+\s+\d+\s+hd([a-z]+)\s/) { 155 # IDE disk (but skip CDs) 156 local $n = $1; 157 if (open(MEDIA, "</proc/ide/hd$n/media")) { 158 local $media = <MEDIA>; 159 close(MEDIA); 160 if ($media =~ /^disk/ && !$_[0]) { 161 push(@devs, "/dev/hd$n"); 162 } 163 } 164 } 165 elsif (/\d+\s+\d+\s+\d+\s+(ide\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/disc)\s+/) { 166 # New-style IDE disk 167 local $idedev = "/dev/$1"; 168 local ($host, $bus, $target, $lun) = ($2, $3, $4, $5); 169 if (!-r $idedev) { 170 push(@devs, "/dev/". 171 &hbt_to_device($host, $bus, $target)); 172 } 173 else { 174 push(@devs, "/dev/$1"); 175 } 176 } 177 elsif (/\d+\s+\d+\s+\d+\s+(rd\/c(\d+)d\d+)\s/) { 178 # Mylex raid device 179 push(@devs, "/dev/$1"); 180 } 181 elsif (/\d+\s+\d+\s+\d+\s+(ida\/c(\d+)d\d+)\s/) { 182 # Compaq raid device 183 push(@devs, "/dev/$1"); 184 } 185 elsif (/\d+\s+\d+\s+\d+\s+(cciss\/c(\d+)d\d+)\s/) { 186 # Compaq Smart Array RAID 187 push(@devs, "/dev/$1"); 188 } 189 elsif (/\d+\s+\d+\s+\d+\s+(ataraid\/disc(\d+)\/disc)\s+/) { 190 # Promise raid controller 191 push(@devs, "/dev/$1"); 192 } 193 elsif (/\d+\s+\d+\s+\d+\s+(vd[a-z]+)\s/) { 194 # Virtio disk from KVM 195 push(@devs, "/dev/$1"); 196 } 197 elsif (/\d+\s+\d+\s+\d+\s+(xvd[a-z]+)\s/) { 198 # PV disk from Xen 199 push(@devs, "/dev/$1"); 200 } 201 elsif (/\d+\s+\d+\s+\d+\s+(mmcblk\d+)\s/) { 202 # SD card / MMC, seen on Raspberry Pi 203 push(@devs, "/dev/$1"); 204 } 205 elsif (/\d+\s+\d+\s+\d+\s+(nvme\d+n\d+)\s/) { 206 # NVME SSD 207 push(@devs, "/dev/$1"); 208 } 209 } 210 close(PARTS); 211 212 # Sort IDE first 213 @devs = sort { ($b =~ /\/hd[a-z]+$/ ? 1 : 0) <=> 214 ($a =~ /\/hd[a-z]+$/ ? 1 : 0) } @devs; 215 } 216return ( ) if (!@devs); # No disks, ie on Xen 217 218# Skip cd-rom drive, identified from symlink. Don't do this if we can identify 219# cds by their media type though 220if (!-d "/proc/ide") { 221 local @cdstat = stat("/dev/cdrom"); 222 if (@cdstat && !$_[0]) { 223 @devs = grep { (stat($_))[1] != $cdstat[1] } @devs; 224 } 225 } 226 227# Get Linux disk ID mapping 228local %id_map; 229local $id_dir = "/dev/disk/by-id"; 230opendir(IDS, $id_dir); 231foreach my $id (readdir(IDS)) { 232 local $id_link = readlink("$id_dir/$id"); 233 if ($id_link) { 234 local $id_real = &simplify_path(&resolve_links("$id_dir/$id")); 235 $id_map{$id_real} = $id; 236 } 237 } 238closedir(IDS); 239 240# Call fdisk to get partition and geometry information 241local $devs = join(" ", @devs); 242local ($disk, $m2); 243if ($has_parted) { 244 open(FDISK, join(" ; ", 245 map { "parted $_ unit cyl print 2>/dev/null || ". 246 "fdisk -l $_ 2>/dev/null" } @devs)." |"); 247 } 248else { 249 open(FDISK, "fdisk -l -u=cylinders $devs 2>/dev/null || fdisk -l $devs 2>/dev/null |"); 250 } 251while(<FDISK>) { 252 if (($m4 = ($_ =~ /Disk\s+([^ :]+):\s+([\d\.]+)\s+(\S+),\s+(\d+)\s+bytes,\s+(\d+)\s+sectors/)) || 253 ($m1 = ($_ =~ /Disk\s+([^ :]+):\s+(\d+)\s+\S+\s+(\d+)\s+\S+\s+(\d+)/)) || 254 ($m2 = ($_ =~ /Disk\s+([^ :]+):\s+(.*)\s+bytes/)) || 255 ($m3 = ($_ =~ /Disk\s+([^ :]+):\s+([0-9\.]+)cyl/))) { 256 # New disk section 257 if ($m3) { 258 # Parted format 259 # Disk /dev/sda: 121601cyl 260 $disk = { 'device' => $1, 261 'prefix' => $1, 262 'cylinders' => $2 }; 263 } 264 elsif ($m2) { 265 # New style fdisk 266 # Disk /dev/sda: 85.8 GB, 85899345920 bytes 267 $disk = { 'device' => $1, 268 'prefix' => $1, 269 'table' => 'msdos', }; 270 <FDISK> =~ /(\d+)\s+\S+\s+(\d+)\s+\S+\s+(\d+)/ || next; 271 $disk->{'heads'} = $1; 272 $disk->{'sectors'} = $2; 273 $disk->{'cylinders'} = $3; 274 } 275 elsif ($m4) { 276 # Even newer disk (sectors/etc come later) 277 # Disk /dev/sda: 10 GiB, 10737418240 bytes, 20971520 sectors 278 $disk = { 'device' => $1, 279 'prefix' => $1, 280 'size' => $4, 281 'table' => 'msdos', }; 282 } 283 else { 284 # Old style fdisk 285 $disk = { 'device' => $1, 286 'prefix' => $1, 287 'heads' => $2, 288 'sectors' => $3, 289 'cylinders' => $4, 290 'table' => 'msdos', }; 291 } 292 $disk->{'index'} = scalar(@disks); 293 $disk->{'parts'} = [ ]; 294 295 local @st = stat($disk->{'device'}); 296 next if (@cdstat && $st[1] == $cdstat[1]); 297 if ($disk->{'device'} =~ /\/sd([a-z]+)$/) { 298 # Old-style SCSI disk 299 $disk->{'desc'} = &text('select_device', 'SCSI', 300 uc($1)); 301 local ($dscsi) = grep { $_->{'dev'} eq "sd$1" } @dscsi; 302 $disk->{'scsi'} = $dscsi ? &indexof($dscsi, @dscsi) 303 : ord(uc($1))-65; 304 $disk->{'type'} = 'scsi'; 305 } 306 elsif ($disk->{'device'} =~ /\/hd([a-z]+)$/) { 307 # IDE disk 308 $disk->{'desc'} = &text('select_device', 'IDE', uc($1)); 309 $disk->{'type'} = 'ide'; 310 } 311 elsif ($disk->{'device'} =~ /\/xvd([a-z]+)$/) { 312 # Xen virtual disk 313 $disk->{'desc'} = &text('select_device', 'Xen', uc($1)); 314 $disk->{'type'} = 'ide'; 315 } 316 elsif ($disk->{'device'} =~ /\/mmcblk([0-9]+)$/) { 317 # SD-card / MMC 318 $disk->{'desc'} = &text('select_device', 'SD-Card', $1); 319 $disk->{'type'} = 'ide'; 320 } 321 elsif ($disk->{'device'} =~ /\/vd([a-z]+)$/) { 322 # KVM virtual disk 323 $disk->{'desc'} = &text('select_device', 324 'VirtIO', uc($1)); 325 $disk->{'type'} = 'ide'; 326 } 327 elsif ($disk->{'device'} =~ /\/(scsi\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/disc)/) { 328 # New complete SCSI disk specification 329 $disk->{'host'} = $2; 330 $disk->{'bus'} = $3; 331 $disk->{'target'} = $4; 332 $disk->{'lun'} = $5; 333 $disk->{'desc'} = &text('select_scsi', 334 "$2", "$3", "$4", "$5"); 335 336 # Work out the SCSI index for this disk 337 local $j; 338 if ($dscsi_mode) { 339 for($j=0; $j<@dscsi; $j++) { 340 if ($dscsi[$j]->{'host'} == $disk->{'host'} && $dscsi[$j]->{'bus'} == $disk->{'bus'} && $dscsi[$j]->{'target'} == $disk->{'target'} && $dscsi[$j]->{'lnun'} == $disk->{'lun'}) { 341 $disk->{'scsi'} = $j; 342 last; 343 } 344 } 345 } 346 else { 347 for($j=0; $j<@pscsi; $j++) { 348 if ($pscsi[$j] =~ /Host:\s+scsi(\d+).*Id:\s+(\d+)/i && $disk->{'host'} == $1 && $disk->{'target'} == $2) { 349 $disk->{'scsi'} = $j; 350 last; 351 } 352 } 353 } 354 $disk->{'type'} = 'scsi'; 355 $disk->{'prefix'} =~ s/disc$/part/g; 356 } 357 elsif ($disk->{'device'} =~ /\/(ide\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/disc)/) { 358 # New-style IDE specification 359 $disk->{'host'} = $2; 360 $disk->{'bus'} = $3; 361 $disk->{'target'} = $4; 362 $disk->{'lun'} = $5; 363 $disk->{'desc'} = &text('select_newide', 364 "$2", "$3", "$4", "$5"); 365 $disk->{'type'} = 'ide'; 366 $disk->{'prefix'} =~ s/disc$/part/g; 367 } 368 elsif ($disk->{'device'} =~ /\/(rd\/c(\d+)d(\d+))/) { 369 # Mylex raid device 370 local ($mc, $md) = ($2, $3); 371 $disk->{'desc'} = &text('select_mylex', $mc, $md); 372 open(RD, "</proc/rd/c$mc/current_status"); 373 while(<RD>) { 374 if (/^Configuring\s+(.*)/i) { 375 $disk->{'model'} = $1; 376 } 377 elsif (/\s+(\S+):\s+([^, ]+)/ && 378 $1 eq $disk->{'device'}) { 379 $disk->{'raid'} = $2; 380 } 381 } 382 close(RD); 383 $disk->{'type'} = 'raid'; 384 $disk->{'prefix'} = $disk->{'device'}.'p'; 385 } 386 elsif ($disk->{'device'} =~ /\/(ida\/c(\d+)d(\d+))/) { 387 # Compaq RAID device 388 local ($ic, $id) = ($2, $3); 389 $disk->{'desc'} = &text('select_cpq', $ic, $id); 390 open(IDA, -d "/proc/driver/array" ? "</proc/driver/array/ida$ic" : "</proc/driver/cpqarray/ida$ic"); 391 while(<IDA>) { 392 if (/^(\S+):\s+(.*)/ && $1 eq "ida$ic") { 393 $disk->{'model'} = $2; 394 } 395 } 396 close(IDA); 397 $disk->{'type'} = 'raid'; 398 $disk->{'prefix'} = $disk->{'device'}.'p'; 399 } 400 elsif ($disk->{'device'} =~ /\/(cciss\/c(\d+)d(\d+))/) { 401 # Compaq Smart Array RAID 402 local ($ic, $id) = ($2, $3); 403 $disk->{'desc'} = &text('select_smart', $ic, $id); 404 open(CCI, "</proc/driver/cciss/cciss$ic"); 405 while(<CCI>) { 406 if (/^\s*(\S+):\s*(.*)/ && $1 eq "cciss$ic") { 407 $disk->{'model'} = $2; 408 } 409 } 410 close(CCI); 411 $disk->{'type'} = 'raid'; 412 $disk->{'prefix'} = $disk->{'device'}.'p'; 413 } 414 elsif ($disk->{'device'} =~ /\/(ataraid\/disc(\d+)\/disc)/) { 415 # Promise RAID controller 416 local $dd = $2; 417 $disk->{'desc'} = &text('select_promise', $dd); 418 $disk->{'type'} = 'raid'; 419 $disk->{'prefix'} =~ s/disc$/part/g; 420 } 421 elsif ($disk->{'device'} =~ /\/nvme(\d+)n(\d+)$/) { 422 # NVME SSD controller 423 $disk->{'desc'} = &text('select_nvme', "$1", "$2"); 424 $disk->{'type'} = 'scsi'; 425 $disk->{'prefix'} = $disk->{'device'}.'p'; 426 } 427 428 # Work out short name, like sda 429 local $short; 430 if (defined($disk->{'host'})) { 431 $short = &hbt_to_device($disk->{'host'}, 432 $disk->{'bus'}, 433 $disk->{'target'}); 434 } 435 else { 436 $short = $disk->{'device'}; 437 $short =~ s/^.*\///g; 438 } 439 $disk->{'short'} = $short; 440 441 $disk->{'id'} = $id_map{$disk->{'device'}} || 442 $id_map{"/dev/$short"}; 443 444 push(@disks, $disk); 445 } 446 elsif (/Geometry:\s+(\d+)\s+heads,\s+(\d+)\s+sectors\/track,\s+(\d+)\s+cylinders/) { 447 # Separate geometry line 448 # Geometry: 255 heads, 63 sectors/track, 1305 cylinders 449 $disk->{'heads'} = $1; 450 $disk->{'sectors'} = $2; 451 $disk->{'cylinders'} = $3; 452 } 453 elsif (/^Units\s*[=:]\s+cylinders\s+of\s+(\d+)\s+\*\s+(\d+)/) { 454 # Unit size for disk from fdisk 455 $disk->{'bytes'} = $2; 456 $disk->{'cylsize'} = $disk->{'heads'} * $disk->{'sectors'} * 457 $disk->{'bytes'}; 458 $disk->{'size'} = $disk->{'cylinders'} * $disk->{'cylsize'}; 459 } 460 elsif (/BIOS\s+cylinder,head,sector\s+geometry:\s+(\d+),(\d+),(\d+)\.\s+Each\s+cylinder\s+is\s+(\d+)(b|kb|mb)/i) { 461 # Unit size for disk from parted 462 $disk->{'cylinders'} = $1; 463 $disk->{'heads'} = $2; 464 $disk->{'sectors'} = $3; 465 $disk->{'cylsize'} = $4 * (lc($5) eq "b" ? 1 : 466 lc($5) eq "kb" ? 1024 : 1024*1024); 467 $disk->{'bytes'} = $disk->{'cylsize'} / $disk->{'heads'} / 468 $disk->{'sectors'}; 469 $disk->{'size'} = $disk->{'cylinders'} * $disk->{'cylsize'}; 470 } 471 elsif (/(\/dev\/\S+?(\d+))[ \t*]+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S{1,2})\s+(.*)/) { 472 # Partition within the current disk from fdisk (msdos format) 473 # /dev/sda1 * 1 1306 1306 10G 83 Linux 474 local $part = { 'number' => $2, 475 'device' => $1, 476 'type' => $7, 477 'start' => $3, 478 'end' => $4, 479 'extended' => $7 eq '5' || $6 eq 'f' ? 1 : 0, 480 'index' => scalar(@{$disk->{'parts'}}), 481 'edittype' => 1, }; 482 $part->{'desc'} = &partition_description($part->{'device'}); 483 $part->{'size'} = ($part->{'end'} - $part->{'start'} + 1) * 484 $disk->{'cylsize'}; 485 push(@{$disk->{'parts'}}, $part); 486 } 487 elsif (/(\/dev\/\S+?(\d+))[ \t*]+\d+\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S{1,2})\s+(.*)/ || /(\/dev\/\S+?(\d+))[ \t*]+(\d+)\s+(\d+)\s+(\S+)\s+(\S{1,2})\s+(.*)/) { 488 # Partition within the current disk from fdisk (msdos format) 489 # /dev/sda1 * 1 9327 74919096 83 Linux 490 local $part = { 'number' => $2, 491 'device' => $1, 492 'type' => $6, 493 'start' => $3, 494 'end' => $4, 495 'blocks' => int($5), 496 'extended' => $6 eq '5' || $6 eq 'f' ? 1 : 0, 497 'index' => scalar(@{$disk->{'parts'}}), 498 'edittype' => 1, }; 499 $part->{'desc'} = &partition_description($part->{'device'}); 500 $part->{'size'} = ($part->{'end'} - $part->{'start'} + 1) * 501 $disk->{'cylsize'}; 502 push(@{$disk->{'parts'}}, $part); 503 } 504 elsif (/(\/dev\/\S+?(\d+))\s+(\d+)\s+(\d+)\s+(\d+)\s+([0-9\.]+[kMGTP])\s+(\S.*)/) { 505 # Partition within the current disk from fdisk (gpt format) 506 local $part = { 'number' => $2, 507 'device' => $1, 508 'type' => $7, 509 'start' => $3, 510 'end' => $4, 511 'blocks' => $5, 512 'index' => scalar(@{$disk->{'parts'}}), 513 'edittype' => 1, }; 514 $part->{'desc'} = &partition_description($part->{'device'}); 515 $part->{'size'} = ($part->{'end'} - $part->{'start'} + 1) * 516 $disk->{'cylsize'}; 517 push(@{$disk->{'parts'}}, $part); 518 } 519 elsif (/^\s*(\d+)\s+(\d+)cyl\s+(\d+)cyl\s+(\d+)cyl\s+(primary|logical|extended)\s*(\S*)\s*(\S*)/) { 520 # Partition within the current disk from parted (msdos format) 521 local $part = { 'number' => $1, 522 'device' => $disk->{'prefix'}.$1, 523 'type' => $6 || 'ext2', 524 'start' => $2+1, 525 'end' => $3+1, 526 'blocks' => $4 * $disk->{'cylsize'}, 527 'extended' => $5 eq 'extended' ? 1 : 0, 528 'raid' => $7 eq 'raid' ? 1 : 0, 529 'index' => scalar(@{$disk->{'parts'}}), 530 'edittype' => 0, }; 531 $part->{'type'} = 'ext2' if ($part->{'type'} =~ /^ext/); 532 $part->{'type'} = 'raid' if ($part->{'type'} eq 'ext2' && 533 $part->{'raid'}); 534 $part->{'desc'} = &partition_description($part->{'device'}); 535 $part->{'size'} = ($part->{'end'} - $part->{'start'} + 1) * 536 $disk->{'cylsize'}; 537 push(@{$disk->{'parts'}}, $part); 538 } 539 elsif (/^\s*(\d+)\s+(\d+)cyl\s+(\d+)cyl\s+(\d+)cyl\s(.*)/) { 540 # Partition within the current disk from parted (gpt format) 541 local $part = { 'number' => $1, 542 'device' => $disk->{'prefix'}.$1, 543 'start' => $2+1, 544 'end' => $3+1, 545 'blocks' => $4 * $disk->{'cylsize'}, 546 'extended' => 0, 547 'index' => scalar(@{$disk->{'parts'}}), 548 'edittype' => 0, }; 549 550 # Work out partition type, name and flags 551 local $rest = $5; 552 $rest =~ s/^\s+//; 553 $rest =~ s/,//g; # Remove commas in flags list 554 local @rest = split(/\s+/, $rest); 555 556 # If first word is a known partition type, assume it is the type 557 if (@rest && &conv_type($rest[0])) { 558 $part->{'type'} = shift(@rest); 559 } 560 561 # Remove flag words from the end 562 local %flags; 563 while(@rest && $rest[$#rest] =~ /boot|lba|root|swap|hidden|raid|LVM/i) { 564 $flags{lc(pop(@rest))} = 1; 565 } 566 567 # Anything left in the middle should be the name 568 if (@rest) { 569 $part->{'name'} = $rest[0]; 570 } 571 if ($flags{'raid'}) { 572 # RAID flag is set 573 $part->{'raid'} = 1; 574 } 575 $part->{'type'} = 'ext2' if (!$part->{'type'} || 576 $part->{'type'} =~ /^ext/); 577 $part->{'type'} = 'raid' if ($part->{'type'} =~ /^ext/ && 578 $part->{'raid'}); 579 $part->{'desc'} = &partition_description($part->{'device'}); 580 $part->{'size'} = ($part->{'end'} - $part->{'start'} + 1) * 581 $disk->{'cylsize'}; 582 push(@{$disk->{'parts'}}, $part); 583 } 584 elsif (/Partition\s+Table:\s+(\S+)/) { 585 # Parted partition table type (from parted) 586 $disk->{'table'} = $1; 587 } 588 elsif (/Disklabel\s+type:\s+(\S+)/) { 589 # Parted partition table type (from fdisk) 590 $disk->{'table'} = $1; 591 } 592 } 593close(FDISK); 594 595# Check /proc/ide for IDE disk models 596foreach $d (@disks) { 597 if ($d->{'type'} eq 'ide') { 598 local $short = $d->{'short'}; 599 $d->{'model'} = &read_file_contents("/proc/ide/$short/model"); 600 $d->{'model'} =~ s/\r|\n//g; 601 $d->{'media'} = &read_file_contents("/proc/ide/$short/media"); 602 $d->{'media'} =~ s/\r|\n//g; 603 if ($d->{'short'} =~ /^vd/ && !$d->{'model'}) { 604 # Fake up model for KVM VirtIO disks 605 $d->{'model'} = "KVM VirtIO"; 606 } 607 } 608 } 609 610# Fill in SCSI information 611foreach $d (@disks) { 612 if ($d->{'type'} eq 'scsi') { 613 local $s = $d->{'scsi'}; 614 local $sysdir = "/sys/block/$d->{'short'}/device"; 615 if (-d $sysdir) { 616 # From kernel 2.6.30+ sys directory 617 $d->{'model'} = &read_file_contents("$sysdir/vendor"). 618 " ". 619 &read_file_contents("$sysdir/model"); 620 $d->{'model'} =~ s/\r|\n//g; 621 $d->{'media'} = &read_file_contents("$sysdir/media"); 622 $d->{'media'} =~ s/\r|\n//g; 623 } 624 elsif ($dscsi_mode) { 625 # From other scsi files 626 $d->{'model'} = "$dscsi[$s]->{'make'} $dscsi[$s]->{'model'}"; 627 $d->{'controller'} = $dscsi[$s]->{'host'}; 628 $d->{'scsiid'} = $dscsi[$s]->{'target'}; 629 } 630 else { 631 # From /proc/scsi/scsi lines 632 if ($pscsi[$s] =~ /Vendor:\s+(\S+).*Model:\s+(.*)\s+Rev:/i) { 633 $d->{'model'} = "$1 $2"; 634 } 635 if ($pscsi[$s] =~ /Host:\s+scsi(\d+).*Id:\s+(\d+)/i) { 636 $d->{'controller'} = int($1); 637 $d->{'scsiid'} = int($2); 638 } 639 } 640 if ($d->{'model'} =~ /ATA/) { 641 # Fake SCSI disk, actually IDE 642 $d->{'scsi'} = 0; 643 $d->{'desc'} =~ s/SCSI/SATA/g; 644 foreach my $p (@{$d->{'parts'}}) { 645 $p->{'desc'} =~ s/SCSI/SATA/g; 646 } 647 } 648 } 649 } 650 651@list_disks_partitions_cache = @disks; 652return @disks; 653} 654 655# partition_description(device) 656# Converts a device path like /dev/hda1 into a human-readable name 657sub partition_description 658{ 659my ($device) = @_; 660return $device =~ /(s|h|xv|v)d([a-z]+)(\d+)$/ ? 661 &text('select_part', $1 eq 's' ? 'SCSI' : 662 $1 eq 'xv' ? 'Xen' : 663 $1 eq 'v' ? 'VirtIO' : 'IDE', uc($2), "$3") : 664 $device =~ /mmcblk(\d+)p(\d+)$/ ? 665 &text('select_part', 'SD-Card', "$1", "$2") : 666 $device =~ /scsi\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/part(\d+)/ ? 667 &text('select_spart', "$1", "$2", "$3", "$4", "$5") : 668 $device =~ /ide\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/part(\d+)/ ? 669 &text('select_snewide', "$1", "$2", "$3", "$4", "$5") : 670 $device =~ /rd\/c(\d+)d(\d+)p(\d+)$/ ? 671 &text('select_mpart', "$1", "$2", "$3") : 672 $device =~ /ida\/c(\d+)d(\d+)p(\d+)$/ ? 673 &text('select_cpart', "$1", "$2", "$3") : 674 $device =~ /cciss\/c(\d+)d(\d+)p(\d+)$/ ? 675 &text('select_smartpart', "$1", "$2", "$3") : 676 $device =~ /ataraid\/disc(\d+)\/part(\d+)$/ ? 677 &text('select_ppart', "$1", "$2") : 678 $device =~ /nvme(\d+)n(\d+)p(\d+)$/ ? 679 &text('select_nvmepart', "$1", "$2", "$3") : 680 "???"; 681} 682 683# hbt_to_device(host, bus, target) 684# Converts an IDE device specified as a host, bus and target to an hdX device 685sub hbt_to_device 686{ 687local ($host, $bus, $target) = @_; 688local $num = $host*4 + $bus*2 + $target; 689return &number_to_device("hd", $num); 690} 691 692# number_to_device(suffix, number) 693sub number_to_device 694{ 695local ($suffix, $num) = @_; 696if ($num < 26) { 697 # Just a single letter 698 return $suffix.(('a' .. 'z')[$num]); 699 } 700else { 701 # Two-letter format 702 local $first = int($num / 26); 703 local $second = $num % 26; 704 return $suffix.(('a' .. 'z')[$first]).(('a' .. 'z')[$second]); 705 } 706} 707 708# change_type(disk, partition, type) 709# Changes the type of an existing partition 710sub change_type 711{ 712my ($disk, $part, $type) = @_; 713&open_fdisk($disk); 714&wprint("t\n"); 715local $rv = &wait_for($fh, 'Partition.*:', 'Selected partition'); 716&wprint("$part\n") if ($rv == 0); 717&wait_for($fh, 'Hex.*:'); 718&wprint("$type\n"); 719&wait_for($fh, 'Command.*:'); 720&wprint("w\n"); sleep(1); 721&close_fdisk(); 722undef(@list_disks_partitions_cache); 723} 724 725# delete_partition(disk, partition) 726# Delete an existing partition 727sub delete_partition 728{ 729my ($disk, $part) = @_; 730if ($has_parted) { 731 # Using parted 732 my $cmd = "parted -s ".$disk." rm ".$part; 733 my $out = &backquote_logged("$cmd </dev/null 2>&1"); 734 if ($?) { 735 &error("$cmd failed : $out"); 736 } 737 } 738else { 739 # Using fdisk 740 &open_fdisk($disk); 741 &wprint("d\n"); 742 local $rv = &wait_for($fh, 'Partition.*:', 'Selected partition'); 743 &wprint("$part\n") if ($rv == 0); 744 &wait_for($fh, 'Command.*:'); 745 &wprint("w\n"); 746 &wait_for($fh, 'Syncing'); 747 sleep(3); 748 &close_fdisk(); 749 } 750undef(@list_disks_partitions_cache); 751} 752 753# create_partition(disk, partition, start, end, type) 754# Create a new partition with the given extent and type 755sub create_partition 756{ 757my ($disk, $part, $start, $end, $type) = @_; 758if ($has_parted) { 759 # Using parted 760 my $pe = $part > 4 ? "logical" : "primary"; 761 my $cmd; 762 if ($type eq "raid") { 763 $cmd = "parted -s ".$disk." unit cyl mkpart ".$pe." ". 764 "ext2 ".($start-1)." ".$end; 765 $cmd .= " ; parted -s ".$disk." set $part raid on"; 766 } 767 elsif ($type && $type ne 'ext2') { 768 $cmd = "parted -s ".$disk." unit cyl mkpart ".$pe." ". 769 $type." ".($start-1)." ".$end; 770 } 771 else { 772 $cmd = "parted -s ".$disk." unit cyl mkpart ".$pe." ". 773 ($start-1)." ".$end; 774 } 775 my $out = &backquote_logged("$cmd </dev/null 2>&1"); 776 if ($?) { 777 &error("$cmd failed : $out"); 778 } 779 } 780else { 781 # Using fdisk 782 &open_fdisk($disk); 783 &wprint("n\n"); 784 local $wf = &wait_for($fh, 'primary.*\r?\n', 'First.*:'); 785 if ($part > 4) { 786 &wprint("l\n"); 787 } 788 else { 789 &wprint("p\n"); 790 local $wf2 = &wait_for($fh, 'Partition.*:', 791 'Selected partition'); 792 &wprint("$part\n") if ($wf2 == 0); 793 } 794 &wait_for($fh, 'First.*:') if ($wf != 1); 795 &wprint("$start\n"); 796 $wf = &wait_for($fh, 'Last.*:', 'First.*:'); 797 $wf < 0 && &error("End of input waiting for first cylinder response"); 798 $wf == 1 && &error("First cylinder is invalid : $wait_for_input"); 799 &wprint("$end\n"); 800 $wf = &wait_for($fh, 'Command.*:', 'Last.*:'); 801 $wf < 0 && &error("End of input waiting for last cylinder response"); 802 $wf == 1 && &error("Last cylinder is invalid : $wait_for_input"); 803 804 &wprint("t\n"); 805 local $rv = &wait_for($fh, 'Partition.*:', 'Selected partition'); 806 &wprint("$part\n") if ($rv == 0); 807 &wait_for($fh, 'Hex.*:'); 808 &wprint("$type\n"); 809 $wf = &wait_for($fh, 'Command.*:', 'Hex.*:'); 810 $wf < 0 && &error("End of input waiting for partition type response"); 811 $wf == 1 && &error("Partition type is invalid : $wait_for_input"); 812 &wprint("w\n"); 813 &wait_for($fh, 'Syncing'); sleep(3); 814 &close_fdisk(); 815 } 816undef(@list_disks_partitions_cache); 817} 818 819# create_extended(disk, partition, start, end) 820# Create a new extended partition 821sub create_extended 822{ 823my ($disk, $part, $start, $end) = @_; 824if ($has_parted) { 825 # Create using parted 826 my $cmd = "parted -s ".$disk." unit cyl mkpart extended ". 827 ($start-1)." ".$end; 828 my $out = &backquote_logged("$cmd </dev/null 2>&1"); 829 if ($?) { 830 &error("$cmd failed : $out"); 831 } 832 } 833else { 834 # Use classic fdisk 835 &open_fdisk($disk); 836 &wprint("n\n"); 837 &wait_for($fh, 'primary.*\r?\n'); 838 &wprint("e\n"); 839 &wait_for($fh, 'Partition.*:'); 840 &wprint("$part\n"); 841 &wait_for($fh, 'First.*:'); 842 &wprint("$start\n"); 843 &wait_for($fh, 'Last.*:'); 844 &wprint("$end\n"); 845 &wait_for($fh, 'Command.*:'); 846 847 &wprint("w\n"); 848 &wait_for($fh, 'Syncing'); 849 sleep(3); 850 &close_fdisk(); 851 } 852undef(@list_disks_partitions_cache); 853} 854 855# list_tags() 856# Returns a list of known partition tag numbers 857sub list_tags 858{ 859if ($has_parted) { 860 # Parted types 861 return sort { $a cmp $b } (keys %parted_tags); 862 } 863else { 864 # Classic fdisk types 865 return sort { hex($a) <=> hex($b) } (keys %tags); 866 } 867} 868 869# tag_name(tag) 870# Returns a human-readable version of a tag 871sub tag_name 872{ 873return $tags{$_[0]} || $parted_tags{$_[0]} || $hidden_tags{$_[0]}; 874} 875 876sub default_tag 877{ 878return $has_parted ? 'ext2' : '83'; 879} 880 881# conv_type(tag) 882# Given a partition tag, returns the filesystem type (assuming it is supported) 883sub conv_type 884{ 885my ($tag) = @_; 886my @rv; 887if ($has_parted) { 888 # Use parted type names 889 if ($tag eq "fat16") { 890 @rv = ( "msdos" ); 891 } 892 elsif ($tag eq "fat32") { 893 @rv = ( "vfat" ); 894 } 895 elsif ($tag =~ /^ext/ || $tag eq "raid") { 896 @rv = ( "ext3", "ext4", "ext2", "xfs", "reiserfs", "btrfs" ); 897 } 898 elsif ($tag eq "hfs" || $tag eq "HFS") { 899 @rv = ( "hfs" ); 900 } 901 elsif ($tag eq "linux-swap") { 902 @rv = ( "swap" ); 903 } 904 elsif ($tag eq "NTFS") { 905 @rv = ( "ntfs" ); 906 } 907 elsif ($tag eq "reiserfs") { 908 @rv = "reiserfs"; 909 } 910 elsif ($tag eq "ufs") { 911 @rv = ( "ufs" ); 912 } 913 else { 914 return ( ); 915 } 916 } 917else { 918 # Use fdisk type IDs 919 if ($tag eq "4" || $tag eq "6" || $tag eq "1" || $tag eq "e") { 920 @rv = ( "msdos" ); 921 } 922 elsif ($tag eq "b" || $tag eq "c") { 923 @rv = ( "vfat" ); 924 } 925 elsif ($tag eq "83") { 926 @rv = ( "ext3", "ext4", "ext2", "xfs", "reiserfs", "btrfs" ); 927 } 928 elsif ($tag eq "82") { 929 @rv = ( "swap" ); 930 } 931 elsif ($tag eq "81") { 932 @rv = ( "minix" ); 933 } 934 else { 935 return ( ); 936 } 937 } 938local %supp = map { $_, 1 } &mount::list_fstypes(); 939@rv = grep { $supp{$_} } @rv; 940return wantarray ? @rv : $rv[0]; 941} 942 943# fstype_name(type) 944# Returns a readable name for a filesystem type 945sub fstype_name 946{ 947return $text{"fs_".$_[0]}; 948} 949 950sub mkfs_options 951{ 952if ($_[0] eq "msdos" || $_[0] eq "vfat") { 953 &opt_input("msdos_ff", "", 1); 954 print &ui_table_row($text{'msdos_F'}, 955 &ui_select("msdos_F", undef, 956 [ [ undef, $text{'default'} ], 957 [ 12 ], [ 16 ], [ 32 ], 958 [ "*", $text{'msdos_F_other'} ] ])." ". 959 &ui_textbox("msdos_F_other", undef, 4)); 960 &opt_input("msdos_i", "", 1); 961 &opt_input("msdos_n", "", 0); 962 &opt_input("msdos_r", "", 1); 963 &opt_input("msdos_s", "sectors", 0); 964 print &ui_table_row($text{'msdos_c'}, 965 &ui_yesno_radio("msdos_c", 0)); 966 } 967elsif ($_[0] eq "minix") { 968 &opt_input("minix_n", "", 1); 969 &opt_input("minix_i", "", 0); 970 &opt_input("minix_b", "", 1); 971 print &ui_table_row($text{'minix_c'}, 972 &ui_yesno_radio("minix_c", 0)); 973 } 974elsif ($_[0] eq "reiserfs") { 975 print &ui_table_row($text{'reiserfs_force'}, 976 &ui_yesno_radio("reiserfs_f", 0)); 977 978 print &ui_table_row($text{'reiserfs_hash'}, 979 &ui_select("reiserfs_h", "", 980 [ [ "", $text{'default'} ], 981 [ "rupasov", "tea" ] ])); 982 } 983elsif ($_[0] =~ /^ext\d+$/) { 984 &opt_input("ext2_b", $text{'bytes'}, 1); 985 &opt_input("ext2_f", $text{'bytes'}, 0); 986 &opt_input("ext2_i", "", 1); 987 &opt_input("ext2_m", "%", 0); 988 &opt_input("ext3_j", "MB", 1); 989 print &ui_table_row($text{'ext2_c'}, 990 &ui_yesno_radio("ext2_c", 0)); 991 } 992elsif ($_[0] eq "xfs") { 993 print &ui_table_row($text{'xfs_force'}, 994 &ui_yesno_radio("xfs_f", 0)); 995 &opt_input("xfs_b", $text{'bytes'}, 0); 996 } 997elsif ($_[0] eq "jfs") { 998 &opt_input("jfs_s", $text{'megabytes'}, 1); 999 print &ui_table_row($text{'jfs_c'}, 1000 &ui_yesno_radio("jfs_c", 0)); 1001 } 1002elsif ($_[0] eq "fatx") { 1003 # Has no options! 1004 print &ui_table_row(undef, $text{'fatx_none'}, 4); 1005 } 1006elsif ($_[0] eq "btrfs") { 1007 &opt_input("btrfs_l", $text{'bytes'}, 0); 1008 &opt_input("btrfs_n", $text{'bytes'}, 0); 1009 &opt_input("btrfs_s", $text{'bytes'}, 0); 1010 } 1011} 1012 1013# mkfs_parse(type, device) 1014# Returns a command to build a new filesystem of the given type on the 1015# given device. Options are taken from %in. 1016sub mkfs_parse 1017{ 1018local($cmd); 1019if ($_[0] eq "msdos" || $_[0] eq "vfat") { 1020 $cmd = "mkfs -t $_[0]"; 1021 $cmd .= &opt_check("msdos_ff", '[1-2]', "-f"); 1022 if ($in{'msdos_F'} eq '*') { 1023 $in{'msdos_F_other'} =~ /^\d+$/ || 1024 &error(&text('opt_error', $in{'msdos_F_other'}, 1025 $text{'msdos_F'})); 1026 $cmd .= " -F ".$in{'msdos_F_other'}; 1027 } 1028 elsif ($in{'msdos_F'}) { 1029 $cmd .= " -F ".$in{'msdos_F'}; 1030 } 1031 $cmd .= &opt_check("msdos_i", '[0-9a-f]{8}', "-i"); 1032 $cmd .= &opt_check("msdos_n", '\S{1,11}', "-n"); 1033 $cmd .= &opt_check("msdos_r", '\d+', "-r"); 1034 $cmd .= &opt_check("msdos_s", '\d+', "-s"); 1035 $cmd .= $in{'msdos_c'} ? " -c" : ""; 1036 $cmd .= " $_[1]"; 1037 } 1038elsif ($_[0] eq "minix") { 1039 local(@plist, $disk, $part, $i, @pinfo); 1040 $cmd = "mkfs -t minix"; 1041 $cmd .= &opt_check("minix_n", '14|30', "-n "); 1042 $cmd .= &opt_check("minix_i", '\d+', "-i "); 1043 $cmd .= $in{'minix_c'} ? " -c" : ""; 1044 $cmd .= &opt_check("minix_b", '\d+', " "); 1045 $cmd .= " $_[1]"; 1046 } 1047elsif ($_[0] eq "reiserfs") { 1048 $cmd = "yes | mkreiserfs"; 1049 $cmd .= " -f" if ($in{'reiserfs_f'}); 1050 $cmd .= " -h $in{'reiserfs_h'}" if ($in{'reiserfs_h'}); 1051 $cmd .= " $_[1]"; 1052 } 1053elsif ($_[0] =~ /^ext\d+$/) { 1054 if (&has_command("mkfs.$_[0]")) { 1055 $cmd = "mkfs -t $_[0]"; 1056 $cmd .= &opt_check("ext3_j", '\d+', "-j"); 1057 } 1058 elsif ($_[0] eq "ext3" && &has_command("mke3fs")) { 1059 $cmd = "mke3fs"; 1060 $cmd .= &opt_check("ext3_j", '\d+', "-j"); 1061 } 1062 elsif ($_[0] eq "ext4" && &has_command("mke4fs")) { 1063 $cmd = "mke4fs"; 1064 $cmd .= &opt_check("ext3_j", '\d+', "-j"); 1065 } 1066 else { 1067 $cmd = "mkfs.ext2 -j"; 1068 if (!$in{'ext3_j_def'}) { 1069 $in{'ext3_j'} =~ /^\d+$/ || 1070 &error(&text('opt_error', $in{'ext3_j'}, 1071 $text{'ext3_j'})); 1072 $cmd .= " -J size=$in{'ext3_j'}"; 1073 } 1074 } 1075 $cmd .= &opt_check("ext2_b", '\d+', "-b"); 1076 $cmd .= &opt_check("ext2_f", '\d+', "-f"); 1077 $cmd .= &opt_check("ext2_i", '\d{4,}', "-i"); 1078 $cmd .= &opt_check("ext2_m", '\d+', "-m"); 1079 $cmd .= $in{'ext2_c'} ? " -c" : ""; 1080 $cmd .= " -q"; 1081 $cmd .= " $_[1]"; 1082 } 1083elsif ($_[0] eq "xfs") { 1084 $cmd = "mkfs -t $_[0]"; 1085 $cmd .= " -f" if ($in{'xfs_f'}); 1086 $cmd .= " -b size=$in{'xfs_b'}" if (!$in{'xfs_b_def'}); 1087 $cmd .= " $_[1]"; 1088 } 1089elsif ($_[0] eq "jfs") { 1090 $cmd = "mkfs -t $_[0] -q"; 1091 $cmd .= &opt_check("jfs_s", '\d+', "-s"); 1092 $cmd .= " -c" if ($in{'jfs_c'}); 1093 $cmd .= " $_[1]"; 1094 } 1095elsif ($_[0] eq "fatx") { 1096 $cmd = "mkfs -t $_[0] $_[1]"; 1097 } 1098elsif ($_[0] eq "btrfs") { 1099 $cmd = "mkfs -t $_[0]"; 1100 $cmd .= " -l $in{'btrfs_l'}" if (!$in{'btrfs_l_def'}); 1101 $cmd .= " -n $in{'btrfs_n'}" if (!$in{'btrfs_n_def'}); 1102 $cmd .= " -s $in{'btrfs_s'}" if (!$in{'btrfs_s_def'}); 1103 $cmd .= " $_[1]"; 1104 } 1105if (&has_command("partprobe")) { 1106 $cmd = "partprobe ; $cmd"; 1107 } 1108return $cmd; 1109} 1110 1111# can_tune(type) 1112# Returns 1 if this filesystem type can be tuned 1113sub can_tune 1114{ 1115return $_[0] =~ /^ext\d+$/; 1116} 1117 1118# tunefs_options(type) 1119# Output HTML for tuning options for some filesystem type 1120sub tunefs_options 1121{ 1122if ($_[0] =~ /^ext\d+$/) { 1123 # Gaps between checks 1124 &opt_input("tunefs_c", "", 1); 1125 1126 # Action on error 1127 print &ui_table_row($text{'tunefs_e'}, 1128 &ui_radio("tunefs_e_def", 1, 1129 [ [ 1, $text{'opt_default'} ], 1130 [ 0, &ui_select("tunefs_e", undef, 1131 [ [ "continue", $text{'tunefs_continue'} ], 1132 [ "remount-ro", $text{'tunefs_remount'} ], 1133 [ "panic", $text{'tunefs_panic'} ] ]) ] ])); 1134 1135 # Reserved user 1136 print &ui_table_row($text{'tunefs_u'}, 1137 &ui_opt_textbox("tunefs_u", undef, 13, $text{'opt_default'})." ". 1138 &user_chooser_button("tunefs_u", 0)); 1139 1140 # Reserved group 1141 print &ui_table_row($text{'tunefs_g'}, 1142 &ui_opt_textbox("tunefs_g", undef, 13, $text{'opt_default'})." ". 1143 &group_chooser_button("tunefs_g", 0)); 1144 1145 # Reserved blocks 1146 &opt_input("tunefs_m", "%", 1); 1147 1148 # Time between checks 1149 $tsel = &ui_select("tunefs_i_unit", undef, 1150 [ [ "d", $text{'tunefs_days'} ], 1151 [ "w", $text{'tunefs_weeks'} ], 1152 [ "m", $text{'tunefs_months'} ] ]); 1153 &opt_input("tunefs_i", $tsel, 0); 1154 } 1155} 1156 1157# tunefs_parse(type, device) 1158# Returns the tuning command based on user inputs 1159sub tunefs_parse 1160{ 1161if ($_[0] =~ /^ext\d+$/) { 1162 $cmd = "tune2fs"; 1163 $cmd .= &opt_check("tunefs_c", '\d+', "-c"); 1164 $cmd .= $in{'tunefs_e_def'} ? "" : " -e$in{'tunefs_e'}"; 1165 $cmd .= $in{'tunefs_u_def'} ? "" : " -u".getpwnam($in{'tunefs_u'}); 1166 $cmd .= $in{'tunefs_g_def'} ? "" : " -g".getgrnam($in{'tunefs_g'}); 1167 $cmd .= &opt_check("tunefs_m",'\d+',"-m"); 1168 $cmd .= &opt_check("tunefs_i", '\d+', "-i"). 1169 ($in{'tunefs_i_def'} ? "" : $in{'tunefs_i_unit'}); 1170 $cmd .= " $_[1]"; 1171 } 1172return $cmd; 1173} 1174 1175# need_reboot(disk) 1176# Returns 1 if a reboot is needed after changing the partitions on some disk 1177sub need_reboot 1178{ 1179local $un = `uname -r`; 1180return $un =~ /^2\.0\./ || $un =~ /^1\./ || $un =~ /^0\./; 1181} 1182 1183# device_status(device) 1184# Returns an array of directory, type, mounted, module 1185sub device_status 1186{ 1187@mounted = &foreign_call("mount", "list_mounted") if (!@mounted); 1188@mounts = &foreign_call("mount", "list_mounts") if (!@mounts); 1189local $label = &get_label($_[0]); 1190local $volid = &get_volid($_[0]); 1191 1192local ($mounted) = grep { &same_file($_->[1], $_[0]) || 1193 $_->[1] eq "LABEL=$label" || 1194 $_->[1] eq "UUID=$volid" } @mounted; 1195local ($mount) = grep { &same_file($_->[1], $_[0]) || 1196 $_->[1] eq "LABEL=$label" || 1197 $_->[1] eq "UUID=$volid" } @mounts; 1198if ($mounted) { return ($mounted->[0], $mounted->[2], 1, 1199 &indexof($mount, @mounts), 1200 &indexof($mounted, @mounted)); } 1201elsif ($mount) { return ($mount->[0], $mount->[2], 0, 1202 &indexof($mount, @mounts)); } 1203if ($raid_module) { 1204 my $raidconf = &foreign_call("raid", "get_raidtab") if (!$raidconf); 1205 foreach $c (@$raidconf) { 1206 foreach $d (&raid::find_value('device', $c->{'members'})) { 1207 return ( $c->{'value'}, "raid", 1, "raid" ) 1208 if ($d eq $_[0]); 1209 } 1210 } 1211 } 1212if ($lvm_module) { 1213 if (!scalar(@physical_volumes)) { 1214 @physical_volumes = (); 1215 foreach $vg (&foreign_call("lvm", "list_volume_groups")) { 1216 push(@physical_volumes, 1217 &foreign_call("lvm", "list_physical_volumes", 1218 $vg->{'name'})); 1219 } 1220 } 1221 foreach my $pv (@physical_volumes) { 1222 return ( $pv->{'vg'}, "lvm", 1, "lvm") 1223 if ($pv->{'device'} eq $_[0]); 1224 } 1225 } 1226if ($iscsi_server_module) { 1227 my $iscsiconf = &iscsi_server::get_iscsi_config(); 1228 foreach my $c (@$iscsiconf) { 1229 if ($c->{'type'} eq 'extent' && $c->{'device'} eq $_[0]) { 1230 return ( $c->{'type'}.$c->{'num'}, "iscsi", 1, 1231 "iscsi-server"); 1232 } 1233 } 1234 } 1235if ($iscsi_target_module) { 1236 my $iscsiconf = &iscsi_target::get_iscsi_config(); 1237 foreach my $t (&iscsi_target::find($iscsiconf, "Target")) { 1238 foreach my $l (&iscsi_target::find($t->{'members'}, "Lun")) { 1239 if ($l->{'value'} =~ /Path=([^, ]+)/ && $1 eq $_[0]) { 1240 return ( $t->{'value'}, "iscsi", 1, 1241 "iscsi-target"); 1242 } 1243 } 1244 } 1245 } 1246return (); 1247} 1248 1249# device_status_link(directory, type, mounted, module) 1250# Converts the list returned by device_status to a link 1251sub device_status_link 1252{ 1253my @stat = @_; 1254my $stat = ""; 1255my $statdesc = $stat[0] =~ /^swap/ ? "<i>$text{'disk_vm'}</i>" 1256 : "<tt>$stat[0]</tt>"; 1257my $ret = $main::initial_module_name; 1258if ($ret !~ /fdisk$/) { 1259 $ret = $module_name; 1260 } 1261if ($stat[1] eq 'raid') { 1262 $stat = $statdesc; 1263 } 1264elsif ($stat[1] eq 'lvm') { 1265 if (&foreign_available("lvm")) { 1266 $stat = "<a href='../lvm/'>". 1267 "LVM VG $statdesc</a>"; 1268 } 1269 else { 1270 $stat = "LVM VG $statdesc"; 1271 } 1272 } 1273elsif ($stat[1] eq 'iscsi') { 1274 $stat = &text('disk_iscsi', $stat[0]); 1275 if (&foreign_available("iscsi-server")) { 1276 $stat = "<a href='../$stat[3]/'>$stat</a>"; 1277 } 1278 } 1279elsif ($stat[0] && !&foreign_available("mount")) { 1280 $stat = $statdesc; 1281 } 1282elsif ($stat[0] && $stat[3] == -1) { 1283 $stat = "<a href='../mount/edit_mount.cgi?". 1284 "index=$stat[4]&temp=1&return=/$ret/'>". 1285 "$statdesc</a>"; 1286 } 1287elsif ($stat[0]) { 1288 $stat = "<a href='../mount/edit_mount.cgi?". 1289 "index=$stat[3]&return=/$ret/'>". 1290 "$statdesc</a>"; 1291 } 1292return $stat; 1293} 1294 1295# can_fsck(type) 1296# Returns 1 if some filesystem type can fsck'd 1297sub can_fsck 1298{ 1299return ($_[0] =~ /^ext\d+$/ && &has_command("fsck.$_[0]") || 1300 $_[0] eq "minix" && &has_command("fsck.minix")); 1301} 1302 1303# fsck_command(type, device) 1304# Returns the fsck command to unconditionally check a filesystem 1305sub fsck_command 1306{ 1307if ($_[0] =~ /^ext\d+$/) { 1308 return "fsck -t $_[0] -p $_[1]"; 1309 } 1310elsif ($_[0] eq "minix") { 1311 return "fsck -t minix -a $_[1]"; 1312 } 1313} 1314 1315# fsck_error(code) 1316# Returns a description of an exit code from fsck 1317sub fsck_error 1318{ 1319return $text{"fsck_err$_[0]"} ? $text{"fsck_err$_[0]"} 1320 : &text("fsck_unknown", $_[0]); 1321} 1322 1323# partition_select(name, value, mode, [&found], [disk_regexp]) 1324# Returns HTML for selecting a disk or partition 1325# mode 0 = floppies and disk partitions 1326# 1 = disks 1327# 2 = floppies and disks and disk partitions 1328# 3 = disk partitions 1329sub partition_select 1330{ 1331local ($name, $value, $mode, $found, $diskre) = @_; 1332local $rv = "<select name=$_[0]>\n"; 1333local @opts; 1334if (($mode == 0 || $mode == 2) && 1335 (-r "/dev/fd0" || $value =~ /^\/dev\/fd[01]$/)) { 1336 push(@opts, [ '/dev/fd0', &text('select_fd', 0) ]) 1337 if (!$diskre || '/dev/fd0' =~ /$diskre/); 1338 push(@opts, [ '/dev/fd1', &text('select_fd', 1) ]) 1339 if (!$diskre || '/dev/fd1' =~ /$diskre/); 1340 ${$found}++ if ($found && $value =~ /^\/dev\/fd[01]$/); 1341 } 1342local @dlist = &list_disks_partitions(); 1343foreach my $d (@dlist) { 1344 local $dev = $d->{'device'}; 1345 next if ($diskre && $dev !~ /$_[4]/); 1346 if ($mode == 1 || $mode == 2) { 1347 local $name = $d->{'desc'}; 1348 $name .= " ($d->{'model'})" if ($d->{'model'}); 1349 push(@opts, [ $dev, $name ]); 1350 ${$found}++ if ($found && $dev eq $_[1]); 1351 } 1352 if ($mode == 0 || $mode == 2 || $mode == 3) { 1353 foreach $p (@{$d->{'parts'}}) { 1354 next if ($p->{'extended'}); 1355 local $name = $p->{'desc'}; 1356 $name .= " (".&tag_name($p->{'type'}).")" 1357 if (&tag_name($p->{'type'})); 1358 push(@opts, [ $p->{'device'}, $name ]); 1359 ${$found}++ if ($found && $value eq $p->{'device'}); 1360 } 1361 } 1362 } 1363return &ui_select($name, $value, \@opts, 1, 0, $value && !$found); 1364} 1365 1366# label_select(name, value, &found) 1367# Returns HTML for selecting a filesystem label 1368sub label_select 1369{ 1370local ($name, $value, $found) = @_; 1371local @opts; 1372local @dlist = &list_disks_partitions(); 1373local $any; 1374foreach my $d (@dlist) { 1375 local $dev = $d->{'device'}; 1376 foreach $p (@{$d->{'parts'}}) { 1377 next if ($p->{'type'} ne '83' && 1378 $p->{'type'} !~ /^ext/); 1379 local $label = &get_label($p->{'device'}); 1380 next if (!$label); 1381 push(@opts, [ $label, $label." (".$p->{'desc'}.")" ]); 1382 ${$found}++ if ($value eq $label && $found); 1383 $any++; 1384 } 1385 } 1386if (@opts) { 1387 return &ui_select($name, $value, \@opts, 1, 0, $value && !$found); 1388 } 1389else { 1390 return undef; 1391 } 1392} 1393 1394# volid_select(name, value, &found) 1395# Returns HTML for selecting a filesystem UUID 1396sub volid_select 1397{ 1398local ($name, $value, $found) = @_; 1399local @dlist = &list_disks_partitions(); 1400local @opts; 1401foreach my $d (@dlist) { 1402 local $dev = $d->{'device'}; 1403 foreach $p (@{$d->{'parts'}}) { 1404 next if ($p->{'type'} ne '83' && $p->{'type'} ne '82' && 1405 $p->{'type'} ne 'b' && $p->{'type'} ne 'c' && 1406 $p->{'type'} !~ /^(ext|xfs)/); 1407 local $volid = &get_volid($p->{'device'}); 1408 next if (!$volid); 1409 push(@opts, [ $volid, "$volid ($p->{'desc'})" ]); 1410 ${$found}++ if ($value eq $volid && $found); 1411 } 1412 } 1413if (@opts) { 1414 return &ui_select($name, $value, \@opts, 1, 0, $value && !$found); 1415 } 1416else { 1417 return undef; 1418 } 1419} 1420 1421############################################################################# 1422# Internal functions 1423############################################################################# 1424sub open_fdisk 1425{ 1426local $fpath = &check_fdisk(); 1427my $cylarg; 1428if ($fpath =~ /\/fdisk/) { 1429 my $out = &backquote_command("$fpath -h 2>&1 </dev/null"); 1430 if ($out =~ /-u\s+<size>/) { 1431 $cylarg = "-u=cylinders"; 1432 } 1433 } 1434($fh, $fpid) = &foreign_call("proc", "pty_process_exec", 1435 join(" ", $fpath, $cylarg, @_)); 1436} 1437 1438sub open_sfdisk 1439{ 1440local $sfpath = &has_command("sfdisk"); 1441($fh, $fpid) = &foreign_call("proc", "pty_process_exec", join(" ",$sfpath, @_)); 1442} 1443 1444sub check_fdisk 1445{ 1446local $fpath = &has_command("fdisk"); 1447&error(&text('open_error', "<tt>fdisk</tt>")) if (!$fpath); 1448return $fpath; 1449} 1450 1451sub close_fdisk 1452{ 1453close($fh); kill('TERM', $fpid); 1454} 1455 1456sub wprint 1457{ 1458syswrite($fh, $_[0], length($_[0])); 1459} 1460 1461sub opt_input 1462{ 1463print &ui_table_row($text{$_[0]}, 1464 &ui_opt_textbox($_[0], undef, 6, $text{'opt_default'})." ".$_[1]); 1465} 1466 1467sub opt_check 1468{ 1469if ($in{"$_[0]_def"}) { return ""; } 1470elsif ($in{$_[0]} !~ /^$_[1]$/) { 1471 &error(&text('opt_error', $in{$_[0]}, $text{$_[0]})); 1472 } 1473else { return " $_[2] $in{$_[0]}"; } 1474} 1475 1476%tags = ('0', 'Empty', 1477 '1', 'FAT12', 1478 '2', 'XENIX root', 1479 '3', 'XENIX usr', 1480 '4', 'FAT16 <32M', 1481 '6', 'FAT16', 1482 '7', 'NTFS', 1483 '8', 'AIX', 1484 '9', 'AIX bootable', 1485 'a', 'OS/2 boot manager', 1486 'b', 'Windows FAT32', 1487 'c', 'Windows FAT32 LBA', 1488 'e', 'Windows FAT16 LBA', 1489 '10', 'OPUS', 1490 '11', 'Hidden FAT12', 1491 '12', 'Compaq diagnostic', 1492 '14', 'Hidden FAT16 < 32M', 1493 '16', 'Hidden FAT16', 1494 '17', 'Hidden NTFS', 1495 '18', 'AST Windows swapfile', 1496 '1b', 'Hidden Windows FAT (1b)', 1497 '1c', 'Hidden Windows FAT (1c)', 1498 '1e', 'Hidden Windows FAT (1e)', 1499 '24', 'NEC DOS', 1500 '3c', 'PartitionMagic recovery', 1501 '40', 'Venix 80286', 1502 '41', 'PPC PReP boot', 1503 '42', 'SFS', 1504 '4d', 'QNX 4.x', 1505 '4e', 'QNX 4.x 2nd partition', 1506 '4f', 'QNX 4.x 3rd partition', 1507 '50', 'OnTrack DM', 1508 '51', 'OnTrack DM6 Aux1', 1509 '52', 'CP/M', 1510 '53', 'OnTrack DM6 Aux3', 1511 '54', 'OnTrack DM6', 1512 '55', 'EZ-Drive', 1513 '56', 'Golden Bow', 1514 '5c', 'Priam Edisk', 1515 '61', 'SpeedStor', 1516 '63', 'GNU HURD or SysV', 1517 '64', 'Novell Netware 286', 1518 '65', 'Novell Netware 386', 1519 '70', 'DiskSecure Multi-Boot', 1520 '75', 'PC/IX', 1521 '80', 'Old Minix', 1522 '81', 'Minix / Old Linux / Solaris', 1523 '82', 'Linux swap', 1524 '83', 'Linux', 1525 '84', 'OS/2 hidden C: drive', 1526 '85', 'Linux extended', 1527 '86', 'NTFS volume set (86)', 1528 '87', 'NTFS volume set (87)', 1529 '8e', 'Linux LVM', 1530 '93', 'Amoeba', 1531 '94', 'Amoeba BBT', 1532 'a0', 'IBM Thinkpad hibernation', 1533 'a5', 'BSD/386', 1534 'a6', 'OpenBSD', 1535 'a7', 'NeXTSTEP', 1536 'b7', 'BSDI filesystem', 1537 'b8', 'BSDI swap', 1538 'c1', 'DRDOS/sec FAT12', 1539 'c4', 'DRDOS/sec FAT16 <32M', 1540 'c6', 'DRDOS/sec FAT16', 1541 'c7', 'Syrinx', 1542 'db', 'CP/M / CTOS', 1543 'e1', 'DOS access', 1544 'e3', 'DOS read-only', 1545 'e4', 'SpeedStor', 1546 'eb', 'BeOS', 1547 'ee', 'GPT', 1548 'f1', 'SpeedStor', 1549 'f4', 'SpeedStor large partition', 1550 'f2', 'DOS secondary', 1551 'fd', 'Linux RAID', 1552 'fe', 'LANstep', 1553 'ff', 'BBT' 1554 ); 1555 1556%hidden_tags = ( 1557 '5', 'Extended', 1558 'f', 'Windows extended LBA', 1559 ); 1560 1561%parted_tags = ( 1562 '', 'None', 1563 'fat16', 'Windows FAT16', 1564 'fat32', 'Windows FAT32', 1565 'ext2', 'Linux EXT', 1566 'xfs', 'Linux XFS', 1567 'raid', 'Linux RAID', 1568 'HFS', 'MacOS HFS', 1569 'linux-swap', 'Linux Swap', 1570 'NTFS', 'Windows NTFS', 1571 'reiserfs', 'ReiserFS', 1572 'ufs', 'FreeBSD UFS', 1573 ); 1574 1575@space_type = ( '1', '4', '5', '6', 'b', 'c', 'e', '83' ); 1576 1577# can_edit_disk(device) 1578sub can_edit_disk 1579{ 1580my ($device) = @_; 1581$device =~ s/\d+$//; 1582foreach (split(/\s+/, $access{'disks'})) { 1583 return 1 if ($_ eq "*" || $_ eq $device); 1584 } 1585return 0; 1586} 1587 1588# disk_space(device, [mountpoint]) 1589# Returns the amount of total and free space for some filesystem, or an 1590# empty array if not appropriate. 1591sub disk_space 1592{ 1593local $w = $_[1] || $_[0]; 1594local $out = `df -k '$w'`; 1595if ($out =~ /Mounted on\s*\n\s*\S+\s+(\S+)\s+\S+\s+(\S+)/i) { 1596 return ($1, $2); 1597 } 1598elsif ($out =~ /Mounted on\s*\n\S+\s*\n\s+(\S+)\s+\S+\s+(\S+)/i) { 1599 return ($1, $2); 1600 } 1601else { 1602 return ( ); 1603 } 1604} 1605 1606# supported_filesystems() 1607# Returns a list of filesystem types that can have mkfs_options called on them 1608sub supported_filesystems 1609{ 1610local @fstypes = ( "ext2" ); 1611push(@fstypes, "ext3") if (&has_command("mkfs.ext3") || 1612 &has_command("mke3fs") || 1613 `mkfs.ext2 -h 2>&1` =~ /\[-j\]/); 1614push(@fstypes, "ext4") if (&has_command("mkfs.ext4") || 1615 &has_command("mke4fs")); 1616push(@fstypes, "reiserfs") if (&has_command("mkreiserfs")); 1617push(@fstypes, "xfs") if (&has_command("mkfs.xfs")); 1618push(@fstypes, "jfs") if (&has_command("mkfs.jfs")); 1619push(@fstypes, "fatx") if (&has_command("mkfs.fatx")); 1620push(@fstypes, "btrfs") if (&has_command("mkfs.btrfs")); 1621push(@fstypes, "msdos"); 1622push(@fstypes, "vfat"); 1623push(@fstypes, "minix"); 1624return @fstypes; 1625} 1626 1627# get_label(device, [type]) 1628# Returns the XFS or EXT label for some device's filesystem 1629sub get_label 1630{ 1631local $label; 1632if ($has_e2label) { 1633 $label = `e2label $_[0] 2>&1`; 1634 chop($label); 1635 } 1636if (($? || $label !~ /\S/) && $has_xfs_db) { 1637 $label = undef; 1638 local $out = &backquote_with_timeout("xfs_db -x -p xfs_admin -c label -r $_[0] 2>&1", 5); 1639 $label = $1 if ($out =~ /label\s*=\s*"(.*)"/ && 1640 $1 ne '(null)'); 1641 } 1642if (($? || $label !~ /\S/) && $has_reiserfstune) { 1643 $label = undef; 1644 local $out = &backquote_command("reiserfstune $_[0]"); 1645 if ($out =~ /LABEL:\s*(\S+)/) { 1646 $label = $1; 1647 } 1648 } 1649return $? || $label !~ /\S/ ? undef : $label; 1650} 1651 1652# get_volid(device) 1653# Returns the UUID for some device's filesystem 1654sub get_volid 1655{ 1656local ($device) = @_; 1657local $uuid; 1658if (-d $uuid_directory) { 1659 # Use UUID mapping directory 1660 opendir(DIR, $uuid_directory); 1661 foreach my $f (readdir(DIR)) { 1662 local $linkdest = &simplify_path( 1663 &resolve_links("$uuid_directory/$f")); 1664 if ($linkdest eq $device) { 1665 $uuid = $f; 1666 last; 1667 } 1668 } 1669 closedir(DIR); 1670 } 1671elsif ($has_volid) { 1672 # Use vol_id command 1673 local $out = &backquote_command( 1674 "vol_id ".quotemeta($device)." 2>&1", 1); 1675 if ($out =~ /ID_FS_UUID=(\S+)/) { 1676 $uuid = $1; 1677 } 1678 } 1679return $uuid; 1680} 1681 1682# set_label(device, label, [type]) 1683# Tries to set the label for some device's filesystem 1684sub set_label 1685{ 1686if ($has_e2label && ($_[2] =~ /^ext[23]$/ || !$_[2])) { 1687 &system_logged("e2label '$_[0]' '$_[1]' >/dev/null 2>&1"); 1688 return 1 if (!$?); 1689 } 1690if ($has_xfs_db && ($_[2] eq "xfs" || !$_[2])) { 1691 &system_logged("xfs_db -x -p xfs_admin -c \"label $_[1]\" $_[0] >/dev/null 2>&1"); 1692 return 1 if (!$?); 1693 } 1694return 0; 1695} 1696 1697# set_name(&disk, &partition, name) 1698# Sets the name of a partition, for partition types that support it 1699sub set_name 1700{ 1701my ($dinfo, $pinfo, $name) = @_; 1702my $cmd = "parted -s ".$dinfo->{'device'}." name ".$pinfo->{'number'}." "; 1703if ($name) { 1704 $cmd .= quotemeta($name); 1705 } 1706else { 1707 $cmd .= " '\"\"'"; 1708 } 1709my $out = &backquote_logged("$cmd </dev/null 2>&1"); 1710if ($?) { 1711 &error("$cmd failed : $out"); 1712 } 1713} 1714 1715# set_partition_table(device, table-type) 1716# Wipe and re-create the partition table on some disk 1717sub set_partition_table 1718{ 1719my ($disk, $table) = @_; 1720my $cmd = "parted -s ".$disk." mktable ".$table; 1721my $out = &backquote_logged("$cmd </dev/null 2>&1"); 1722if ($?) { 1723 &error("$cmd failed : $out"); 1724 } 1725} 1726 1727# supports_label(&partition) 1728# Returns 1 if the label can be set on a partition 1729sub supports_label 1730{ 1731my ($part) = @_; 1732return $part->{'type'} eq '83' || $part->{'type'} eq 'ext2'; 1733} 1734 1735# supports_name(&disk) 1736# Returns 1 if the name can be set on a disk's partitions 1737sub supports_name 1738{ 1739my ($disk) = @_; 1740return $disk->{'table'} eq 'gpt'; 1741} 1742 1743# supports_hdparm(&disk) 1744sub supports_hdparm 1745{ 1746local ($d) = @_; 1747return $d->{'type'} eq 'ide' || $d->{'type'} eq 'scsi' && $d->{'model'} =~ /ATA/; 1748} 1749 1750# supports_relabel(&disk) 1751# Return 1 if a disk can have it's partition table re-written 1752sub supports_relabel 1753{ 1754return $has_parted ? 1 : 0; 1755} 1756 1757# supports_smart(&disk) 1758sub supports_smart 1759{ 1760return &foreign_installed("smart-status") && 1761 &foreign_available("smart-status"); 1762} 1763 1764# supports_extended(&disk) 1765# Return 1 if some disk can support extended partitions 1766sub supports_extended 1767{ 1768my ($disk) = @_; 1769return $disk->{'label'} eq 'msdos' ? 1 : 0; 1770} 1771 1772# list_table_types(&disk) 1773# Returns the list of supported partition table types for a disk 1774sub list_table_types 1775{ 1776if ($has_parted) { 1777 return ( 'msdos', 'gpt', 'bsd', 'dvh', 'loop', 'mac', 'pc98', 'sun' ); 1778 } 1779else { 1780 return ( 'msdos' ); 1781 } 1782} 1783 1784# get_parted_version() 1785# Returns the version number of parted that is installed 1786sub get_parted_version 1787{ 1788my $out = &backquote_command("parted -v 2>&1 </dev/null"); 1789return $out =~ /parted.*\s([0-9\.]+)/i ? $1 : undef; 1790} 1791 1792# identify_disk(&disk) 1793# Blinks the activity LED of the drive sixty times 1794sub identify_disk 1795{ 1796local ($d) = @_; 1797$count = 1; 1798while ($count <= 60) { 1799 &system_logged("dd if=".quotemeta($d->{'device'}). 1800 " of=/dev/null bs=10M count=1"); 1801 sleep(1); 1802 print "$count "; 1803 $count ++; 1804 } 1805} 1806 18071; 1808