1#!/usr/bin/env python3

2# Copyright 2015-2020 Canonical Ltd.

3# Written by:

4# Shawn Wang <shawn.wang@canonical.com>

5# Jonathan Cave <jonathan.cave@canonical.com>

6#

7# This program is free software: you can redistribute it and/or modify

8# it under the terms of the GNU General Public License version 3,

9# as published by the Free Software Foundation.

10#

11# This program is distributed in the hope that it will be useful,

12# but WITHOUT ANY WARRANTY; without even the implied warranty of

13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

14# GNU General Public License for more details.

15#

16# You should have received a copy of the GNU General Public License

17# along with this program. If not, see <http://www.gnu.org/licenses/>.

18

19"""Show the recovery partition information for the preinstalled OS."""

20

21 import os

22 import re

23 import subprocess

24 import sys

25 import tempfile

26

27 import xml.dom.minidom as minidom

28

29

30 RECOVERY_PACKAGES = ["dell-recovery", "ubuntu-recovery"]

31

32

33 def get_recovery_package():

34 """

35 Test with RECOVERY_PACKAGES.

36

37 to check recovery application is installed or not

38

39 :return:

40 string of package_version or None

41 """

42 for pkg in RECOVERY_PACKAGES: 42 ↛ 50 line 42 didn't jump to line 50 because the loop on line 42 didn't complete

43 output = subprocess.check_output(

44 ["apt-cache", "policy", pkg], universal_newlines=True

45 )

46 for line in output.split("\n"): 46 ↛ 42 line 46 didn't jump to line 42 because the loop on line 46 didn't complete

47 if line.startswith(" Installed:"):

48 ver = line.split(": ")[1]

49 return "{}_{}".format(pkg, ver.strip())

50 return None

51

52

53 RECOVERY_LABELS = {

54 "HP_TOOLS": "HP",

55 "PQSERVICE": "UBUNTU",

56 "BACKUP": "TEST",

57 "INSTALL": "DELL",

58 "OS": "DELL",

59 "RECOVERY": "DELL",

60}

61

62

63_escape_pattern = re.compile(r"\\x([0-9a-fA-F][0-9a-fA-F])")

64

65

66 def lsblk_unescape(label):

67 """Un-escape text escaping done by lsblk(8)."""

68 return _escape_pattern.sub(

69 lambda match: chr(int(match.group(1), 16)), label

70 )

71

72

73 def get_recovery_partition():

74 """

75 Get the type and location of the recovery partition.

76

77 :return:

78 (recovery_type, recovery_partition) or None

79

80 Use lsblk(8) to inspect available block devices looking

81 for a partition with FAT or NTFS and a well-known label.

82 """

83 cmd = ["lsblk", "-o", "TYPE,FSTYPE,NAME,LABEL", "--raw"]

84 for line in subprocess.check_output(cmd).splitlines()[1:]: 84 ↛ exit line 84 didn't return from function 'get_recovery_partition' because the loop on line 84 didn't complete

85 type, fstype, name, label = line.split(b" ", 3)

86 # Skip everything but partitions

87 if type != b"part":

88 continue

89 # Skip everything but FAT and NTFS

90 if fstype != b"vfat" and fstype != b"ntfs":

91 continue

92 label = lsblk_unescape(label.decode("utf-8"))

93 recovery_type = RECOVERY_LABELS.get(label)

94 # Skip unknown labels

95 if recovery_type is None:

96 continue

97 recovery_partition = "/dev/{}".format(name.decode("utf-8"))

98 return (recovery_type, recovery_partition)

99

100

101 class MountedPartition(object):

102 """

103 Mount Manager to mount partition on tempdir.

104

105 e.g.

106 with MountedPartition("/dev/sda1") as tmp:

107 print("This is the mount point: {}".format(tmp))

108 do_stuff()

109 """

110

111 def __init__(self, part):

