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