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