* 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 partitionsnext
@@ -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 | |||
@@ -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; | |||
} | |||
@@ -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 |
@@ -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){ | |||
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 |
@@ -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 |
@@ -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; |
@@ -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 { | |||