6b615d1569c275f22f61501be2178e218906f7f1
[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                 }
465         }
466
467 # Fill in SCSI information
468 foreach $d (@disks) {
469         if ($d->{'type'} eq 'scsi') {
470                 local $s = $d->{'scsi'};
471                 local $sysdir = "/sys/block/$d->{'short'}/device";
472                 if (-d $sysdir) {
473                         # From kernel 2.6.30+ sys directory
474                         $d->{'model'} = &read_file_contents("$sysdir/vendor").
475                                         " ".
476                                         &read_file_contents("$sysdir/model");
477                         $d->{'model'} =~ s/\r|\n//g;
478                         $d->{'media'} = &read_file_contents("$sysdir/media");
479                         $d->{'media'} =~ s/\r|\n//g;
480                         }
481                 elsif ($dscsi_mode) {
482                         # From other scsi files
483                         $d->{'model'} = "$dscsi[$s]->{'make'} $dscsi[$s]->{'model'}";
484                         $d->{'controller'} = $dscsi[$s]->{'host'};
485                         $d->{'scsiid'} = $dscsi[$s]->{'target'};
486                         }
487                 else {
488                         # From /proc/scsi/scsi lines
489                         if ($pscsi[$s] =~ /Vendor:\s+(\S+).*Model:\s+(.*)\s+Rev:/i) {
490                                 $d->{'model'} = "$1 $2";
491                                 }
492                         if ($pscsi[$s] =~ /Host:\s+scsi(\d+).*Id:\s+(\d+)/i) {
493                                 $d->{'controller'} = int($1);
494                                 $d->{'scsiid'} = int($2);
495                                 }
496                         }
497                 if ($d->{'model'} =~ /ATA/) {
498                         # Fake SCSI disk, actually IDE
499                         $d->{'scsi'} = 0;
500                         $d->{'desc'} =~ s/SCSI/SATA/g;
501                         foreach my $p (@{$d->{'parts'}}) {
502                                 $p->{'desc'} =~ s/SCSI/SATA/g;
503                                 }
504                         }
505                 }
506         }
507
508 @list_disks_partitions_cache = @disks;
509 return @disks;
510 }
511
512 # partition_description(device)
513 # Converts a device path like /dev/hda into a human-readable name
514 sub partition_description
515 {
516 my ($device) = @_;
517 return $device =~ /(.)d([a-z]+)(\d+)$/ ?
518          &text('select_part', $1 eq 's' ? 'SCSI' : 'IDE', uc($2), "$3") :
519        $device =~ /scsi\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/part(\d+)/ ?
520          &text('select_spart', "$1", "$2", "$3", "$4", "$5") :
521        $device =~ /ide\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/part(\d+)/ ?
522          &text('select_snewide', "$1", "$2", "$3", "$4", "$5") :
523        $device =~ /rd\/c(\d+)d(\d+)p(\d+)$/ ? 
524          &text('select_mpart', "$1", "$2", "$3") :
525        $device =~ /ida\/c(\d+)d(\d+)p(\d+)$/ ? 
526          &text('select_cpart', "$1", "$2", "$3") :
527        $device =~ /cciss\/c(\d+)d(\d+)p(\d+)$/ ? 
528          &text('select_smartpart', "$1", "$2", "$3") :
529        $device =~ /ataraid\/disc(\d+)\/part(\d+)$/ ?
530          &text('select_ppart', "$1", "$2") :
531          "???";
532 }
533
534 # hbt_to_device(host, bus, target)
535 # Converts an IDE device specified as a host, bus and target to an hdX device
536 sub hbt_to_device
537 {
538 local ($host, $bus, $target) = @_;
539 local $num = $host*4 + $bus*2 + $target;
540 return &number_to_device("hd", $num);
541 }
542
543 # number_to_device(suffix, number)
544 sub number_to_device
545 {
546 local ($suffix, $num) = @_;
547 if ($num < 26) {
548         # Just a single letter
549         return $suffix.(('a' .. 'z')[$num]);
550         }
551 else {
552         # Two-letter format
553         local $first = int($num / 26);
554         local $second = $num % 26;
555         return $suffix.(('a' .. 'z')[$first]).(('a' .. 'z')[$second]);
556         }
557 }
558
559 # change_type(disk, partition, type)
560 # Changes the type of an existing partition
561 sub change_type
562 {
563 &open_fdisk("$_[0]");
564 &wprint("t\n");
565 local $rv = &wait_for($fh, 'Partition.*:', 'Selected partition');
566 &wprint("$_[1]\n") if ($rv == 0);
567 &wait_for($fh, 'Hex.*:');
568 &wprint("$_[2]\n");
569 &wait_for($fh, 'Command.*:');
570 &wprint("w\n"); sleep(1);
571 &close_fdisk();
572 undef(@list_disks_partitions_cache);
573 }
574
575 # delete_partition(disk, partition)
576 # Delete an existing partition
577 sub delete_partition
578 {
579 my ($disk, $part) = @_;
580 if ($has_parted) {
581         # Using parted
582         my $cmd = "parted -s ".$disk." rm ".$part;
583         my $out = &backquote_logged("$cmd </dev/null 2>&1");
584         if ($?) {
585                 &error("$cmd failed : $out");
586                 }
587         }
588 else {
589         # Using fdisk
590         &open_fdisk($disk);
591         &wprint("d\n");
592         local $rv = &wait_for($fh, 'Partition.*:', 'Selected partition');
593         &wprint("$part\n") if ($rv == 0);
594         &wait_for($fh, 'Command.*:');
595         &wprint("w\n");
596         &wait_for($fh, 'Syncing');
597         sleep(3);
598         &close_fdisk();
599         }
600 undef(@list_disks_partitions_cache);
601 }
602
603 # create_partition(disk, partition, start, end, type)
604 # Create a new partition with the given extent and type
605 sub create_partition
606 {
607 my ($disk, $part, $start, $end, $type) = @_;
608 if ($has_parted) {
609         # Using parted
610         my $pe = $part > 4 ? "logical" : "primary";
611         my $cmd;
612         if ($type) {
613                 $cmd = "parted -s ".$disk." unit cyl mkpartfs ".$pe." ".
614                        $type." ".($start-1)." ".$end;
615                 }
616         else {
617                 $cmd = "parted -s ".$disk." unit cyl mkpart ".$pe." ".
618                        ($start-1)." ".$end;
619                 }
620         my $out = &backquote_logged("$cmd </dev/null 2>&1");
621         if ($?) {
622                 &error("$cmd failed : $out");
623                 }
624         }
625 else {
626         # Using fdisk
627         &open_fdisk($disk);
628         &wprint("n\n");
629         local $wf = &wait_for($fh, 'primary.*\r?\n', 'First.*:');
630         if ($part > 4) {
631                 &wprint("l\n");
632                 }
633         else {
634                 &wprint("p\n");
635                 local $wf2 = &wait_for($fh, 'Partition.*:',
636                                             'Selected partition');
637                 &wprint("$part\n") if ($wf2 == 0);
638                 }
639         &wait_for($fh, 'First.*:') if ($wf != 1);
640         &wprint("$start\n");
641         &wait_for($fh, 'Last.*:');
642         &wprint("$end\n");
643         &wait_for($fh, 'Command.*:');
644
645         &wprint("t\n");
646         local $rv = &wait_for($fh, 'Partition.*:', 'Selected partition');
647         &wprint("$part\n") if ($rv == 0);
648         &wait_for($fh, 'Hex.*:');
649         &wprint("$type\n");
650         &wait_for($fh, 'Command.*:');
651         &wprint("w\n");
652         &wait_for($fh, 'Syncing'); sleep(3);
653         &close_fdisk();
654         }
655 undef(@list_disks_partitions_cache);
656 }
657
658 # create_extended(disk, partition, start, end)
659 # Create a new extended partition
660 sub create_extended
661 {
662 my ($disk, $part, $start, $end) = @_;
663 if ($has_parted) {
664         # Create using parted
665         my $cmd = "parted -s ".$disk." unit cyl mkpart extended ".
666                   ($start-1)." ".$end;
667         my $out = &backquote_logged("$cmd </dev/null 2>&1");
668         if ($?) {
669                 &error("$cmd failed : $out");
670                 }
671         }
672 else {
673         # Use classic fdisk
674         &open_fdisk($disk);
675         &wprint("n\n");
676         &wait_for($fh, 'primary.*\r?\n');
677         &wprint("e\n");
678         &wait_for($fh, 'Partition.*:');
679         &wprint("$part\n");
680         &wait_for($fh, 'First.*:');
681         &wprint("$start\n");
682         &wait_for($fh, 'Last.*:');
683         &wprint("$end\n");
684         &wait_for($fh, 'Command.*:');
685
686         &wprint("w\n");
687         &wait_for($fh, 'Syncing');
688         sleep(3);
689         &close_fdisk();
690         }
691 undef(@list_disks_partitions_cache);
692 }
693
694 # list_tags()
695 # Returns a list of known partition tag numbers
696 sub list_tags
697 {
698 if ($has_parted) {
699         # Parted types
700         return sort { $a cmp $b } (keys %parted_tags);
701         }
702 else {
703         # Classic fdisk types
704         return sort { hex($a) <=> hex($b) } (keys %tags);
705         }
706 }
707
708 # tag_name(tag)
709 # Returns a human-readable version of a tag
710 sub tag_name
711 {
712 return $tags{$_[0]} || $parted_tags{$_[0]} || $hidden_tags{$_[0]};
713 }
714
715 sub default_tag
716 {
717 return $has_parted ? 'ext2' : '83';
718 }
719
720 # conv_type(tag)
721 # Given a partition tag, returns the filesystem type (assuming it is supported)
722 sub conv_type
723 {
724 my ($tag) = @_;
725 my @rv;
726 if ($has_parted) {
727         # Use parted type names
728         if ($tag eq "fat16") {
729                 @rv = ( "msdos" );
730                 }
731         elsif ($tag eq "fat32") {
732                 @rv = ( "vfat" );
733                 }
734         elsif ($tag eq "ext2") {
735                 @rv = ( "ext3", "ext4", "ext2", "xfs", "reiserfs" );
736                 }
737         elsif ($tag eq "hfs") {
738                 @rv = ( "hfs" );
739                 }
740         elsif ($tag eq "linux-swap") {
741                 @rv = ( "swap" );
742                 }
743         elsif ($tag eq "NTFS") {
744                 @rv = ( "ntfs" );
745                 }
746         elsif ($tag eq "reiserfs") {
747                 @rv = "reiserfs";
748                 }
749         elsif ($tag eq "ufs") {
750                 @rv = ( "ufs" );
751                 }
752         else {
753                 return ( );
754                 }
755         }
756 else {
757         # Use fdisk type IDs
758         if ($tag eq "4" || $tag eq "6" || $tag eq "1" || $tag eq "e") {
759                 @rv = ( "msdos" );
760                 }
761         elsif ($tag eq "b" || $tag eq "c") {
762                 @rv = ( "vfat" );
763                 }
764         elsif ($tag eq "83") {
765                 @rv = ( "ext3", "ext4", "ext2", "xfs", "reiserfs" );
766                 }
767         elsif ($tag eq "82") {
768                 @rv = ( "swap" );
769                 }
770         elsif ($tag eq "81") {
771                 @rv = ( "minix" );
772                 }
773         else {
774                 return ( );
775                 }
776         }
777 local %supp = map { $_, 1 } &mount::list_fstypes();
778 @rv = grep { $supp{$_} } @rv;
779 return wantarray ? @rv : $rv[0];
780 }
781
782 # fstype_name(type)
783 # Returns a readable name for a filesystem type
784 sub fstype_name
785 {
786 return $text{"fs_".$_[0]};
787 }
788
789 sub mkfs_options
790 {
791 if ($_[0] eq "ext2") {
792         &opt_input("ext2_b", $text{'bytes'}, 1);
793         &opt_input("ext2_f", $text{'bytes'}, 0);
794         &opt_input("ext2_i", "", 1);
795         &opt_input("ext2_m", "%", 0);
796         &opt_input("ext2_g", "", 1);
797         print &ui_table_row($text{'ext2_c'},
798                 &ui_yesno_radio("ext2_c", 0));
799         }
800 elsif ($_[0] eq "msdos" || $_[0] eq "vfat") {
801         &opt_input("msdos_ff", "", 1);
802         print &ui_table_row($text{'msdos_F'},
803              &ui_select("msdos_F", undef,
804                         [ [ undef, $text{'default'} ],
805                           [ 12 ], [ 16 ], [ 32 ],
806                           [ "*", $text{'msdos_F_other'} ] ])." ".
807              &ui_textbox("msdos_F_other", undef, 4));
808         &opt_input("msdos_i", "", 1);
809         &opt_input("msdos_n", "", 0);
810         &opt_input("msdos_r", "", 1);
811         &opt_input("msdos_s", "sectors", 0);
812         print &ui_table_row($text{'msdos_c'},
813                 &ui_yesno_radio("msdos_c", 0));
814         }
815 elsif ($_[0] eq "minix") {
816         &opt_input("minix_n", "", 1);
817         &opt_input("minix_i", "", 0);
818         &opt_input("minix_b", "", 1);
819         print &ui_table_row($text{'minix_c'},
820                 &ui_yesno_radio("minix_c", 0));
821         }
822 elsif ($_[0] eq "reiserfs") {
823         print &ui_table_row($text{'reiserfs_force'},
824                 &ui_yesno_radio("reiserfs_f", 0));
825
826         print &ui_table_row($text{'reiserfs_hash'},
827                 &ui_select("reiserfs_h", "",
828                            [ [ "", $text{'default'} ],
829                              [ "rupasov", "tea" ] ]));
830         }
831 elsif ($_[0] =~ /^ext\d+$/) {
832         &opt_input("ext2_b", $text{'bytes'}, 1);
833         &opt_input("ext2_f", $text{'bytes'}, 0);
834         &opt_input("ext2_i", "", 1);
835         &opt_input("ext2_m", "%", 0);
836         &opt_input("ext3_j", "MB", 1);
837         print &ui_table_row($text{'ext2_c'},
838                 &ui_yesno_radio("ext2_c", 0));
839         }
840 elsif ($_[0] eq "xfs") {
841         print &ui_table_row($text{'xfs_force'},
842                 &ui_yesno_radio("xfs_f", 0));
843         &opt_input("xfs_b", $text{'bytes'}, 0);
844         }
845 elsif ($_[0] eq "jfs") {
846         &opt_input("jfs_s", $text{'megabytes'}, 1);
847         print &ui_table_row($text{'jfs_c'},
848                 &ui_yesno_radio("jfs_c", 0));
849         }
850 elsif ($_[0] eq "fatx") {
851         # Has no options!
852         print &ui_table_row(undef, $text{'fatx_none'}, 4);
853         }
854 }
855
856 # mkfs_parse(type, device)
857 # Returns a command to build a new filesystem of the given type on the
858 # given device. Options are taken from %in.
859 sub mkfs_parse
860 {
861 local($cmd);
862 if ($_[0] eq "ext2") {
863         $cmd = "mkfs -t ext2";
864         $cmd .= &opt_check("ext2_b", '\d+', "-b");
865         $cmd .= &opt_check("ext2_f", '\d+', "-f");
866         $cmd .= &opt_check("ext2_i", '\d{4,}', "-i");
867         $cmd .= &opt_check("ext2_m", '\d+', "-m");
868         $cmd .= &opt_check("ext2_g", '\d+', "-g");
869         $cmd .= $in{'ext2_c'} ? " -c" : "";
870         $cmd .= " -q";
871         $cmd .= " $_[1]";
872         }
873 elsif ($_[0] eq "msdos" || $_[0] eq "vfat") {
874         $cmd = "mkfs -t $_[0]";
875         $cmd .= &opt_check("msdos_ff", '[1-2]', "-f");
876         if ($in{'msdos_F'} eq '*') {
877                 $in{'msdos_F_other'} =~ /^\d+$/ ||
878                         &error(&text('opt_error', $in{'msdos_F_other'},
879                                                   $text{'msdos_F'}));
880                 $cmd .= " -F ".$in{'msdos_F_other'};
881                 }
882         elsif ($in{'msdos_F'}) {
883                 $cmd .= " -F ".$in{'msdos_F'};
884                 }
885         $cmd .= &opt_check("msdos_i", '[0-9a-f]{8}', "-i");
886         $cmd .= &opt_check("msdos_n", '\S{1,11}', "-n");
887         $cmd .= &opt_check("msdos_r", '\d+', "-r");
888         $cmd .= &opt_check("msdos_s", '\d+', "-s");
889         $cmd .= $in{'msdos_c'} ? " -c" : "";
890         $cmd .= " $_[1]";
891         }
892 elsif ($_[0] eq "minix") {
893         local(@plist, $disk, $part, $i, @pinfo);
894         $cmd = "mkfs -t minix";
895         $cmd .= &opt_check("minix_n", '14|30', "-n ");
896         $cmd .= &opt_check("minix_i", '\d+', "-i ");
897         $cmd .= $in{'minix_c'} ? " -c" : "";
898         $cmd .= &opt_check("minix_b", '\d+', " ");
899         $cmd .= " $_[1]";
900         }
901 elsif ($_[0] eq "reiserfs") {
902         $cmd = "yes | mkreiserfs";
903         $cmd .= " -f" if ($in{'reiserfs_f'});
904         $cmd .= " -h $in{'reiserfs_h'}" if ($in{'reiserfs_h'});
905         $cmd .= " $_[1]";
906         }
907 elsif ($_[0] =~ /^ext\d+$/) {
908         if (&has_command("mkfs.$_[0]")) {
909                 $cmd = "mkfs -t $_[0]";
910                 $cmd .= &opt_check("ext3_j", '\d+', "-j");
911                 }
912         elsif ($_[0] eq "ext3" && &has_command("mke3fs")) {
913                 $cmd = "mke3fs";
914                 $cmd .= &opt_check("ext3_j", '\d+', "-j");
915                 }
916         elsif ($_[0] eq "ext4" && &has_command("mke4fs")) {
917                 $cmd = "mke4fs";
918                 $cmd .= &opt_check("ext3_j", '\d+', "-j");
919                 }
920         else {
921                 $cmd = "mkfs.ext2 -j";
922                 if (!$in{'ext3_j_def'}) {
923                         $in{'ext3_j'} =~ /^\d+$/ ||
924                                 &error(&text('opt_error', $in{'ext3_j'},
925                                              $text{'ext3_j'}));
926                         $cmd .= " -J size=$in{'ext3_j'}";
927                         }
928                 }
929         $cmd .= &opt_check("ext2_b", '\d+', "-b");
930         $cmd .= &opt_check("ext2_f", '\d+', "-f");
931         $cmd .= &opt_check("ext2_i", '\d{4,}', "-i");
932         $cmd .= &opt_check("ext2_m", '\d+', "-m");
933         $cmd .= $in{'ext2_c'} ? " -c" : "";
934         $cmd .= " -q";
935         $cmd .= " $_[1]";
936         }
937 elsif ($_[0] eq "xfs") {
938         $cmd = "mkfs -t $_[0]";
939         $cmd .= " -f" if ($in{'xfs_f'});
940         $cmd .= " -b size=$in{'xfs_b'}" if (!$in{'xfs_b_def'});
941         $cmd .= " $_[1]";
942         }
943 elsif ($_[0] eq "jfs") {
944         $cmd = "mkfs -t $_[0] -q";
945         $cmd .= &opt_check("jfs_s", '\d+', "-s");
946         $cmd .= " -c" if ($in{'jfs_c'});
947         $cmd .= " $_[1]";
948         }
949 elsif ($_[0] eq "fatx") {
950         $cmd = "mkfs -t $_[0] $_[1]";
951         }
952 if (&has_command("partprobe")) {
953         $cmd .= "partprobe ; $cmd";
954         }
955 return $cmd;
956 }
957
958 # can_tune(type)
959 # Returns 1 if this filesystem type can be tuned
960 sub can_tune
961 {
962 return $_[0] =~ /^ext\d+$/;
963 }
964
965 # tunefs_options(type)
966 # Output HTML for tuning options for some filesystem type
967 sub tunefs_options
968 {
969 if ($_[0] =~ /^ext\d+$/) {
970         # Gaps between checks
971         &opt_input("tunefs_c", "", 1);
972
973         # Action on error
974         print &ui_table_row($text{'tunefs_e'},
975                 &ui_radio("tunefs_e_def", 1,
976                         [ [ 1, $text{'opt_default'} ],
977                           [ 0, &ui_select("tunefs_e", undef,
978                                 [ [ "continue", $text{'tunefs_continue'} ],
979                                   [ "remount-ro", $text{'tunefs_remount'} ],
980                                   [ "panic", $text{'tunefs_panic'} ] ]) ] ]));
981
982         # Reserved user
983         print &ui_table_row($text{'tunefs_u'},
984                 &ui_opt_textbox("tunefs_u", undef, 13, $text{'opt_default'})." ".
985                 &user_chooser_button("tunefs_u", 0));
986
987         # Reserved group
988         print &ui_table_row($text{'tunefs_g'},
989                 &ui_opt_textbox("tunefs_g", undef, 13, $text{'opt_default'})." ".
990                 &group_chooser_button("tunefs_g", 0));
991
992         # Reserved blocks
993         &opt_input("tunefs_m", "%", 1);
994
995         # Time between checks
996         $tsel = &ui_select("tunefs_i_unit", undef,
997                            [ [ "d", $text{'tunefs_days'} ],
998                              [ "w", $text{'tunefs_weeks'} ],
999                              [ "m", $text{'tunefs_months'} ] ]);
1000         &opt_input("tunefs_i", $tsel, 0);
1001         }
1002 }
1003
1004 # tunefs_parse(type, device)
1005 # Returns the tuning command based on user inputs
1006 sub tunefs_parse
1007 {
1008 if ($_[0] =~ /^ext\d+$/) {
1009         $cmd = "tune2fs";
1010         $cmd .= &opt_check("tunefs_c", '\d+', "-c");
1011         $cmd .= $in{'tunefs_e_def'} ? "" : " -e$in{'tunefs_e'}";
1012         $cmd .= $in{'tunefs_u_def'} ? "" : " -u".getpwnam($in{'tunefs_u'});
1013         $cmd .= $in{'tunefs_g_def'} ? "" : " -g".getgrnam($in{'tunefs_g'});
1014         $cmd .= &opt_check("tunefs_m",'\d+',"-m");
1015         $cmd .= &opt_check("tunefs_i", '\d+', "-i").
1016                 ($in{'tunefs_i_def'} ? "" : $in{'tunefs_i_unit'});
1017         $cmd .= " $_[1]";
1018         }
1019 return $cmd;
1020 }
1021
1022 # need_reboot(disk)
1023 # Returns 1 if a reboot is needed after changing the partitions on some disk
1024 sub need_reboot
1025 {
1026 local $un = `uname -r`;
1027 return $un =~ /^2\.0\./ || $un =~ /^1\./ || $un =~ /^0\./;
1028 }
1029
1030 # device_status(device)
1031 # Returns an array of  directory, type, mounted
1032 sub device_status
1033 {
1034 @mounted = &foreign_call("mount", "list_mounted") if (!@mounted);
1035 @mounts = &foreign_call("mount", "list_mounts") if (!@mounts);
1036 local $label = &get_label($_[0]);
1037 local $volid = &get_volid($_[0]);
1038
1039 local ($mounted) = grep { &same_file($_->[1], $_[0]) ||
1040                           $_->[1] eq "LABEL=$label" ||
1041                           $_->[1] eq "UUID=$volid" } @mounted;
1042 local ($mount) = grep { &same_file($_->[1], $_[0]) ||
1043                         $_->[1] eq "LABEL=$label" ||
1044                         $_->[1] eq "UUID=$volid" } @mounts;
1045 if ($mounted) { return ($mounted->[0], $mounted->[2], 1,
1046                         &indexof($mount, @mounts),
1047                         &indexof($mounted, @mounted)); }
1048 elsif ($mount) { return ($mount->[0], $mount->[2], 0,
1049                          &indexof($mount, @mounts)); }
1050 if ($raid_module) {
1051         $raidconf = &foreign_call("raid", "get_raidtab") if (!$raidconf);
1052         foreach $c (@$raidconf) {
1053                 foreach $d (&raid::find_value('device', $c->{'members'})) {
1054                         return ( $c->{'value'}, "raid", 1 ) if ($d eq $_[0]);
1055                         }
1056                 }
1057         }
1058 if ($lvm_module) {
1059         if (!scalar(@physical_volumes)) {
1060                 @physical_volumes = ();
1061                 foreach $vg (&foreign_call("lvm", "list_volume_groups")) {
1062                         push(@physical_volumes,
1063                                 &foreign_call("lvm", "list_physical_volumes",
1064                                                      $vg->{'name'}));
1065                         }
1066                 }
1067         foreach $pv (@physical_volumes) {
1068                 return ( $pv->{'vg'}, "lvm", 1)
1069                         if ($pv->{'device'} eq $_[0]);
1070                 }
1071         }
1072 return ();
1073 }
1074
1075 # can_fsck(type)
1076 # Returns 1 if some filesystem type can fsck'd
1077 sub can_fsck
1078 {
1079 return ($_[0] =~ /^ext\d+$/ && &has_command("fsck.$_[0]") ||
1080         $_[0] eq "minix" && &has_command("fsck.minix"));
1081 }
1082
1083 # fsck_command(type, device)
1084 # Returns the fsck command to unconditionally check a filesystem
1085 sub fsck_command
1086 {
1087 if ($_[0] =~ /^ext\d+$/) {
1088         return "fsck -t $_[0] -p $_[1]";
1089         }
1090 elsif ($_[0] eq "minix") {
1091         return "fsck -t minix -a $_[1]";
1092         }
1093 }
1094
1095 # fsck_error(code)
1096 # Returns a description of an exit code from fsck
1097 sub fsck_error
1098 {
1099 return $text{"fsck_err$_[0]"} ? $text{"fsck_err$_[0]"}
1100                               : &text("fsck_unknown", $_[0]);
1101 }
1102
1103 # partition_select(name, value, mode, [&found], [disk_regexp])
1104 # Returns HTML for selecting a disk or partition
1105 # mode 0 = floppies and disk partitions
1106 #      1 = disks
1107 #      2 = floppies and disks and disk partitions
1108 #      3 = disk partitions
1109 sub partition_select
1110 {
1111 local $rv = "<select name=$_[0]>\n";
1112 local ($found, $d, $p);
1113 if (($_[2] == 0 || $_[2] == 2) &&
1114     (-r "/dev/fd0" || $_[1] =~ /^\/dev\/fd[01]$/)) {
1115         $rv .= sprintf "<option %s value=/dev/fd0>%s\n",
1116                 $_[1] eq "/dev/fd0" ? "selected" : "",
1117                 &text('select_fd', 0) if (!$_[4] || "/dev/fd0" =~ /$_[4]/);
1118         $rv .= sprintf "<option %s value=/dev/fd1>%s\n",
1119                 $_[1] eq "/dev/fd1" ? "selected" : "",
1120                 &text('select_fd', 1) if (!$_[4] || "/dev/fd1" =~ /$_[4]/);
1121         $found++ if ($_[1] =~ /^\/dev\/fd[01]$/);
1122         }
1123 local @dlist = &list_disks_partitions();
1124 foreach $d (@dlist) {
1125         local $dev = $d->{'device'};
1126         next if ($_[4] && $dev !~ /$_[4]/);
1127         if ($_[2] == 1 || $_[2] == 2) {
1128                 local $name = $d->{'desc'};
1129                 $name .= " ($d->{'model'})" if ($d->{'model'});
1130                 $rv .= sprintf "<option value=%s %s>%s\n",
1131                         $dev, $_[1] eq $dev ? "selected" : "", $name;
1132                 $found++ if ($dev eq $_[1]);
1133                 }
1134         if ($_[2] == 0 || $_[2] == 2 || $_[2] == 3) {
1135                 foreach $p (@{$d->{'parts'}}) {
1136                         next if ($p->{'extended'});
1137                         local $name = $p->{'desc'};
1138                         $name .= " (".&tag_name($p->{'type'}).")"
1139                                 if (&tag_name($p->{'type'}));
1140                         $rv .= sprintf "<option %s value=%s>%s\n",
1141                                   $_[1] eq $p->{'device'} ? "selected" : "",
1142                                   $p->{'device'}, $name;
1143                         $found++ if ($_[1] eq $p->{'device'});
1144                         }
1145                 }
1146         }
1147 if (!$found && $_[1] && !$_[3]) {
1148         $rv .= "<option selected>$_[1]\n";
1149         }
1150 if ($_[3]) {
1151         ${$_[3]} = $found;
1152         }
1153 $rv .= "</select>\n";
1154 return $rv;
1155 }
1156
1157 # label_select(name, value, &found)
1158 # Returns HTML for selecting a filesystem label
1159 sub label_select
1160 {
1161 local $rv = "<select name=$_[0]>\n";
1162 local @dlist = &list_disks_partitions();
1163 local $any;
1164 foreach $d (@dlist) {
1165         local $dev = $d->{'device'};
1166         foreach $p (@{$d->{'parts'}}) {
1167                 next if ($p->{'type'} ne '83');
1168                 local $label = &get_label($p->{'device'});
1169                 next if (!$label);
1170                 $rv .= sprintf "<option %s value=%s>%s (%s)\n",
1171                           $_[1] eq $label ? "selected" : "",
1172                           $label, $label, $p->{'desc'};
1173                 ${$_[2]}++ if ($_[1] eq $label);
1174                 $any++;
1175                 }
1176         }
1177 if (!$found && $_[1] && !$_[2]) {
1178         $rv .= "<option selected>$_[1]\n";
1179         }
1180 $rv .= "</select>\n";
1181 return $any ? $rv : undef;
1182 }
1183
1184 # volid_select(name, value, &found)
1185 # Returns HTML for selecting a filesystem UUID
1186 sub volid_select
1187 {
1188 local ($name, $value, $found) = @_;
1189 local @dlist = &list_disks_partitions();
1190 local @opts;
1191 foreach my $d (@dlist) {
1192         local $dev = $d->{'device'};
1193         foreach $p (@{$d->{'parts'}}) {
1194                 next if ($p->{'type'} ne '83' && $p->{'type'} ne '82' &&
1195                          $p->{'type'} ne 'b' && $p->{'type'} ne 'c');
1196                 local $volid = &get_volid($p->{'device'});
1197                 next if (!$volid);
1198                 push(@opts, [ $volid, "$volid ($p->{'desc'})" ]);
1199                 $$found++ if ($value eq $volid);
1200                 }
1201         }
1202 if (@opts) {
1203         return &ui_select($name, $value, \@opts, 1, 0, $value ? 1 : 0);
1204         }
1205 else {
1206         return undef;
1207         }
1208 }
1209
1210 #############################################################################
1211 # Internal functions
1212 #############################################################################
1213 sub open_fdisk
1214 {
1215 local $fpath = &check_fdisk();
1216 ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", join(" ",$fpath, @_));
1217 }
1218
1219 sub open_sfdisk
1220 {
1221 local $sfpath = &has_command("sfdisk");
1222 ($fh, $fpid) = &foreign_call("proc", "pty_process_exec", join(" ",$sfpath, @_));
1223 }
1224
1225 sub check_fdisk
1226 {
1227 local $fpath = &has_command("fdisk");
1228 &error(&text('open_error', "<tt>fdisk</tt>")) if (!$fpath);
1229 return $fpath;
1230 }
1231
1232 sub close_fdisk
1233 {
1234 close($fh); kill('TERM', $fpid);
1235 }
1236
1237 sub wprint
1238 {
1239 syswrite($fh, $_[0], length($_[0]));
1240 }
1241
1242 sub opt_input
1243 {
1244 print &ui_table_row($text{$_[0]},
1245         &ui_opt_textbox($_[0], undef, 6, $text{'opt_default'})." ".$_[1]);
1246 }
1247
1248 sub opt_check
1249 {
1250 if ($in{"$_[0]_def"}) { return ""; }
1251 elsif ($in{$_[0]} !~ /^$_[1]$/) {
1252         &error(&text('opt_error', $in{$_[0]}, $text{$_[0]}));
1253         }
1254 else { return " $_[2] $in{$_[0]}"; }
1255 }
1256
1257 %tags = ('0', 'Empty',
1258          '1', 'FAT12',
1259          '2', 'XENIX root',
1260          '3', 'XENIX usr',
1261          '4', 'FAT16 <32M',
1262          '6', 'FAT16',
1263          '7', 'NTFS',
1264          '8', 'AIX',
1265          '9', 'AIX bootable',
1266          'a', 'OS/2 boot manager',
1267          'b', 'Windows FAT32',
1268          'c', 'Windows FAT32 LBA',
1269          'e', 'Windows FAT16 LBA',
1270         '10', 'OPUS',
1271         '11', 'Hidden FAT12',
1272         '12', 'Compaq diagnostic',
1273         '14', 'Hidden FAT16 < 32M',
1274         '16', 'Hidden FAT16',
1275         '17', 'Hidden NTFS',
1276         '18', 'AST Windows swapfile',
1277         '1b', 'Hidden Windows FAT (1b)',
1278         '1c', 'Hidden Windows FAT (1c)',
1279         '1e', 'Hidden Windows FAT (1e)',
1280         '24', 'NEC DOS',
1281         '3c', 'PartitionMagic recovery',
1282         '40', 'Venix 80286',
1283         '41', 'PPC PReP boot',
1284         '42', 'SFS',
1285         '4d', 'QNX 4.x',
1286         '4e', 'QNX 4.x 2nd partition',
1287         '4f', 'QNX 4.x 3rd partition',
1288         '50', 'OnTrack DM',
1289         '51', 'OnTrack DM6 Aux1',
1290         '52', 'CP/M',
1291         '53', 'OnTrack DM6 Aux3',
1292         '54', 'OnTrack DM6',
1293         '55', 'EZ-Drive',
1294         '56', 'Golden Bow',
1295         '5c', 'Priam Edisk',
1296         '61', 'SpeedStor',
1297         '63', 'GNU HURD or SysV',
1298         '64', 'Novell Netware 286',
1299         '65', 'Novell Netware 386',
1300         '70', 'DiskSecure Multi-Boot',
1301         '75', 'PC/IX',
1302         '80', 'Old Minix',
1303         '81', 'Minix / Old Linux / Solaris',
1304         '82', 'Linux swap',
1305         '83', 'Linux',
1306         '84', 'OS/2 hidden C: drive',
1307         '85', 'Linux extended',
1308         '86', 'NTFS volume set (86)',
1309         '87', 'NTFS volume set (87)',
1310         '8e', 'Linux LVM',
1311         '93', 'Amoeba',
1312         '94', 'Amoeba BBT',
1313         'a0', 'IBM Thinkpad hibernation',
1314         'a5', 'BSD/386',
1315         'a6', 'OpenBSD',
1316         'a7', 'NeXTSTEP',
1317         'b7', 'BSDI filesystem',
1318         'b8', 'BSDI swap',
1319         'c1', 'DRDOS/sec FAT12',
1320         'c4', 'DRDOS/sec FAT16 <32M',
1321         'c6', 'DRDOS/sec FAT16',
1322         'c7', 'Syrinx',
1323         'db', 'CP/M / CTOS',
1324         'e1', 'DOS access',
1325         'e3', 'DOS read-only',
1326         'e4', 'SpeedStor',
1327         'eb', 'BeOS',
1328         'ee', 'GPT',
1329         'f1', 'SpeedStor',
1330         'f4', 'SpeedStor large partition',
1331         'f2', 'DOS secondary',
1332         'fd', 'Linux RAID',
1333         'fe', 'LANstep',
1334         'ff', 'BBT'
1335         );
1336
1337 %hidden_tags = (
1338          '5', 'Extended',
1339          'f', 'Windows extended LBA',
1340         );
1341
1342 %parted_tags = (
1343         '', 'None',
1344         'fat16', 'Windows FAT16',
1345         'fat32', 'Windows FAT32',
1346         'ext2', 'Linux',
1347         'HFS', 'MacOS HFS',
1348         'linux-swap', 'Linux Swap',
1349         'NTFS', 'Windows NTFS',
1350         'reiserfs', 'ReiserFS',
1351         'ufs', 'FreeBSD UFS',
1352         );
1353         
1354 @space_type = ( '1', '4', '5', '6', 'b', 'c', 'e', '83' );
1355
1356 # can_edit_disk(device)
1357 sub can_edit_disk
1358 {
1359 my ($device) = @_;
1360 $device =~ s/\d+$//;
1361 foreach (split(/\s+/, $access{'disks'})) {
1362         return 1 if ($_ eq "*" || $_ eq $device);
1363         }
1364 return 0;
1365 }
1366
1367 # disk_space(device, [mountpoint])
1368 # Returns the amount of total and free space for some filesystem, or an
1369 # empty array if not appropriate.
1370 sub disk_space
1371 {
1372 local $w = $_[1] || $_[0];
1373 local $out = `df -k '$w'`;
1374 if ($out =~ /Mounted on\s*\n\s*\S+\s+(\S+)\s+\S+\s+(\S+)/i) {
1375         return ($1, $2);
1376         }
1377 elsif ($out =~ /Mounted on\s*\n\S+\s*\n\s+(\S+)\s+\S+\s+(\S+)/i) {
1378         return ($1, $2);
1379         }
1380 else {
1381         return ( );
1382         }
1383 }
1384
1385 # supported_filesystems()
1386 # Returns a list of filesystem types that can have mkfs_options called on them
1387 sub supported_filesystems
1388 {
1389 local @fstypes = ( "ext2" );
1390 push(@fstypes, "ext3") if (&has_command("mkfs.ext3") ||
1391                            &has_command("mke3fs") ||
1392                            `mkfs.ext2 -h 2>&1` =~ /\[-j\]/);
1393 push(@fstypes, "ext4") if (&has_command("mkfs.ext4") ||
1394                            &has_command("mke4fs"));
1395 push(@fstypes, "reiserfs") if (&has_command("mkreiserfs"));
1396 push(@fstypes, "xfs") if (&has_command("mkfs.xfs"));
1397 push(@fstypes, "jfs") if (&has_command("mkfs.jfs"));
1398 push(@fstypes, "fatx") if (&has_command("mkfs.fatx"));
1399 push(@fstypes, "msdos");
1400 push(@fstypes, "vfat");
1401 push(@fstypes, "minix");
1402 return @fstypes;
1403 }
1404
1405 # get_label(device, [type])
1406 # Returns the XFS or EXT label for some device's filesystem
1407 sub get_label
1408 {
1409 local $label;
1410 if ($has_e2label) {
1411         $label = `e2label $_[0] 2>&1`;
1412         chop($label);
1413         }
1414 if (($? || $label !~ /\S/) && $has_xfs_db) {
1415         $label = undef;
1416         local $out = &backquote_with_timeout("xfs_db -x -p xfs_admin -c label -r $_[0] 2>&1", 5);
1417         $label = $1 if ($out =~ /label\s*=\s*"(.*)"/ &&
1418                         $1 ne '(null)');
1419         }
1420 if (($? || $label !~ /\S/) && $has_reiserfstune) {
1421         $label = undef;
1422         local $out = &backquote_command("reiserfstune $_[0]");
1423         if ($out =~ /LABEL:\s*(\S+)/) {
1424                 $label = $1;
1425                 }
1426         }
1427 return $? || $label !~ /\S/ ? undef : $label;
1428 }
1429
1430 # get_volid(device)
1431 # Returns the UUID for some device's filesystem
1432 sub get_volid
1433 {
1434 local ($device) = @_;
1435 local $uuid;
1436 if (-d $uuid_directory) {
1437         # Use UUID mapping directory
1438         opendir(DIR, $uuid_directory);
1439         foreach my $f (readdir(DIR)) {
1440                 local $linkdest = &simplify_path(
1441                         &resolve_links("$uuid_directory/$f"));
1442                 if ($linkdest eq $device) {
1443                         $uuid = $f;
1444                         last;
1445                         }
1446                 }
1447         closedir(DIR);
1448         }
1449 elsif ($has_volid) {
1450         # Use vol_id command
1451         local $out = &backquote_command(
1452                         "vol_id ".quotemeta($device)." 2>&1", 1);
1453         if ($out =~ /ID_FS_UUID=(\S+)/) {
1454                 $uuid = $1;
1455                 }
1456         }
1457 return $uuid;
1458 }
1459
1460 # set_label(device, label, [type])
1461 # Tries to set the label for some device's filesystem
1462 sub set_label
1463 {
1464 if ($has_e2label && ($_[2] =~ /^ext[23]$/ || !$_[2])) {
1465         &system_logged("e2label '$_[0]' '$_[1]' >/dev/null 2>&1");
1466         return 1 if (!$?);
1467         }
1468 if ($has_xfs_db && ($_[2] eq "xfs" || !$_[2])) {
1469         &system_logged("xfs_db -x -p xfs_admin -c \"label $_[1]\" $_[0] >/dev/null 2>&1");
1470         return 1 if (!$?);
1471         }
1472 return 0;
1473 }
1474
1475 # set_name(&disk, &partition, name)
1476 # Sets the name of a partition, for partition types that support it
1477 sub set_name
1478 {
1479 my ($dinfo, $pinfo, $name) = @_;
1480 my $cmd = "parted -s ".$dinfo->{'device'}." name ".$pinfo->{'number'}." ";
1481 if ($name) {
1482         $cmd .= quotemeta($name);
1483         }
1484 else {
1485         $cmd .= " '\"\"'";
1486         }
1487 my $out = &backquote_logged("$cmd </dev/null 2>&1");
1488 if ($?) {
1489         &error("$cmd failed : $out");
1490         }
1491 }
1492
1493 # set_partition_table(device, table-type)
1494 # Wipe and re-create the partition table on some disk
1495 sub set_partition_table
1496 {
1497 my ($disk, $table) = @_;
1498 my $cmd = "parted -s ".$disk." mktable ".$table;
1499 my $out = &backquote_logged("$cmd </dev/null 2>&1");
1500 if ($?) {
1501         &error("$cmd failed : $out");
1502         }
1503 }
1504
1505 # supports_label(&partition)
1506 # Returns 1 if the label can be set on a partition
1507 sub supports_label
1508 {
1509 my ($part) = @_;
1510 return $part->{'type'} eq '83' || $part->{'type'} eq 'ext2';
1511 }
1512
1513 # supports_name(&disk)
1514 # Returns 1 if the name can be set on a disk's partitions
1515 sub supports_name
1516 {
1517 my ($disk) = @_;
1518 return $disk->{'table'} eq 'gpt';
1519 }
1520
1521 # supports_hdparm(&disk)
1522 sub supports_hdparm
1523 {
1524 local ($d) = @_;
1525 return $d->{'type'} eq 'ide' || $d->{'type'} eq 'scsi' && $d->{'model'} =~ /ATA/;
1526 }
1527
1528 # supports_relabel(&disk)
1529 # Return 1 if a disk can have it's partition table re-written
1530 sub supports_relabel
1531 {
1532 return $has_parted ? 1 : 0;
1533 }
1534
1535 # supports_smart(&disk)
1536 sub supports_smart
1537 {
1538 return &foreign_installed("smart-status") &&
1539        &foreign_available("smart-status");
1540 }
1541
1542 # supports_extended(&disk)
1543 # Return 1 if some disk can support extended partitions
1544 sub supports_extended
1545 {
1546 my ($disk) = @_;
1547 return $disk->{'label'} eq 'msdos' ? 1 : 0;
1548 }
1549
1550 # list_table_types(&disk)
1551 # Returns the list of supported partition table types for a disk
1552 sub list_table_types
1553 {
1554 if ($has_parted) {
1555         return ( 'msdos', 'gpt', 'bsd', 'dvh', 'loop', 'mac', 'pc98', 'sun' );
1556         }
1557 else {
1558         return ( 'msdos' );
1559         }
1560 }
1561
1562 # get_parted_version()
1563 # Returns the version number of parted that is installed
1564 sub get_parted_version
1565 {
1566 my $out = &backquote_command("parted -v 2>&1 </dev/null");
1567 return $out =~ /parted.*\s([0-9\.]+)/i ? $1 : undef;
1568 }
1569
1570 1;