grubeditor.sh 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. #!/bin/bash
  2. #
  3. # grubeditor.sh -- conveniently edit grub{test}.cfg files by automating their
  4. # extraction with cbfstool and the user's editor of choice.
  5. #
  6. # Copyright (C) 2017 Zyliwax <zyliwax@protonmail.com>
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation, either version 3 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. # Usage:
  22. # ./grubeditor.sh [options] romimage
  23. #
  24. # Supported options:
  25. #
  26. # -h | --help: show usage help
  27. #
  28. # -r | --realcfg: generate grub.cfg instead of grubtest.cfg
  29. #
  30. # -i | --inplace: do not create a .modified romfile, instead modify the
  31. # existing file
  32. #
  33. # -e | --editor /path/to/editor: open the cfg file with /path/to/editor instead
  34. # of $EDITOR
  35. #
  36. # -s | --swapcfg: swap grub.cfg and grubtest.cfg, incompatible with other
  37. # options besides -i
  38. #
  39. # -x | --extractcfg: extract either grub.cfg or grubtest.cfg depending on
  40. # whether -r is set
  41. #
  42. # -d | --diffcfg: diff grub.cfg and grubtest.cfg, incompatible with other
  43. # options besides -D
  44. #
  45. # -D | --differ [/path/to/]differ: use /path/to/differ instead of "diff", can
  46. # be an interactive program like vimdiff
  47. #
  48. # THIS BLOCK IS EXPERIMENTAL
  49. # Allow debugging by running DEBUG= ${0}.
  50. [[ "x${DEBUG+set}" = 'xset' ]] && set -v
  51. # -u kills the script if any variables are unassigned
  52. # -e kills the script if any function returns not-zero
  53. #set -u
  54. # Version number of script
  55. geversion="0.1.1"
  56. # Define the list of available option in both short and long form.
  57. shortopts="hrie:sdD:"
  58. longopts="help,realcfg,inplace,editor:,swapcfgs,diffcfgs,differ:"
  59. # Variables for modifying the program's operation
  60. edit_realcfg=0
  61. edit_inplace=0
  62. do_swapcfgs=0
  63. do_diffcfgs=0
  64. # Path to cbfstool, filled by detect_architecture
  65. # (Possible to provide explicitly and disclaim warranty?)
  66. cbfstool=""
  67. # Editor variables
  68. use_editor=""
  69. default_editor="vi"
  70. editor_rawarg=""
  71. # Differ variables
  72. use_differ=""
  73. default_differ="diff"
  74. differ_rawarg=""
  75. # Last but not least, the rom file itself
  76. romfile=""
  77. # This program works primarily from a cascade of functions. Let's define them.
  78. get_options() {
  79. # Test for enhanced getopt.
  80. getopt --test > /dev/null
  81. if [[ $? -ne 4 ]]; then
  82. echo "Your environment lacks enhanced getopt, so you cannot run this script properly."
  83. exit 205
  84. fi
  85. # Parse the command line options based on the previously defined values.
  86. parsedopts=$(getopt --options ${shortopts} --longoptions ${longopts} --name "${0}" -- "$@")
  87. if [[ $? -ne 0 ]]; then # getopt didn't approve of your arguments
  88. echo "Unrecognized options."
  89. exit 206
  90. fi
  91. # Use eval set to properly quote the arguments
  92. eval set -- "$parsedopts"
  93. # Interpret the arguments one-by-one until you run out.
  94. while [[ 1 ]]; do
  95. case "$1" in
  96. -h|--help)
  97. show_help
  98. # I return non-zero here just so nobody thinks we successfully edited grub{,test}.cfg
  99. exit 200
  100. ;;
  101. -v|--version)
  102. show_version
  103. # I return non-zero here just so nobody thinks we successfully edited grub{,test}.cfg
  104. exit 201
  105. ;;
  106. -r|--realcfg)
  107. edit_realcfg=1
  108. shift
  109. ;;
  110. -i|--inplace)
  111. edit_inplace=1
  112. shift
  113. ;;
  114. -e|--editor)
  115. editor_rawarg="$2"
  116. shift 2
  117. ;;
  118. -s|--swapcfgs)
  119. do_swapcfgs=1
  120. shift
  121. ;;
  122. -d|--diffcfgs)
  123. do_diffcfgs=1
  124. shift
  125. ;;
  126. -D|--differ)
  127. differ_rawarg="$2"
  128. shift 2
  129. ;;
  130. --)
  131. # Stop interpreting arguments magically.
  132. shift
  133. break
  134. ;;
  135. *)
  136. echo "Something went wrong while interpreting the arguments!"
  137. echo "I hit \"${1}\" and don't know what to do with it."
  138. exit 209
  139. ;;
  140. esac
  141. done
  142. # The only option remaining should be the input file. Ensure it was
  143. # provided before proceeding with the rest of the script and capture it if
  144. # it was.
  145. if [[ $# -ne 1 ]]; then
  146. echo "You have specified multiple (or no) input files, which is unsupported."
  147. echo "Please rerun this command with just a single filename to remove any chance for ambiguity."
  148. exit 210
  149. fi
  150. romfile="$1"
  151. }
  152. determine_architecture() {
  153. # The cbfstool in _util is packed only for i686, x86_64, and armv7l. This
  154. # procedure is necessary for this process to not fail. After this process
  155. # is over, the variable $cbfstool gets filled with the appropriate value
  156. # for use by the rest of the script.
  157. arch="$(uname -m)"
  158. case "${arch}" in
  159. armv7l|i686|x86_64)
  160. echo "Supported architecture \"${arch}\" detected. You may proceed."
  161. cbfstool="${0%/*}/cbfstool/${arch}/cbfstool"
  162. ;;
  163. *)
  164. echo "Unsupported architecture \"${arch}\" detected! You may not proceed."
  165. exit 230
  166. ;;
  167. esac
  168. }
  169. determine_operation() {
  170. if [[ $do_swapcfgs -eq 1 ]]; then
  171. swap_configs
  172. exit $?
  173. elif [[ $do_diffcfgs -eq 1 ]]; then
  174. diff_configs
  175. exit $?
  176. else
  177. edit_config
  178. exit $?
  179. fi
  180. }
  181. # These functions are not part of the primary function cascade but are
  182. # referenced within them either directly or indirectly from other helper
  183. # functions depending on the operations requested by the user.
  184. # External command finders.
  185. find_differ() {
  186. found_differ=0
  187. if [[ -n "${differ_rawarg}" ]]; then
  188. which "${differ_rawarg}" &> /dev/null
  189. if [[ $? -eq 0 ]]; then
  190. echo "Using differ \"${differ_rawarg}\"..."
  191. use_differ="${differ_rawarg}"
  192. found_differ=1
  193. else
  194. echo "The provided \"${differ_rawarg}\" is not a valid command!"
  195. echo "Defaulting to ${default_differ}..."
  196. use_differ="${default_differ}"
  197. fi
  198. fi
  199. if [[ $found_differ -eq 1 ]]; then
  200. return
  201. else
  202. echo "Defaulting to ${default_differ}..."
  203. use_differ="${default_differ}"
  204. fi
  205. }
  206. find_editor() {
  207. found_editor=0
  208. if [[ -n "${editor_rawarg}" ]]; then
  209. which "${editor_rawarg}" &> /dev/null
  210. if [[ $? -eq 0 ]]; then
  211. echo "Using editor \"${editor_rawarg}\"..."
  212. use_editor="${editor_rawarg}"
  213. found_editor=1
  214. else
  215. echo "The provided \"${editor_rawarg}\" is not a valid command!"
  216. echo "Defaulting to ${default_editor}..."
  217. use_editor="${default_editor}"
  218. fi
  219. fi
  220. if [[ $found_editor -eq 1 ]]; then
  221. return
  222. else
  223. if [[ -n "${EDITOR}" ]]; then
  224. which "${EDITOR}" &> /dev/null
  225. if [[ $? -ne 0 ]]; then
  226. echo "Your \$EDITOR is defined as ${EDITOR}, but is not a valid command!"
  227. echo "(This is bad. I highly suggest fixing this in your ~/.bashrc.)"
  228. echo "Defaulting to ${default_editor}..."
  229. use_editor="${default_editor}"
  230. else
  231. echo "Using your defined \$EDITOR \"$EDITOR\"..."
  232. use_editor="${EDITOR}"
  233. fi
  234. else
  235. echo "\$EDITOR blank, defaulting to ${default_editor}..."
  236. use_editor="${default_editor}"
  237. fi
  238. fi
  239. }
  240. # Filename randomizer.
  241. random_filer() {
  242. # Inputs:
  243. # $1 is a descriptive label for the file
  244. # $2 is directory (becomes /tmp if not set)
  245. [[ -n "${1}" ]] && label="${1}" || label="tempfile"
  246. [[ -n "${2}" ]] && savedir="${2%/}" || savedir="/tmp"
  247. # Hardcoded string size for multiple good reasons (no processing weird
  248. # input, prevent malicious overflows, etc.)
  249. size=5
  250. # Loop forever until a free filename is found.
  251. while [[ 1 ]]; do
  252. # Read data from /dev/urandom and convert into random ASCII strings.
  253. rand=$(cat /dev/urandom | tr -dc 'a-zA-Z' | fold -w $size | head -n 1)
  254. # Build a complete filename with a hardcoded extension.
  255. possible="${savedir}/${label}_${rand}.tmp.cfg"
  256. # See if file doesn't exist and return it as string or keep going.
  257. if [[ ! -f "${possible}" ]]; then
  258. echo "${possible}"
  259. break
  260. fi
  261. done
  262. }
  263. # Primary command functions.
  264. show_help() {
  265. cat << HELPSCREEN
  266. "${0}" -- conveniently edit grub{test}.cfg files in Libreboot ROM image files by automating their extraction with cbfstool and the user's editor of choice.
  267. Usage:
  268. "${0}" [OPTIONS] [ROMFILE]
  269. Options:
  270. -h | --help: show usage help
  271. -r | --realcfg: generate grub.cfg instead of grubtest.cfg
  272. -i | --inplace: do not create a .modified romfile, instead modify the
  273. existing file
  274. -e | --editor [/path/to/]editor: open the cfg file with /path/to/editor instead of the value of \$EDITOR
  275. -s | --swapcfg: swap grub.cfg and grubtest.cfg
  276. -d | --diffcfg: diff grub.cfg and grubtest.cfg
  277. -D | --differ [/path/to/]differ: use /path/to/differ instead of "diff", can be an interactive program like vimdiff
  278. HELPSCREEN
  279. }
  280. show_version() {
  281. echo "${geversion}"
  282. }
  283. swap_configs() {
  284. # Procedure:
  285. # 1. Call cbfstool twice, once each to extract grub.cfg and grubtest.cfg.
  286. # 2. If --inplace not specified, copy ${romfile} to ${romfile}.modified and
  287. # implement remaining steps on this copy. Otherwise, implement remaining
  288. # steps on ${romfile}.
  289. # 3. Call cbfstool twice, once each to delete grub.cfg and grubtest.cfg
  290. # from romfile.
  291. # 4. Call cbfstool twice, once to embed grubtest.cfg as grub.cfg into
  292. # romfile and again to embed grub.cfg as grubtest.cfg into romfile.
  293. # 5. Delete the extracted grub.cfg and grubtest.cfg files.
  294. # 6. You're done!
  295. # Extract config files from provided romfile.
  296. real2test="$(random_filer "real2test")"
  297. test2real="$(random_filer "test2real")"
  298. "${cbfstool}" "${romfile}" extract -n grub.cfg -f "${real2test}"
  299. "${cbfstool}" "${romfile}" extract -n grubtest.cfg -f "${test2real}"
  300. # Determine whether to edit inplace or make a copy.
  301. if [[ $edit_inplace -eq 1 ]]; then
  302. outfile="${romfile}"
  303. else
  304. cp "${romfile}" "${romfile}.modified"
  305. outfile="${romfile}.modified"
  306. fi
  307. # Remove config files from the output file.
  308. "${cbfstool}" "${outfile}" remove -n grub.cfg
  309. "${cbfstool}" "${outfile}" remove -n grubtest.cfg
  310. # Embed new configs into the output file.
  311. "${cbfstool}" ${outfile} add -t raw -n grub.cfg -f "${test2real}"
  312. "${cbfstool}" ${outfile} add -t raw -n grubtest.cfg -f "${real2test}"
  313. # Delete the tempfiles.
  314. rm "${test2real}" "${real2test}"
  315. }
  316. diff_configs() {
  317. # Procedure:
  318. # 1. Call cbfstool twice, once to extract grub.cfg and grubtest.cfg.
  319. # 2. Execute ${use_differ} grub.cfg grubtest.cfg #.
  320. # 3. Delete the extracted grub.cfg and grubtest.cfg files.
  321. # 4. You're done!
  322. # Determine the differ command to use.
  323. find_differ
  324. grubcfg="$(random_filer "grubcfg")"
  325. testcfg="$(random_filer "testcfg")"
  326. # Extract config files from provided romfile.
  327. "${cbfstool}" "${romfile}" extract -n grub.cfg -f "${grubcfg}"
  328. "${cbfstool}" "${romfile}" extract -n grubtest.cfg -f "${testcfg}"
  329. # Run the differ command with real as first option, test as second option.
  330. "${use_differ}" "${grubcfg}" "${testcfg}"
  331. # Delete the temporary copies of the configuration files.
  332. rm "${grubcfg}"
  333. rm "${testcfg}"
  334. }
  335. edit_config() {
  336. # Procedure:
  337. # 1. If --realcfg specified, set ${thisconfig} to "grub.cfg". Otherwise,
  338. # set ${thisconfig} to "grubtest.cfg".
  339. # 2. Call cbfstool once to extract ${thisconfig} from ${romfile}.
  340. # 3. Run ${use_editor} ${thisconfig}.
  341. # 4. If ${use_editor} returns zero, proceed with update procedure:
  342. # 5. Call cbfstool once to extract ${thisconfig} from ${romfile}.
  343. # 6. Quietly diff the extracted file with the edited file. If diff returns
  344. # zero, take no action: warn the user that the files were the same, delete
  345. # both files, then skip the remaining steps (you're done)! Otherwise, the
  346. # files are different and you must continue with the update procedure.
  347. # 7. If --inplace not specified, copy ${romfile} to ${romfile}.modified and
  348. # implement remaining steps on this copy. Otherwise, implement remaining
  349. # steps on ${romfile}.
  350. # 8. Call cbfstool once to delete internal pre-update ${thisconfig} from
  351. # the rom file.
  352. # 9. Call cbfstool once to embed the updated ${thisconfig} that was just
  353. # edited into the rom file.
  354. # 10. Alert the user of success (either explicitly or by not saying
  355. # anything, either way return zero).
  356. # 11. You're done!
  357. # Determine the editor command to use.
  358. find_editor
  359. # Determine whether we are editing the real config or the test config.
  360. if [[ $edit_realcfg -eq 1 ]]; then
  361. thisconfig="grub.cfg"
  362. else
  363. thisconfig="grubtest.cfg"
  364. fi
  365. # Extract the desired configuration file from the romfile.
  366. tmp_editme="$(random_filer "${thisconfig%.cfg}")"
  367. "${cbfstool}" "${romfile}" extract -n "${thisconfig}" -f "${tmp_editme}"
  368. # Launch the editor!
  369. "${use_editor}" "${tmp_editme}"
  370. # Did the user commit the edit?
  371. if [[ $? -eq 0 ]]; then
  372. # See if it actually changed from what exists in the cbfs.
  373. tmp_refcfg="/tmp/${thisconfig%.cfg}_ref.cfg"
  374. "${cbfstool}" "${romfile}" extract -n "${thisconfig}" -f "${tmp_refcfg}"
  375. # Diff the files as quietly as possible.
  376. diff -q "${tmp_editme}" "${tmp_refcfg}" &> /dev/null
  377. if [[ $? -ne 0 ]]; then
  378. # The files differ, so it won't be frivolous to update the config.
  379. # See if the user wants to edit the file in place.
  380. # (This code should really be genericized and placed in a function
  381. # to avoid repetition.)
  382. if [[ $edit_inplace -eq 1 ]]; then
  383. outfile="${romfile}"
  384. else
  385. cp "${romfile}" "${romfile}.modified"
  386. outfile="${romfile}.modified"
  387. fi
  388. # Remove the old config, add in the new one.
  389. "${cbfstool}" "${outfile}" remove -n "${thisconfig}"
  390. "${cbfstool}" "${outfile}" add -t raw -n "${thisconfig}" -f "${tmp_editme}"
  391. else
  392. echo "No changes to config file. Not updating the ROM image."
  393. fi
  394. # We are done with the config files. Delete them.
  395. rm "${tmp_editme}"
  396. rm "${tmp_refcfg}"
  397. fi
  398. }
  399. # Run through the primary function cascade.
  400. get_options $@
  401. determine_architecture
  402. determine_operation