grubeditor.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  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. # Define the list of available option in both short and long form.
  48. shortopts="hrie:sdD:"
  49. longopts="help,realcfg,inplace,editor:,swapcfgs,diffcfgs,differ:"
  50. # Variables for modifying the program's operation
  51. edit_realcfg=0
  52. edit_inplace=0
  53. do_swapcfgs=0
  54. do_diffcfgs=0
  55. # Path to cbfstool, filled by detect_architecture
  56. # (Possible to provide explicitly and disclaim warranty?)
  57. cbfstool=""
  58. # Editor variables
  59. use_editor=""
  60. default_editor="vi"
  61. editor_rawarg=""
  62. # Differ variables
  63. use_differ=""
  64. default_differ="diff"
  65. differ_rawarg=""
  66. # Last but not least, the rom file itself
  67. romfile=""
  68. # This program works primarily from a cascade of functions. Let's define them.
  69. get_options() {
  70. # Test for enhanced getopt.
  71. getopt --test > /dev/null
  72. if [[ $? -ne 4 ]]; then
  73. echo "Your environment lacks enhanced getopt, so you cannot run this script properly."
  74. exit 205
  75. fi
  76. # Parse the command line options based on the previously defined values.
  77. parsedopts=$(getopt --options ${shortopts} --longoptions ${longopts} --name "${0}" -- "$@")
  78. if [[ $? -ne 0 ]]; then # getopt didn't approve of your arguments
  79. echo "Unrecognized options."
  80. exit 206
  81. fi
  82. # Use eval set to properly quote the arguments
  83. eval set -- "$parsedopts"
  84. # Interpret the arguments one-by-one until you run out.
  85. while [[ 1 ]]; do
  86. case "$1" in
  87. -h|--help)
  88. show_help
  89. # I return non-zero here just so nobody thinks we successfully edited grub.cfg
  90. exit 200
  91. ;;
  92. -r|--realcfg)
  93. edit_realcfg=1
  94. shift
  95. ;;
  96. -i|--inplace)
  97. edit_inplace=1
  98. shift
  99. ;;
  100. -e|--editor)
  101. editor_rawarg="$2"
  102. shift 2
  103. ;;
  104. -s|--swapcfgs)
  105. do_swapcfgs=1
  106. shift
  107. ;;
  108. -d|--diffcfgs)
  109. do_diffcfgs=1
  110. shift
  111. ;;
  112. -D|--differ)
  113. differ_rawarg="$2"
  114. shift 2
  115. ;;
  116. --)
  117. # Stop interpreting arguments magically.
  118. shift
  119. break
  120. ;;
  121. *)
  122. echo "Something went wrong while interpreting the arguments!"
  123. echo "I hit \"${1}\" and don't know what to do with it."
  124. exit 209
  125. ;;
  126. esac
  127. done
  128. # The only option remaining should be the input file. Ensure it was
  129. # provided before proceeding with the rest of the script and capture it if
  130. # it was.
  131. if [[ $# -ne 1 ]]; then
  132. echo "You have specified multiple (or no) input files, which is unsupported."
  133. echo "Please rerun this command with just a single filename to remove any chance for ambiguity."
  134. exit 210
  135. fi
  136. romfile="$1"
  137. }
  138. determine_architecture() {
  139. # The cbfstool in _util is packed only for i686, x86_64, and armv7l. This
  140. # procedure is necessary for this process to not fail. After this process
  141. # is over, the variable $cbfstool gets filled with the appropriate value
  142. # for use by the rest of the script.
  143. arch="$(uname -m)"
  144. case "${arch}" in
  145. armv7l|i686|x86_64)
  146. echo "Supported architecture \"${arch}\" detected. You may proceed."
  147. cbfstool="./cbfstool/${arch}/cbfstool"
  148. ;;
  149. *)
  150. echo "Unsupported architecture \"${arch}\" detected! You may not proceed."
  151. exit 230
  152. ;;
  153. esac
  154. }
  155. determine_operation() {
  156. if [[ $do_swapcfgs -eq 1 ]]; then
  157. swap_configs
  158. exit $?
  159. elif [[ $do_diffcfgs -eq 1 ]]; then
  160. diff_configs
  161. exit $?
  162. else
  163. edit_config
  164. exit $?
  165. fi
  166. }
  167. # These functions are not part of the primary function cascade but are
  168. # referenced within them either directly or indirectly from other helper
  169. # functions depending on the operations requested by the user.
  170. show_help() {
  171. cat << HELPSCREEN
  172. ${0} -- conveniently edit grub{test}.cfg files in Libreboot ROM image files by
  173. automating their extraction with cbfstool and the user's editor of choice.
  174. -h | --help: show usage help
  175. -r | --realcfg: generate grub.cfg instead of grubtest.cfg
  176. -i | --inplace: do not create a .modified romfile, instead modify the
  177. existing file
  178. -e | --editor [/path/to/]editor: open the cfg file with /path/to/editor instead
  179. of the value of \$EDITOR
  180. -s | --swapcfg: swap grub.cfg and grubtest.cfg
  181. -d | --diffcfg: diff grub.cfg and grubtest.cfg
  182. -D | --differ [/path/to/]differ: use /path/to/differ instead of "diff", can be
  183. an interactive program like vimdiff
  184. HELPSCREEN
  185. }
  186. swap_configs() {
  187. # Procedure:
  188. # 1. Call cbfstool twice, once each to extract grub.cfg and grubtest.cfg.
  189. # 2. If --inplace not specified, copy ${romfile} to ${romfile}.modified and
  190. # implement remaining steps on this copy. Otherwise, implement remaining
  191. # steps on ${romfile}.
  192. # 3. Call cbfstool twice, once each to delete grub.cfg and grubtest.cfg
  193. # from romfile.
  194. # 4. Call cbfstool twice, once to embed grubtest.cfg as grub.cfg into
  195. # romfile and again to embed grub.cfg as grubtest.cfg into romfile.
  196. # 5. Delete the extracted grub.cfg and grubtest.cfg files.
  197. # 6. You're done!
  198. # Extract config files from provided romfile.
  199. ${cbfstool} ${romfile} extract -n grub.cfg -f /tmp/real2test.cfg
  200. ${cbfstool} ${romfile} extract -n grubtest.cfg -f /tmp/test2real.cfg
  201. # Determine whether to edit inplace or make a copy.
  202. if [[ $edit_inplace -eq 1 ]]; then
  203. outfile="${romfile}"
  204. else
  205. cp "${romfile}" "${romfile}.modified"
  206. outfile="${romfile}.modified"
  207. fi
  208. # Remove config files from the output file.
  209. ${cbfstool} ${outfile} remove -n grub.cfg
  210. ${cbfstool} ${outfile} remove -n grubtest.cfg
  211. # Embed new configs into the output file.
  212. ${cbfstool} ${outfile} add -t raw -n grub.cfg -f /tmp/test2real.cfg
  213. ${cbfstool} ${outfile} add -t raw -n grubtest.cfg -f /tmp/real2test.cfg
  214. # Delete the tempfiles.
  215. rm /tmp/test2real.cfg /tmp/real2test.cfg
  216. }
  217. diff_configs() {
  218. # Procedure:
  219. # 1. Call cbfstool twice, once to extract grub.cfg and grubtest.cfg.
  220. # 2. Execute ${use_differ} grub.cfg grubtest.cfg #.
  221. # 3. Delete the extracted grub.cfg and grubtest.cfg files.
  222. # 4. You're done!
  223. # Determine the differ command to use.
  224. find_differ
  225. # Extract config files from provided romfile.
  226. ${cbfstool} ${romfile} extract -n grub.cfg -f /tmp/grub_tmpdiff.cfg
  227. ${cbfstool} ${romfile} extract -n grubtest.cfg -f /tmp/grubtest_tmpdiff.cfg
  228. # Run the differ command with real as first option, test as second option.
  229. ${use_differ} /tmp/grub_tmpdiff.cfg /tmp/grubtest_tmpdiff.cfg
  230. }
  231. edit_config() {
  232. # Procedure:
  233. # 1. If --realcfg specified, set ${thisconfig} to "grub.cfg". Otherwise,
  234. # set ${thisconfig} to "grubtest.cfg".
  235. # 2. Call cbfstool once to extract ${thisconfig} from ${romfile}.
  236. # 3. Run ${use_editor} ${thisconfig}.
  237. # 4. If ${use_editor} returns zero, proceed with update procedure:
  238. # 5. Call cbfstool once to extract ${thisconfig} from ${romfile}.
  239. # 6. Quietly diff the extracted file with the edited file. If diff returns
  240. # zero, take no action: warn the user that the files were the same, delete
  241. # both files, then skip the remaining steps (you're done)! Otherwise, the
  242. # files are different and you must continue with the update procedure.
  243. # 7. If --inplace not specified, copy ${romfile} to ${romfile}.modified and
  244. # implement remaining steps on this copy. Otherwise, implement remaining
  245. # steps on ${romfile}.
  246. # 8. Call cbfstool once to delete internal pre-update ${thisconfig} from
  247. # the rom file.
  248. # 9. Call cbfstool once to embed the updated ${thisconfig} that was just
  249. # edited into the rom file.
  250. # 10. Alert the user of success (either explicitly or by not saying
  251. # anything, either way return zero).
  252. # 11. You're done!
  253. # Determine the editor command to use.
  254. find_editor
  255. # Determine whether we are editing the real config or the test config.
  256. if [[ $edit_realcfg -eq 1 ]]; then
  257. thisconfig="grub.cfg"
  258. else
  259. thisconfig="grubtest.cfg"
  260. fi
  261. # Extract the desired configuration file from the romfile.
  262. tmp_editme="/tmp/${thisconfig%.cfg}_editme.cfg"
  263. ${cbfstool} ${romfile} extract -n ${thisconfig} -f ${tmp_editme}
  264. # Launch the editor!
  265. ${use_editor} ${tmp_editme}
  266. # Did the user commit the edit?
  267. if [[ $? -eq 0 ]]; then
  268. # See if it actually changed from what exists in the cbfs.
  269. tmp_refcfg="/tmp/${thisconfig%.cfg}_ref.cfg"
  270. ${cbfstool} ${romfile} extract -n ${thisconfig} -f ${tmp_refcfg}
  271. # Diff the files as quietly as possible.
  272. diff -q ${tmp_editme} ${tmp_refcfg} &> /dev/null
  273. if [[ $? -ne 0 ]]; then
  274. # The files differ, so it won't be frivolous to update the config.
  275. # See if the user wants to edit the file in place.
  276. # (This code should really be genericized and placed in a function
  277. # to avoid repetition.)
  278. if [[ $edit_inplace -eq 1 ]]; then
  279. outfile="${romfile}"
  280. else
  281. cp "${romfile}" "${romfile}.modified"
  282. outfile="${romfile}.modified"
  283. fi
  284. # Remove the old config, add in the new one.
  285. ${cbfstool} ${outfile} remove -n ${thisconfig}
  286. ${cbfstool} ${outfile} add -t raw -n ${thisconfig} -f ${tmp_editme}
  287. else
  288. echo "No changes to config file. Not updating the ROM image."
  289. fi
  290. # We are done with the config files. Delete them.
  291. rm ${tmp_editme}
  292. rm ${tmp_refcfg}
  293. fi
  294. }
  295. find_differ() {
  296. found_differ=0
  297. if [[ -n "${differ_rawarg}" ]]; then
  298. which "${differ_rawarg}" &> /dev/null
  299. if [[ $? -eq 0 ]]; then
  300. echo "Using differ \"${differ_rawarg}\"..."
  301. use_differ="${differ_rawarg}"
  302. found_differ=1
  303. else
  304. echo "The provided \"${differ_rawarg}\" is not a valid command!"
  305. echo "Defaulting to ${default_differ}..."
  306. use_differ="${default_differ}"
  307. fi
  308. fi
  309. if [[ $found_differ -eq 1 ]]; then
  310. return
  311. else
  312. echo "Defaulting to ${default_differ}..."
  313. use_differ="${default_differ}"
  314. fi
  315. }
  316. find_editor() {
  317. found_editor=0
  318. if [[ -n "${editor_rawarg}" ]]; then
  319. which "${editor_rawarg}" &> /dev/null
  320. if [[ $? -eq 0 ]]; then
  321. echo "Using editor \"${editor_rawarg}\"..."
  322. use_editor="${editor_rawarg}"
  323. found_editor=1
  324. else
  325. echo "The provided \"${editor_rawarg}\" is not a valid command!"
  326. echo "Defaulting to ${default_editor}..."
  327. use_editor="${default_editor}"
  328. fi
  329. fi
  330. if [[ $found_editor -eq 1 ]]; then
  331. return
  332. else
  333. if [[ -n "${EDITOR}" ]]; then
  334. which "${EDITOR}" &> /dev/null
  335. if [[ $? -ne 0 ]]; then
  336. echo "Your \$EDITOR is defined as ${EDITOR}, but is not a valid command!"
  337. echo "(This is bad. I highly suggest fixing this in your ~/.bashrc.)"
  338. echo "Defaulting to ${default_editor}..."
  339. use_editor="${default_editor}"
  340. else
  341. echo "Using your defined \$EDITOR \"$EDITOR\"..."
  342. use_editor="${EDITOR}"
  343. fi
  344. else
  345. echo "\$EDITOR blank, defaulting to ${default_editor}..."
  346. use_editor="${default_editor}"
  347. fi
  348. fi
  349. }
  350. # Run through the primary function cascade.
  351. get_options $@
  352. determine_architecture
  353. determine_operation