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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
|
# First Aid Kit - diagnostic and repair tool for Linux
# Copyright (C) 2008 Joel Andres Granados <jgranado@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
from pyfirstaidkit.plugins import Plugin,Flow
from pyfirstaidkit.returns import *
from pyfirstaidkit.utils import *
from pyfirstaidkit.reporting import PLUGIN
from pyfirstaidkit.issue import SimpleIssue
from pyfirstaidkit import Config
from pyfirstaidkit.errors import *
from grubUtils import Dname
import grubUtils
import os
class Grub(Plugin):
"""Plugin to detect and fix grub failure."""
flows = Flow.init(Plugin)
name = "Grub"
version = "0.0.1"
author = "Joel Andres Granados"
description = "Plugin to recover lost grub bootloader functionality"
@classmethod
def getDeps(cls):
return set(["root", "filesystem"])
@classmethod
def revert(cls, backup, report):
""" Use the backup object to replace the first 446 bytes in all devs.
"""
report.info("Entering revert...", origin = Grub)
try:
firstblockdict = backup.restoreValue("firstblockdict")
except:
report.debug("I found the grub backup dir empty", origin = Grub)
return
if len(firstblockdict) == 0:
report.info("I found a backup object but it had no elements to " \
"backup, exiting...", origin = Grub)
return
for (dev, val) in firstblockdict.iteritems():
devpath = val[1].path()
first446bytes = val[0]
report.info("Reverting changes for device %s." % devpath, \
origin = Grub)
try:
fd = os.open(devpath, os.O_WRONLY)
os.write(fd, first446bytes)
os.close(fd)
except IOError, ie:
report.debug("There was an error writing to %s, It is very " \
"probable that this device is in an invalid state." \
"Error: %s" % (devpath, ie), origin = Grub)
except Exception, e:
report.debug("There was an unexpected error: %s" % e, \
origin = Grub)
report.info("Successfully reverted changes to device %s." % devpath, \
origin = Grub)
def __init__(self, *args, **kwargs):
Plugin.__init__(self, *args, **kwargs)
# The key is a dev name, the value is a list of partitions in drive.
self.devices = {}
# The partitions where the grub directory and all the necessary files
# found.
self.grub_dir_parts = []
# The partitions where grub binary will be installed (first 446 bytes).
self.install_grub_parts = []
# The devs where grub binary will be installed (first 446 bytes).
self.install_grub_devs = []
# Initialize our list of related issues.
self.issue_grub_dir = SimpleIssue(self.name+"_DIR", "Missing grub " \
"dir or dir files.")
self.issue_grub_image = SimpleIssue(self.name+"_IMAGE", "Bad grub " \
"stage1 image.")
# Initialize the backup space.
self.backupSpace = self._backups.getBackup( \
self.__module__.split('.')[-1:][0], persistent = True)
# Parce the parameters passed to the plugin.
self.args = grubUtils.get_grub_opts(self._args)
# Place where the grub files, kernel and initrd files are.
self.grubroot = None
def prepare(self):
self._reporting.info("Initializing the search for all the grub " \
"related elements.", origin = self)
try:
# We end up with a structure where the keys are the drive names.
self.devices = grubUtils.get_all_devs()
# We must search in all the possible partitions for the grub files:
# stage1, stage1.5, stage2.... When we find these directories we
# will be able to install grub from there. And the vmliuz (kernel
# image), initrd.img, the grub.conf is probably in these places as
# well.
self._reporting.info("Searching for grub related files in the " \
"system storage devices.", origin = self)
for (dev, parts) in self.devices.iteritems():
for part in parts:
try:
if grubUtils.grub_dir_in_partition(part):
self._reporting.info("Found grub dir in %s " \
"partition." % part.path(), origin = self)
self.grub_dir_parts.append(part)
except Exception, e:
# If something happened while checking the partition
# it will not be taken into account.
self._reporting.error("There was an error while " \
"searching partition %s. Error: %s." % \
(part.path(), e) ,origin = self)
# Out of all the dirs we found we select one.
# FIXME: extend the grub root concept so it can handle varous roots in
# a system. For now we just select the first and hope it has
# everything.
self.grubroot = grubUtils.find_grub_root(self.grub_dir_parts)
# We decide to which devices the grub image will be installed.
# 1. If no arguments were passed, then no installation is done.
# 2. If the list of devices was passes we use that.
# 3. if the install_auto arg was passes we install were we dont
# break anything.
# 4. If the install_all is passed we install in every device and
# partition.
#
self._reporting.info("Searching for locations in which to " \
"install grub.", origin = self)
if len(self.args.installto_devs)+len(self.args.installto_parts) > 0:
# We install to the selected devices. Since grub-install
# will screat in case the device names are not valid, I think
# its not necesary to check here.
for dev in self.args.installto_devs:
self.install_grub_devs.append(Dname(dev))
for part in self.args.installto_parts:
self.install_grub_parts.append(Dname(part))
elif self.args.install_all:
# We install to all the devices
for (dev, parts) in self.devices.iteritems():
self.install_grub_devs.append(Dname(dev))
for part in parts:
self.install_grub_parts.append(Dname(part))
elif self.args.install_auto:
# Skip devices with other bootloader (default).
for (dev, parts) in self.devices.iteritems():
# We see if we can install grub in device.
# FIXME: Create exception for failed bootloader search.
if not grubUtils.other_bootloader_present(Dname(dev)):
self._reporting.info("Found no other bootloader in " \
"%s device." % Dname.asPath(dev), \
origin = self)
self.install_grub_devs.append(Dname(dev))
# Now we see if we can install in the partitions.
for part in parts:
if not grubUtils.other_bootloader_present(Dname(part)):
self._reporting.info("Found no other bootloader " \
"in %s partition." % Dname.asPath(part), \
origin = self)
self.install_grub_parts.append(Dname(part))
else:
# If not arguments where specified the right thing to do is to
# leave everything alone:)
self.install_grub_parts = []
self.install_grub_devs = []
self._reporting.info("Grub wont modify any drive. " \
"If you want grub to take action you must specify: " \
"--installto-devs, --installto-parts, " \
"--install-all or --install-auto.", \
origin = self)
self._result = ReturnSuccess
except Exception, e:
self._reporting.error("An error has ocurred while searching for " \
"grubs elements. Error: %s" % e, origin = self)
self._result = ReturnFailure
def diagnose(self):
# FIXME ATM we will not take care of the cases that are missing some
# stuff from the grub directory, like images and conffile.
#
# The diagnose is tricky because we have no way of knowing (yet) if
# the images found in the devices actually map correctly to the stuff
# that we found on the directories. This means that we will allways
# reinstall grub in the mbr (the changes will be revertable so its
# not that bad) unless we don't find any grub dir. If no dir is
# found, then we fail (fail in this case means postponing the decision
# until the fix step to actually ReturnFailure)
self._reporting.info("Diagnosing the current state of grub.",
origin = self)
if len(self.grub_dir_parts) < 0:
self._reporting.error("No grub directories where found.",
origin = self)
self.issue_grub_dir.set(checked = True, happened = True,
reporting = self._reporting, origin = self)
self.issue_grub_image.set(checked = False,
reporting = self._reporting, origin = self)
self._result = ReturnFailure
return
self.issue_grub_dir.set(checked = True, happened = False,
reporting = self._reporting, origin = self)
if len(self.install_grub_devs) + len(self.install_grub_parts) == 0:
# Since we dont check the validity of the images in the mbr we
# consider all the drives to be in a faulty state
# FIXME: this defenetly has to change
self.issue_grub_image.set(checked = True, happened = True, \
reporting = self._reporting, origin = self)
else:
self.issue_grub_image.set(checked = False, happened = False, \
reporting = self._reporting, origin = self)
self._result = ReturnFailure
def backup(self):
# The grub process is related to the 446 firsta bytes of the partitions
# of the device. This means we must backup all the beginings fo the
# partitions and devices that we will possibly modify.
# Since we are going to install the stage1 grub image in all the
# devices in self.install_grub_devs, we will backup all of them.
# FIXME: We have to modify the plugin to consider partitions.
firstblockdict = {}
if len(self.install_grub_devs) > 0:
self._reporting.info("Going to backup all the first 446 bytes of " \
"%s." % self.install_grub_devs, origin = self)
for device in self.install_grub_devs:
fd = os.open(device.path(), os.O_RDONLY)
first446btemp = os.read(fd, 446)
os.close(fd)
firstblockdict[device.name()] = [first446btemp, device]
if len(self.install_grub_parts) > 0:
self._reporting.info("Going to backup all the first 446 bytes of " \
"%s." % self.install_grub_parts, origin = self)
for part in self.install_grub_parts:
fd = os.open(part.path(), os.O_RDONLY)
first446btemp = os.read(fd, 446)
os.close(fd)
firstblockdict[part.name()] = [first446btemp, part]
self.backupSpace.backupValue( firstblockdict, "firstblockdict")
self._result = ReturnSuccess
def fix(self):
# We are to fail if there are no dirs.
if len(self.grub_dir_parts) == 0:
self._reporting.error("No grub directories where found... exiting.",
origin = self)
self.issue_grub_dir.set(fixed = False, \
reporting = self._reporting, origin = self)
self._result = ReturnFailure
return
# We are to fail if there are no devs or parts to install to.
if len(self.install_grub_devs) + len(self.install_grub_parts) == 0:
self._reporting.error("No devices or partitions to install to.",
origin = self)
self.issue_grub_image.set(fixed = False, \
reporting = self._reporting, origin = self)
self._result = ReturnFailure
return
# Install the grub in all devs pointing at the special root part.
for drive in self.install_grub_devs:
self._reporting.info("Trying to install grub on drive %s, " \
"pointing to grub directory in %s."%(drive.path(), \
self.grubroot.path()), origin = self)
try:
grubUtils.install_grub(self.grubroot, drive)
except Exception, e:
self._reporting.error("Grub installation on drive %s, " \
"pointing to grub directory in %s has failed." % \
(drive.path(), self.grubroot.path()), \
origin = self)
# If one installation fails its safer to just restore
# everything
self._result = ReturnFailure
return
for part in self.install_grub_parts:
self._reporting.info("Trying to install grub on part %s, " \
"pointing to grub directory in %s." % (part.path(), \
self.grubroot.path()), origin = self)
try:
grubUtils.install_grub(self.grubroot, part)
except:
self._reporting.error("Grub installation on part %s, " \
"pointing to grub directory in %s has failed." % \
(part.path(), self.grubroot.path()), \
origin = self)
self._result = ReturnFailure
return
self.issue_grub_image.set(fixed = True, reporting = self._reporting, \
origin = self)
self._reporting.info("Grub has successfully installed in all the " \
"chosen devices.", origin = self)
self._result = ReturnSuccess
def restore(self):
firstblockdict = self.backupSpace.restoreValue("firstblockdict")
for (dev, val) in firstblockdict.iteritems():
devpath = val[1].path()
first446bytes = val[0]
self._reporting.info("Restoring changes for device %s." % devpath, \
origin = self)
try:
fd = os.open(devpath, os.O_WRONLY)
os.write(fd, first446bytes)
os.close(fd)
except IOError, ie:
self._reporting.debug("There was an error writing to %s, It " \
" is very probable that this device is in an invalid " \
"state. Error: %s" % (devpath, ie), origin = self)
continue
except Exception, e:
self._reporting.debug("There was an unexpected error: %s" % e, \
origin = self)
continue
self._reporting.info("Successfully restored changes to device " \
"%s." % devpath, \
origin = self)
self._result = ReturnSuccess
def clean(self):
self._result = ReturnSuccess
|