#!perl -w # # ActiveBlockDev -- a single blockdev, as found in /sys # Copyright (C) 2005 Erik van Konijnenburg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # # The blockdev can be a whole device or a partition. # descriptors contain: # - name, path as found in /sys (sda, sda/sda1) # - devno, as found in /sys (8:1) # - parent, undef for whole device, maj:minor of parent otherwise # - hw, path to underlying hardware device, # eg an ide controller somewhere on a PCI bus. # this is a path relative to /sys/devices, # and can be undef (eg for ram disk) # - yspecial, a block special file giving access to the device # in the generated initrd image. # Starts being undefined. # - creator, the tool that will be used when booting to create # yspecial; this may # - partitions, list of partitions contained in the device. # these partitions are also ActiveBlockDevs. # # NOTE: the partition list relies on ActiveBlockDevTab making # a complete scan of all block devices, each partition registering # itself as partition with the parent device. # # # An issue that deserves special attention is the naming of block devices. # Internally, the kernel uses major:minor numbers to keep track of devices; # as an example, 3:1 could be used for the first partition of the first IDE disk. # However, that's not how fsck and mount refer to block devices; they want a pathname # for a block special file, like /dev/hda1. # # If we want to use a block device on the initial boot image, for example because # we want to mount it, we need to know the pathname that the device has. # The tricky part is that there are different tools that can create the pathname # for a device, and they all have different ideas of what is a good pathname. # Different tools can use different names for the same kind of device. # Here's an overview of tools that make block devices on initial boot images: # # * mknod. In the simple case, we do our own mknod after loading the modules # that activate the device. We have a simple convention here: for a device # that shows up in /sys/block/hda, we create /dev/hda. # # * lvm2 and cryptsetup. These are both based on libdevmapper, and produce # a device file such as /dev/mapper/vg0-root; lvm2 adds a bit of sugar with # a symlink from /dev/vg0/root to /dev/mapper. # # * mdadm. Produces /dev/md0 [unless the configuration file fouls things up] # # * evms. This has its own library that puts pathnames in /dev/evms. # And it has compatibility modules, with name such as /dev/evms/lvm2/vg0/lv0, # and stuff like /dev/evms/.nodes/hdc, which you're not supposed to use. # # * udev. We don't currently use that, but it can generate pathnames # such as /dev/disk/by-label/root, that remain stable even if you change # the hardware. (Or it can be configured to produce a completely random # pathname; udev is *very* flexible) # # For yaird this means that when we create a device pathname, we must # remember the tool we use. Simplyfying a bit: # # my $abd = ActiveBlockDevTab::findByDevno("254:0"); # ... # $actions->add ("vgchange", $vgnam); # ... # $abd->setCreatedBy ("lvm"); # # And it means that when we mount we need a function that # # my $abd = ActiveBlockDevTab::findByDevno("254:0"); # ... # my $yspecial = $abd->yspecial(); # ... # $actions->add ("mount", $mountPoint, device => $yspecial); # # Here the function yspecial() returns the pathname to use for a device; it # needs to take into account the tool that was used to create the device: # if it was created by lvm, use /dev/mapper/something, if it was created # by evms, use /dev/evms/something. # # Note that there are some consistency checks that we can add: # if we ask for a name with yspecial() before any tool created the # pathname, we have a bug in yaird. If the same device is marked # as created by first lvm, then later as created by mknod, that also # suggests a bug. # use strict; use warnings; use BlockSpecialFileTab; package ActiveBlockDev; use base 'Obj'; sub fill { my $self = shift; $self->SUPER::fill(); $self->takeArgs ('name', 'devno', 'parent', 'hw'); $self->{partitions} = []; if (defined ($self->parent)) { push @{$self->parent->partitions}, $self; } $self->{creator} = undef; $self->{yspecial} = undef; } sub name { return $_[0]->{name}; } sub devno { return $_[0]->{devno}; } sub parent { return $_[0]->{parent}; } sub hw { return $_[0]->{hw}; } sub partitions { return $_[0]->{partitions}; } sub creator { return $_[0]->{creator}; } sub string { my $self = shift; my $name = $self->name; my $devno = $self->devno; my $parent = (defined($self->parent) ? $self->parent->name : "--"); my $hw = ($self->hw or "--"); my $creator = ($self->creator or "--"); return "$name($devno) in $parent at $hw by $creator"; } sub yspecial { my $self = shift; my $name = $self->name; my $yspecial = $self->{yspecial}; if (! defined ($yspecial)) { Base::bug ("Bug in yspecial for $name"); } return $yspecial; } # # setCreator -- Note which tool (mknod, lvm, evms etc) will be used to create # the block special file when the initial boot image is running. # As a side effect, determine what the filename of the block special # file will be, and store that name in yspecial. # sub setCreator { my ($self, $creator) = @_; my $name = $self->name; my $devno = $self->devno; if (defined ($self->creator)) { if ($self->creator eq $creator) { # dont bother setting the same creator twice return; } Base::bug ("Bug in setCreator for $name/$creator"); } $self->{creator} = $creator; if ($creator eq "mkbdev") { Base::assert ($name !~ /^dm-\d+$/); $self->{yspecial} = "/dev/$name"; } elsif ($creator eq "mdadm") { Base::assert ($name =~ /^md\d+$/); $self->{yspecial} = "/dev/$name"; } elsif ($creator eq "devmapper") { Base::assert ($name =~ /^dm-\d+$/); my $paths = BlockSpecialFileTab::pathsByDevno ($devno); my @matches = grep { m!^/dev/mapper/[^/]+$! } @{$paths}; if (@matches < 1) { Base::fatal ("Cannot find a device file in /dev/mapper for $name"); } elsif (@matches > 1) { my $all = join (', ', @matches); Base::fatal ("Need a device file in /dev/mapper for $name, but cannot choose between $all"); } $self->{yspecial} = $matches[0]; } elsif ($creator eq "evms") { Base::assert ($name =~ /^dm-\d+$/); my $paths = BlockSpecialFileTab::pathsByDevno ($devno); # Allow subdirs (/dev/evms/lvm2/vg0/lv1), but not dot-dirs # (/dev/emvs/.nodes/hdc) my @matches = grep { m!^/dev/evms/[^.]! } @{$paths}; if (@matches < 1) { Base::fatal ("Cannot find a device file in /dev/evms for $name"); } elsif (@matches > 1) { my $all = join (', ', @matches); Base::fatal ("Need a device file in /dev/evms for $name, but cannot choose between $all"); } $self->{yspecial} = $matches[0]; } else { Base::bug ("Bug2 in setCreator for $name/$creator"); } Base::assert (defined ($self->{yspecial})); } sub hasPartitions { my $self = shift; return ($#{$self->partitions} >= 0); } 1;