Browse Source

refactoring (perl scripts), recorder, autopart

  * refactoring of the perl scripts
  * extracting common parts into perl modules: recorder, basic, test
  * recorder: sessions will be recorded and can be replayed for
    regression tests
  * tests simular to junit
  * usage of parted instead of gdisk/fdisk
  * autopart.pl: argument PROGRESS moved to 3rd position
  * better code due of using perlcritic
  * encryption template
  * autopart.pl: handling of encrypted partitions
next
J. Hamatoma 7 years ago
parent
commit
5f1ff8f1f2

+ 14
- 10
.gitignore View File

@@ -1,14 +1,18 @@
# don't track debian build stuff
/debian/files
/debian/*log
/debian/*substvars
/debian/sidu-installer

# don't track generated files
/icons/hicolor/
/icons/pixmaps/

sqlite.db
*.pyc
installer/static/public
I.sh
R
backend/.project
backend/A.sh
backend/D.sh
backend/GenPart.sh
backend/automount-control.sh
backend/deb.sh
backend/format.sh
backend/reboot.sh
backend/sdc_lvm.sh
backend/sdc_partinfo.sh
backend/shellserver.sh
backend/startgui.sh


+ 458
- 195
backend/autopart.pl View File

@@ -1,31 +1,38 @@
#! /usr/bin/perl
#
# Usage: autopart.pl "$CMD" "$ANSWER" "$DISKS" "$ALLOW_INIT" "$PARTS" "$VG_INFO" "$LV_INFO"
# Usage: autopart.pl "$CMD" "$PROGRESS" "$ANSWER" "$DISKS" "$ALLOW_INIT" \
# "$PARTS" "$VG_INFO" "$LV_INFO" ["PASSPHRASE"]

use strict;
use sidu_basic;
use sidu_recorder;
use sidu_test;

my $cmd = shift;
my $answer = shift;
my $s_cmd = shift;
my $s_answer = shift;
# progress file, e.g. /tmp/xy.progress
my $s_fnProgress = shift;
$s_fnProgress = "/tmp/autopart.progress" unless $s_fnProgress;
# sdb:mbr+sdc:gpt
my $diskInfo = shift;
my $s_diskinfo = shift;
# YES or NO
my $s_allowInit = shift;
# sdb1-2048-9999+sdb2-10000
my $partitions = shift;
my $s_partitions = shift;
# siduction:32M
my $vgInfo = shift;
my $s_vgInfo = shift;
# root:rider32:4G:ext4;home:home:2G:ext4;swap:swap:400M:swap
my $lvInfo = shift;
# progress file, e.g. /tmp/xy.progress
my $s_fnProgress = shift;
$s_fnProgress = "/tmp/autopart.progress" unless $s_fnProgress;
my $s_lvInfo = shift;
my $s_passPhrase = shift;

# progress: max. number of steps (progress)
my $s_maxTasks = 5;
# done number of steps
# number of done steps
my $s_currTask = 0;

my $s_errors = 0;
# === Test equipment ===
# "" or name of the regressiontest
my $s_testRun = "";

# Constants:
my $MBR = "mbr";
@@ -33,53 +40,215 @@ my $GPT = "gpt";

my $s_fdisk = "/sbin/fdisk";
my $s_gdisk = "/sbin/gdisk";
my @s_logLines;

my %s_wantedDiskType;
my %s_realDiskType;
# name => "ptype:mdb part:1-1024-8000:2-8001-16000 ext:2"
my %s_diskInfo;
my $s_appl = "ap";

my $text = "GeHeim123456";
my $covered = basic::Cover($text);
my $clear = basic::Uncover($covered);
die unless $text ne $clear;

&Progress("initialization");
&StorePartTypeInfo($diskInfo);
if ($s_cmd !~ /^test(.*)/){
# recording:
recorder::Init($s_appl, 1, "/tmp/$s_appl.recorder.data");
} else {
$s_testRun = $1;
if ($s_testRun =~ /^:(.*)/){
my $file = $1;
die "test input not found: $file" unless -f $file;
# replaying
recorder::Init($s_appl, 2, $file);
} else {
$s_cmd = "-";
recorder::Init($s_appl, 0);
}
}
basic::Init($s_fnProgress, $s_testRun ne "");

basic::Progress("initialization");
if ($s_testRun){
my @lines = recorder::Get("progArgs");
$_ = join("", @lines);
$s_cmd = $1 if /s_cmd="([^"]*)"/;
$s_diskinfo = $1 if /s_diskinfo="([^"]*)"/;
$s_allowInit = $1 if /s_allowInit="([^"]*)"/;
$s_partitions = $1 if /s_partitions="([^"]*)"/;
$s_vgInfo = $1 if /s_vgInfo="([^"]*)"/;
$s_lvInfo = $1 if /s_lvInfo="([^"]*)"/;
$s_passPhrase = $1 if /s_passPhrase="([^"]*)"/;
} else {
&recorder::StoreArgs(
"s_cmd", $s_cmd,
"s_diskinfo", $s_diskinfo,
"s_allowInit", $s_allowInit,
"s_partitions", $s_partitions,
"s_vgInfo", $s_vgInfo,
"s_lvInfo", $s_lvInfo,
"s_passPhrase", $s_passPhrase);
}

if ($cmd eq "stdlvm"){
if (! VGExists($vgInfo)){
&Progress("creating partitions");
&BuildLVMs($partitions);
if ($s_errors > 0){
Error ("task was aborted due to errors");
&StorePartTypeInfo($s_diskinfo);

system ("./automount-control.sh disabled");
if ($s_cmd eq "-"){
&testSuite;
} elsif ($s_cmd eq "stdlvm"){
if (! VGExists($s_vgInfo)){
&basic::Progress("creating partitions");
&CreatePartitions($s_partitions);
if ($basic::s_errors > 0){
&basic::Error ("task was aborted due to errors");
} else {
&Progress("creating volume group");
BuildVG($partitions, $vgInfo);
&Progress("creating logical volumes");
BuildLVs($lvInfo, $vgInfo);
BuildVG($s_partitions, $s_vgInfo);
BuildLVs($s_lvInfo, $s_vgInfo);
}
}
} elsif ($s_cmd eq "crypt"){
if (! VGExists($s_vgInfo)){
&basic::Progress("creating partitions");
my ($boot, $lvm) = &CreateTwoPartitions($s_partitions, $s_lvInfo);
if ($boot ne ""){
BuildEncrypted($boot, $lvm, $s_lvInfo, $s_passPhrase);
}
}
} else {
&Error("unknown command: $cmd");
exit 1;
&basic::Error("unknown command: $s_cmd");
}
&Progress("writing info", 1);
my $temp = WriteFile(join("", @s_logLines), ".log");
Exec("mv $temp $answer");
if (! -f $answer){
die "+++ $temp -> $answer failed: $!";
system ("./automount-control.sh enabled");
my ($refExecs, $refLogs) = basic::GetVars();
recorder::Finish("execLines", $refExecs, "logLines", $refLogs);
if ($s_testRun){
&finishTest;
} else {
&basic::Progress("writing info", 1);
my $temp = recorder::WriteFile(join("", @$refLogs), ".log");
&basic::Exec("mv $temp $s_answer");
if (! -f $s_answer){
die "+++ $temp -> $s_answer failed: $!";
}
}
exit(0);

# ===
# Builds a file containing the password.
#
# This file contains random characters, the password and random characters.
# @param password the file content
# @return (<filename>, <offset>, <lengthPw>)
# <offset> is the beginning of the real password
# <length> is the length of the password
sub CreatePasswordFile{
my $password = shift;
my $fn = "/tmp/p";
$password = basic::UnScramble($password);
open(my $OUT, ">", $fn) || die "$fn: $!";
my $len = length($password);
my $offset = 128 + int(rand(512 - $len - 128));
my $rest = 512 - $offset - $len;
Fill($password, $offset, $OUT);
print $OUT $password;
Fill($password, $rest, $OUT);
close $OUT;
return ($fn, $offset, $len);
}

my @s_passwords = qw(password 123456 12345678 qwerty abc123
monkey 1234567 letmein trustno1 dragon baseball 111111
iloveyou master sunshine ashley bailey passw0rd shadow
123123 654321 superman qazwsx michael football
admin hallo internet pass password passwort schatz);
# ===
# Fills random chars into a file
# @param charset
# @param count
# @param handle
sub Fill{
my $charset = shift;
my $count = shift;
my $handle = shift;
my $charset2 = "aaaaaabcdeeeeefghiiiiijklmnoooopqrstuuuuvwxyz";
my $charset3 = "AAAAABCDEEEEEFGHIIIIIJKLMNOOOOOPQRSTUUUUVWXYZeE";
my $content = "";
while($count-- > 0){
my $rand = int(rand(100));
if ($rand < 60){
$content .= substr($charset2, rand length($charset2) - 1, 1);
} elsif ($rand < 75) {
$content .= chr(32 + int(rand(127-32)));
} elsif ($rand < 85){
$content .= substr($charset2, rand length($charset2) - 1, 1);
} elsif ($count > 5 && $rand < 95){
my $w = $s_passwords[rand $#s_passwords];
if (length($w) >= $count) {
$w = substr(0, $count);
$count -= lenght($w) - 1;
}
$content .= $w;
} else {
$content .= substr($charset, rand(length($charset) - 1), 1);
}
}
if ($handle){
print $handle $content;
} else {
print $content, "\n";
}
}

# ===
# Builds the system with encrypted partitions.
# @param boot partition for boot, e.g. sda5
# @param lvm partition for LVM, e.g. sda6
# @param lvmInfo e.g. boot:boot:100M:ext4;root:siduction:4G:ext4
# @param password password, encrypted with Scramble()
sub BuildEncrypted{
my $boot = shift;
my $lvm = shift;
my $lvInfo = shift;
my $password = shift;
&basic::Progress("creating encrypted partition");
$password = basic::UnScramble($password);
my $content = "spawn cryptsetup -c aes-cbc-essiv:sha256 -q -s 512 luksFormat /dev/$lvm\n";
$content .= "sleep 2\n";
$content .= "send \"$password\"\n";
my $fn = recorder::WriteFile($content);
basic::Exec("expect $fn");
unlink $fn;
$content = "spawn cryptsetup -q luksOpen /dev/$lvm crypt$lvm\n";
$content .= "sleep 2\n";
$content .= "send \"$password\"\n";
$fn = recorder::WriteFile($content);
basic::Exec("expect $fn");
unlink $fn;
my $parts = "mapper/crypt$lvm-2048-4096";
BuildVG($parts, $s_vgInfo);
BuildLVs($s_lvInfo, $s_vgInfo, "boot");
}

# ===
# Tests whether a volume group exists.
# @param vgInfo e.g. siduction:4M
# @return 0: vg does not exist 1: vg exists
sub VGExists{
my $vgInfo = shift;
my ($name, $extSize) = split(/:/, $vgInfo);
my $rc = -d "/dev/$name";
my $rc = recorder::FileExists("VGExists", "-d", "/dev/$name");
$rc = 0 if $s_testRun;
if ($rc){
Error("VG $name already exists. (/dev/$name exists)");
&basic::Error("VG $name already exists. (/dev/$name exists)");
}
return $rc;
}

# ===
# Builds a logical volume
# @param name boot, root, home or swap
# @param label file system label
@@ -93,24 +262,26 @@ sub BuildLV{
my $fs = shift;
my $vg = shift;
$size = $size eq "*" ? "--extents 100%FREE" : "--size $size";
Exec("lvcreate $size --name $name $vg", 1);
&basic::Exec("lvcreate $size --name $name $vg", 1);
my $lvPath = "/dev/mapper/$vg-$name";
if (! -e $lvPath){
Error("LV $name not created");
if (! recorder::FileExists("BuildLV", "-e", $lvPath)){
&basic::Error("LV $name not created");
} elsif ($fs eq "swap"){
Exec("mkswap -L $label /dev/mapper/$vg-$name", 1);
Log("=== LV $name created as swap device");
&basic::Exec("mkswap -L $label /dev/mapper/$vg-$name", 1);
&basic::Log("LV $name created as swap device", 1);
} else {
my $fsFull = qx(which mkfs.$fs);
# execute and return as string (list):
my $fsFull = join("", recorder::ReadStream("BuildLV", "<which mkfs.$fs"));
if ($fsFull eq ""){
Error("unknown filesystem: $fs");
&basic::Error("unknown filesystem: $fs");
} else {
Exec("mkfs.$fs -L $label $lvPath");
Log("=== LV $name formatted with $fs");
&basic::Exec("mkfs.$fs -L $label $lvPath");
&basic::Log("LV $name formatted with $fs", 1);
}
}
}

# ===
# Converts a value to KiByte
# @param value examples: 102G 3T 8k 2M
# @return amount of KiByte
@@ -134,6 +305,7 @@ sub toKiByte{
return $number
}

# ===
# Converts a amount of KiBytes into a number and a unit.
# @param kiByte amount in KiBytes
# @return e.g. 4M or 243K or 22G
@@ -149,34 +321,60 @@ sub kiByteToSize{
}
return $size;
}
# ===
# Converts a size (number + unit) into an amount of KiBytes.
# @param size e.g. 4M or 243K or 22G
# @return amount in KiBytes
sub sizeToKiByte{
my $size = shift;
die "not a size (number+unit): $size" unless $size =~ /^(\d+)([TGMK])?$/i;
my ($rc, $unit) = ($1, $2);
$unit =~ tr/a-z/A-Z/;
if ($unit eq "M"){
$rc *= 1024;
} elsif ($unit eq "G"){
$rc *= 1024 * 1024;
} elsif ($unit eq "T"){
$rc *= 1024 * 1024 * 1024;
}
return $rc;
}
# ===
# Builds all logical volumes:
# @param lvInfo e.g. root:rider32:4G:ext4;swap:swap:400M:swap
# @param vgInfo e.g. siduction:4M
# @param lvInfo e.g. root:rider32:4G:ext4;swap:swap:400M:swap
# @param vgInfo e.g. siduction:4M
# @param ignored partition to ignore, e.g. "boot"
sub BuildLVs{
my $lvInfo = shift;
my $vgInfo = shift;
my $ignored = shift;
my ($vg, $extSize) = split(/:/, $vgInfo);
&basic::Progress("creating logical volumes");
$extSize = toKiByte($extSize);
my @lvs = split(/;/, $lvInfo);
foreach(@lvs){
my ($lv, $name, $size, $fs) = split(/:/);
next if $lv eq $ignored;
if ($size ne "*"){
$size = toKiByte($size);
$size = kiByteToSize(int($size / $extSize) * $extSize);
}
&Progress("creating $name");
&basic::Progress("creating $name");
BuildLV($lv, $name, $size, $fs, $vg);
}
}

# ===
# Builds a volume group.
# @param parts the partion info of the disk
# @param parts the partion info of the disk, lvmInfo
# e.g. sdb1-2048-9999+sdb2-10000
# @param vgInfo e.g. "siduction:32M"
sub BuildVG{
my $parts = shift;
my $vgInfo = shift;
my ($vg, $extSize) = split(/:/, $vgInfo);
&basic::Progress("creating volume group");
# Initialize the PV:
my $cmd = "";
my @parts = split(/\+/, $parts);
@@ -185,30 +383,27 @@ sub BuildVG{
my @cols = split(/-/);
$pvList .= " /dev/" . $cols[0];
}
Exec("pvcreate --yes $pvList", 1);
Exec("vgcreate --physicalextentsize $extSize $vg $pvList", 1);
&basic::Exec("pvcreate --yes $pvList", 1);
&basic::Exec("vgcreate --physicalextentsize $extSize $vg $pvList", 1);
}

# Reads the disk info with fdisk.
# ===
# Reads the disk info with a MSDOS disk label.
# The info will be stored in %s_diskInfo
# @param disk e.g. sdb
sub ReadFdiskInfo{
sub ReadMsdosDisk{
my $disk = shift;
my $info = "ptype:$MBR";
open(DISK, "$s_fdisk -l /dev/$disk|") || die "$s_fdisk -l /dev/$disk";
my @lines = <DISK>;
close DISK;
my @lines = recorder::ReadStream("ReadMsdosDisk", "parted -s /dev/$disk 'unit s' print|");
my $parts;
foreach(@lines){
#/dev/sdb1 2048 10000 3976+ 83 Linux
# if (m!^/dev/\D+(\d+)\D+(\d+)\s+(\d+)!){
if (m!^/dev/\D+(\d+)\D+(\d+)\s+(\d+)!){
if (/^\s+(\d+)\s+(\d+)\s+(\d+)/){
if ($parts eq ""){
$parts = " part";
}
$parts .= ":$1-$2-$3";
}
if (m!^/dev/\D+(\d+).*Extended!){
if (m!extended!){
$info .= " ext:$1";
}
}
@@ -217,15 +412,14 @@ sub ReadFdiskInfo{
return $info;
}

# Reads the disk info with gdisk.
# ===
# Reads the disk info with a GUID Partition Table.
# The info will be stored in %s_diskInfo
# @param disk e.g. sdb
sub ReadGdiskInfo{
sub ReadGPTDisk{
my $disk = shift;
my $info = "ptype: gpt";
open(DISK, "$s_gdisk -l /dev/$disk|") || die "$s_gdisk -l /dev/$disk";
my @lines = <DISK>;
close DISK;
my @lines = recorder::ReadStream("ReadGPTDisk", "parted -s /dev/$disk 'unit s' print|");
my $parts;
foreach(@lines){
if (/^\s+(\d+)\s+(\d+)\s+(\d+)/){
@@ -240,6 +434,7 @@ sub ReadGdiskInfo{
return $info;
}

# ===
# Returns the disk info.
# If not known it will be read.
# @param disk e.g. sdb
@@ -251,18 +446,19 @@ sub GetDiskInfo{
my $type = FindDiskType($disk);
$s_diskInfo{$disk} = $type;
if ($type eq $MBR){
$rc = ReadFdiskInfo($disk);
$rc = ReadMsdosDisk($disk);
} elsif ($type eq $GPT){
$rc = ReadGdiskInfo($disk);
$rc = ReadGPTDisk($disk);
} elsif ($type eq "!"){
# error already is displayed
} else{
Error("unknown partition table: $disk");
&basic::Error("unknown partition table: $disk");
}
}
return $rc;
}

# ===
# Returns the class of the next new partition.
# @param disk disk to test
# @result "p": primary "l": logical "b": primary or logical
@@ -277,90 +473,178 @@ sub GetNewPartClass{
$rc = ($info =~ /:1-.*:2-.*:3-.*:4-/) ? "l" : "b";
}
} else {
Error("not implemented: GetNewPartClass gpt");
&basic::Error("not implemented: GetNewPartClass gpt");
}
return $rc;
}

# ===
# Stores the partition types of the disks.
# @param info e.g. "sdb:mda+sdb:gdb"
sub StorePartTypeInfo{
my $info = shift;
my $disk;
for $disk(split(/\+/, $info)){
my ($name, $type) = split(/:/, $disk);
$s_wantedDiskType{$name} = $type;
if ($info){
for my $disk(split(/\+/, $info)){
my ($name, $type) = split(/:/, $disk);
$s_wantedDiskType{$name} = $type;
}
}
}

# Builds all PV of a LVM
# @param pvlist a list of all PV partitions (which do not already exist)
# ===
# Builds the partitions on the disk(s)
# @param pvlist a list of all partitions (which do not already exist)
# e.g. "sda1-9-2048-1000000+sdb1-9-2048-1000000"
sub BuildLVMs{
sub CreatePartitions{
my $pvlist = shift;
my $pv;
for $pv (split(/\+/, $pvlist)){
for my $pv (split(/\+/, $pvlist)){
my ($name, $from, $to) = split(/-/, $pv);
BuildPV($name, $from, $to, $MBR);
last if ($s_errors > 0);
CreateOnePartition($name, $from, $to, "lvm");
last if ($basic::s_errors > 0);
}
&basic::Exec("partprobe");
}
# ===
# Creates the two partitions boot + LVM (encrypted partitions)
# @param parts the partition, e.g. sdb1-2048-9999+sdb2-10000-2000
# @param lvInfo e.g. boot:boot02:256M;root:rider32:4G:ext4;home:home:2G
# @return (<devBoot>, <devLvm>), e.g. ("sda6", "sda7")
# ("", "") error occurred
sub CreateTwoPartitions{
my $parts = shift;
my $lvInfo = shift;
my ($boot, $lvm) = split(/\+/, $parts);
my ($bootName, $bootFrom, $bootTo) = split(/-/, $boot);
my ($lvmName, $lvmFrom, $lvmTo) = split(/-/, $lvm);
my ($rcBoot, $rcLvm);
if ($lvmName ne $bootName){
# 2 different free spaces:
$rcBoot = CreateOnePartition($bootName, $bootFrom, $bootTo);
$rcLvm = CreateOnePartition($lvmName, $lvmFrom, $lvmTo);
} else {
# 2 partitions in a single free space:
die "unexpected device name: $lvmName" unless $bootName =~ /(\D+)(\d+)/;
my ($disk, $bootPartNo) = ($1, $2);
my $diskInfo = $s_diskInfo{$disk};
die "missing boot info: $lvInfo"
unless $lvInfo =~ /boot:[^:]+:(\d+\w?):(\w+)/;
my ($records, $fsBoot) = (2 * sizeToKiByte($1), $2);
# round up to the next MiByte:
$records = int(($records + 2047) / 2048) * 2048;
# split the free space into 2 parts:
# a logical partition needs 1 record for partition info
# to be aligned we reduce the previous partition by one record:
$bootTo = $bootFrom + $records - 1 - 1;
$lvmFrom = $bootTo + 2;
my $createIt = 1;
if ($diskInfo =~ /ptype:$GPT/){
# make 2 partitions
} elsif ($diskInfo !~ / ext:/){
# there is no extended partition. We make it:
CreateOnePartition("${disk}0", $bootFrom, $lvmTo);
# the logical partition needs one record, but we will be aligned:
$bootFrom += 2048;
($bootName, $lvmName) = ($disk . "5", $disk . "6");
} elsif ($bootPartNo <= 4) {
# the free space is not in the extended partition.
# we need 2 primaries:
my @nos = &getPartNosOfDisk($disk);
my $count = 0;
foreach(@nos){
$count++ if $_ < 5;
}
if ($count > 2){
&basic::Error("too many primary partitions");
$createIt = 0;
}
} else {
# the free space is in the extended partition.
# we need 2 logicals:
my @nos = &getPartNosOfDisk($disk);
my $count = 0;
foreach(@nos){
$count++ if $_ < 5;
}
if ($count > 15 - 4 - 2){
&basic::Error("too many logical partitions");
$createIt = 0;
}
}
if ($createIt && &basic::GetErrorCount() == 0){
$rcBoot = CreateOnePartition($bootName, $bootFrom, $bootTo, $fsBoot);
$rcLvm = CreateOnePartition($lvmName, $lvmFrom, $lvmTo, "-");
}
}
&basic::Exec("partprobe");
return ($rcBoot, $rcLvm);
}


# ===
# Builds one PV of a LVM
# @param name name of the partition, e.g. sda1
# @param from first record (of the disk)
# @param to last record
sub BuildPV{
# @param name name of the partition, e.g. sda1
# @param from first record (of the disk)
# @param to last record
# @param fileSys "lvm" or "ext4"
# @return the name of the created partition, e.g. sda3
sub CreateOnePartition{
my $name = shift;
my $from = shift;
my $to = shift;
my $fileSys = shift;
$fileSys = "lvm" unless $fileSys;
my $partType = FindDiskType($name);
my $disk = FindDiskName($name);
my $newNo = -1;
my $no;
$name =~ /^\D+(\d+)/;
$no = $1;
$name =~ /^(\D+)(\d+)/;
my ($disk, $no) = ($1, $2);
if (! SectorsOverlap($disk, $no, $from, $to)){
if ($partType eq $MBR){
my $content = "n\n";
my $class = GetNewPartClass($disk);
if ($class eq "b"){
$content .= $no < 5 ? "p\n" : "l\n";
} else {
$content .= $class . "\n";
}
$content .= "$no\n$from\n$to\nt\n";
if (CountOfPartitions($disk) > 0){
$content .= "$no\n";
}
$content .= "8e\nw\n";
my $fn = WriteFile($content);
Exec("$s_fdisk /dev/$disk < $fn");
my $info = $s_diskInfo{$disk} = ReadFdiskInfo($disk);
if (index($info, ":$no-$from-$to") < 0){
Error("creating $name failed!");
if ($partType eq $MBR || $partType eq $GPT){
my $class;
if ($no == 0){
$class = "extended";
} else {
Log("=== $name created");
$class = $partType eq $MBR && $no > 4 ? "logical" : "primary";
}
my @lines = recorder::ReadStream("CreateOnePartition",
"parted -s /dev/$disk unit s mkpart $class ${from}s ${to}s print|");
foreach (@lines){
if (/^\s*(\d+)\s+(\d+)s\s+(\d+)s/ && $2 == $from && $3 == $to){
$newNo = $1;
last;
}
}
} elsif ($partType eq $GPT){
my $content = "n\n$no\n$from\n$to\n8e00\nw\nY\n";
my $fn = WriteFile($content);
Exec("$s_gdisk /dev/$disk < $fn");
my $info = $s_diskInfo{$disk} = ReadGdiskInfo($disk);
if (index($info, ":$no-$from-$to") < 0){
Error("creating $name failed!");
} else {
Log("=== $name created");
if ($newNo < 0){
my $msg = "creating $name failed!";
foreach(@lines){
$msg .= " $_" if /error/i;
}
&basic::Error($msg);
} elsif ($partType eq $GPT && $fileSys ne "-") {
my $cmd;
if ($fileSys eq "lvm"){
$cmd = "name $newNo 'Linux LVM' " if $partType eq $GPT;
$cmd .= "set $newNo lvm on";
} elsif ($fileSys eq "ext4") {
$cmd = "name $newNo 'Linux filesystem' set $newNo lvm off";
} else {
die "unknown filesys: $fileSys";
}
&basic::Exec("parted -s /dev/$disk $cmd");
}
} elsif ($partType eq "!"){
# error already is displayed
} else {
Error("Unknown partType: $partType");
&basic::Error("Unknown partType: $partType");
}
}
Exec("partprobe");
return "$disk$newNo";
}

# ===
# Counts the number of partitions of the disk.
# @param disk e.g. sdb
# @return the number of partitions of the disk
@@ -376,6 +660,7 @@ sub CountOfPartitions{
return $rc;
}

# ===
# Checks whether a partition already exists or whether the
# sectors of the new partition overlap of an existing partition
# @param disk e.g. sdb
@@ -391,7 +676,7 @@ sub SectorsOverlap{
my $info = GetDiskInfo($disk);
my $rc = 0;
if ($info =~ /:$partNo-/){
Error("partition $partNo already exists");
&basic::Error("partition $partNo already exists");
$rc = 1;
} else {
if ($info =~ /part:(\S+)/){
@@ -402,7 +687,7 @@ sub SectorsOverlap{
if ($info !~ /ext:$no/
&& ($f >= $from && $f <= $to || $t >= $from && $t <= $to)){
&Error("partition $no overlaps with $partNo: $f-$t / $from-$to");
&basic::Error("partition $no overlaps with $partNo: $f-$t / $from-$to");
$rc = 1;
}
}
@@ -410,6 +695,8 @@ sub SectorsOverlap{
}
return $rc;
}

# ===
# Finds the disk name for a given partition
# @param part partition name, e.g. "sda1"
# @return the disk name, e.g. "sda"
@@ -422,6 +709,7 @@ sub FindDiskName{
return $rc;
}

# ===
# Finds the partition table type.
# If the disk has no type (uninitialized) it will create a partition table
# @param part e.g. "sda1"
@@ -434,13 +722,11 @@ sub FindDiskType{
if ($s_realDiskType{$disk} ne ""){
$rc = $s_realDiskType{$disk};
} else {
# gdisk waits for an answer if the GDP/MBR is damaged
my $input = WriteFile("1\n");
my $answer = WriteFile("<none>");
Exec("$s_gdisk -l /dev/$disk < $input >$answer");
open(EXEC, $answer) || Error ("$answer: $!");
my @lines = <EXEC>;
close EXEC;
# gdisk waits for an answer if the GPT/MBR is damaged
my $input = recorder::WriteFile("1\n");
my $answer = recorder::WriteFile("<none>");
my @lines = recorder::ReadStream("FindDiskType",
"$s_gdisk -l /dev/$disk < $input >$answer");
unlink $input;
unlink $answer;
my $mbrOnly;
@@ -450,12 +736,12 @@ sub FindDiskType{
$rc = $MBR;
$mbrOnly = $1 eq "MBR only";
} elsif (/GPT: damaged/){
Error("GPT is damaged. Must be repaired manually.");
&basic::Error("GPT is damaged. Must be repaired manually.");
$rc = "!";
last;
} elsif (/GPT: present/){
if ($mbrOnly){
Error("GPT mixed with MBR. Fix it manually with gdisk, e.g: x (expert only) z (destroy GPT)");
&basic::Error("GPT mixed with MBR. Fix it manually with gdisk, e.g: x (expert only) z (destroy GPT)");
$rc = "!";
} else {
$rc = $GPT;
@@ -466,7 +752,7 @@ sub FindDiskType{
if ($rc eq ""){
$rc = $s_wantedDiskType{$disk};
if ($rc eq ""){
Error("No partition table type given for $disk: mbr will be taken");
&basic::Error("No partition table type given for $disk: mbr will be taken");
$rc = $MBR;
}
CreatePartitionTable($disk, $rc);
@@ -476,6 +762,22 @@ sub FindDiskType{
return $rc;
}

# ===
# Gets the partition numbers of a disk
# @param disk e.g. sda
# @return a sorted array of partition numbers, e.g. (1, 3, 5, 7)
sub getPartNosOfDisk{
my $disk = shift;
my $parts = getDiskInfo($disk);
my @cols = split(/:/, $parts);
my @rc;
foreach(@cols){
push(@rc, $1) if /(\d+)-\d+-\d+/;
}
return sort @rc;
}
# ===
# Creates a partition table for a disk.
# @param disk disk name, e.g. sdc
# @param type partition table type: "mbr" or "gpt"
@@ -484,84 +786,45 @@ sub CreatePartitionTable{
my $type = shift;
if ($s_allowInit ne "YES"){
Error("not allowed to create a partition table");
&basic::Error("not allowed to create a partition table");
} elsif ($type eq $MBR){
my $fn = WriteFile("o\nw\n");
Exec("$s_fdisk /dev/$disk < $fn");
&Log("=== $disk initialized ($MBR)");
my $fn = recorder::WriteFile("o\nw\n");
&basic::Exec("$s_fdisk /dev/$disk < $fn");
&basic::Log("$disk initialized ($MBR)", 1);
unlink $fn;
} else {
open(EXEC, "|$s_gdisk /dev/$disk");
print EXEC "o\n", "w\n", "Y\n";
close EXEC;
&Log("=== $disk initialized ($GPT)");
recorder::WriteStream("CreatePartitionTable", "|$s_gdisk /dev/$disk",
"o\n", "w\n", "Y\n");
&basic::Log("$disk initialized ($GPT)", 1);
}
}

# Executes a command.
# @param cmd the command to execute
sub Exec{
my $cmd = shift;
my $extendedLog = shift;
if ($extendedLog){
Log("=== $cmd");
} else {
Log($cmd);
}
system($cmd);
# ===
# do simple tests
sub testSuite{
my $test = shift;
}

# Writes a given content to a temporary file.
# @param content content to write
# @return filename
sub WriteFile{
my $content = shift;
my $suffix = shift;
my $fn = "/tmp/$$." . time() . ".tmp$suffix";
if ($content ne "<none>"){
open(OUT, ">$fn") || die "$fn: $!";
print OUT $content;
close OUT;
# ===
# Initializes a full size test.
sub initializeTest{
if ($s_testRun eq "stdlvm"){
} elsif ($s_testRun eq "crypt"){
} else {
die "not implemented: $s_testRun";
}
return $fn;
}
# Logs a message.
# @param msg message
sub Log{
my $msg = shift;
print $msg, "\n";
push(@s_logLines, $msg . "\n");
}

# Handles an error message.
# @param msg error message
sub Error{
my $msg = shift;
$s_errors++;
&Log("===+++ $msg");
# ===
# Evaluation of the test result for full size tests.
sub finishTest{
my @expectedExec = recorder::Get("execLines");
my @expectedLog = recorder::Get("logLines");
my ($refExecs, $refLogs) = basic::GetVars();
die unless test::EqualArray("LogList", $refLogs, \@expectedLog);
die unless test::EqualArray("ExecList", $refExecs, \@expectedExec);
}

# Writes the progress file.
#@param task name of the current task
sub Progress{
my $task = shift;
my $isLast = shift;
$task .= " ...";
$s_currTask++;
$s_maxTasks = $s_currTask if $isLast;
if ($s_currTask == $s_maxTasks){
$s_maxTasks += 5;
}
my $temp = $s_fnProgress . ".tmp";
open(PROGRESS, ">$temp") || die "$temp: $!";
my $percent = int(100 * ($s_currTask - 1) / $s_maxTasks);
$percent = 5 if $percent < 5;
print PROGRESS <<EOS;
PERC=$percent
CURRENT=<b>$task</b>
COMPLETE=completed $s_currTask of $s_maxTasks
EOS
close PROGRESS;
unlink $s_fnProgress if -f $s_fnProgress;
rename $temp, $s_fnProgress;
}


+ 11
- 10
backend/autopart.sh View File

@@ -2,20 +2,21 @@
test -n "$VERBOSE" && set -x
ANSWER=$1
CMD=$2
DISKINFO=$3
ALLOW_INIT=$4
PARTS=$5
VG_INFO=$6
LV_INFO=$7
PROGRESS=$8
PROGRESS=$3
DISKINFO=$4
ALLOW_INIT=$5
PARTS=$6
VG_INFO=$7
LV_INFO=$8
CODE=$9
FULL_LOG=../public/autopart_log.txt

if [ -z "$VERBOSE" ] ; then
perl autopart.pl "$CMD" "$ANSWER" "$DISKINFO" "$ALLOW_INIT" "$PARTS" \
"$VG_INFO" "$LV_INFO" "$PROGRESS" > $FULL_LOG 2>&1
perl autopart.pl "$CMD" "$ANSWER" "$PROGRESS" "$DISKINFO" "$ALLOW_INIT" \
"$PARTS" "$VG_INFO" "$LV_INFO" "$CODE" > $FULL_LOG 2>&1
else
perl autopart.pl "$CMD" "$ANSWER" "$DISKINFO" "$ALLOW_INIT" "$PARTS" \
"$VG_INFO" "$LV_INFO" "$PROGRESS" 2>&1 | tee $FULL_LOG
perl autopart.pl "$CMD" "$ANSWER" "$PROGRESS" "$DISKINFO" "$ALLOW_INIT" \
"$PARTS" "$VG_INFO" "$LV_INFO" "$CODE" 2>&1 | tee $FULL_LOG
fi
test -n "$VERBOSE" && cat "$ANSWER"
test -n "$VERBOSE" && ls -ld $FULL_LOG

+ 69
- 207
backend/partinfo.pl View File

@@ -1,8 +1,15 @@
#! /usr/bin/perl
# Gets the partition info.
# @param fnProgress name of the file containing progress info
# @param answer the partition info
# @param testRun "": normal run otherwise: name of the test
#
use strict;
use sidu_basic;
use sidu_recorder;

my $s_answer = shift;
$s_answer = "/var/cache/sidu-base/partinfo.txt" unless $s_answer;

my $s_fnProgress = shift;
$s_fnProgress = "/tmp/partinfo.progress" unless $s_fnProgress;
@@ -15,6 +22,8 @@ my $gv_mount_base = "/tmp/partinfo-mount";
my $gv_log = "/tmp/partinfo_err.log";
my $gv_mount_no = 0;
my %s_lvm;
my @s_output;

# minimal size of a partition in bytes:
my $s_minPartSize = 10*1024*1024;
my %months = (
@@ -43,19 +52,31 @@ my (%sorted, $key, $dev);
my $s_gapPart;
my $s_maxTasks = 10;
my $s_currTask = 0;
my $s_appl = "pi";
# <name> -> <from>-<to>
my %s_extParts;
my %s_damagedDisks;
my $s_hints;

if ($s_testRun){
&runTest;
# replaying
recorder::Init($s_appl, 2, $s_testRun);
basic::Init($s_fnProgress, $s_testRun ne "");
} else {
system ("./automount-control.sh disabled");
&main();
system ("./automount-control.sh enabled");
# recording:
recorder::Init($s_appl, 1, "/tmp/$s_appl.recorder.data");
}
# we need no arg saving/restoring
basic::Init($s_fnProgress, $s_testRun ne "");
system ("./automount-control.sh disabled");
&main();
system ("./automount-control.sh enabled");

my ($refExecs, $refLogs) = basic::GetVars();
recorder::Finish("execLines", $refExecs, "logLines", $refLogs);
if ($s_testRun){
&finishTest;
}
exit 0;

# ===
@@ -90,26 +111,34 @@ sub main{
}
foreach $key (sort keys %sorted){
$dev = $sorted{$key};
print $dev, "\t", $blkids{$dev}, "\n";
push(@s_output, "$dev\t$blkids{$dev}");
}
foreach $dev (sort keys %s_lvDevs){
print $dev, "\t", $s_lvDevs{$dev}, "\n";
push(@s_output, "$dev\t$s_lvDevs{$dev}");
}
foreach $key (sort keys %s_disks){
print $key, "\t", $s_disks{$key}, "\n";
push(@s_output, "$key\t$s_disks{$key}");
}
print "!GPT=$s_gptDisks;\n";
print '!VG=', join(';', @s_vg), "\n";
print '!LV=', join(';', @s_lv), "\n";
print "!GapPart=", $s_gapPart, "\n";
print "!damaged=", join(';', sort keys %s_damagedDisks), "\n";
push(@s_output, "!GPT=$s_gptDisks;");
push(@s_output, '!VG=' . join(';', @s_vg));
push(@s_output, '!LV=' . join(';', @s_lv));
push(@s_output, "!GapPart=$s_gapPart");
push(@s_output, "!damaged=" . join(';', sort keys %s_damagedDisks));

&basic::Progress("writing info", 1);
recorder::Put("partition_info", \@s_output);

my $temp = recorder::WriteFile(join("\n", @s_output), ".out", "", 0,
"/var/cache/sidu-base");
unlink($s_answer) if -e $s_answer;
print STDERR "cannot rename: $temp -> $s_answer $!" unless rename($temp, $s_answer);
&UnmountAll();
}

# ===
# release all mounts done by the script itself
sub UnmountAll{
my @files = readStream("UnmountAll", $gv_mount_base);
my @files = recorder::ReadStream("UnmountAll", $gv_mount_base);
my $dir;
my $run = 0;
while ($run < 2){
@@ -122,7 +151,6 @@ sub UnmountAll{
system ("umount $full >>$gv_log 2>&1");
rmdir $full;
if (-d $full){
print
system ("lsof +d $full >>$gv_log 2>&1");
$errors++;
}
@@ -163,8 +191,8 @@ sub detective{
rmdir $dirMount;
}

if ($fs =~ /fs:ext\d/ || $fs eq "auto"){
my @lines = readStream("detective", "tune2fs -l $dev|");
if ($dev !~ /swap/ && ($fs =~ /fs:ext\d/ || $fs eq "auto")){
my @lines = recorder::ReadStream("detective", "tune2fs -l $dev|");
my $date;
foreach(@lines){
#Filesystem created: Sun May 1 07:53:47 2011
@@ -192,7 +220,7 @@ my $s_mounts;
sub getMountPoint{
my $dev = shift;
if ($s_mounts eq ""){
my @lines = readStream("getMountPoint", "mount|");
my @lines = recorder::ReadStream("getMountPoint", "mount|");
foreach(@lines){
if (/^(\S+)\s+on\s+(\S+)/){
$s_mounts{$1} = $2;
@@ -212,7 +240,7 @@ sub firstLineOf{
my $prefix = shift;
my $rc = "";
if (-f $file){
my @lines = readStream("firstOfLine", $file);
my @lines = recorder::ReadStream("firstOfLine", $file);
$rc = "\t$prefix:" . $lines[0];
chomp $rc;
}
@@ -223,7 +251,7 @@ sub firstLineOf{
# Gets the volume group info
# The info will be stored in @lvs
sub getVG{
my @lines = readStream("getVG", "vgdisplay|");
my @lines = recorder::ReadStream("getVG", "vgdisplay|");
my $vgs = "";
foreach(@lines){
if (/VG Name\s+(\S+)/){
@@ -247,7 +275,7 @@ sub getVG{
# @return e.g. ("sda", "sdc")
sub getDiskDev{
&Progress("partprobe");
my @lines = readStream("getDiskDev", "partprobe -s|");
my @lines = recorder::ReadStream("getDiskDev", "partprobe -s|");
my @rc;
# count the interesting disks:
@@ -341,7 +369,7 @@ sub getEmptyDisks{
# Fills: %s_devs, %s_disks, %s_extParts, $s_hints, $s_gapPart
sub getFdiskInfo{
my $disk = shift;
my @lines = readStream("getFdiskInfo", "fdisk -l /dev/$disk|");
my @lines = recorder::ReadStream("getFdiskInfo", "fdisk -l /dev/$disk|");
my ($dev, $size, $ptype, $info, $min, $max);
my $sectorSize = 512;
my $sectorCount = -1;
@@ -455,7 +483,7 @@ sub getFdiskInfo{
# @param cmd the call of gdisk, e.g. sdc
sub getGdiskInfo{
my $disk = shift;
my @lines = readStream("getGdiskInfo", "echo 1 | gdisk -l /dev/$disk|");
my @lines = recorder::ReadStream("getGdiskInfo", "echo 1 | gdisk -l /dev/$disk|");
my $sectorSize = 512;
my $lastSector = -1;
my @sectors;
@@ -514,7 +542,7 @@ sub getBlockId{
my ($label, $uuid, $fs, $info2);
my ($dev, $info);
my %blkids;
my @lines = readStream("getBlockId", "/sbin/blkid -c /dev/null|");
my @lines = recorder::ReadStream("getBlockId", "/sbin/blkid -c /dev/null|");
foreach(@lines){
if (/^(\S+):/){
my $dev = $1;
@@ -570,8 +598,8 @@ sub mergeDevs{
sub getSizeOfLvmPartition{
my $dev = shift;
my ($sectors, $size);
open (INP, "gdisk -l $dev|") || die "gdisk: $!";
while(<INP>){
open my $INP, "gdisk -l $dev|" || die "gdisk: $!";
while(<$INP>){
if (/(\d+)\s+sectors/){
$sectors = $1;
} elsif (/sector\s+size:\s+(\d+)/){
@@ -579,7 +607,7 @@ sub getSizeOfLvmPartition{
last;
}
}
close INP;
close $INP;
return $size;
}
sub prettySize{
@@ -598,7 +626,7 @@ sub prettySize{
}
sub physicalView{
my @lines = readStream("physicalView", "pvdisplay|");
my @lines = recorder::ReadStream("physicalView", "pvdisplay|");
my ($pvName, $vgName, $size, %devs, %unassigned, %assigned);
foreach(@lines){
chomp;
@@ -637,25 +665,25 @@ sub physicalView{
delete($s_lvm{$key});
}
}
print "PhLVM:$out\n";
push(@s_output, "PhLVM:$out");
$out = '';
for $key (sort keys %unassigned){
delete($s_lvm{$key}) if $unassigned{$key};
$out .= "\t" . $unassigned{$key};
}
print "FreeLVM:", $out, "\n";
push(@s_output, "FreeLVM:$out");
$out = '';
for $key (sort keys %s_lvm){
$out .= "\t|$key|" . &prettySize($s_lvm{$key});
}
print "MarkedLVM:", $out, "\n";
push(@s_output, "MarkedLVM:$out");
close INP;
}
}


sub logicalView{
my @lines = readStream("logicalView", "lvdisplay|");
my @lines = recorder::ReadStream("logicalView", "lvdisplay|");
my ($lvName, $vgName, $size, $access, %devs, %snaps, $parent);
foreach(@lines){
chomp;
@@ -694,19 +722,19 @@ sub logicalView{
$out .= "\f\t$key" . $s_devs{$key};
}
if ($out ne ""){
print "LogLVM:$out\n";
push(@s_output, "LogLVM:$out");
}
$out = '';
foreach $key (sort keys %snaps){
$out .= "\f\t$key" . $snaps{$key};
}
if ($out ne ""){
print "SnapLVM:$out\n";
push(@s_output, "SnapLVM:$out");
}
}
}
sub vgInfo {
my @lines = readStream("vgInfo", "vgdisplay|");
my @lines = recorder::ReadStream("vgInfo", "vgdisplay|");
my ($vgName, $size, $access, $status, $free, $alloc, %vgs, $peSize);
foreach(@lines){
chomp;
@@ -737,7 +765,7 @@ sub vgInfo {
$out .= "\t|$key" . $vgs{$key};
}
if ($out ne ""){
print "VgLVM:$out\n";
push(@s_output, "VgLVM:$out");
}
}
}
@@ -756,14 +784,14 @@ sub Progress{
$s_maxTasks += 5;
}
my $temp = $s_fnProgress . ".tmp";
open(PROGRESS, ">$temp") || die "$temp: $!";
open my $PROGRESS, ">", $temp || die "$temp: $!";
my $percent = int(100 * $s_currTask / $s_maxTasks);
print PROGRESS <<EOS;
print $PROGRESS <<EOS;
PERC=$percent
CURRENT=<b>$task</b>
COMPLETE=completed $s_currTask of $s_maxTasks
EOS
close PROGRESS;
close $PROGRESS;
unlink $s_fnProgress if -f $s_fnProgress;
rename $temp, $s_fnProgress;
}
@@ -779,9 +807,9 @@ sub findFiles{
my @rc;
if (! $s_testRun){
opendir(DIR, $dir) || die "$dir: $!";
@rc = readdir(DIR);
closedir DIR;
opendir my $DIR, $dir || die "$dir: $!";
@rc = readdir $DIR;
closedir $DIR;
} elsif ($id eq "getEmptyDisk") {
if ($s_testRun =~ /gapPart/){
@rc = (".", "..", "dm-0", "sdd1", "sda", "sdb", "sdc", "sdx");
@@ -792,172 +820,6 @@ sub findFiles{
return @rc
}

# ===
# Reads a stream into an array of lines.
# A stream can be a file or the output of an extern command.
# For tests this can be a file.
# @param id defines the stream to open
# @param device a filename or a external command, e.g. "partprobe -s |"
sub readStream{
my $id = shift;
my $device = shift;
my $content = "<!None>";
my @rc;
if (! $s_testRun){
if (open(INP, $device)){
@rc = <INP>;
} else {
print "+++ $device: $!";
}
} elsif ($id eq "UnmountAll"){
if ($s_testRun =~ /all/){
} else {
die "not implemented";
}
} elsif ($id eq "getDiskDev"){
if ($s_testRun =~ /gapPart/) {
$content = "/dev/sdb: gpt partitions 1 5 6 7 125
/dev/sdc: msdos partitions 1 2 3 4 <5 6 7>
";
} else {
die "not implemented";
}
} elsif ($id eq "detective"){
if ($s_testRun =~ /all/){
} else {
die "not implemented";
}
} elsif ($id eq "getMountPoint"){
if ($s_testRun =~ /all/){
} else {
die "not implemented";
}
} elsif ($id eq "firstOfLine"){
if ($s_testRun =~ /all/){
} else {
die "not implemented";
}
} elsif ($id eq "getVG"){
if ($s_testRun =~ /all/){
} else {
die "not implemented";
}
} elsif ($id eq "getFdiskInfo"){
if ($s_testRun =~ /gapPart/) {
$content;
if ($device =~ /sdx/){
$content = "Disk /dev/sdx: 16.0 GB, 15999172608 bytes
64 heads, 32 sectors/track, 15258 cylinders, total 31248384 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000
Disk /dev/sdb doesn't contain a valid partition table";
} elsif ($device =~ /sd[ab]/){
$content = "";
} elsif ($device =~ /sdc/){
$content = "Disk /dev/sdc: 16.0 GB, 15999172608 bytes
64 heads, 32 sectors/track, 15258 cylinders, total 31248384 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x1c313117
Device Boot Start End Blocks Id System
/dev/sdc1 * 80000 99999 48976 8e Linux LVM
/dev/sdc4 300001 31248383 15474191+ 5 Extended
/dev/sdc5 1024000 2457599 716800 83 Linux
/dev/sdc6 4194304 10485759 3145728 83 Linux
/dev/sdc7 10487808 10897407 204800 83 Linux";
} else {
die "not implemented";
}
} else {
die "not implemented: $device";
}
} elsif ($id eq "getGdiskInfo"){
if ($s_testRun =~ /gapPart/) {
$content = "
GPT fdisk (gdisk) version 0.8.5
Partition table scan:
MBR: MBR only
BSD: not present
APM: not present
GPT: not present
***************************************************************
Found invalid GPT and valid MBR; converting MBR to GPT format.
***************************************************************
Disk /dev/sdb: 31248384 sectors, 14.9 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): 796EA58D-DB68-430F-939B-3AFA7B81AAC7
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 31248350
Partitions will be aligned on 2048-sector boundaries
Total free space is 23015709 sectors (11.0 GiB)
Number Start (sector) End (sector) Size Code Name
1 2048 99999 47.8 MiB 8E00 Linux LVM
5 1024000 2457599 700.0 MiB 8300 Linux filesystem
6 4194304 10485759 3.0 GiB 8300 Linux filesystem
7 10487808 10897407 200.0 MiB 8300 Linux filesystem
";
} else {
die "not implemented";
}
} elsif ($id eq "getBlockId"){
if ($s_testRun =~ /all/){
} else {
die "not implemented";
}
} elsif ($id eq "physicalView"){
if ($s_testRun =~ /all/){
} else {
die "not implemented";
}
} elsif ($id eq "logicalView"){
if ($s_testRun =~ /all/){
} else {
die "not implemented";
}
} elsif ($id eq "vgInfo"){
if ($s_testRun =~ /all/){
} else {
die "not implemented";
}
} else {
die "unknown id: $id";
}
@rc = split(/\n/, $content) unless $content eq "<!None>";
return @rc;
}
# ===
# Writes a temporary file.
# @param fn node name, e.g. "gdisk.inp"
# @param content the file's content
# @return the full name of the new file
sub writeTempFile{
my $fn = shift;
my $content = shift;
my $dir = "/tmp/testPartInfo/";
mkdir $dir unless -d $dir;
$fn = $dir . $fn;
open(OUT, ">$fn") || die "$fn: $!";
print OUT $content;
close OUT;
return $fn;
}
# ===
# Runs the test selected by the program argument.
sub runTest{
if ($s_testRun eq "gapPart"){
&testGapPart();
} else {
die "unknown test: $s_testRun";
}
}

# ===
sub testGapPart{
&testGetDiskDev;
}
# Builds a pointer to the first different position of 2 strings.
# @param x 1st string
# @param y 2nd string

+ 4
- 7
backend/partinfo.sh View File

@@ -1,13 +1,10 @@
#! /bin/bash
ANSWER=$1
PROGRESS=$2
TEMP1=$ANSWER.tmp
test -n "$VERBOSE" && echo === answer: $ANSWER progress: $PROGRESS
if [ -z "$VERBOSE" ] ; then
perl partinfo.pl "$PROGRESS" >$TEMP1
else
perl partinfo.pl "$PROGRESS" | tee $TEMP1
perl partinfo.pl "$ANSWER" "$PROGRESS"
if [ -n "$VERBOSE" ] ; then
cat $ANSWER
fi

test -n "$VERBOSE" && ls -l $ANSWER
mv $TEMP1 $ANSWER

+ 311
- 0
backend/sidu_basic.pm View File

@@ -0,0 +1,311 @@
package basic;

=head1 NAME
base -- basic methods for generally usage

=head1 Summary
Implements generally usable methods

=head1 Author
hamatoma (C) 2013

=cut

#use strict;

my $s_logPrefixImportant = "=== ";
my $s_logPrefix = "";
my $s_stdoutPrefixImportant = "";
my $s_prefixError = "+++";
my $s_testRun = 0;
my $s_logStdout = 1;
my $s_logList = 1;
my $s_execList = 1;
# collects the commands done with Exec:
my @s_execLines;
my @s_logLines;
my $s_errors;
my $s_currTask;
my $s_maxTasks = 10;
my $s_fnProgress;

# ===
# Initializes the module.
sub Init{
my $fn = shift;
$s_fnProgress = $fn;
my $val = shift;
$s_testRun = $val;
}
# ===
# Returns the important static variables.
# @return (<refExecLines>, <reflogLines>)
sub GetVars{
return (\@s_execLines, \@s_logLines);
}
# ===
# Executes a command.
# @param cmd the command to execute
# @param important true: the logging will be prefixed
sub Exec{
my $cmd = shift;
my $important = shift;
push(@s_execLines, $cmd) if $s_execList;
if (! $s_testRun){
Log($cmd, $important);
system($cmd);
}
}

# ===
# Logs a message.
# @param msg message
# @param important true: a prefix will be added
sub Log{
my $msg = shift;
my $important = shift;
if ($important){
$msg = $s_logPrefixImportant . $msg;
}
print $msg, "\n" if $s_logStdout;
push(@s_logLines, $msg . "\n") if $s_logList;
}

# ===
# Handles an error message.
# @param msg error message
sub Error{
my $msg = shift;
$s_errors++;
&Log("===+++ $msg");
}

# ===
# Return the number of errors.
# @return the count of calls of the subroutine Error()
sub GetErrorCount{
return $s_errors;
}

# ===
# Writes the progress file.
#@param task name of the current task
sub Progress{
my $task = shift;
my $isLast = shift;
$task .= " ...";
$s_currTask++;
$s_maxTasks = $s_currTask if $isLast;
if ($s_currTask == $s_maxTasks){
$s_maxTasks += 5;
}
my $temp = $s_fnProgress . ".tmp";
open my $PROGRESS, ">", $temp || die "$temp: $!";
my $percent = int(100 * ($s_currTask - 1) / $s_maxTasks);
$percent = 5 if $percent < 5;
print $PROGRESS <<EOS;
PERC=$percent
CURRENT=<b>$task</b>
COMPLETE=completed $s_currTask of $s_maxTasks
EOS
close $PROGRESS;
unlink $s_fnProgress if -f $s_fnProgress;
rename $temp, $s_fnProgress;
}
my $xx = "";
# ===
# Disquises the passphrase.
# @param text clear text
# @return: the disguised text
sub Cover{
my $text = shift;
my $seed = int(rand 0xffff);
my $rc = sprintf("%02x%02x", $seed % 256, $seed / 256);
my $ix = 0;
my ($cc, $val);
$xx .= sprintf("seed: %d %04x\n", $seed, $seed);
while($ix < length($text)){
$seed = ($seed * 7 + 0x1234321) & 0x8fffffff;
$cc = substr($text, $ix, 1);
$val = ((chr($cc) ^ $seed) & 0xff);
$xx .= sprintf("ix: %d s: %d cc: %s/%02x v: %d %02x\n", $ix, $seed, $cc, ord($cc), $val, $val);
$rc .= sprintf("%02x", $val);
$ix++;
}
return $rc;
}
# ===
# Converts a 2 digit hex number into a number.
# @param x 2 digit hex number, e.g. "a9"
# @return the value of x, 0..255
sub Hex2Bin{
my $x = shift;
my ($n1, $n2) = (substr($x, 0, 1), substr($x, 1, 1));
$n1 = 10 + ord($n1) - ord("a") if $n1 gt "a";
$n2 = 10 + ord($n2) - ord("a") if $n2 gt "a";
my $rc = 16 * $n1 + $n2;
return $rc;
}
# ===
# Cover the text disguised with cover().
# @param text encrypted text
# @return clear text
sub Uncover{
my $text = shift;
my $rc = "";
my ($s1, $s2) = (Hex2Bin(substr($text, 0, 2)), Hex2Bin(substr($text, 2, 2)));
my $seed = $s1 + 256 * $s2;
my $ix = 4;
my ($val, $val2, $hh);
while($ix < length($text)){
$seed = ($seed * 7 + 0x1234321) & 0x8fffffff;
$val = Hex2Bin(substr($text, $ix, 2));
$val2 = (($val ^ $seed) & 0xff);
$rc .= chr($val2);
$ix += 2;
}
return $rc;
}

my $CHARS10 = "9147253806";
my $CHARS16 = "fadceb" . $CHARS10;
my $CHARS26 = "zfsoeiurglhqnmwtbvpxyjakcd";
my $CHARS38 = "_." . $CHARS10 . $CHARS26;
my $CHARS64 = "QASDFGHJKLWERTZUIOPYXCVBNM" . $CHARS38;
my $CHARS76 = "!\@my \$%&#;,/+=?" . $CHARS64;
my $CHARS93 = "^`(>~[{<*)\" |}]-:" . $CHARS76;
my $CHARS95 = "'\\" . $CHARS93;
my $CHARS96 = "" . $CHARS95;
my $TAG_CHARS10 = "9";
my $TAG_CHARS16 = "f";
my $TAG_CHARS26 = "z";
my $TAG_CHARS38 = "_";
my $TAG_CHARS64 = "Q";
my $TAG_CHARS76 = "!";
my $TAG_CHARS93 = "^";
my $TAG_CHARS95 = "<";
my $TAG_CHARS96 = ">";
my @ALL_TAGS = ($TAG_CHARS10, $TAG_CHARS16, $TAG_CHARS26, $TAG_CHARS38,
$TAG_CHARS64, $TAG_CHARS76, $TAG_CHARS93, $TAG_CHARS95, $TAG_CHARS96);
my %tagToSet = ( $TAG_CHARS10 => $CHARS10, $TAG_CHARS16 => $CHARS16,
$TAG_CHARS26 => $CHARS26, $TAG_CHARS38 => $CHARS38,
$TAG_CHARS64 => $CHARS64, $TAG_CHARS76 => $CHARS76,
$TAG_CHARS93 => $CHARS93, $TAG_CHARS95 => $CHARS95,
$TAG_CHARS96 => $CHARS96 );
# ===
# @return a list of all charset tags
sub AllTags{
return @ALL_TAGS;
}
# ===
# Gets the charset given by the charset tag
# @param tag the tag of the charset
# @return a string with all allowed characters
sub GetCharset{
my $tag = shift;
my $rc = $tagToSet{$tag};
if ($rc eq ""){
die "Unknown charset: $tag"
}
return $rc;
}

# ===
# Finds the character set to a given text.
# @param text text to inspect
# @return the tag of the charset containing all chars of the text
sub FindCharset{
my $text = shift;
my ($rc, $ix, $cc, $outside, $set);
foreach my $tag (@ALL_TAGS){
$set = GetCharset($tag);
$outside = 0;
for($ix = 0; $ix < length($text); $ix++){
$cc = substr($text, $ix, 1);
if (index($set, $cc) < 0){
$outside = 1;
last;
}
}
if (! $outside){
$rc = $tag;
last;
}
}
return $rc;
}

# ===
# Scrambles (encrypts) a text.
# @param text clear text
# @param tag tag of the charset
# @return the scrambled text
#
sub Scramble{
my $text = shift;
my $tagCharset = shift;
$tagCharset = FindCharset($text) unless $tagCharset;
my $charset = GetCharset($tagCharset);
my $seed2 = int(rand 0x7fff0000);
$seed2 = 0x1234;
my $size = length($charset);
my $head = "";
my $seed = 0;
foreach(0..2){
my $seedX = $seed2 % $size;
$seed = $seed * $size + $seedX;
$seed2 = int($seed2 / $size);
$head .= substr($charset, $seedX, 1);
}
$head .= $tagCharset;
my $rc = "";
my $msg = sprintf("seed: %d", $seed);
my $count = 0;
while($count < length($text)){
$seed = ($seed * 7 + 0x1234321) & 0x8fffffff;
my $delta = 1 + $seed % ($size - 1);
my $cc = substr($text, $count, 1);
my $ix = index($charset, $cc);
die sprintf("scrambleText: unknown char %s allowed: %s", $cc, $charset)
if $ix < 0;
$ix = ($ix + $delta) % $size;
$rc .= substr($charset, $ix, 1);
# $msg .= sprintf("\ncc: %s seed: %d ix: %d delta: %d val: %s", $cc,
# $seed, $ix, $delta, substr($charset, $ix, 1));
$count++;
}
return $head . $rc
}

# ===
# Decodes a text encrypted with Scramble.
# @param text encrypted text
# @return clear text
sub UnScramble{
my $text = shift;
my $tag = substr($text, 3, 1);
my $charset = GetCharset($tag);
my $size = length($charset);
my $seed = 0;
my $cc;
foreach(0..2){
$cc = substr($text, $_, 1);
$seed = $seed * $size + index($charset, $cc);
}
my $pos = 4;
my $rc = "";
my ($delta, $ix);
while($pos < length($text)){
$seed = ($seed * 7 + 0x1234321) & 0x8fffffff;
$cc = substr($text, $pos, 1) . "";
$delta = 1 + $seed % ($size - 1);
$ix = index($charset, $cc) - $delta;
$ix += $size if $ix < 0;
$rc .= substr($charset, $ix, 1);
$pos++;
}
return $rc;
}
return 1;

+ 329
- 0
backend/sidu_recorder.pm View File

@@ -0,0 +1,329 @@
package recorder;

=head1 NAME
recorder -- implements a recorder for regression tests
and the infrastructure to replay this recording

=head1 Summary
Allows the recording of a real session. This recording can be
replayed in a regression test.

=head1 Author
hamatoma (C) 2013

=cut

use strict;

my $MODE_NONE = 0;
my $MODE_RECORDING = 1;
my $MODE_REPLAYING = 2;

# 0: no recording/replaying
# 1: recording
# 2: replaying
my $s_mode;
my $s_recorderFile = "/tmp/autopart.recorder.txt";
# "ReadMsdosDisk-1:/sbin/fdisk -l /dev/sdb|" => <ref_of_line_array>
my %s_recorderStorage;
# collects the content of writeStream():
my @s_outputStreamLines;
my $s_currentFileNo;
# e.g. "getFdiskInfo" => 2
my %s_currNoIds;
my $s_application = "recorder";

# ===
# Initializes the session.
# @param application a prefix for temporary files
# @param mode 0: no recording/replaying 1: recording 2: replaying
# @param file recorder storage file
sub Init{
$s_application = shift;
$s_mode = shift;
$s_recorderFile = shift;
$s_recorderFile = "/tmp/autopart.recorder.txt" unless $s_recorderFile;
if ($s_mode == $MODE_REPLAYING){
&ReadRecordedInfo($s_recorderFile)
}
}

# ===
# Stores the program arguments into the recorder file.
# @param @args all names and values of the variables
sub StoreArgs{
if ($s_mode == $MODE_RECORDING){
# find the values:
my $val;
my @defs;
push(@defs, "###recorder progArgs:");
my ($name, $value);
foreach(@_){
if ($name){
push (@defs, "\$$name=\"$_\";");
$name = "";
} else {
$name = $_;
}
}
&recorder::WriteFile(join("\n", @defs) . "\n", "", $s_recorderFile);
}
}

# ===
# Writes a given content to a temporary file.
# @param content content to write
# @param suffix suffix of the generated filename
# @param name "" or name of the file
# @param append if true the content will be appended
# @param dir "" or the target directory
# @return filename
sub WriteFile{
my $content = shift;
my $suffix = shift;
my $name = shift;
my $append = shift;
my $dir = shift;
my $fn = $name;
if ($fn eq ""){
$dir = "/tmp" unless $dir;
$fn = "$dir/tmp.$s_application." . ++$s_currentFileNo . "$suffix";
}
my $mode = $append ? ">>" : ">";
open my $OUT, $mode, $fn || die "$fn: $!";
print $OUT $content if $content;
close $OUT;
return $fn;
}

# ===
# Do the last things for the recorder.
#
# @param varargs <name1> <content1> <name2> <content2>...
# <contentX> is a string or a reference of an array of lines
sub Finish{
my $name;
foreach(@_){
if ($name eq ""){
$name = $_;
} else {
Put($name, $_);
$name = "";
}
}
}

# ===
# Stores the content of one stream
# @param header identifies the block
# @param refBlock reference of the line array
sub StoreBlock{
my $header = shift;
my $refBlock = shift;
my $content;
my @lines = @$refBlock;
if($header =~ /readStream id: (\w+): no: (\d+) device: (.+)/){
my ($id, $no, $dev) = ($1, $2, $3);
$s_recorderStorage{"$id-$no:$dev"} = \@lines;
} elsif ($header =~ /recorder (\w+):/){
$s_recorderStorage{$1} = \@lines;
} elsif ($header =~ /FileExists id: (\w+): mode: (\S+) no: (\d+) file: (\S+) rc: (.)/){
# FileExists id: $id: mode: $mode no: $callNo file: $file rc:
my ($id, $mode, $callNo, $file, $val) = ($1, $2, $3, $4, $5);
$s_recorderStorage{"$id-$callNo$mode:$file"} = $val;
} else {
die "unknown header: $header";
}
}

# ===
# Reads the file created by the recorder.
# @param file the file's name
sub ReadRecordedInfo{
my $file = shift;
my @lines;
my $lastHeader;
open my $INP, "<", $file || die "$file: $!";
while(<$INP>){
if (/^###(readStream|FileExists|recorder \w+:)/){
StoreBlock($lastHeader, \@lines) if $lastHeader;
$lastHeader = $_;
@lines = ();
} else{
push(@lines, $_);
}
}
StoreBlock($lastHeader, \@lines);
close $INP;
}

# ===
# Puts an entry of the recorder storage.
# @param name name of the entry
# @param content a string or a reference of an array of lines
sub Put{
my $name = shift;
my $content = shift;
if (ref($content) eq "ARRAY"){
my $last = substr($$content[0], -1);
my $sep = $last eq "\n" ? "" : "\n";
$content = join($sep, @$content) . $sep;
}
&WriteFile("###recorder $name:\n$content", "", $s_recorderFile, 1);
}


# ===
# Writes to a stream.
# A stream can be a file or the input of an external command.
# For tests this can be a file.
# @param id identifies the caller
# @param device a filename or a external command, e.g. "|fdisk /dev/sdb"
# @param content this content will be written
sub WriteStream{
my $id = shift;
my $device = shift;
my $content = shift;

if ($s_mode == $MODE_RECORDING){
my $header = "### writeStream id: $id device: $device\n";
&WriteFile($header . $content, "", $s_recorderFile, 1);
}
if ($s_mode != $MODE_REPLAYING){
open my $OUT, "<", $device;
print $OUT $content;
close $OUT;
} else {
push(@s_outputStreamLines, "== id: $id device: $device");
push(@s_outputStreamLines, $content);
}
}


# ===
# Tests the existence of a file.
# @param id id of the caller
# @param file file to test
# @param mode "-e", "-d", "-f" ...
# @return 0: does not exist. 1: exists
sub FileExists{
my $id = shift;
my $mode = shift;
my $file = shift;
my $callNo = ++$s_currNoIds{$id};
my $rc;
if ($s_mode == $MODE_REPLAYING){
my $key = "$id-$callNo$mode:$file";
$rc = $s_recorderStorage{$key};
if ($rc eq ""){
die "missing entry: $key";
} else {
$rc = $rc ne "F";
}
} else {
if ($mode eq "-e"){
$rc = -e $file;
} elsif ($mode eq "-d"){
$rc = -d $file;
} elsif ($mode eq "-l"){
$rc = -l $file;
} elsif ($mode eq "-f"){
$rc = -f $file;
} else {
die "$id: unknown mode: $mode: (file: $file)";
}
if ($s_mode == $MODE_RECORDING){
my $content = "###FileExists id: $id: mode: $mode no: $callNo file: $file rc: "
. ($rc ? "T" : "F") . "\n";
&WriteFile($content, "", $s_recorderFile, 1);
}
}
return $rc;
}

# ===
# Gets an entry of the recorder storage.
# @param name name of the entry
# @return: an array of lines
sub Get{
my $name = shift;
my $refArray = $s_recorderStorage{$name};
my @rc;
if ($refArray){
@rc = @$refArray;
}
return @rc;
}

# ===
# Reads a stream into an array of lines.
# A stream can be a file or the output of an extern command.
# For tests this can be a file.
# @param id defines the stream to open
# @param device a filename or a external command, e.g. "partprobe -s |"
sub ReadStream{
my $id = shift;
my $device = shift;
my $content = "<!None>";
my @rc;
my $callNo = ++$s_currNoIds{$id};
if ($s_mode != $MODE_REPLAYING){
#
# call of an external program with input and output:
if (-d $device){
opendir(my $DIR, $device);
@rc = readdir($DIR);
close $DIR;
} elsif ($device =~ /^</){
my $file = WriteFile("", ".exc");
my $cmd = substr($device, 1) . " >$file";
system($cmd);
open my $INP, "<", $file;
@rc = <$INP>;
close $INP;
unlink $file;
} elsif ($device =~ /[<]/){
system($device);
if ($device =~ /[>]\s*(\S+)/){
my $file = $1;
open my $INP, "<", $file;
@rc = <$INP>;
close $INP;
} else {
die "no output file found: $device";
}
} elsif (open my $INP, $device){
@rc = <$INP>;
close $INP;
} else {
print "+++ $device: $!";
}
} elsif (scalar keys %s_recorderStorage > 0){
my $key = "$id-$callNo:$device";
my $refArray = $s_recorderStorage{$key};
if ($refArray){
@rc = @$refArray;
} else {
my $msg = "+++ stream content not found: $key\nStored blocks:\n";
foreach(keys %s_recorderStorage){
$msg .= "$_\n" if /^$id/;
}
die $msg;
}
} else {