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