diff --git a/zipl/src/Makefile b/zipl/src/Makefile index d116a70b1..47da62fe3 100644 --- a/zipl/src/Makefile +++ b/zipl/src/Makefile @@ -16,13 +16,16 @@ libs = $(rootdir)/libutil/libutil.a \ objects = misc.o error.o scan.o job.o boot.o bootmap.o disk.o \ install.o zipl.o $(rootdir)/zipl/boot/data.o -zipl_helpers = $(wildcard zipl_helper.*) +zipl_helpers = $(basename $(wildcard zipl_helper.*)) chreipl_helpers = $(subst zipl_,chreipl_, $(zipl_helpers)) all: zipl $(chreipl_helpers) zipl: $(objects) $(libs) +zipl_helper.device-mapper: $(rootdir)/libdasdview/libdasdview.a \ + $(libs) zipl_helper.device-mapper.o + chreipl_helper.%: zipl_helper.% ln -s $< $@ @@ -34,7 +37,7 @@ install: all $(CP) --no-dereference $(chreipl_helpers) $(DESTDIR)$(TOOLS_LIBDIR) clean: - rm -f *.o $(chreipl_helpers) zipl + rm -f *.o $(zipl_helpers) $(chreipl_helpers) zipl .PHONY: all install clean diff --git a/zipl/src/zipl_helper.device-mapper b/zipl/src/zipl_helper.device-mapper deleted file mode 100644 index 840beffd8..000000000 --- a/zipl/src/zipl_helper.device-mapper +++ /dev/null @@ -1,862 +0,0 @@ -#!/usr/bin/perl -w -# -# zipl_helper.device-mapper: print zipl parameters for a device-mapper device -# -# Copyright IBM Corp. 2009, 2017 -# -# s390-tools is free software; you can redistribute it and/or modify -# it under the terms of the MIT license. See LICENSE for details. -# -# Depending on the name by which the script is called, it serves one of two -# purposes: -# -# 1. Usage: zipl_helper.device-mapper or -# -# -# This tool attempts to obtain zipl parameters for a target directory or -# partition located on a device-mapper device. It assumes that the -# device-mapper table for this device conforms to the following rules: -# - directory is located on a device consisting of a single device-mapper -# target -# - only linear, mirror and multipath targets are supported -# - supported physical device types are DASD and SCSI devices -# - all of the device which contains the directory must be located on a single -# physical device (which may be mirrored or accessed through a multipath -# target) -# - any mirror in the device-mapper setup must include block 0 of the -# physical device -# -# 2. Usage: chreipl_helper.device-mapper -# -# This tool identifies the physical device which contains the specified -# device-mapper target devices. If the physical device was found, its -# major:minor parameters are printed. Otherwise, the script exits with an -# error message and a non-zero return code. -# - -use strict; -use File::Basename; -use POSIX qw/locale_h/; - -# Required tools -our $dmsetup = "dmsetup"; -our $mknod = "mknod"; -our $dasdview = "dasdview"; -our $blockdev = "blockdev"; - -# Constants -our $SECTOR_SIZE = 512; -our $DASD_PARTN_MASK = 0x03; -our $SCSI_PARTN_MASK = 0x0f; - -# Internal constants -our $DEV_TYPE_CDL = 0; -our $DEV_TYPE_LDL = 1; -our $DEV_TYPE_FBA = 2; -our $DEV_TYPE_SCSI = 3; - -our $TARGET_START = 0; -our $TARGET_LENGTH = 1; -our $TARGET_TYPE = 2; -our $TARGET_DATA = 3; - -our $TARGET_TYPE_LINEAR = 0; -our $TARGET_TYPE_MIRROR = 1; -our $TARGET_TYPE_MULTIPATH = 2; - -our $LINEAR_MAJOR = 0; -our $LINEAR_MINOR = 1; -our $LINEAR_START_SECTOR = 2; - -our $MIRROR_MAJOR = 0; -our $MIRROR_MINOR = 1; -our $MIRROR_START_SECTOR = 2; - -our $MULTIPATH_MAJOR = 0; -our $MULTIPATH_MINOR = 1; - -sub get_physical_device_dir($); -sub get_physical_device($$); -sub get_major_minor($); -sub get_table($$); -sub get_linear_data($$); -sub get_mirror_data($$); -sub get_multipath_status($); -sub get_multipath_data($$); -sub filter_table($$$); -sub get_target_start($); -sub get_target_major_minor($); -sub create_temp_device_node($$$); -sub get_blocksize($); -sub get_dasd_info($); -sub get_partition_start($$); -sub is_dasd($); -sub get_partition_base($$$); -sub get_device_characteristics($$); -sub get_type_name($); -sub check_for_mirror($@); -sub get_target_base($$$$@); -sub get_device_name($$); -sub check_tools(); - -my $phy_geometry; # Disk geometry of physical device -my $phy_blocksize; # Blocksize of physical device -my $phy_offset; # Offset in 512-byte sectors between start of physical - # device and start of filesystem -my $phy_type; # Type of physical device -my $phy_bootsectors; # Size of boot record in 512-byte sectors -my $phy_partstart; # Partition offset of physical device -my $phy_major; # Major device number of physical device -my $phy_minor; # Minor device number of physical device -my @target_list; # List of dm-targets between filesystem and physical - # device. -my $base_major; # Major device number of base device. -my $base_minor; # Minor device number of base device -my $directory; # Command line parameter -my $toolname; # Name of tool - -# Start -$toolname = basename($0); - -# Setup and use a standard locale to -# avoid localized scripting output -$ENV{LC_ALL} = "C"; # for child processes -setlocale(LC_ALL, "C"); # for current process - -# Use alternate code path if called as chreipl helper -if ($toolname eq "chreipl_helper.device-mapper") { - if (!defined($ARGV[0]) || !($ARGV[0] =~ /^\s*(\d+)\s*:\s*(\d+)\s*$/)) { - die("Usage: $toolname \n"); - } - ($phy_major, $phy_minor, $phy_offset, @target_list) = - get_physical_device($1, $2); - print("$phy_major:$phy_minor\n"); - exit(0); -} - -$directory = $ARGV[0]; -if (!defined($directory)) { - die("Usage: $toolname or \n"); -} - -# check if needed tools are available -check_tools(); - -if (($ARGV[0] =~ /^\s*(\d+)\s*:\s*(\d+)\s*$/)) { - # Determine physical (non-dm) device on which partition is located - ($phy_major, $phy_minor, $phy_offset, @target_list) = - get_physical_device($1,$2); -} -else -{ - # Determine physical (non-dm) device on which directory is located - ($phy_major, $phy_minor, $phy_offset, @target_list) = - get_physical_device_dir($directory); -} - -# Determine type and characteristics of physical device -($phy_type, $phy_blocksize, $phy_geometry, $phy_bootsectors, $phy_partstart) = - get_device_characteristics($phy_major, $phy_minor); - -# Handle partitions -if ($phy_partstart > 0) { - # Only the partition of the physical device is mapped so only the - # physical device can provide access to the boot record. - ($base_major, $base_minor) = - get_partition_base($phy_type, $phy_major, $phy_minor); - # Check for mirror - check_for_mirror(scalar(@target_list) - 1, @target_list); - # Adjust filesystem offset - $phy_offset += $phy_partstart * ($phy_blocksize / $SECTOR_SIZE); - $phy_partstart = 0; - # Update device geometry - (undef, undef, $phy_geometry, undef, undef) = - get_device_characteristics($base_major, $base_minor); -} else { - # All of the device is mapped, so the base device is the top most - # dm device which provides access to boot sectors - ($base_major, $base_minor) = - get_target_base($phy_major, $phy_minor, 0, $phy_bootsectors, - @target_list); -} - -# Check for valid offset of file system -if (($phy_offset % ($phy_blocksize / $SECTOR_SIZE)) != 0) { - die("Error: File system not aligned on physical block size\n"); -} - -# Print resulting information -print("targetbase=$base_major:$base_minor\n"); -print("targettype=".get_type_name($phy_type)."\n"); -if (defined($phy_geometry)) { - print("targetgeometry=$phy_geometry\n"); -} -print("targetblocksize=$phy_blocksize\n"); -print("targetoffset=".($phy_offset / ($phy_blocksize / $SECTOR_SIZE))."\n"); - -exit(0); - -# get_physical_device_from_dir(dir) -# Returns (phy_major, phy_minor, phy_offset, @target_list). -# target_list: [target_data1, target_data2, ..., target_datan] -# target_data: [major, minor, target] -sub get_physical_device_dir($) -{ - my ($directory) = @_; - my ($major, $minor) = get_major_minor($directory); - - return get_physical_device($major, $minor); -} - -# get_physical_device(major, minor) -# Returns (phy_major, phy_minor, phy_offset, @target_list). -# target_list: [target_data1, target_data2, ..., target_datan] -# target_data: [major, minor, target] -sub get_physical_device($$) -{ - my ($major, $minor) = @_; - my $table; - my $target; - my $start; - my $length; - my @target_list; - - $table = get_table($major, $minor); - if (scalar(@$table) == 0) { - die("Error: Could not retrieve device-mapper information for ". - "device '".get_device_name($major, $minor)."'\n"); - } - # Filesystem must be on a single dm target - if (scalar(@$table) != 1) { - die("Error: Unsupported setup: Directory '$directory' is ". - "located on a multi-target device-mapper device\n"); - } - - $target = $table->[0]; - push(@target_list, [$major, $minor, $target]); - $start = $target->[$TARGET_START]; - $length = $target->[$TARGET_LENGTH]; - while (1) { - # Convert fs_start to offset on parent dm device - $start += get_target_start($target); - ($major, $minor) = get_target_major_minor($target); - $table = get_table($major, $minor); - if (scalar(@$table) == 0) { - # Found non-dm device - return ($major, $minor, $start, @target_list); - } - # Get target in parent table which contains filesystem - $table = filter_table($table, $start, $length); - if (scalar(@$table) != 1) { - die("Error: Unsupported setup: Could not map ". - "directory '$directory' to a single physical ". - "device\n"); - } - $target = $table->[0]; - push(@target_list, [$major, $minor, $target]); - # Convert fs_start to offset on parent target - $start -= $target->[$TARGET_START]; - } -} - -# get_major_minor(filename) -# Returns: (device major, device minor) of the device containing the -# specified file. -sub get_major_minor($) -{ - my ($filename) = @_; - my @stat; - my $dev; - my $major; - my $minor; - - @stat = stat($filename); - if (!@stat) { - die("Error: Could not stat '$filename'\n"); - } - $dev = $stat[0]; - $major = ($dev & 0xfff00) >> 8; - $minor = ($dev & 0xff) | (($dev >> 12) & 0xfff00); - - return ($major, $minor); -} - -# get_table(major, minor) -# Returns: [target1, target2, ..., targetn] -# target: [start, length, type, data] -# data: linear_data|mirror_data|multipath_data -sub get_table($$) -{ - my ($major, $minor) = @_; - my @table; - my $dev_name = get_device_name($major, $minor); - local *HANDLE; - - open(HANDLE, "$dmsetup table -j $major -m $minor 2>/dev/null|") or - return undef; - while () { - if (!(/^(\d+)\s+(\d+)\s+(\S+)\s+(\S.*)$/)) { - die("Error: Unrecognized device-mapper table format ". - "for device '$dev_name'\n"); - } - my ($start, $length, $target_type, $args) = ($1, $2, $3, $4); - my $data; - my $type; - - if ($target_type eq "linear") { - $type = $TARGET_TYPE_LINEAR; - $data = get_linear_data($dev_name, $args); - } elsif ($target_type eq "mirror") { - $type = $TARGET_TYPE_MIRROR; - $data = get_mirror_data($dev_name, $args); - } elsif ($target_type eq "multipath") { - $type = $TARGET_TYPE_MULTIPATH; - $data = get_multipath_data($dev_name, $args); - } else { - die("Error: Unsupported setup: Unsupported ". - "device-mapper target type '$target_type' for ". - "device '$dev_name'\n"); - } - push(@table, [$start, $length, $type, $data]); - } - close(HANDLE); - return \@table; -} - -# get_linear_data(dev_name, args) -# Returns: [major, minor, start_sector] -sub get_linear_data($$) -{ - my ($dev_name, $args) = @_; - - if (!($args =~ /^(\d+):(\d+)\s+(\d+)$/)) { - die("Error: Unrecognized device-mapper table format for ". - "device '$dev_name'\n"); - } - return [$1, $2, $3]; -} - -# get_mirror_data(dev_name, args) -# Returns [[major1, minor1, start_sector1], [major2, minor2, start_sector2], ..] -sub get_mirror_data($$) -{ - my ($dev_name, $args) = @_; - my @argv = split(/\s+/, $args); - my @data; - my $offset; - my $num; - - # Remove log_type + #logargs + logargs - splice(@argv, 0, $argv[1] + 2); - if (!@argv) { - goto out_error; - } - $num = shift(@argv); - while ($num-- > 0) { - if (!($argv[0] =~ /^(\d+):(\d+)$/)) { - goto out_error; - } - push(@data, [$1, $2, $argv[1]]); - if (!defined($offset)) { - $offset = $argv[1]; - } elsif ($argv[1] != $offset) { - die("Error: Unsupported setup: Mirror target on ". - "device '$dev_name' contains entries with varying ". - "sector offsets\n"); - } - splice(@argv, 0, 2); - } - if (!scalar(@data)) { - goto out_error; - } - return \@data; - -out_error: - die("Error: Unrecognized device-mapper table format for device ". - "'$dev_name'\n"); -} - -# get_multipath_status(device) -# Return map of nodes to F/A flags (i.e. $map{$node} is either "A" or "F"). -# See linux/drivers/md/dm-mpath.c:multipath_status for details. -sub get_multipath_status($) -{ - my $dev = shift(); - my %map; - my $str; - my $failed = 0; - - open(my $fh, "$dmsetup status /dev/$dev 2>/dev/null|") or return undef; - while ($str = <$fh>) { - # sample output (single line): - # 0 67108864 multipath \ - # 2 0 0 \ - # 0 \ - # 2 2 \ - # E 0 \ - # 2 2 \ - # 8:16 F 1 \ - # 0 1 \ - # 8:0 F 1 \ - # 0 1 \ - # A 0 \ - # 2 2 \ - # 8:32 A 0 \ - # 0 1 \ - # 8:48 A 0 \ - # 0 1 - my @line = split(/\s+/, $str); - next if !@line; - - my ($start, $length, $type) = splice(@line, 0, 3); - next if $type ne "multipath"; - - my $cnt = shift(@line); # Remove #multipath_feature_args + - splice(@line, 0, $cnt) if $cnt > 0; # multipath_feature_args - - $cnt = shift(@line); # Remove #handler_status_args + - splice(@line, 0, $cnt) if $cnt > 0; # handler_status_args - - # num_groups init_group_number ... - my ($ngr, $ign) = splice(@line, 0, 2); - for (my $g = 0; $g < $ngr; $g++) { - # Remove group_state + #ps_status_args - # group_state: D(isabled), A(ctive), or E(nabled) - my ($state, $cnt) = splice(@line, 0, 2); - # Remove ps_status_args* - splice(@line, 0, $cnt) if $cnt > 0; - # Remove #paths + #selector_args - my ($paths, $nsa) = splice(@line, 0, 2); - for (my $p = 0; $p < $paths; $p++) { - # Fetch single path description - my ($node, $active, $fail_cnt) - = splice(@line, 0, 3); - # active: A(ctive) or F(ailed) - $map{$node} = $active; - $failed++ if $active ne "A"; - # Remove selector_args* - splice(@line, 0, $nsa) if $nsa > 0; - } - } - } - close($fh); - - die ("Error: No paths found for '$dev'\n") if scalar(keys %map) == 0; - if ($failed) { - die ("Error: All paths for '$dev' failed\n") - if $failed == scalar(keys %map); - - print(STDERR "Warning: There are one or more failed paths for" - ." device '$dev'\n"); - } - return \%map; -} - -# get_multipath_data(dev_name, args) -# Returns [[major1, minor1], [major2, minor2], ..] -sub get_multipath_data($$) -{ - my ($dev_name, $args) = @_; - my $status = get_multipath_status($dev_name); - my @argv = split(/\s+/, $args); - my @data; - - # Remove #features + features - splice(@argv, 0, $argv[0] + 1); - if (!@argv) { - goto out_error; - } - # Remove #handlerargs + handlerargs - splice(@argv, 0, $argv[0] + 1); - if (!@argv) { - goto out_error; - } - # Remove #pathgroups + pathgroup - splice(@argv, 0, 2); - while (@argv) { - # Remove pathselector + #selectorargs + selectorargs - splice(@argv, 0, 2 + $argv[1]); - if (!@argv) { - goto out_error; - } - my $num_paths = $argv[0]; - my $num_path_args = $argv[1]; - # Remove #paths + #pathargs - splice(@argv, 0, 2); - while ($num_paths-- > 0) { - if (!@argv) { - goto out_error; - } - if (!($argv[0] =~ /(\d+):(\d+)/)) { - goto out_error; - } - push(@data, [$1, $2]) if $status->{$argv[0]} eq "A"; - # Remove device + deviceargs - splice(@argv, 0, 1 + $num_path_args); - } - } - if (!@data) { - goto out_error; - } - return \@data; - -out_error: - die("Error: Unrecognized device-mapper table format for device ". - "'$dev_name'\n"); -} - -# filter_table(table, start, length) -# Returns table containing only targets between start and start + length - 1. -sub filter_table($$$) -{ - my ($table, $start, $length) = @_; - my $end = $start + $length - 1; - my @result; - my $target; - - foreach $target (@$table) { - my $target_start = $target->[$TARGET_START]; - my $target_end = $target_start + $target->[$TARGET_LENGTH] - 1; - - if (!(($target_end < $start) || ($target_start > $end))) { - push(@result, $target); - } - } - return \@result; -} - -# get_target_start(target) -# Returns the start sector of target. -sub get_target_start($) -{ - my ($target) = @_; - my $type = $target->[$TARGET_TYPE]; - my $data = $target->[$TARGET_DATA]; - - if ($type == $TARGET_TYPE_LINEAR) { - return $data->[$LINEAR_START_SECTOR]; - } elsif ($type == $TARGET_TYPE_MIRROR) { - my $mirror_data = $data->[0]; - return $mirror_data->[$MIRROR_START_SECTOR]; - } else { - return 0; - } -} - -# get_target_major_minor(target) -# Returns (major, minor) of target of target. -sub get_target_major_minor($) -{ - my ($target) = @_; - my $type = $target->[$TARGET_TYPE]; - my $data = $target->[$TARGET_DATA]; - my $major; - my $minor; - - if ($type == $TARGET_TYPE_LINEAR) { - $major = $data->[$LINEAR_MAJOR]; - $minor = $data->[$LINEAR_MINOR]; - } elsif ($type == $TARGET_TYPE_MIRROR) { - # Use data of first device in list - my $mirror_data = $data->[0]; - $major = $mirror_data->[$MIRROR_MAJOR]; - $minor = $mirror_data->[$MIRROR_MINOR]; - } elsif ($type == $TARGET_TYPE_MULTIPATH) { - # Use data of first device in list - my $multipath_data = $data->[0]; - $major = $multipath_data->[$MULTIPATH_MAJOR]; - $minor = $multipath_data->[$MULTIPATH_MINOR]; - } - return ($major, $minor); -} - -# create_temp_device_node(type, major, minor) -# Returns the name of a temporary device node. -sub create_temp_device_node($$$) -{ - my ($type, $major, $minor) = @_; - my $path = "/dev"; - my $name; - my $num; - - for ($num = 0; $num < 100; $num++) { - $name = sprintf("$path/zipl-dm-temp-%02d", $num); - if (-e $name) { - next; - } - if (system("$mknod $name $type $major $minor --mode 0600 ". - "2>/dev/null")) { - next; - } - return $name; - } - die("Error: Could not create temporary device node in '$path'\n"); -} - -# get_blocksize(device) -# # Return blocksize in bytes for device. -sub get_blocksize($) -{ - my ($dev) = @_; - my $blocksize; - local *HANDLE; - - open(HANDLE, "$blockdev --getss $dev 2>/dev/null|") or - return undef; - $blocksize = ; - chomp($blocksize); - close(HANDLE); - - return $blocksize; -} - -# get_dasd_info(device) -# Returns (type, cylinders, heads, sectors) -sub get_dasd_info($) -{ - my ($dev) = @_; - my $disk_type; - my $format; - my $cyl; - my $heads; - my $sectors; - my $type; - local *HANDLE; - - open(HANDLE, "$dasdview -x $dev 2>/dev/null|") or - # dasdview returned with an error - return undef; - while () { - if (/^number of cylinders.*\s(\d+)\s*$/) { - $cyl = $1; - } elsif (/^tracks per cylinder.*\s(\d+)\s*$/) { - $heads = $1; - } elsif (/^blocks per track.*\s(\d+)\s*$/) { - $sectors = $1; - } elsif (/^type\s+:\s+(\S+)\s*$/) { - $disk_type = $1; - } elsif (/^format.*\s+dec\s(\d+)\s/) { - $format = $1; - } - } - close(HANDLE); - if (!defined($cyl) || !defined($heads) || !defined($sectors) || - !defined($disk_type) || !defined($format)) { - # Unrecognized dadsview output format - return undef; - } - if ($disk_type eq "FBA") { - $type = $DEV_TYPE_FBA; - } elsif ($disk_type eq "ECKD") { - if ($format == 1) { - $type = $DEV_TYPE_LDL; - } elsif ($format == 2) { - $type = $DEV_TYPE_CDL; - } - } - - return ($type, $cyl, $heads, $sectors); -} - -# get_partition_start(major, minor) -# Return the partition offset of device. -sub get_partition_start($$) -{ - my ($major, $minor) = @_; - my $dir = "/sys/dev/block/$major:$minor"; - my $offset; - local *HANDLE; - - return undef if (!-d $dir); - - open(HANDLE, "<", "$dir/start") or return 0; - $offset = ; - close(HANDLE); - - chomp($offset); - - return $offset; -} - -# is_dasd(type) -# Return whether disk with type is a DASD. -sub is_dasd($) -{ - my ($type) = @_; - - return ($type == $DEV_TYPE_CDL) || ($type == $DEV_TYPE_LDL) || - ($type == $DEV_TYPE_FBA); -} - -# get_partition_base(type, major, minor) -# Return (major, minor) of the base device on which the partition is located. -sub get_partition_base($$$) -{ - my ($type, $major, $minor) = @_; - - if (is_dasd($type)) { - return ($major, $minor & ~$DASD_PARTN_MASK); - } else { - return ($major, $minor & ~$SCSI_PARTN_MASK); - } -} - -# get_device_characteristics(major, minor) -# Returns (type, blocksize, geometry, bootsectors, partstart) for device. -sub get_device_characteristics($$) -{ - my ($major, $minor) = @_; - my $dev; - my $blocksize; - my $type; - my $cyl; - my $heads; - my $sectors; - my $geometry; - my $bootsectors; - my $partstart; - - $dev = create_temp_device_node("b", $major, $minor); - $blocksize = get_blocksize($dev); - if (!defined($blocksize)) { - unlink($dev); - die("Error: Could not get block size for ". - get_device_name($major, $minor)."\n"); - } - ($type, $cyl, $heads, $sectors) = get_dasd_info($dev); - if (defined($type)) { - $geometry = "$cyl,$heads,$sectors"; - if ($type == $DEV_TYPE_CDL) { - # First track contains IPL records - $bootsectors = $blocksize * $sectors / $SECTOR_SIZE; - } elsif ($type == $DEV_TYPE_LDL) { - # First two blocks contain IPL records - $bootsectors = $blocksize * 2 / $SECTOR_SIZE; - } elsif ($type == $DEV_TYPE_FBA) { - # First block contains IPL records - $bootsectors = $blocksize / $SECTOR_SIZE; - } - } else { - # Assume SCSI if get_dasd_info failed - $type = $DEV_TYPE_SCSI; - # First block contains IPL records - $bootsectors = $blocksize / $SECTOR_SIZE; - } - $partstart = get_partition_start($major, $minor); - unlink($dev); - if (!defined($partstart)) { - die("Error: Could not determine partition start for ". - get_device_name($major, $minor)."\n"); - } - # Convert partition start in sectors to blocks - $partstart = $partstart / ($blocksize / $SECTOR_SIZE); - return ($type, $blocksize, $geometry, $bootsectors, $partstart); -} - -# get_type_name(type) -# Return textual representation of device type. -sub get_type_name($) -{ - my ($type) = @_; - - if ($type == $DEV_TYPE_CDL) { - return "CDL"; - } elsif ($type == $DEV_TYPE_LDL) { - return "LDL"; - } elsif ($type == $DEV_TYPE_FBA) { - return "FBA"; - } elsif ($type == $DEV_TYPE_SCSI) { - return "SCSI"; - } - return undef; -} - - -# check_for_mirror(index, target_list) -# Die if there is a mirror target between index and 0. -sub check_for_mirror($@) -{ - my ($i, @target_list) = @_; - - for (;$i >= 0; $i--) { - my $entry = $target_list[$i]; - my ($major, $minor, $target) = @$entry; - - if ($target->[$TARGET_TYPE] == $TARGET_TYPE_MIRROR) { - # IPL records are not mirrored. - die("Error: Unsupported setup: Block 0 is not ". - "mirrored in device '". - get_device_name($major, $minor)."'\n"); - } - } -} - -# get_target_base(bottom_major, bottom_minor, start, length, target_list) -# Return (major, minor) for the top most target in the target list that maps -# the region on (bottom_major, bottom_minor) defined by start and length at -# offset 0. -sub get_target_base($$$$@) -{ - my ($bot_major, $bot_minor, $start, $length, @target_list) = @_; - my $entry; - my $top_major; - my $top_minor; - my $i; - - # Pre-initialize with bottom major-minor - $top_major = $bot_major; - $top_minor = $bot_minor; - # Process all entries starting with the last one - for ($i = scalar(@target_list) - 1; $i >= 0; $i--) { - my $entry = $target_list[$i]; - my ($major, $minor, $target) = @$entry; - - if (($target->[$TARGET_START] != 0) || - (get_target_start($target) != 0) || - ($target->[$TARGET_LENGTH] < $length)) { - last; - } - $top_major = $major; - $top_minor = $minor; - } - # Check for mirrorring between base device and fs device. - check_for_mirror($i, @target_list); - return ($top_major, $top_minor); -} - -# get_device_name(major, minor) -# Return the name of the device specified by major and minor. -sub get_device_name($$) -{ - my ($major, $minor) = @_; - my $name; - local *HANDLE; - - $name = "$major:$minor"; - open(HANDLE, ") { - if (/^\s*(\d+)\s+(\d+)\s+\d+\s+(\S+)\s*$/) { - if (($major == $1) && ($minor == $2)) { - $name = $3; - last; - } - } - } - close(HANDLE); -out: - return $name; -} - -sub check_tools() -{ -system("$dmsetup --version &> /dev/null") >> 8 != 127 - or die("Error: dmsetup not found\n"); -system("$mknod --version &> /dev/null") >> 8 != 127 - or die("Error: mknod not found\n"); -system("$dasdview --version &> /dev/null") >> 8 != 127 - or die("Error: dasdview not found\n"); -system("$blockdev --version &> /dev/null") >> 8 != 127 - or die("Error: blockdev not found\n"); - -return 0; -} diff --git a/zipl/src/zipl_helper.device-mapper.c b/zipl/src/zipl_helper.device-mapper.c new file mode 100644 index 000000000..d1e9244a4 --- /dev/null +++ b/zipl/src/zipl_helper.device-mapper.c @@ -0,0 +1,1098 @@ +/* + * + * zipl_helper.device-mapper: print zipl parameters for a device-mapper device + * + * Copyright IBM Corp. 2009, 2017 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + * + * Depending on the name by which the script is called, it serves one of two + * purposes: + * + * 1. Usage: zipl_helper.device-mapper or + * + * + * This tool attempts to obtain zipl parameters for a target directory or + * partition located on a device-mapper device. It assumes that the + * device-mapper table for this device conforms to the following rules: + * - directory is located on a device consisting of a single device-mapper + * target + * - only linear, mirror and multipath targets are supported + * - supported physical device types are DASD and SCSI devices + * - all of the device which contains the directory must be located on a single + * physical device (which may be mirrored or accessed through a multipath + * target) + * - any mirror in the device-mapper setup must include block 0 of the + * physical device + * + * 2. Usage: chreipl_helper.device-mapper + * + * This tool identifies the physical device which contains the specified + * device-mapper target devices. If the physical device was found, its + * major:minor parameters are printed. Otherwise, the script exits with an + * error message and a non-zero return code. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/util_base.h" +#include "lib/util_list.h" +#include "lib/util_libc.h" +#include "lib/util_file.h" +#include "lib/dasdview.h" + +struct target_status { + char *path; + char status; + struct util_list_node list; +}; + +struct target_data { + dev_t device; + unsigned long start; + struct util_list_node list; +}; + +struct target { + unsigned long start; + unsigned long length; + unsigned short type; + struct util_list *data; + struct util_list_node list; +}; + +struct target_entry { + dev_t device; + struct target *target; + struct util_list_node list; +}; + +struct physical_device { + dev_t device; + unsigned long offset; + struct util_list *target_list; +}; + +struct device_characteristics { + unsigned short type; + unsigned long blocksize; + struct { + unsigned long cylinders; + unsigned long heads; + unsigned long sectors; + } geometry; + unsigned long bootsectors; + unsigned long partstart; +}; + +/* From include/linux/fs.h */ +#define BDEVNAME_SIZE 32 + +#define PARTITIONS_FILE "/proc/partitions" + +/* Constants */ +const unsigned int SECTOR_SIZE = 512; +const unsigned int DASD_PARTN_MASK = 0x03; +const unsigned int SCSI_PARTN_MASK = 0x0f; + +const char CHREIPL_HELPER[] = "chreipl_helper.device-mapper"; + +/* Internal constants */ +enum dev_type { + DEV_TYPE_CDL = 0, + DEV_TYPE_LDL, + DEV_TYPE_FBA, + DEV_TYPE_SCSI +}; + +enum target_type { + TARGET_TYPE_LINEAR = 0, + TARGET_TYPE_MIRROR, + TARGET_TYPE_MULTIPATH +}; + + +static void +get_type_name(char *name, unsigned short type) +{ + switch (type) { + case DEV_TYPE_SCSI: + strcpy(name, "SCSI"); + break; + case DEV_TYPE_CDL: + strcpy(name, "CDL"); + break; + case DEV_TYPE_FBA: + strcpy(name, "FBA"); + break; + case DEV_TYPE_LDL: + strcpy(name, "LDL"); + break; + default: + util_error_print("Unrecognized dev type %d", type); + } +} + + +static FILE * +execute_command_and_get_output_stream(const char *fmt, ...) +{ + char *cmd; + va_list ap; + FILE *stream; + + va_start(ap, fmt); + util_vasprintf(&cmd, fmt, ap); + va_end(ap); + + stream = popen(cmd, "r"); + if (stream == NULL) + util_error_print("'%s' failed", cmd); + + free(cmd); + return stream; +} + + +static inline struct target_data * +target_data_new(unsigned int maj, unsigned int min, unsigned int start) +{ + struct target_data *td = util_malloc(sizeof(struct target_data)); + td->device = makedev(maj, min); + td->start = start; + + return td; +} + +static inline void +target_data_free(struct target_data *td) +{ + free(td); +} + +static void +target_data_list_free(struct util_list *data) +{ + struct target_data *td, *n; + + util_list_iterate_safe(data, td, n) { + util_list_remove(data, td); + target_data_free(td); + } + util_list_free(data); +} + +static inline struct target * +target_new(unsigned long start, unsigned long length, unsigned short type, struct util_list *data) +{ + struct target *entry = util_malloc(sizeof(struct target)); + + entry->start = start; + entry->length = length; + entry->type = type; + entry->data = data; + + return entry; +} + +static void +target_free(struct target *target) +{ + struct target_data *e, *n; + + util_list_iterate_safe(target->data, e, n) { + util_list_remove(target->data, e); + target_data_free(e); + } + util_list_free(target->data); + free(target); +} + +static inline unsigned long +target_get_start(struct target *t) +{ + struct target_data *td = util_list_start(t->data); + return td->start; +} + +static inline void +target_get_major_minor(struct target *t, unsigned int *maj, unsigned int *min) +{ + struct target_data *td = util_list_start(t->data); + *maj = major(td->device); + *min = minor(td->device); +} + +static struct target_entry * +target_entry_new(dev_t dev, struct target *t) +{ + struct target_entry *te = util_malloc(sizeof(struct target_entry)); + te->device = dev; + te->target = t; + + return te; +} + +static inline void +target_entry_free(struct target_entry *entry) +{ + target_free(entry->target); + free(entry); +} + +static struct target_entry * +target_list_get_first_by_type(struct util_list *target_list, unsigned short type) +{ + struct target_entry *entry; + + util_list_iterate(target_list, entry) { + if (entry->target->type == type) + return entry; + } + return NULL; +} + +static void +target_list_free(struct util_list *target_list) +{ + struct target_entry *entry, *n; + + util_list_iterate_safe(target_list, entry, n) { + util_list_remove(target_list, entry); + target_entry_free(entry); + } + util_list_free(target_list); +} + + +static void +get_device_name(char *devname, dev_t dev) +{ + FILE *fd = NULL; + char *line = NULL; + size_t len = 0; + ssize_t read; + + sprintf(devname, "%u:%u", major(dev), minor(dev)); + + fd = fopen(PARTITIONS_FILE, "r"); + if (fd == NULL) + return; + + if (getline(&line, &len, fd) == -1) /* Ignore header */ + goto out; + if (getline(&line, &len, fd) == -1) /* Ignore empty line */ + goto out; + + while ((read = getline(&line, &len, fd)) != -1) { + char buf[BDEVNAME_SIZE]; + unsigned int mj, mn; + + sscanf(line, "%d %d %*u %s", &mj, &mn, buf); + if (major(dev) == mj && minor(dev) == mn) { + strcpy(devname, buf); + break; + } + } + +out: + free(line); + fclose(fd); +} + + +static long +get_blocksize(const char *device) +{ + int fd; + long size; + + fd = open(device, O_RDONLY); + if (fd == -1) + return -1; + if (ioctl(fd, BLKSSZGET, &size) != 0) { + close(fd); + return -1; + } + close(fd); + + return size; +} + + +static int +create_temp_device_node(char *name, unsigned int major, unsigned int minor) +{ + int num; + const char path_base[] = "/dev"; + char buf[PATH_MAX]; + + for (num = 0; num < 100; num++) { + snprintf(buf, sizeof(buf), "%s/zipl-dm-temp-%02d", path_base, num); + if (access(buf, F_OK) == 0) + continue; + if (mknod(buf, S_IFBLK, makedev(major, minor)) != 0) + continue; + strcpy(name, buf); + return 0; + } + util_error_print("Could not create temporary device node in '%s'", + path_base); + return -1; +} + + +static int +get_partition_start(unsigned int maj, unsigned int min) +{ + unsigned long val; + if (util_file_read_ul(&val, 10, "/sys/dev/block/%u:%u/start", + maj, min) != 0) { + return 0; + } + return val; +} + + +static int +get_device_characteristics(struct device_characteristics *dc, dev_t dev) +{ + int res; + dasdview_info_t info; + + if (create_temp_device_node(info.device, major(dev), minor(dev)) != 0) + return -1; + + res = dasdview_get_info(&info); + if (res != 0) { // assume SCSI if dasdinfo failed + long blocksize = get_blocksize(info.device); + if (blocksize < 0) { + unlink(info.device); + util_error_print("Could not get block size for '%s'", + info.device); + return -1; + } + dc->blocksize = blocksize; + dc->type = DEV_TYPE_SCSI; + // first block contains IPL records + dc->bootsectors = dc->blocksize / SECTOR_SIZE; + } else { + unsigned int sectors = info.geo.sectors; + dc->blocksize = info.blksize; + if (strcmp(info.dasd_info.type, "FBA") == 0) { + dc->type = DEV_TYPE_FBA; + dc->bootsectors = dc->blocksize / SECTOR_SIZE; + } + else if (strcmp(info.dasd_info.type, "ECKD") == 0) { + if (info.dasd_info.format == 1) { + dc->type = DEV_TYPE_LDL; + dc->bootsectors = dc->blocksize * 2 / SECTOR_SIZE; + } + else if (info.dasd_info.format == 2) { + dc->type = DEV_TYPE_CDL; + dc->bootsectors = dc->blocksize * sectors / SECTOR_SIZE; + } + } + dc->geometry.cylinders = info.hw_cylinders; + dc->geometry.heads = info.geo.heads; + dc->geometry.sectors = info.geo.sectors; + } + dc->partstart = get_partition_start(major(dev), minor(dev)); + dc->partstart /= (dc->blocksize / SECTOR_SIZE); + + unlink(info.device); + return 0; +} + + +static struct util_list * +get_linear_data(const char *devname, char *args) +{ + struct util_list *data; + unsigned int maj, min, start; + + if (sscanf(args, "%u:%u %u", &maj, &min, &start) < 3) { + util_error_print("Unrecognized device-mapper table format " + "for device '%s'", devname); + return NULL; + } + + data = util_list_new(struct target_data, list); + util_list_add_head(data, target_data_new(maj, min, start)); + + return data; +} + + +#define STR_TOKEN_OR_GOTO(string, tok, label) \ + do { \ + char *tp = strtok(string, " "); \ + if (tp == NULL) { \ + goto label; \ + } \ + tok = tp; \ + } while (0) + +#define NEXT_STR_TOKEN_OR_GOTO(tok, label) \ + STR_TOKEN_OR_GOTO(NULL, tok, label) + +#define INT_TOKEN_OR_GOTO(string, tok, label) \ + do { \ + char *tp = strtok(string, " "); \ + if (tp == NULL) { \ + goto label; \ + } \ + errno = 0; \ + tok = strtol(tp, NULL, 10); \ + if (((errno == ERANGE) && \ + (tok == LONG_MIN || tok == LONG_MAX)) ||\ + (errno != 0 && tok == 0)) { \ + goto label; \ + } \ + } while (0) + +#define NEXT_INT_TOKEN_OR_GOTO(tok, label) \ + INT_TOKEN_OR_GOTO(NULL, tok, label) + +#define SKIP_NEXT_TOKENS_OR_GOTO(count, label) \ + do { \ + for (; count > 0; count--) { \ + if (strtok(NULL, " ") == NULL) { \ + goto label; \ + } \ + } \ + } while (0) + +/* + * There is no kernel documentation for the mirror target. Parameters obtained + * from Linux sources: drivers/md/dm-log.c and drivers/md/dm-raid1.c + * + * mirror \ + * <#log_args> ... \ + * <#devs> ... \ + * <#features> ... + */ +static struct util_list * +get_mirror_data(const char *devname __attribute__((unused)), char *args) +{ + char *token; + long nlogs, ndevs, nfeats; + struct util_list *data = util_list_new(struct target_data, list); + + STR_TOKEN_OR_GOTO(args, token, out); // log_type + + NEXT_INT_TOKEN_OR_GOTO(nlogs, out); // #log_args + SKIP_NEXT_TOKENS_OR_GOTO(nlogs, out); // log_args* + + NEXT_INT_TOKEN_OR_GOTO(ndevs, out); + for (; ndevs > 0; ndevs--) { + char *name; + long offset; + unsigned int maj, min; + + NEXT_STR_TOKEN_OR_GOTO(name, out); + if (sscanf(name, "%u:%u", &maj, &min) < 2) + goto out; + NEXT_INT_TOKEN_OR_GOTO(offset, out); + util_list_add_head(data, target_data_new(maj, min, offset)); + } + NEXT_INT_TOKEN_OR_GOTO(nfeats, out); + SKIP_NEXT_TOKENS_OR_GOTO(nfeats, out); + + return data; + +out: + target_data_list_free(data); + return NULL; +} + + +static struct target_status * +target_status_new(const char *path, char status) +{ + struct target_status *ts = util_malloc(sizeof(struct target_status)); + ts->path = util_strdup(path); + ts->status = status; + + return ts; +} + +static void +target_status_free(struct target_status *ts) +{ + free(ts->path); + free(ts); +} + +static void +status_list_free(struct util_list *status) +{ + struct target_status *ts, *n; + + util_list_iterate_safe(status, ts, n) { + util_list_remove(status, ts); + target_status_free(ts); + } + util_list_free(status); +} + +static char +status_list_get_status(struct util_list *status, const char *node) +{ + struct target_status *ts; + + util_list_iterate(status, ts) { + if (strcmp(ts->path, node) == 0) + return ts->status; + } + return 'F'; +} + +static struct util_list * +get_multipath_status(const char *devname) +{ + FILE *fp; + size_t n = 0; + int len, failed = 0; + char *line = NULL; + struct util_list *status; + + fp = execute_command_and_get_output_stream( + "dmsetup status /dev/%s 2>/dev/null", devname); + if (fp == NULL) { + util_error_print("No paths found for %s", devname); + return NULL; + } + + status = util_list_new(struct target_status, list); + while (getline(&line, &n, fp) != -1) { + long cnt, ngr, ign, length; + char *token = NULL; + /* sample output (single line): + * 0 67108864 multipath \ + * 2 0 0 \ + * 0 \ + * 2 2 \ + * E 0 \ + * 2 2 \ + * 8:16 F 1 \ + * 0 1 \ + * 8:0 F 1 \ + * 0 1 \ + * A 0 \ + * 2 2 \ + * 8:32 A 0 \ + * 0 1 \ + * 8:48 A 0 \ + * 0 1 + */ + STR_TOKEN_OR_GOTO(line, token, out); + NEXT_INT_TOKEN_OR_GOTO(length, out); + NEXT_STR_TOKEN_OR_GOTO(token, out); // dtype + if (strcmp(token, "multipath") != 0) + continue; + NEXT_INT_TOKEN_OR_GOTO(cnt, out); // #mp_feature_args + SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); // mp_feature_args* + NEXT_INT_TOKEN_OR_GOTO(cnt, out); // #handler_status_args + SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); // handler_status_args* + + NEXT_INT_TOKEN_OR_GOTO(ngr, out); + NEXT_INT_TOKEN_OR_GOTO(ign, out); + for (; ngr > 0; ngr--) { + long npaths, nsa; + NEXT_STR_TOKEN_OR_GOTO(token, out); + NEXT_INT_TOKEN_OR_GOTO(cnt, out); // #ps_status_args + SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); // ps_status_args* + NEXT_INT_TOKEN_OR_GOTO(npaths, out); + NEXT_INT_TOKEN_OR_GOTO(nsa, out); + for (; npaths > 0; npaths--) { + char *path, *active; + NEXT_STR_TOKEN_OR_GOTO(path, out); + NEXT_STR_TOKEN_OR_GOTO(active, out); + util_list_add_tail(status, + target_status_new(path, active[0])); + NEXT_INT_TOKEN_OR_GOTO(cnt, out); + failed += (*active != 'A'); + SKIP_NEXT_TOKENS_OR_GOTO(nsa, out); + } + } + } + + free(line); + pclose(fp); + + len = util_list_len(status); + if (len == 0) { + util_error_print("No paths found for '%s'", devname); + goto out; + } if (failed == len) { + util_error_print("All paths for '%s' failed", devname); + goto out; + } else if (failed > 0) { + util_error_print("There are one or more failed paths for " + "device '%s'", devname); + } + + return status; + +out: + free(line); + pclose(fp); + status_list_free(status); + return NULL; +} + +static struct util_list * +get_multipath_data(const char *devname, char *args) +{ + char *token; + long cnt, pgroups; + struct util_list *data = util_list_new(struct target_data, list); + struct util_list *status = get_multipath_status(devname); + + if (status == NULL) + goto out; + + INT_TOKEN_OR_GOTO(args, cnt, out); // #feat + SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); // feats* + NEXT_INT_TOKEN_OR_GOTO(cnt, out); // #handlers + SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); // handlers* + NEXT_INT_TOKEN_OR_GOTO(pgroups, out); + NEXT_STR_TOKEN_OR_GOTO(token, out); // pathgroup + for (; pgroups > 0; pgroups--) { + long npaths; + + NEXT_STR_TOKEN_OR_GOTO(token, out); //path_selector + NEXT_INT_TOKEN_OR_GOTO(cnt, out); // #selectorargs + SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); + NEXT_INT_TOKEN_OR_GOTO(npaths, out); + NEXT_INT_TOKEN_OR_GOTO(cnt, out); // #np_args + for (; npaths > 0; npaths--) { + char *path; + unsigned int maj, min; + NEXT_STR_TOKEN_OR_GOTO(path, out); + if (sscanf(path, "%d:%d", &maj, &min) < 2) + goto out; + if (status_list_get_status(status, path) == 'A') + util_list_add_head(data, target_data_new(maj, min, 0)); + SKIP_NEXT_TOKENS_OR_GOTO(cnt, out); + } + } + + status_list_free(status); + return data; + +out: + status_list_free(status); + target_data_list_free(data); + return data; +} + + +static void +table_free(struct util_list *table) +{ + struct target *t, *n; + + util_list_iterate_safe(table, t, n) { + util_list_remove(table, t); + target_free(t); + } + util_list_free(table); +} + +static void +filter_table(struct util_list *table, unsigned int start, unsigned int length) +{ + struct target *target, *n; + + util_list_iterate_safe(table, target, n) { + if (!(((target->start + target->length - 1) >= start) && + (target->start <= (start + length - 1)))) { + util_list_remove(table, target); + target_free(target); + } + } +} + +/* + * Returns list of target devices + */ +static struct util_list * +get_table(dev_t dev) +{ + FILE *fp; + size_t n = 0; + char *line = NULL; + char devname[BDEVNAME_SIZE]; + struct util_list *table; + + table = util_list_new(struct target, list); + if (table == NULL) + return NULL; + + fp = execute_command_and_get_output_stream("dmsetup table -j %u -m %u 2>/dev/null", major(dev), minor(dev)); + if (fp == NULL) + return table; + + while (getline(&line, &n, fp) != -1) { + unsigned long start, length; + unsigned short ttype; + char *type = NULL, *args = NULL; + struct util_list *data = NULL; + + if (sscanf(line, "%lu %lu %ms %m[a-zA-Z0-9: -]", + &start, &length, &type, &args) < 4) { + get_device_name(devname, dev); + util_error_print("Unrecognized device-mapper table " + "format for device '%s'", devname); + goto out; + } + + if (strcmp(type, "linear") == 0) { + data = get_linear_data(devname, args); + ttype = TARGET_TYPE_LINEAR; + } else if (strcmp(type, "mirror") == 0) { + data = get_mirror_data(devname, args); + ttype = TARGET_TYPE_MIRROR; + } else if (strcmp(type,"multipath") == 0) { + data = get_multipath_data(devname, args); + ttype = TARGET_TYPE_MULTIPATH; + } else { + get_device_name(devname, dev); + util_error_print("Unsupported setup: Unsupported " + "device-mapper target type '%s' for device '%s'", + type, devname); + } + free(type); + free(args); + if (data == NULL) + goto out; + util_list_add_head(table, target_new(start, length, ttype, data)); + } + + free(line); + pclose(fp); + + return table; + +out: + free(line); + pclose(fp); + table_free(table); + return table; +} + + +static inline bool +is_dasd(unsigned short type) +{ + return (type == DEV_TYPE_CDL) || (type == DEV_TYPE_LDL) || + (type == DEV_TYPE_FBA); +} + + +static int +get_physical_device(struct physical_device *pd, dev_t dev, const char *directory) +{ + unsigned int start, length; + struct target *target; + struct util_list *table = NULL; + struct util_list *target_list = NULL; + + table = get_table(dev); + if (table == NULL || util_list_start(table) == NULL) { + char devname[BDEVNAME_SIZE]; + get_device_name(devname, dev); + util_error_print("Could not retrieve device-mapper information " + "for device '%s'", devname); + if (table != NULL) + table_free(table); + return -1; + } + + target = util_list_start(table); + + /* Filesystem must be on a single dm target */ + if (util_list_next(table, target) != NULL) { + util_error_print("Unsupported setup: Directory '%s' is " + "located on a multi-target device-mapper device", + directory); + table_free(table); + return -1; + } + util_list_remove(table, target); + table_free(table); + + target_list = util_list_new(struct target_entry, list); + util_list_add_head(target_list, target_entry_new(dev, target)); + start = target->start; + length = target->length; + while (true) { + unsigned int mmaj, mmin; + + /* convert fs_start to offset on parent dm device */ + start += target_get_start(target); + target_get_major_minor(target, &mmaj, &mmin); + table = get_table(makedev(mmaj, mmin)); + /* Found non-dm device */ + if (table == NULL || util_list_start(table) == NULL) { + pd->device = makedev(mmaj, mmin); + pd->offset = start; + pd->target_list = target_list; + if (table != NULL) + table_free(table); + return 0; + } + /* + * Get target in parent table which contains filesystem + * We are interested only in targets between [start,start+length-1] + */ + filter_table(table, start, length); + target = util_list_start(table); + if (target == NULL || util_list_next(table, target) != NULL) { + util_error_print("Unsupported setup: Could not map " + "directory '%s' to a single physical device", + directory); + table_free(table); + target_list_free(target_list); + return -1; + } + util_list_remove(table, target); + util_list_add_head(target_list, + target_entry_new(makedev(mmaj, mmin), target)); + table_free(table); + /* Convert fs_start to offset on parent target */ + start -= target->start; + } +} + + +static inline int +get_major_minor(dev_t *dev, const char *filename) +{ + struct stat buf; + + if (stat(filename, &buf) != 0) { + util_error_print("Could not stat '%s'", filename); + return -1; + } + *dev = buf.st_dev; + return 0; +} + +static int +get_physical_device_dir(struct physical_device *pd, const char *directory) +{ + dev_t dev; + + if (get_major_minor(&dev, directory) != 0) + return -1; + return get_physical_device(pd, dev, directory); +} + + +static int +get_target_base(dev_t *base, dev_t bottom, unsigned int start __attribute__((unused)), unsigned int length, struct util_list *target) +{ + dev_t top = bottom; + struct target_entry *te, *mirror; + + util_list_iterate(target, te) { + if ((te->target->start != 0) || (target_get_start(te->target) != 0) || + (te->target->start < length)) { + break; + } + top = te->device; + } + + /* Check for mirroring between base device and fs device */ + while ((mirror = util_list_next(target, te)) != NULL) { + if (mirror->target->type == TARGET_TYPE_MIRROR) { + char name[BDEVNAME_SIZE]; + get_device_name(name, mirror->device); + util_error_print("Unsupported setup: Block 0 is not " + "mirrored in device '%s'", name); + return -1; + } + } + + *base = top; + return 0; +} + + +static inline dev_t +get_partition_base(unsigned short type, dev_t dev) +{ + return makedev(major(dev), minor(dev) & + (is_dasd(type) ? ~DASD_PARTN_MASK : ~SCSI_PARTN_MASK)); +} + + +static int +extract_major_minor_from_cmdline(int argc, char *argv[], unsigned int *maj, unsigned int *min) +{ + int i; + char *cmdline = NULL; + + for (i = 1; i < argc; i++) + cmdline = util_strcat_realloc(cmdline, argv[i]); + + if (sscanf(cmdline, "%u:%u", maj, min) != 2) { + free(cmdline); + return -1; + } + + free(cmdline); + return 0; +} + + +static inline bool +toolname_is_chreipl_helper(const char *toolname) +{ + int tlen = strlen(toolname); + int clen = strlen(CHREIPL_HELPER); + + if (tlen < clen) + return false; + + return strcmp(toolname + tlen - clen, CHREIPL_HELPER) == 0; +} + + +void +print_usage(const char *toolname) +{ + fprintf(stderr, "%s ", toolname); + if (!toolname_is_chreipl_helper(toolname)) + fprintf(stderr, " or "); + fprintf(stderr, "\n"); +} + + +int +main(int argc, char *argv[]) +{ + int res; + dev_t base; + unsigned int maj, min; + char type_name[8]; + char *directory = NULL; + const char *toolname = argv[0]; + struct physical_device pd; + struct device_characteristics dc; + + if (argc == 1) + goto usage; + + if (setlocale(LC_ALL, "C") == NULL) { + util_error_print("Could not use standard locale"); + exit(EXIT_FAILURE); + } + + if (toolname_is_chreipl_helper(toolname)) { + if (extract_major_minor_from_cmdline(argc, argv, &maj, &min) != 0) + goto usage; + if (get_physical_device(&pd, makedev(maj, min), argv[1]) != 0) + exit(EXIT_FAILURE); + printf("%u:%u\n", major(pd.device), minor(pd.device)); + target_list_free(pd.target_list); + exit(EXIT_SUCCESS); + } + + directory = argv[1]; + if (extract_major_minor_from_cmdline(argc, argv, &maj, &min) == 0) + res = get_physical_device(&pd, makedev(maj, min), directory); + else if (argc == 2) + res = get_physical_device_dir(&pd, directory); + else + goto usage; + + if (res != 0) + exit(EXIT_FAILURE); + + if (get_device_characteristics(&dc, pd.device) != 0) + goto error; + + /* Handle partitions */ + if (dc.partstart > 0) { + struct target_entry *mirror; + struct device_characteristics ndc; + /* Only the partition of the physical device is mapped so only the + * physical device can provide access to the boot record */ + base = get_partition_base(dc.type, pd.device); + /* Check for mirror */ + mirror = target_list_get_first_by_type(pd.target_list, TARGET_TYPE_MIRROR); + if (mirror != NULL) { + char name[BDEVNAME_SIZE]; + get_device_name(name, mirror->device); + // IPL records are not mirrored + util_error_print("Unsupported setup: Block 0 is not " + "mirrored in device '%s'", name); + goto error; + } + /* Adjust filesystem offset */ + pd.offset += (dc.partstart * (dc.blocksize / SECTOR_SIZE)); + dc.partstart = 0; + /* Update device geometry */ + get_device_characteristics(&ndc, base); + dc.geometry = ndc.geometry; + } else { + /* All of the device is mapped, so the base device is the top most dm + * device which provides access to boot sectors */ + if (get_target_base(&base, pd.device, 0, dc.bootsectors, pd.target_list) != 0) + goto error; + } + + target_list_free(pd.target_list); + + /* Check for valid offset of filesystem */ + if ((pd.offset % (dc.blocksize / SECTOR_SIZE)) != 0) { + util_error_print("File system not aligned on physical block size"); + goto error; + } + + /* Print resulting information */ + printf("targetbase=%u:%u\n", major(base), minor(base)); + get_type_name(type_name, dc.type); + printf("targettype=%s\n", type_name); + if (dc.geometry.cylinders != 0 && + dc.geometry.heads != 0 && + dc.geometry.sectors != 0) { + printf("targetgeometry=%lu,%lu,%lu\n", + dc.geometry.cylinders, + dc.geometry.heads, + dc.geometry.sectors); + } + printf("targetblocksize=%lu\n", dc.blocksize); + printf("targetoffset=%lu\n", (pd.offset / (dc.blocksize / SECTOR_SIZE))); + + exit(EXIT_SUCCESS); + +error: + if (pd.target_list != NULL) + target_list_free(pd.target_list); + exit(EXIT_FAILURE); + +usage: + print_usage(toolname); + exit(EXIT_FAILURE); +}