112 """

113 Prepare the mntdir point.

114

115 :param part: string of the partition device file, like /dev/sda2

116 """

117 self.part = part

118 self.mntdir = tempfile.mkdtemp()

119

120 def __enter__(self):

121 """

122 __enter__ method for python's with statement.

123

124 Mount the partition device to the mntdir.

125 """

126 cmd = ["mount", self.part, self.mntdir]

127 subprocess.check_output(cmd, universal_newlines=True)

128 return self.mntdir

129

130 def __exit__(self, type, value, traceback):

131 """

132 __exit__ method for python's with statement.

133

134 Unmount and remove the mntdir.

135 """

136 subprocess.check_output(

137 ["umount", self.mntdir], universal_newlines=True

138 )

139 os.rmdir(self.mntdir)

140

141

142 class RecoveryInfo:

143 """

144 Inspect the recovery partition.

145

146 This command can be used to inspect the recovery partition. It has several

147 sub-commands that do various tasks. If the system has no recovery

148 partition, the command exits with the error code 1.

149 """

150

151 def main(self):

152 partition = get_recovery_partition()

153 if len(sys.argv) == 1:

154 # no subcommand == detect test

155 if partition is None: 155 ↛ 156 line 155 didn't jump to line 156 because the condition on line 155 was never true

156 raise SystemExit("FAIL: Recovery partition not found")

157 else:

158 print("Found recovery partition")

159 return

160

161 (recovery_type, recovery_partition) = partition

162

163 subcommand = sys.argv[1]

164 sub_commands = ("version", "file", "checktype")

165 if subcommand not in sub_commands: 165 ↛ 166 line 165 didn't jump to line 166 because the condition on line 165 was never true

166 raise SystemExit("ERROR: unexpected subcommand")

167

168 if subcommand == "checktype": 168 ↛ 181 line 168 didn't jump to line 181 because the condition on line 168 was always true

169 if len(sys.argv) != 3: 169 ↛ 170 line 169 didn't jump to line 170 because the condition on line 169 was never true

170 raise SystemExit(

171 "ERROR: recovery_info.py checktype EXPECTED_TYPE"

172 )

173 expected_type = sys.argv[2]

174 if recovery_type != expected_type:

175 raise SystemExit(

176 "FAIL: expected {}, found {}".format(

177 expected_type, recovery_type

178 )

179 )

180

181 if subcommand == "file": 181 ↛ 182 line 181 didn't jump to line 182 because the condition on line 181 was never true

182 if len(sys.argv) != 3:

183 raise SystemExit("ERROR: recovery_info.py file FILE")

184 file = sys.argv[2]

185 with MountedPartition(recovery_partition) as mnt:

186 return subprocess.call(["cat", "--", os.path.join(mnt, file)])

187

188 if subcommand == "version": 188 ↛ 189 line 188 didn't jump to line 189 because the condition on line 188 was never true

189 if os.path.isfile("/etc/buildstamp"):

190 with open("/etc/buildstamp", "rt", encoding="UTF-8") as stream:

191 data = stream.readlines()

192 print("image_version: {}".format(data[1].strip()))

193

194 with MountedPartition(recovery_partition) as mntdir:

195 fname = "{}/bto.xml".format(mntdir)

196 if os.path.isfile(fname):

197 o = minidom.parse("{}/bto.xml".format(mntdir))

198 bto_platform = o.getElementsByTagName("platform")

199 bto_revision = o.getElementsByTagName("revision")

200 if bto_platform and bto_revision:

201 bto_platform = bto_platform[0].firstChild.data

202 bto_revision = bto_revision[0].firstChild.data

203 bto_version = bto_platform + " " + bto_revision

204 else:

205 bto_iso = o.getElementsByTagName("iso")

206 bto_version = bto_iso[0].firstChild.data

207 print("bto_version: {}".format(bto_version))

208

209

210 if __name__ == "__main__":

211 RecoveryInfo().main()