swupdate-common.bbclass 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. DEPENDS += "\
  2. cpio-native \
  3. ${@ 'openssl-native' if d.getVar('SWUPDATE_SIGNING') or d.getVar('SWUPDATE_ENCRYPT_SWDESC') or d.getVarFlags('SWUPDATE_IMAGES_ENCRYPTED') else ''} \
  4. "
  5. do_swuimage[umask] = "022"
  6. SSTATETASKS += "do_swuimage"
  7. SSTATE_SKIP_CREATION_task-swuimage = '1'
  8. SWUDEPLOYDIR = "${WORKDIR}/deploy-${PN}-swuimage"
  9. do_swuimage[dirs] = "${SWUDEPLOYDIR}"
  10. do_swuimage[cleandirs] += "${SWUDEPLOYDIR}"
  11. do_swuimage[sstate-inputdirs] = "${SWUDEPLOYDIR}"
  12. do_swuimage[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}"
  13. do_swuimage[stamp-extra-info] = "${MACHINE}"
  14. python () {
  15. deps = " " + swupdate_getdepends(d)
  16. d.appendVarFlag('do_swuimage', 'depends', deps)
  17. d.delVarFlag('do_fetch', 'noexec')
  18. d.delVarFlag('do_unpack', 'noexec')
  19. }
  20. def get_pwd_file_args(d, passfile):
  21. pwd_args = []
  22. pwd_file = d.getVar(passfile, True)
  23. if pwd_file:
  24. pwd_args = ["-passin", "file:%s" % pwd_file]
  25. return pwd_args
  26. def swupdate_getdepends(d):
  27. def adddep(depstr, deps):
  28. for i in (depstr or "").split():
  29. if i not in deps:
  30. deps.append(i)
  31. deps = []
  32. images = (d.getVar('IMAGE_DEPENDS', True) or "").split()
  33. for image in images:
  34. adddep(image , deps)
  35. depstr = ""
  36. for dep in deps:
  37. depstr += " " + dep + ":do_build"
  38. return depstr
  39. def swupdate_get_sha256(d, s, filename):
  40. import hashlib
  41. m = hashlib.sha256()
  42. with open(os.path.join(s, filename), 'rb') as f:
  43. while True:
  44. data = f.read(1024)
  45. if not data:
  46. break
  47. m.update(data)
  48. return m.hexdigest()
  49. def swupdate_extract_keys(keyfile_path):
  50. try:
  51. with open(keyfile_path, 'r') as f:
  52. lines = f.readlines()
  53. except IOError:
  54. bb.fatal("Failed to open file with keys %s" % (keyfile))
  55. data = {}
  56. for _ in lines:
  57. k,v = _.split('=',maxsplit=1)
  58. data[k.rstrip()] = v
  59. key = data['key'].rstrip('\n')
  60. iv = data['iv'].rstrip('\n')
  61. return key,iv
  62. def swupdate_encrypt_file(f, out, key, ivt):
  63. import subprocess
  64. encargs = ["openssl", "enc", "-aes-256-cbc", "-in", f, "-out", out]
  65. encargs += ["-K", key, "-iv", ivt, "-nosalt"]
  66. subprocess.run(encargs, check=True)
  67. def swupdate_write_sha256(s):
  68. import re
  69. write_lines = []
  70. with open(os.path.join(s, "sw-description"), 'r') as f:
  71. for line in f:
  72. shastr = r"sha256.+=.+@(.+\")"
  73. #m = re.match(r"^(?P<before_placeholder>.+)sha256.+=.+(?P<filename>\w+)", line)
  74. m = re.match(r"^(?P<before_placeholder>.+)(sha256|version).+[=:].*(?P<quote>[\'\"])@(?P<filename>.*)(?P=quote)", line)
  75. if m:
  76. filename = m.group('filename')
  77. hash = swupdate_get_sha256(None, s, filename)
  78. write_lines.append(line.replace("@%s" % (filename), hash))
  79. else:
  80. write_lines.append(line)
  81. with open(os.path.join(s, "sw-description"), 'w+') as f:
  82. for line in write_lines:
  83. f.write(line)
  84. def swupdate_create_func_line(s, function, parms):
  85. parmlist = parms.split(',')
  86. cmd = "'" + s + "'"
  87. for parm in parmlist:
  88. if len(cmd):
  89. cmd = cmd + ','
  90. cmd = cmd + "'" + parm + "'"
  91. cmd = function + '(' + cmd + ')'
  92. return cmd
  93. def swupdate_exec_functions(d, s, write_lines):
  94. import re
  95. for index, line in enumerate(write_lines):
  96. m = re.match(r"^(?P<before_placeholder>.+)\$(?P<bitbake_function_name>\w+)\((?P<parms>.+)\)(?P<after_placeholder>.+)$", line)
  97. if m:
  98. bb.warn("Found function")
  99. fun = m.group('bitbake_function_name') + "(d, \"" + s + "\", \"" + m.group('parms') + "\")"
  100. ret = eval(fun)
  101. bb.warn("Fun : %s" % fun)
  102. bb.warn ("%s return %s " % (m.group('bitbake_function_name'), ret))
  103. cmd = swupdate_create_func_line(s, m.group('bitbake_function_name'), m.group('parms') )
  104. bb.warn ("Returned command %s" % cmd)
  105. line = m.group('before_placeholder') + ret + m.group('after_placeholder') + "\n"
  106. #ret = eval(cmd)
  107. bb.warn ("==> Returned command %s : %s" % (cmd, ret))
  108. write_lines[index] = line
  109. def swupdate_expand_bitbake_variables(d, s):
  110. write_lines = []
  111. with open(os.path.join(s, "sw-description"), 'r') as f:
  112. import re
  113. for line in f:
  114. found = False
  115. while True:
  116. m = re.match(r"^(?P<before_placeholder>.+)@@(?P<bitbake_variable_name>\w+)@@(?P<after_placeholder>.+)$", line)
  117. if m:
  118. bitbake_variable_value = d.getVar(m.group('bitbake_variable_name'), True)
  119. if bitbake_variable_value is None:
  120. bitbake_variable_value = ""
  121. bb.warn("BitBake variable %s not set" % (m.group('bitbake_variable_name')))
  122. line = m.group('before_placeholder') + bitbake_variable_value + m.group('after_placeholder')
  123. found = True
  124. continue
  125. else:
  126. m = re.match(r"^(?P<before_placeholder>.+)@@(?P<bitbake_variable_name>.+)\[(?P<flag_var_name>.+)\]@@(?P<after_placeholder>.+)$", line)
  127. if m:
  128. bitbake_variable_value = (d.getVarFlag(m.group('bitbake_variable_name'), m.group('flag_var_name'), True) or "")
  129. if bitbake_variable_value is None:
  130. bitbake_variable_value = ""
  131. line = m.group('before_placeholder') + bitbake_variable_value + m.group('after_placeholder')
  132. continue
  133. if found:
  134. line = line + "\n"
  135. break
  136. write_lines.append(line)
  137. swupdate_exec_functions(d, s, write_lines)
  138. with open(os.path.join(s, "sw-description"), 'w+') as f:
  139. for line in write_lines:
  140. f.write(line)
  141. # Get all the variables referred by the sw-description at parse time.
  142. def swupdate_find_bitbake_variables(d):
  143. import re
  144. vardeps = []
  145. filespath = d.getVar('FILESPATH')
  146. sw_desc_path = bb.utils.which(filespath, "sw-description")
  147. try:
  148. with open(sw_desc_path, "r") as f:
  149. for line in f:
  150. found = False
  151. while True:
  152. m = re.match(r"^(?P<before_placeholder>.+)@@(?P<bitbake_variable_name>\w+)@@(?P<after_placeholder>.+)$", line)
  153. if m:
  154. bitbake_variable_value = m.group('bitbake_variable_name')
  155. vardeps.append(bitbake_variable_value)
  156. line = m.group('before_placeholder') + bitbake_variable_value + m.group('after_placeholder')
  157. found = True
  158. continue
  159. else:
  160. m = re.match(r"^(?P<before_placeholder>.+)@@(?P<bitbake_variable_name>.+)\[(?P<flag_var_name>.+)\]@@(?P<after_placeholder>.+)$", line)
  161. if m:
  162. bitbake_variable_value = m.group('bitbake_variable_name')
  163. vardeps.append(bitbake_variable_value)
  164. flag_name = m.group('flag_var_name')
  165. vardeps.append(flag_name)
  166. line = m.group('before_placeholder') + bitbake_variable_value + m.group('after_placeholder')
  167. continue
  168. break
  169. except IOError:
  170. pass
  171. return ' '.join(set(vardeps))
  172. def swupdate_expand_auto_versions(d, s):
  173. import re
  174. import oe.packagedata
  175. AUTO_VERSION_TAG = "@SWU_AUTO_VERSION"
  176. AUTOVERSION_REGEXP = "version\s*=\s*\"%s" % AUTO_VERSION_TAG
  177. with open(os.path.join(s, "sw-description"), 'r') as f:
  178. data = f.read()
  179. def get_package_name(group, file_list):
  180. package = None
  181. m = re.search(r"%s:(?P<package>.+?(?=[\"@]))" % (AUTOVERSION_REGEXP), group)
  182. if m:
  183. package = m.group('package')
  184. return (package, True)
  185. for filename in file_list:
  186. if filename in group:
  187. package = filename
  188. if not package:
  189. bb.fatal("Failed to find file in group %s" % (group))
  190. return (package, False)
  191. def get_packagedata_key(group):
  192. m = re.search(r"%s.+?(?<=@)(?P<key>.+?(?=\"))" % (AUTOVERSION_REGEXP), group)
  193. if m:
  194. return (m.group('key'), True)
  195. return ("PV", False)
  196. regexp = re.compile(r"\{[^\{]*%s.[^\}]*\}" % (AUTOVERSION_REGEXP))
  197. while True:
  198. m = regexp.search(data)
  199. if not m:
  200. break
  201. group = data[m.start():m.end()]
  202. (package, pkg_name_defined) = get_package_name(group, (d.getVar('SWUPDATE_IMAGES', True) or "").split())
  203. pkg_info = os.path.join(d.getVar('PKGDATA_DIR'), 'runtime-reverse', package)
  204. pkgdata = oe.packagedata.read_pkgdatafile(pkg_info)
  205. (key, key_defined) = get_packagedata_key(group)
  206. if not key in pkgdata.keys():
  207. bb.warn("\"%s\" not set for package %s - using \"1.0\"" % (key, package))
  208. version = "1.0"
  209. else:
  210. version = pkgdata[key].split('+')[0]
  211. replace_str = AUTO_VERSION_TAG
  212. if pkg_name_defined:
  213. replace_str = replace_str + ":" + package
  214. if key_defined:
  215. replace_str = replace_str + "@" + key
  216. group = group.replace(replace_str, version)
  217. data = data[:m.start()] + group + data[m.end():]
  218. with open(os.path.join(s, "sw-description"), 'w+') as f:
  219. f.write(data)
  220. def prepare_sw_description(d):
  221. import shutil
  222. import subprocess
  223. s = d.getVar('S', True)
  224. swupdate_expand_bitbake_variables(d, s)
  225. swupdate_expand_auto_versions(d, s)
  226. swupdate_write_sha256(s)
  227. encrypt = d.getVar('SWUPDATE_ENCRYPT_SWDESC', True)
  228. if encrypt:
  229. bb.note("Encryption of sw-description")
  230. shutil.copyfile(os.path.join(s, 'sw-description'), os.path.join(s, 'sw-description.plain'))
  231. key,iv = swupdate_extract_keys(d.getVar('SWUPDATE_AES_FILE', True))
  232. swupdate_encrypt_file(os.path.join(s, 'sw-description.plain'), os.path.join(s, 'sw-description'), key, iv)
  233. signing = d.getVar('SWUPDATE_SIGNING', True)
  234. if signing == "1":
  235. bb.warn('SWUPDATE_SIGNING = "1" is deprecated, falling back to "RSA". It is advised to set it to "RSA" if using RSA signing.')
  236. signing = "RSA"
  237. if signing:
  238. sw_desc_sig = os.path.join(s, 'sw-description.sig')
  239. sw_desc = os.path.join(s, 'sw-description.plain' if encrypt else 'sw-description')
  240. if signing == "CUSTOM":
  241. signcmd = []
  242. sign_tool = d.getVar('SWUPDATE_SIGN_TOOL', True)
  243. signtool = sign_tool.split()
  244. for i in range(len(signtool)):
  245. signcmd.append(signtool[i])
  246. if not signcmd:
  247. bb.fatal("Custom SWUPDATE_SIGN_TOOL is not given")
  248. elif signing == "RSA":
  249. privkey = d.getVar('SWUPDATE_PRIVATE_KEY', True)
  250. if not privkey:
  251. bb.fatal("SWUPDATE_PRIVATE_KEY isn't set")
  252. if not os.path.exists(privkey):
  253. bb.fatal("SWUPDATE_PRIVATE_KEY %s doesn't exist" % (privkey))
  254. signcmd = ["openssl", "dgst", "-sha256", "-sign", privkey] + get_pwd_file_args(d, 'SWUPDATE_PASSWORD_FILE') + ["-out", sw_desc_sig, sw_desc]
  255. elif signing == "CMS":
  256. cms_cert = d.getVar('SWUPDATE_CMS_CERT', True)
  257. if not cms_cert:
  258. bb.fatal("SWUPDATE_CMS_CERT is not set")
  259. if not os.path.exists(cms_cert):
  260. bb.fatal("SWUPDATE_CMS_CERT %s doesn't exist" % (cms_cert))
  261. cms_key = d.getVar('SWUPDATE_CMS_KEY', True)
  262. if not cms_key:
  263. bb.fatal("SWUPDATE_CMS_KEY isn't set")
  264. if not os.path.exists(cms_key):
  265. bb.fatal("SWUPDATE_CMS_KEY %s doesn't exist" % (cms_key))
  266. signcmd = ["openssl", "cms", "-sign", "-in", sw_desc, "-out", sw_desc_sig, "-signer", cms_cert, "-inkey", cms_key] + \
  267. get_pwd_file_args(d, 'SWUPDATE_PASSWORD_FILE') + ["-outform", "DER", "-nosmimecap", "-binary"]
  268. else:
  269. bb.fatal("Unrecognized SWUPDATE_SIGNING mechanism.")
  270. subprocess.run(' '.join(signcmd), shell=True, check=True)
  271. def swupdate_add_src_uri(d, list_for_cpio):
  272. import shutil
  273. s = d.getVar('S', True)
  274. fetch = bb.fetch2.Fetch([], d)
  275. # Add files listed in SRC_URI to the swu file
  276. for url in fetch.urls:
  277. local = fetch.localpath(url)
  278. filename = os.path.basename(local)
  279. aes_file = d.getVar('SWUPDATE_AES_FILE', True)
  280. if aes_file:
  281. key,iv = swupdate_extract_keys(d.getVar('SWUPDATE_AES_FILE', True))
  282. if (filename != 'sw-description') and (os.path.isfile(local)):
  283. encrypted = (d.getVarFlag("SWUPDATE_IMAGES_ENCRYPTED", filename, True) or "")
  284. dst = os.path.join(s, "%s" % filename )
  285. if encrypted == '1':
  286. bb.note("Encryption requested for %s" %(filename))
  287. if not key or not iv:
  288. bb.fatal("Encryption required, but no key found")
  289. swupdate_encrypt_file(local, dst, key, iv)
  290. else:
  291. shutil.copyfile(local, dst)
  292. list_for_cpio.append(filename)
  293. def add_image_to_swu(d, deploydir, imagename, s, encrypt, list_for_cpio):
  294. import shutil
  295. src = os.path.join(deploydir, imagename)
  296. if not os.path.isfile(src):
  297. return False
  298. target_imagename = os.path.basename(imagename) # allow images in subfolders of DEPLOY_DIR_IMAGE
  299. dst = os.path.join(s, target_imagename)
  300. if encrypt == '1':
  301. key,iv = swupdate_extract_keys(d.getVar('SWUPDATE_AES_FILE', True))
  302. bb.note("Encryption requested for %s" %(imagename))
  303. swupdate_encrypt_file(src, dst, key, iv)
  304. else:
  305. shutil.copyfile(src, dst)
  306. list_for_cpio.append(target_imagename)
  307. return True
  308. def swupdate_add_artifacts(d, list_for_cpio):
  309. import shutil
  310. # Search for images listed in SWUPDATE_IMAGES in the DEPLOY directory.
  311. images = (d.getVar('SWUPDATE_IMAGES', True) or "").split()
  312. deploydir = d.getVar('DEPLOY_DIR_IMAGE', True)
  313. imgdeploydir = d.getVar('SWUDEPLOYDIR', True)
  314. s = d.getVar('S', True)
  315. for image in images:
  316. fstypes = (d.getVarFlag("SWUPDATE_IMAGES_FSTYPES", image, True) or "").split()
  317. encrypted = (d.getVarFlag("SWUPDATE_IMAGES_ENCRYPTED", image, True) or "")
  318. if fstypes:
  319. noappend_machine = d.getVarFlag("SWUPDATE_IMAGES_NOAPPEND_MACHINE", image, True)
  320. if noappend_machine == "0": # Search for a file explicitly with MACHINE
  321. imagebases = [ image + '-' + d.getVar('MACHINE', True) ]
  322. elif noappend_machine == "1": # Search for a file explicitly without MACHINE
  323. imagebases = [ image ]
  324. else: # None, means auto mode. Just try to find an image file with MACHINE or without MACHINE
  325. imagebases = [ image + '-' + d.getVar('MACHINE', True), image ]
  326. for fstype in fstypes:
  327. image_found = False
  328. for imagebase in imagebases:
  329. image_found = add_image_to_swu(d, deploydir, imagebase + fstype, s, encrypted, list_for_cpio)
  330. if image_found:
  331. break
  332. if not image_found:
  333. bb.fatal("swupdate cannot find image file: %s" % os.path.join(deploydir, imagebase + fstype))
  334. else: # Allow also complete entries like "image.ext4.gz" in SWUPDATE_IMAGES
  335. if not add_image_to_swu(d, deploydir, image, s, encrypted, list_for_cpio):
  336. bb.fatal("swupdate cannot find %s image file" % image)
  337. def swupdate_create_cpio(d, swudeploydir, list_for_cpio):
  338. s = d.getVar('S', True)
  339. os.chdir(s)
  340. updateimage = d.getVar('IMAGE_NAME', True) + '.swu'
  341. updateimage_link = d.getVar('IMAGE_LINK_NAME', True) + '.swu'
  342. line = 'for i in ' + ' '.join(list_for_cpio) + '; do echo $i;done | cpio -ov -H crc > ' + os.path.join(swudeploydir, updateimage)
  343. os.system(line)
  344. os.chdir(swudeploydir)
  345. os.symlink(updateimage, updateimage_link)
  346. python do_swuimage () {
  347. import shutil
  348. list_for_cpio = ["sw-description"]
  349. workdir = d.getVar('WORKDIR', True)
  350. s = d.getVar('S', True)
  351. imgdeploydir = d.getVar('SWUDEPLOYDIR', True)
  352. shutil.copyfile(os.path.join(workdir, "sw-description"), os.path.join(s, "sw-description"))
  353. if d.getVar('SWUPDATE_SIGNING', True):
  354. list_for_cpio.append('sw-description.sig')
  355. # Add artifacts added via SRC_URI
  356. if not d.getVar('INHIBIT_SWUPDATE_ADD_SRC_URI', True):
  357. swupdate_add_src_uri(d, list_for_cpio)
  358. # Add artifacts set via SWUPDATE_IMAGES
  359. swupdate_add_artifacts(d, list_for_cpio)
  360. prepare_sw_description(d)
  361. swupdate_create_cpio(d, imgdeploydir, list_for_cpio)
  362. }