-
Notifications
You must be signed in to change notification settings - Fork 22
/
boot_record_backup.rb
123 lines (103 loc) · 3.57 KB
/
boot_record_backup.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
require "yast"
require "date"
require "shellwords"
module Bootloader
# Responsibility of class is to manage backup of MBR, respective PBR of disk,
# respective partition.
class BootRecordBackup
include Yast::Logger
BASH_PATH = Yast::Path.new(".target.bash")
BASH_OUTPUT_PATH = Yast::Path.new(".target.bash_output")
TARGET_SIZE = Yast::Path.new(".target.size")
MAIN_BACKUP_DIR = "/var/lib/YaST2/backup_boot_sectors/".freeze
KEPT_BACKUPS = 10
attr_reader :device
# Exception from this class
class Error < RuntimeError; end
# Exception used to indicate that backup missing, so any action with it is
# not possible
class Missing < Error
def initialize
super "Backup for boot record missing."
end
end
# Create backup handling class for given device
# @param device[String] expect kernel name of device like "/dev/sda"
def initialize(device)
@device = device
end
# Write fresh backup of MBR or PBR of given device.
# Backup is stored in /var/lib/YaST2/backup_boot_sectors, in logs
# directory and if it is MBR of primary disk, then also in /boot/backup_mbr
def write
Yast::SCR.Execute(BASH_PATH, "/usr/bin/mkdir -p #{MAIN_BACKUP_DIR.shellescape}")
if exists?
rotate
reduce_backup_count
end
copy_br(device, device_file_path)
# save MBR to yast2 log directory
logs_path = "/var/log/YaST2/" + device_file
copy_br(device, logs_path)
end
# Restore backup
# @raise [::Bootloader::BootRecordBackup::Missing] if backup missing
# @return true if copy is successful
def restore
raise Missing unless exists?
# Copy only 440 bytes for Vista booting problem bnc #396444
# and also to not destroy partition table
copy_br(device_file_path, device, bs: 440) == 0
end
private
def device_file
@device_file ||= @device.tr("/", "_")
end
def device_file_path
@device_file_path ||= MAIN_BACKUP_DIR + device_file
end
def exists?
Yast::SCR.Read(TARGET_SIZE, device_file_path) > 0
end
# Get last change time of file
# @param [String] filename string name of file
# @return [String] last change date as YYYY-MM-DD-HH-MM-SS
def formated_file_ctime(filename)
stat = Yast::SCR.Read(Yast::Path.new(".target.stat"), filename)
ctime = stat["ctime"] or raise(Error, "Cannot get modification time of file #{filename}")
time = DateTime.strptime(ctime.to_s, "%s")
time.strftime("%Y-%m-%d-%H-%M-%S")
end
def copy_br(device, target_path, bs: 512)
Yast::SCR.Execute(
BASH_PATH,
"/bin/dd if=#{device.shellescape} of=#{target_path.shellescape} " \
"bs=#{bs.to_s.shellescape} count=1 2>&1"
)
end
def reduce_backup_count
files = Yast::SCR.Read(Yast::Path.new(".target.dir"), MAIN_BACKUP_DIR)
# clean only backups for this device
files.select! do |c|
c =~ /#{Regexp.escape(device_file)}-\d{4}(?:-\d{2}){5}/
end
# and sort so we can benefit from its ascending order
files.sort!
files.drop(KEPT_BACKUPS).each do |file_name|
Yast::SCR.Execute(
Yast::Path.new(".target.remove"),
MAIN_BACKUP_DIR + file_name
)
end
end
def rotate
# move it so we do not overwrite it
change_date = formated_file_ctime(device_file_path)
Yast::SCR.Execute(
BASH_PATH,
format("/bin/mv %{path} %{path}-%{date}",
path: device_file_path.shellescape, date: change_date.shellescape)
)
end
end
end