#!/usr/bin/perl -w
#Scan the known partitions for volume labels and generate /dev/volumnes/
#Do not even think about running this with a uncontrolled environment
#(without sudo, suid)
#Could be easily extended to also extract the volume id and the last 
#mount point.
#$Id: voldev,v 1.3 1999/10/18 14:51:11 andi Exp andi $
	
$swapcnt=0; 

open(F, "/proc/partitions") || do { 
	system("mount /proc") || &out("cannot mount /proc");
	
	$tmpproc = 1; 
	open(F, "/proc/partitions") || &out("cannot open /proc/partitions"); 
};
	
$head = <F>; 

%columns = &parsehead($head); 

unless (-d "/dev/volumes/") {
	&cmd("mkdir -m066 /dev/volumes"); 
}

&cmd("cd /dev/volumes"); 

my %fparts = ();

# read /proc/partitions
while (<F>) { 
	my @f = split; 
	next unless @f;

	my $name = $f[$columns{"name"}];
	my $minor = $f[$columns{"minor"}];
	my $major = $f[$columns{"major"}];

	# is it a device that may have partitions?
	unless ($name =~ /\d$/) { 
		%fparts = &fdisk($name); 
		next; 
	}

	my $ptype; 
	unless ($fparts{$name} && ($ptype = $fparts{$name}))  { 
		&warn("fdisk knows nothing about $name");
		next;
	} 

	#XXX: DOS, NTFS
	unless ($ptype =~ /Linux/) { 
		unless ($ptype =~ /Extended/) {
			&warn("ignoring $name with $ptype");
		}
		next;
	}
	if ($ptype =~ /swap/) { 
		#XXX: use free space in new swap format as volumelabel.
		$vname = "swap$swapcnt"; 
		$swapcnt++; 
	} else { 
		$vname = &volumename($name); 
		next if (!$vname);  
	}

	if ($vname =~ /none/) {
		&warn("$name has no volume label; using $name");
		$vname = $name;
	}
		
	&cmd("mknod $vname b $major $minor");
}

# read the volume name of a ext2 device.
sub volumename {
	my($name) = (@_); 

	if (!open(T, "/sbin/tune2fs 2>&1 -l /dev/$name |")) { 
		return ""; 
	}
	
	my $vname = ""; 
	while (<T>) { 
		($vname) =  /^Filesystem volume name:\s+(.+)/;
		last if $vname; 
	} 
	close(T); 

	&warn("tune2fs failed for $name.") if $?; 

	return $vname; 
}

# return a hash with the partitionnames of a given physical device.
sub fdisk {
	my ($name) = (@_); 
	my (%parts) = (); 

	open(D, "(echo p ; echo q) | /sbin/fdisk 2>/dev/null /dev/$name |") 
		|| &out("cannot run fdisk /dev/$name: $?"); 

	# find header in fdisk output.
	my %fcol; 
	while (<D>) { 
		if (/Device.*System/) {
			%fcol = parsehead($_); 
			last;
		}
	}

	# parse devices.
	while (<D>) {
		my (@f) = split;
		my $dev; 

		next unless (($dev) = /^\/dev\/(\w+)/); 

		# crude hack to insert empty column when Boot is missing
		unless ($f[$fcol{"Boot"}] && $f[$fcol{"Boot"}] eq "*") { 
			splice(@f,$fcol{"Boot"},0,"dummy"); 
		}

		$parts{$dev}  = $f[$fcol{"System"}];
	}

	close D; 
	&out( "fdisk failed: $?" ) if $?;

	return %parts;
}

#intelligent /proc parser.
#don't hardcode columns, instead get them from the file header.
#return a hash which maps columns names to columns
sub parsehead { 
	my($head) = (@_); 
	my %columns = (); 

	$head =~ s/^\s+//; 
	
	my @f = split(/\s+/, $head); 
	my $c = 0; 
	foreach $i (@f) { 
		$columns{$i} = $c;
		$c++;
	}
	return %columns;
} 
	
sub out { 
	my($msg) = (@_); 
	system("umount /proc") if $tmpproc;
	print STDERR "$msg: $!\n"; 
	exit 1;
}
	
sub warn { 
	my($msg) = (@_); 
	print STDERR "warning: $msg\n";
}

sub cmd { 
	my($cmd) = (@_); 
	print "$cmd\n"; 
}
