#!/usr/bin/env perl #-*- Mode: perl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- # Simple fixed media configurator. Designed to be architecture- and distribution independent. # # Copyright (C) 2000-2001 Ximian, Inc. # # Authors: Hans Petter Jansson # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Library General Public License as published # by the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU Library General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # Best viewed with 100 columns of width. # Configuration files affected: # # /etc/fstab # Running programs affected/used: # # fdisk # mount BEGIN { require "___scriptsdir___/general.pl"; require "___scriptsdir___/platform.pl"; require "___scriptsdir___/util.pl"; require "___scriptsdir___/file.pl"; require "___scriptsdir___/xml.pl"; require "___scriptsdir___/filesys.pl"; } # --- Tool information --- # $name = "disks"; $version = "___version___"; @platforms = ("redhat-5.2", "redhat-6.0", "redhat-6.1", "redhat-6.2", "redhat-7.0", "redhat-7.1", "mandrake-7.2", "debian-2.2", "debian-woody", "suse-7.0", "turbolinux-7.0"); $description =<<"end_of_description;"; Configures locally mounted partitioned media. end_of_description; $progress_max = 16; # --- System config file locations --- # # We list each config file type with as many alternate locations as possible. # They are tried in array order. First found = used. # Right now there's only one entry per array, as I couldn't find any # typical deviations. @fstab_names = ( "/etc/fstab" ); # --- Internal configuration variables --- # # Configuration is parsed/read to, and printed/written from, these temporary variables. @cf_disks = (); # --- Backend-specific helper subs --- # sub update_partition { my ($disk, $device, $point, $fs, $options, $check); my ($listed, $bootable, $detected); my ($disk_found, $point_found) = (0, 0); my $label; ($disk, $device, $point, $fs, $options, $check, $size, $listed, $bootable, $detected, $label) = @_; if ($fs eq "auto") { $fs = ""; } if ($label eq "") { if ($device eq "") { return; } $label = xst_filesys_ext2_device_to_label ($device); } for ($i = 0; $cf_disks[$i]; $i++) { if ($disk eq "" || ($cf_disks[$i])->{device} eq $disk) { # Found disk. Now look for partition. for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++) { if ($cf_disks[$i]->{partitions}[$j]->{device} eq $device || ($label ne "") && ($cf_disks[$i]->{partitions}[$j]->{label} eq $label)) { # Found partition. if ($options ne "") { if ($options =~ /noauto/) { $cf_disks[$i]->{partitions}[$j]->{mounted} = 0; } else { $cf_disks[$i]->{partitions}[$j]->{mounted} = 1; } } if ($point ne "") { $cf_disks[$i]->{partitions}[$j]->{point} = $point; } if ($fs) { $cf_disks[$i]->{partitions}[$j]->{type} = $fs; } if ($listed) { $cf_disks[$i]->{partitions}[$j]->{listed} = 1; } if ($bootable) { $cf_disks[$i]->{partitions}[$j]->{bootable} = 1; } if ($detected) { $cf_disks[$i]->{partitions}[$j]->{detected} = 1; } if ($check) { $cf_disks[$i]->{partitions}[$j]->{check} = 1; } if ($size) { $cf_disks[$i]->{partitions}[$j]->{size} = $size; } if ($label) { $cf_disks[$i]->{partitions}[$j]->{label} = $label; } $disk_found = 1; $point_found = 1; last; } } if (!$point_found && $device) { # Make new partition entry. my %partition; if ($options =~ /noauto/) { %partition->{mounted} = 0; } else { %partition->{mounted} = 1; } %partition->{device} = $device; %partition->{point} = $point; %partition->{type} = $fs; %partition->{listed} = $listed; if ($bootable) { %partition->{bootable} = 1; } if ($detected) { %partition->{detected} = 1; } if ($check) { %partition->{check} = 1; } %partition->{size} = $size; %partition->{label} = $label; $bleh = $cf_disks[$i]->{partitions}; push(@$bleh, \%partition); $disk_found = 1; last; } } } if (!$disk_found) { # Make new disk entry containing this partition. my (%disk, %partition); if ($options =~ /noauto/) { %partition->{mounted} = 0; } else { %partition->{mounted} = 1; } %partition->{device} = $device; %partition->{point} = $point; %partition->{type} = $fs; %partition->{listed} = $listed; if ($bootable) { %partition->{bootable} = 1; } if ($detected) { %partition->{detected} = 1; } if ($check) { %partition->{check} = 1; } %partition->{size} = $size; %partition->{label} = $label; %disk->{device} = $disk; %disk->{partitions} = []; $bleh = %disk->{partitions}; push(@$bleh, \%partition); push(@cf_disks, \%disk); } } sub get_partition { my ($disk, $device, $label) = @_; my ($i, $j); for ($i = 0; $cf_disks[$i]; $i++) { if ($disk eq "" || ($cf_disks[$i])->{device} eq $disk) { # Found disk. Now look for partition. for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++) { if ($cf_disks[$i]->{partitions}[$j]->{device} eq $device || ($device eq "" && $cf_disks[$i]->{partitions}[$j]->{label} eq $label)) { # Found partition. return ($cf_disks[$i]->{partitions}[$j]); } } } } } sub get_partition_data { my ($disk, $device, $label); ($disk, $device, $label) = @_; for ($i = 0; $cf_disks[$i]; $i++) { if ($disk eq "" || ($cf_disks[$i])->{device} eq $disk) { # Found disk. Now look for partition. for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++) { if ($cf_disks[$i]->{partitions}[$j]->{device} eq $device || ($device eq "" && $cf_disks[$i]->{partitions}[$j]->{label} eq $label)) { # Found partition. return ($cf_disks[$i]->{partitions}[$j]->{point}, $cf_disks[$i]->{partitions}[$j]->{type}, $cf_disks[$i]->{partitions}[$j]->{listed}, $cf_disks[$i]->{partitions}[$j]->{mounted}, $cf_disks[$i]->{partitions}[$j]->{bootable}, $cf_disks[$i]->{partitions}[$j]->{check}, $cf_disks[$i]->{partitions}[$j]->{label}); } } } } } sub update_disk_size { my ($disk, $size); ($disk, $size) = @_; for ($i = 0; $cf_disks[$i]; $i++) { if (($cf_disks[$i])->{device} eq $disk) { # Found disk. $cf_disks[$i]->{size} = $size; last; } } } # --- Configuration file manipulation --- # # /etc/fstab # # # # ... # # Exists: (Presumably everywhere) # # Absent: (Presumably nowhere) sub read_fstab { my $fstab_file; local *FSTAB_FILE; # Find the file. $fstab_file = &xst_file_open_read_from_names(@fstab_names); if (not $fstab_file) { return; } # We didn't find it. *FSTAB_FILE = $fstab_file; # Parse the file. while () { my ($disk, $device, $point, $fs, $options, $check, $label); @line = split(/[ \n\r\t]+/, $_); if ($line[0] eq "") { shift @line; } if ($line[0] eq "") { next; } if (&xst_ignore_line($line[0])) { next; } ($device, $point, $fs, $options, $dump, $check) = @line; if ($device =~ /$LABEL=(.*)/) { $label = $1; $device = ""; } else { $label = ""; } if ($fs eq "nfs" || $fs eq "smbfs" || $fs eq "proc" || $fs eq "devpts" || $fs eq "iso9660" || $fs eq "swap") { next; # We can skip these filesystems for sure. } if ($point eq "none") { $dir = ""; } ($disk) = ($device =~ /([a-zA-Z\/]+)/); if ($disk eq "/dev/fd" || ($disk ne "" && $disk eq $device)) { next; # Skip floppies and CD-ROMs. } # (Find and update) or (add) our internal disk/partition record. if ($disk ne "" || $label ne "") { &update_partition($disk, $device, $point, $fs, $options, $check, "", 1, 0, "", $label); } } close(FILE); } sub write_fstab { my ($ifh, $ofh); local (*INFILE, *OUTFILE); ($ifh, $ofh) = &xst_file_open_filter_write_from_names(@fstab_names); if (not $ofh) { return; } # No point if we can't write. *INFILE = $ifh; *OUTFILE = $ofh; while () { my ($disk, $device, $point, $fs, $options, $dump, $check, $label); my ($ipoint, $itype, $icheck, $ilisted, $imounted, $ibootable, $ilabel); @line = split(/[ \n\r\t]+/, $_); if ($line[0] eq "") { shift @line; } if ($line[0] eq "") { print OUTFILE; next; } if (&xst_ignore_line($line[0])) { print OUTFILE; next; } ($device, $point, $fs, $options, $dump, $check) = @line; if ($fs eq "nfs" || $fs eq "smbfs" || $fs eq "proc" || $fs eq "devpts" || $fs eq "iso9660" || $fs eq "swap" || $device =~ /$\/dev\/fd.*/) { print OUTFILE; next; # We can skip these filesystems for sure. } # By now, we know that the "entry" is "interesting". Check if known. if ($device =~ /$LABEL=(.*)/) { $label = $1; $device = ""; $disk = ""; } else { $label = ""; ($disk) = ($device =~ /([a-zA-Z\/]+)/); } if ($disk eq "/dev/fd") { print OUTFILE; next; } ($ipoint, $itype, $ilisted, $imounted, $ibootable, $icheck, $ilabel) = &get_partition_data($disk, $device, $label); if ($ilisted) { # Write record if listedness requested. if ($ilabel ne "") { print OUTFILE "LABEL=" . $ilabel . " "; } else { print OUTFILE $device . " "; } if ($ipoint eq "") { print OUTFILE "none "; } else { print OUTFILE $ipoint . " "; } if ($itype eq "") { print OUTFILE "auto "; } else { print OUTFILE $itype . " "; } # Options merging and printing. my $prev = 0; if (!$imounted) { print OUTFILE "noauto"; $prev = 1; } my @options = ($options =~ /([a-zA-Z0-9=-]+),?/mg); for $option (@options) { # Strip options we handle, keep the rest. if ($option eq "auto" || $option eq "noauto" || $option eq "defaults") { next; } if ($prev) { print OUTFILE ","; } print OUTFILE $option; $prev = 1; } if (!$prev) { print OUTFILE "defaults"; } # Leave dump alone. print OUTFILE " $dump "; # Fsck onboot priority. if ($icheck eq "") { $icheck = 0; } if ($icheck == 1) { if ($ipoint eq "/") { print OUTFILE "1\n"; } else { print OUTFILE "2\n"; } } else { print OUTFILE "0\n"; } # Indicate that parameters for this partition have been stored. my $partition = &get_partition($disk, $device, $label); %$partition->{stored} = 1; } # Unknown or unlisted-by-request partitions are not written. } # Print the remaining partitions from our internal list. These are # newly added, and didn't exist in the fstab previously. for ($i = 0; $cf_disks[$i]; $i++) { for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++) { if ($cf_disks[$i]->{partitions}[$j]->{listed} && !$cf_disks[$i]->{partitions}[$j]->{stored}) { my $part = $cf_disks[$i]->{partitions}[$j]; &xst_report ("disks_fstab_add", $cf_disks[$i]->{partitions}[$j]->{device}); # Write record. print OUTFILE %$part->{device} . " "; if (%$part->{point} eq "") { print OUTFILE "none "; } else { print OUTFILE %$part->{point} . " "; } if (%$part->{type} eq "") { print OUTFILE "auto "; } else { print OUTFILE %$part->{type} . " "; } # Options printing. if (!%$part->{mounted}) { print OUTFILE "noauto "; } else { print OUTFILE "defaults "; } # No dumping by default. print OUTFILE " 0 "; # Fsck onboot priority. if (%$part->{check} == 1) { if (%$part->{point} eq "/") { print OUTFILE "1\n"; } else { print OUTFILE "2\n"; } } else { print OUTFILE "0\n"; } } } } close OUTFILE; } # fdisk -l # # <&filtered lines> # # [*] # [*] # ... # # Exists: Red Hat 6.2 (Presumably all Linux) # # Absent: # # The star is optional, and means that the partition is bootable. We could've # used /proc/partitions to get this information, but it lists CD-ROM devices # and whatnot as well, without a disambiguating identifier. Then there is # portability, forward-compatibility, people without /proc, etc. # fdisk -s # # sub get_fdisk { my $fdisk_tool; # Okay, so this is strictly not portable either. Patches welcome. my @check_devs = ( "/dev/hda", "/dev/hdb", "/dev/hdc", "/dev/hdd", "/dev/hde", "/dev/hdf", "/dev/hdg", "/dev/hdh", "/dev/sda", "/dev/sdb", "/dev/sdc", "/dev/sdd", "/dev/sde", "/dev/sdf", "/dev/eda", "/dev/edb", "/dev/edc", "/dev/edd", "/dev/xda", "/dev/xdb" ); $fdisk_tool = &xst_file_locate_tool("fdisk"); for $dev (@check_devs) { my ($disk, $device, $point, $fs, $options, $check, $size, $bootable); &xst_report ("disks_partition_probe", $dev); open(FDISK_HD, "$fdisk_tool -l $dev 2>/dev/null |"); while () { if (/^\/dev/) { @line = split(/[ \n\r\t]+/, $_); $device = $line[0]; shift @line; ($disk) = ($device =~ /([a-zA-Z\/]+)/); if ($line[0] eq "\*") { # NOTE: Currently unused. $bootable = 1; shift @line; } else { $bootable = 0; } shift @line; shift @line; # Start and end clusters. ($size) = ($line[0] =~ /([0-9]+)/); shift @line; # FIXME: add new popular ones, such as reiser and xfs and add # those documented by fdisk. if ($line[0] eq "82" || $line[0] eq "5" || $line[0] eq "f" || $line[0] eq "85") { next; } # Swap or extended. elsif ($line[0] eq "83") { $type = "ext2"; } elsif ($line[0] eq "e") { $type = "vfat"; } elsif ($line[0] eq "c") { $type = "fat32"; } elsif ($line[0] eq "b") { $type = "fat32"; } elsif ($line[0] eq "6") { $type = "msdos"; } elsif ($line[0] eq "4") { $type = "msdos"; } elsif ($line[0] eq "1") { $type = "msdos"; } elsif ($line[0] eq "7") { $type = "hpfs"; } else { $type = ""; } &update_partition($disk, $device, "", $type, "noauto", 0, $size, 0, $bootable, 1); } } close(FDISK_HD); &xst_report ("disks_size_query", $dev); open(FDISK_HD, "$fdisk_tool -s $dev 2>/dev/null |"); ($size) = ( =~ /([0-9]+)/); if ($size ne "") { &update_disk_size($dev, $size); } close(FDISK_HD); &xst_print_progress(); } } # --- XML parsing --- # sub xml_parse { # Scan XML to tree. $tree = &xst_xml_scan; # Walk the tree recursively and extract configuration parameters. # This is the top level - find and enter the toplevel tag. while (@$tree) { if ($$tree[0] eq "disks") { &xml_parse_toplevel($$tree[1]); } shift @$tree; shift @$tree; } return($tree); } sub xml_parse_toplevel { my $tree = $_[0]; shift @$tree; # Skip attributes. while (@$tree) { if ($$tree[0] eq "disk") { &xml_parse_disk($$tree[1]); } shift @$tree; shift @$tree; } } sub xml_parse_disk { my %disk; my $tree = $_[0]; shift @$tree; # Skip attributes. %disk->{partitions} = []; # Init partition list. while (@$tree) { if ($$tree[0] eq "device") { %disk->{device} = &xst_xml_get_word($$tree[1]); } elsif ($$tree[0] eq "size") { %disk->{size} = &xst_xml_get_word($$tree[1]); } elsif ($$tree[0] eq "partition") { my %partition = &xml_parse_partition($$tree[1]); $bleh = %disk->{partitions}; push(@$bleh, \%partition); } shift @$tree; shift @$tree; } push(@cf_disks, \%disk); } sub xml_parse_partition { my %partition; my $tree = $_[0]; shift @$tree; # Skip attributes. while (@$tree) { if ($$tree[0] eq "device") { %partition->{device} = &xst_xml_get_word($$tree[1]); } elsif ($$tree[0] eq "type") { %partition->{type} = &xst_xml_get_word($$tree[1]); } elsif ($$tree[0] eq "point") { %partition->{point} = &xst_xml_get_word($$tree[1]); } elsif ($$tree[0] eq "label") { %partition->{label} = &xst_xml_get_word($$tree[1]); } elsif ($$tree[0] eq "size") { %partition->{size} = &xst_xml_get_word($$tree[1]); } elsif ($$tree[0] eq "bootable") { %partition->{bootable} = &xml_parse_state($$tree[1]); } elsif ($$tree[0] eq "integritycheck") { %partition->{check} = &xml_parse_state($$tree[1]); } elsif ($$tree[0] eq "mounted") { %partition->{mounted} = &xml_parse_state($$tree[1]); } elsif ($$tree[0] eq "listed") { %partition->{listed} = &xml_parse_state($$tree[1]); } elsif ($$tree[0] eq "detected") { %partition->{detected} = &xml_parse_state($$tree[1]); } shift @$tree; shift @$tree; } return(%partition); } sub xml_parse_state { my $tree = $_[0]; # Check attribute; 'yes', 'true', 'no', 'false'. return(&xst_read_boolean($$tree[0]->{state})); } # --- XML printing --- # sub xml_print { &xst_xml_print_begin (); &xst_xml_print_line ("\n"); &xst_xml_print_vspace (); my @disks = @cf_disks; while (@disks) { if ($disks[0]) { my $disk = $disks[0]; &xst_xml_print_vspace (); &xst_xml_print_line ("\n"); &xst_xml_enter (); &xst_xml_print_line ("" . %$disk->{device} . "\n"); if (%$disk->{size}) { &xst_xml_print_line ("" . %$disk->{size} . "\n"); } my $partitions = %$disk->{partitions}; while (@$partitions) { my $partition = $$partitions[0]; &xst_xml_print_vspace (); &xst_xml_print_line ("\n"); &xst_xml_enter (); &xst_xml_print_line ("" . %$partition->{device} . "\n"); if (%$partition->{type}) { &xst_xml_print_line ("" . %$partition->{type} . "\n"); } if (%$partition->{point}) { &xst_xml_print_line ("" . %$partition->{point} . "\n"); } if (%$partition->{label}) { &xst_xml_print_line ("\n"); } if (%$partition->{size}) { &xst_xml_print_line ("" . %$partition->{size} . "\n"); } &xst_xml_print_state_tag ("bootable", %$partition->{bootable}); &xst_xml_print_state_tag ("integritycheck", %$partition->{check}); &xst_xml_print_state_tag ("mounted", %$partition->{mounted}); &xst_xml_print_state_tag ("listed", %$partition->{listed}); &xst_xml_print_state_tag ("detected", %$partition->{detected}); &xst_xml_leave (); &xst_xml_print_line ("\n"); shift @$partitions; } &xst_xml_leave (); &xst_xml_print_line ("\n"); &xst_xml_print_vspace (); } shift @disks; } &xst_xml_print_end (); } # --- Get (read) config --- # sub get { &get_fdisk; &read_fstab; &xst_end(); &xml_print (); } # --- Set (write) config --- # sub set_immediate { my $mount_tool; my $umount_tool; $mount_tool = &xst_file_locate_tool("mount"); $umount_tool = &xst_file_locate_tool("umount"); # Count partitions. my $i; my $j; my $num_partitions = 0; my $num_done = 0; for ($i = 0; $cf_disks[$i]; $i++) { for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++) { $num_partitions++; } } # Update mount status. if (($mount_tool ne "") && ($umount_tool ne "")) { my $i; my $j; for ($i = 0; $cf_disks[$i]; $i++) { for ($j = 0; $cf_disks[$i]->{partitions}[$j]; $j++) { if ($cf_disks[$i]->{partitions}[$j]->{mounted}) { &xst_report ("disks_mount", $cf_disks[$i]->{partitions}[$j]->{device}); system "$mount_tool " . $cf_disks[$i]->{partitions}[$j]->{device} . " " . $cf_disks[$i]->{partitions}[$j]->{point} . " >/dev/null 2>/dev/null"; } else { &xst_report ("disks_umount", $cf_disks[$i]->{partitions}[$j]->{device}); system "$umount_tool " . $cf_disks[$i]->{partitions}[$j]->{device} . " >/dev/null 2>/dev/null"; } $num_done++; &xst_progress(10 + (80 / ($num_partitions - $num_done + 1))); } } } else { &xst_report ("disks_mount_error"); } &xst_progress(90); } sub set { &xml_parse (); &write_fstab; &xst_progress(10); if ($xst_do_immediate) { &set_immediate; } &xst_end(); } # --- Filter config: XML in, XML out --- # sub filter { &xml_parse (); &xst_end(); &xml_print (); } # --- Main --- # # get, set and filter are special cases that don't need more parameters than a ref to their function. # Read general.pl.in:xst_run_directive to know about the format of this hash. $directives = { "get" => [ \&get, [], "" ], "set" => [ \&set, [], "" ], "filter" => [ \&filter, [], "" ] }; $tool = &xst_init ($name, $version, $description, $directives, @ARGV); &xst_platform_ensure_supported ($tool, @platforms); &xst_run ($tool);