fwcf.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. #!/bin/sh
  2. # Copyright (c) 2006-2007
  3. # Thorsten Glaser <tg@mirbsd.de>
  4. # Copyright (c) 2009-2024
  5. # Waldemar Brodkorb <wbx@openadk.org>
  6. #
  7. # Provided that these terms and disclaimer and all copyright notices
  8. # are retained or reproduced in an accompanying document, permission
  9. # is granted to deal in this work without restriction, including un-
  10. # limited rights to use, publicly perform, distribute, sell, modify,
  11. # merge, give away, or sublicence.
  12. #
  13. # This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
  14. # the utmost extent permitted by applicable law, neither express nor
  15. # implied; without malicious intent or gross negligence. In no event
  16. # may a licensor, author or contributor be held liable for indirect,
  17. # direct, other damage, loss, or other issues arising in any way out
  18. # of dealing in the work, even if advised of the possibility of such
  19. # damage or existence of a defect, except proven that it results out
  20. # of said person's immediate fault when using the work as intended.
  21. # Possible return values:
  22. # 0 - everything ok
  23. # 1 - syntax error
  24. # 1 - no 'cfgfs' mtd/cf/nand partition found
  25. # 1 - cfgfs erase: failed
  26. # 1 - cfgfs setup: already run
  27. # 3 - cfgfs setup: mount --bind problems
  28. # 4 - cfgfs setup: can't create or write to temporary filesystem
  29. # 5 - cfgfs setup: can't bind the tmpfs to /etc
  30. # 6 - cfgfs commit: cannot write to partition
  31. # 6 - cfgfs restore: cannot write to partition
  32. # 7 - cfgfs commit: won't write to flash because of unclean setup
  33. # 8 - cfgfs status: differences found
  34. # 9 - cfgfs status: old status file not found
  35. # 10 - cfgfs dump: failed
  36. # 11 - cfgfs commit: cfgfs setup not yet run (use -f to force)
  37. # 11 - cfgfs status: cfgfs setup not yet run
  38. # 12 - cfgfs restore: cannot read the backup
  39. # 255 - cfgfs erase: failed
  40. # 255 - internal error
  41. export PATH=/bin:/sbin:/usr/bin:/usr/sbin
  42. wd=$(pwd)
  43. cd /
  44. what='Configuration Filesystem Utility (cfgfs), Version 1.12'
  45. who=$(id -u)
  46. if [ $who -ne 0 ]; then
  47. echo 'Exit. Configuration Filesystem Utility must be run as root.'
  48. exit 1
  49. fi
  50. usage() {
  51. cat >&2 <<EOF
  52. $what
  53. Usage:
  54. { halt | poweroff | reboot } [-Ffn] [-d delay]
  55. cfgfs { commit | erase | setup | status | diff | dump | restore } [flags]
  56. EOF
  57. exit 1
  58. }
  59. case $0 in
  60. (*cfgfs*) me=cfgfs ;;
  61. (*halt*) me=halt ;;
  62. (*poweroff*) me=poweroff ;;
  63. (*reboot*) me=reboot ;;
  64. (*) usage ;;
  65. esac
  66. if [[ $me != cfgfs ]]; then
  67. dval=
  68. dflag=0
  69. fflag=0
  70. nocfgfs=0
  71. nflag=0
  72. while getopts ":d:Ffn" ch; do
  73. case $ch in
  74. (d) dflag=1; dval=$OPTARG ;;
  75. (F) nocfgfs=1 ;;
  76. (f) fflag=1 ;;
  77. (n) nflag=1 ;;
  78. (*) usage ;;
  79. esac
  80. done
  81. shift $((OPTIND - 1))
  82. [[ $nocfgfs -eq 0 ]] && [[ $fflag -eq 0 ]] && if ! cfgfs status -q; then
  83. echo "error: will not $me: unsaved changes in /etc found!"
  84. echo "Either run 'cfgfs commit' before trying to $me"
  85. echo "or retry with '$me -F${*+ }$*' to force a ${me}."
  86. echo "Run 'cfgfs status' to see which files are changed."
  87. exit 2
  88. fi
  89. [[ $fflag -eq 1 ]] && me="$me -f"
  90. [[ $nflag -eq 1 ]] && me="$me -n"
  91. [[ $dflag -eq 1 ]] && me="$me -d '$dval'"
  92. eval exec busybox $me
  93. fi
  94. case $1 in
  95. (commit|erase|setup|status|diff|dump|restore) ;;
  96. (*) cat >&2 <<EOF
  97. $what
  98. Syntax:
  99. $0 commit [-f]
  100. $0 erase
  101. $0 setup [-N]
  102. $0 status [-rq]
  103. $0 diff [<diff options>]
  104. $0 { dump | restore } [<filename>]
  105. EOF
  106. exit 1 ;;
  107. esac
  108. mtd=0
  109. if [ -x /sbin/nand ];then
  110. mtdtool=/sbin/nand
  111. fi
  112. if [ -x /sbin/mtd ];then
  113. mtdtool=/sbin/mtd
  114. fi
  115. # find backend device, first try to find partition with ID 88
  116. rootdisk=$(rdev|cut -f 1 -d\ )
  117. # strip partitions (f.e. mmcblk0p2, sda2, ..)
  118. rootdisk=${rootdisk%p*}
  119. # do not cut 1-9 from mmcblk device names
  120. echo $rootdisk|grep mmcblk >/dev/null 2>&1
  121. if [ $? -ne 0 ]; then
  122. rootdisk=${rootdisk%[1-9]}
  123. fi
  124. # find last partition with an 88 id
  125. part=$(fdisk -l $rootdisk 2>/dev/null|awk '{if ($2=="*") { print $1" "$9} else {print $1" "$8}}'|grep '^/dev.*88.*'|tail -1|awk '{ print $1 }')
  126. if [ -f .cfgfs ]; then
  127. . /.cfgfs
  128. fi
  129. if [ -z $part ]; then
  130. part=$(fdisk -l /dev/sda 2>/dev/null|awk '{if ($2=="*") { print $1" "$9} else {print $1" "$8}}'|grep '^/dev.*88.*'|tail -1|awk '{ print $1 }')
  131. # find GPT partition
  132. if [ -z $part ]; then
  133. partnum=$(gdisk -l $rootdisk 2>/dev/null|fgrep "cfgfs"|awk '{ print $1 }')
  134. if [ ! -z $partnum ]; then
  135. echo $rootdisk|grep mmcblk >/dev/null 2>&1
  136. if [ $? -eq 0 ]; then
  137. part=${rootdisk}p${partnum}
  138. else
  139. part=${rootdisk}${partnum}
  140. fi
  141. fi
  142. fi
  143. if [ -z $part ]; then
  144. # otherwise search for MTD device with name cfgfs
  145. part=/dev/mtd$(fgrep '"cfgfs"' /proc/mtd 2>/dev/null | sed 's/^mtd\([^:]*\):.*$/\1/')ro
  146. mtd=1
  147. fi
  148. fi
  149. if [[ ! -e $part ]]; then
  150. echo 'cfgfs: fatal error: no "cfgfs" partition found!'
  151. exit 1
  152. fi
  153. if test $1 = erase; then
  154. dd if="$part" 2>&1 | md5sum 2>&1 >/dev/urandom
  155. if [ $mtd -eq 1 ]; then
  156. cfgfs.helper -Me | eval $mtdtool -F write - cfgfs
  157. else
  158. cfgfs.helper -Me | cat > $part
  159. fi
  160. exit $?
  161. fi
  162. if test $1 = setup; then
  163. if test -e /tmp/.cfgfs; then
  164. echo 'cfgfs: error: "cfgfs setup" already run!'
  165. exit 1
  166. fi
  167. mkdir /tmp/.cfgfs
  168. if test ! -d /tmp/.cfgfs; then
  169. echo 'cfgfs: error: cannot create temporary directory!'
  170. exit 4
  171. fi
  172. chown 0:0 /tmp/.cfgfs
  173. chmod 700 /tmp/.cfgfs
  174. mkdir /tmp/.cfgfs/root
  175. mount --bind /etc /tmp/.cfgfs/root
  176. mkdir /tmp/.cfgfs/temp
  177. mount -t tmpfs none /tmp/.cfgfs/temp
  178. (cd /tmp/.cfgfs/root; tar cf - .) | (cd /tmp/.cfgfs/temp; tar xpf - 2>/dev/null)
  179. unclean=0
  180. if [[ $1 = -N ]]; then
  181. unclean=2
  182. else
  183. x=$(dd if="$part" bs=4 count=1 2>/dev/null)
  184. [[ "$x" = "FWCF" ]] || \
  185. if [ $mtd -eq 1 ]; then
  186. cfgfs.helper -Me | eval $mtdtool -F write - cfgfs
  187. else
  188. cfgfs.helper -Me | cat > $part
  189. fi
  190. if ! cfgfs.helper -U /tmp/.cfgfs/temp <"$part"; then
  191. unclean=1
  192. echo 'cfgfs: error: cannot extract'
  193. echo unclean startup | logger -t 'cfgfs setup'
  194. fi
  195. if test -e /tmp/.cfgfs/temp/.cfgfs_deleted; then
  196. while IFS= read -r file; do
  197. rm -f "/tmp/.cfgfs/temp/$file"
  198. done </tmp/.cfgfs/temp/.cfgfs_deleted
  199. rm -f /tmp/.cfgfs/temp/.cfgfs_deleted
  200. fi
  201. fi
  202. test $unclean = 0 || echo -n >/tmp/.cfgfs/temp/.cfgfs_unclean
  203. rm -f /tmp/.cfgfs/temp/.cfgfs_done
  204. if test -e /tmp/.cfgfs/temp/.cfgfs_done; then
  205. echo 'cfgfs: fatal: this is not Kansas any more'
  206. umount /tmp/.cfgfs/temp
  207. umount /tmp/.cfgfs/root
  208. rm -rf /tmp/.cfgfs
  209. exit 3
  210. fi
  211. echo -n >/tmp/.cfgfs/temp/.cfgfs_done
  212. if test ! -e /tmp/.cfgfs/temp/.cfgfs_done; then
  213. echo 'cfgfs: fatal: cannot write to tmpfs'
  214. umount /tmp/.cfgfs/temp
  215. umount /tmp/.cfgfs/root
  216. rm -rf /tmp/.cfgfs
  217. exit 4
  218. fi
  219. chmod 755 /tmp/.cfgfs/temp
  220. mount --bind /tmp/.cfgfs/temp /etc
  221. if test ! -e /etc/.cfgfs_done; then
  222. umount /etc
  223. echo 'cfgfs: fatal: binding to /etc failed'
  224. if test $unclean = 0; then
  225. echo 'cfgfs: configuration is preserved' \
  226. in /tmp/.cfgfs/temp
  227. else
  228. umount /tmp/.cfgfs/temp
  229. fi
  230. exit 5
  231. fi
  232. umount /tmp/.cfgfs/temp
  233. echo complete, unclean=$unclean | logger -t 'cfgfs setup'
  234. cd /etc
  235. rm -f .rnd
  236. find . -type f | grep -v -e '^./.cfgfs' -e '^./.rnd$' | sort | \
  237. xargs md5sum | sed 's! ./! !' | \
  238. cfgfs.helper -Z - /tmp/.cfgfs/status.asz
  239. exit 0
  240. fi
  241. if test $1 = commit; then
  242. umount /tmp/.cfgfs/temp >/dev/null 2>&1
  243. if test ! -e /tmp/.cfgfs; then
  244. cat >&2 <<-EOF
  245. cfgfs: error: not yet initialised
  246. explanation: "cfgfs setup" was not yet run
  247. EOF
  248. [[ $1 = -f ]] || exit 11
  249. fi
  250. if test -e /etc/.cfgfs_unclean; then
  251. cat >&2 <<-EOF
  252. cfgfs: error: unclean startup (or setup run with -N)!
  253. explanation: during boot, the cfgfs filesystem could not
  254. be extracted; saving the current /etc to flash will
  255. result in data loss; to override this check, remove
  256. the file /etc/.cfgfs_unclean and try again.
  257. EOF
  258. [[ $1 = -f ]] || exit 7
  259. fi
  260. mount -t tmpfs none /tmp/.cfgfs/temp
  261. (cd /etc; tar cf - .) | (cd /tmp/.cfgfs/temp; tar xpf - 2>/dev/null)
  262. cd /tmp/.cfgfs/temp
  263. find . -type f | grep -v -e '^./.cfgfs' -e '^./.rnd$' | sort | \
  264. xargs md5sum | sed 's! ./! !' | \
  265. cfgfs.helper -Z - /tmp/.cfgfs/status.asz
  266. cd /tmp/.cfgfs/root
  267. rm -f /tmp/.cfgfs/temp/.cfgfs_* /tmp/.cfgfs/temp/.rnd
  268. find . -type f | while read f; do
  269. f=${f#./}
  270. if [[ ! -e /tmp/.cfgfs/temp/$f ]]; then
  271. [[ $f = .rnd ]] && continue
  272. printf '%s\n' "$f" >>/tmp/.cfgfs/temp/.cfgfs_deleted
  273. continue
  274. fi
  275. x=$(md5sum "$f" 2>/dev/null)
  276. y=$(cd ../temp; md5sum "$f" 2>/dev/null)
  277. [[ "$x" = "$y" ]] && rm "../temp/$f"
  278. done
  279. find /tmp/.cfgfs/temp -type d -empty -delete
  280. rv=0
  281. if [ $mtd -eq 1 ]; then
  282. if ! ( cfgfs.helper -M /tmp/.cfgfs/temp | eval $mtdtool -F write - cfgfs ); then
  283. echo 'cfgfs: error: cannot write to $part!'
  284. rv=6
  285. fi
  286. else
  287. if ! ( cfgfs.helper -M /tmp/.cfgfs/temp | cat > $part ); then
  288. echo 'cfgfs: error: cannot write to $part!'
  289. rv=6
  290. fi
  291. fi
  292. umount /tmp/.cfgfs/temp
  293. exit $rv
  294. fi
  295. if test $1 = status; then
  296. if test ! -e /tmp/.cfgfs; then
  297. cat >&2 <<-EOF
  298. cfgfs: error: not yet initialised
  299. explanation: "cfgfs setup" was not yet run
  300. EOF
  301. [[ $1 = -f ]] || exit 11
  302. fi
  303. rm -f /tmp/.cfgfs/*_status /tmp/.cfgfs/*_files
  304. rflag=0
  305. q=printf # or : (true) if -q
  306. shift
  307. while getopts "rq" ch; do
  308. case $ch in
  309. (r) rflag=1 ;;
  310. (q) q=: ;;
  311. esac
  312. done
  313. shift $((OPTIND - 1))
  314. if test $rflag = 1; then
  315. f=/tmp/.cfgfs/rom_status
  316. cd /tmp/.cfgfs/root
  317. find . -type f | grep -v -e '^./.cfgfs' -e '^./.rnd$' | sort | \
  318. xargs md5sum | sed 's! ./! !' >$f
  319. else
  320. f=/tmp/.cfgfs/status
  321. cfgfs.helper -Zd $f.asz $f || rm -f $f
  322. fi
  323. if [[ ! -e $f ]]; then
  324. echo 'cfgfs: error: old status file not found'
  325. exit 9
  326. fi
  327. cd /etc
  328. find . -type f | grep -v -e '^./.cfgfs' -e '^./.rnd$' | sort | \
  329. xargs md5sum | sed 's! ./! !' >/tmp/.cfgfs/cur_status || exit 255
  330. cd /tmp/.cfgfs
  331. sed 's/^[0-9a-f]* //' <$f >old_files
  332. sed 's/^[0-9a-f]* //' <cur_status >cur_files
  333. # make *_status be of exactly the same length, for benefit of the
  334. # while ... read <old, read <new loop below, and sort it
  335. comm -23 old_files cur_files | while read name; do
  336. echo "<NULL> $name" >>cur_status
  337. done
  338. comm -13 old_files cur_files | while read name; do
  339. echo "<NULL> $name" >>$f
  340. done
  341. # this implementation of sort -o sucks: doesn't do in-place edits
  342. # workaround a busybox bug?
  343. touch sold_status snew_status
  344. sort -k2 -o sold_status $f
  345. sort -k2 -o snew_status cur_status
  346. gotany=0
  347. while :; do
  348. IFS=' ' read oldsum oldname <&3 || break
  349. IFS=' ' read newsum newname <&4 || exit 255
  350. [[ "$oldname" = "$newname" ]] || exit 255
  351. [[ "$oldsum" = "$newsum" ]] && continue
  352. [[ $gotany = 0 ]] && $q '%-32s %-32s %s\n' \
  353. 'MD5 hash of old file' 'MD5 hash of new file' 'filename'
  354. gotany=8
  355. test $q = : && break
  356. $q '%32s %32s %s\n' "$oldsum" "$newsum" "$oldname"
  357. done 3<sold_status 4<snew_status
  358. rm -f /tmp/.cfgfs/*_status /tmp/.cfgfs/*_files
  359. exit $gotany
  360. fi
  361. if test $1 = dump; then
  362. fn=$2
  363. [[ -n $fn ]] || fn=-
  364. rm -rf /tmp/.cfgfs.dump
  365. mkdir -m 0700 /tmp/.cfgfs.dump
  366. cd /tmp/.cfgfs.dump
  367. if ! cat "$part" | cfgfs.helper -UD dump; then
  368. cd /
  369. rm -rf /tmp/.cfgfs.dump
  370. exit 10
  371. fi
  372. dd if=/dev/urandom of=seed bs=256 count=1 >/dev/null 2>&1
  373. tar -cf - dump seed | (cd "$wd"; cfgfs.helper -Z - $fn)
  374. cd /
  375. rm -rf /tmp/.cfgfs.dump
  376. case $fn in
  377. (-) echo "cfgfs: dump to standard output complete."
  378. ;;
  379. (*) echo "cfgfs: dump to '$fn' complete."
  380. ls -l "$fn" >&2
  381. ;;
  382. esac
  383. exit 0
  384. fi
  385. if test $1 = restore; then
  386. if test -e /tmp/.cfgfs; then
  387. echo 'cfgfs: warning: "cfgfs setup" already run!'
  388. echo 'please reboot after restoring; in no event'
  389. echo 'run "cfgfs commit" to prevent data loss'
  390. echo -n >/etc/.cfgfs_unclean
  391. fi
  392. fn=$2
  393. [[ -n $fn ]] || fn=-
  394. rm -rf /tmp/.cfgfs.restore
  395. mkdir -m 0700 /tmp/.cfgfs.restore
  396. cd /tmp/.cfgfs.restore
  397. if ! (cd "$wd"; cfgfs.helper -Zd "$fn") | tar -xf -; then
  398. cd /
  399. rm -rf /tmp/.cfgfs.restore
  400. exit 12
  401. fi
  402. dd if=seed of=/dev/urandom bs=256 count=1 >/dev/null 2>&1
  403. if test ! -e dump; then
  404. echo 'cfgfs: error: invalid backup'
  405. cd /
  406. rm -rf /tmp/.cfgfs.restore
  407. exit 12
  408. fi
  409. if [ $mtd -eq 1 ]; then
  410. if ! ( cfgfs.helper -MD dump | eval $mtdtool -F write - cfgfs ); then
  411. echo 'cfgfs: error: cannot write to $part!'
  412. exit 6
  413. fi
  414. else
  415. if ! ( cfgfs.helper -MD dump | cat > $part ); then
  416. echo 'cfgfs: error: cannot write to $part!'
  417. exit 6
  418. fi
  419. fi
  420. cd /
  421. rm -rf /tmp/.cfgfs.restore
  422. case $fn in
  423. (-) echo "cfgfs: restore from standard output complete."
  424. ;;
  425. (*) echo "cfgfs: restore from '$fn' complete."
  426. ls -l "$fn" >&2
  427. ;;
  428. esac
  429. exit 0
  430. fi
  431. if test $1 = diff; then
  432. if test ! -e /tmp/.cfgfs; then
  433. cat >&2 <<-EOF
  434. cfgfs: error: not yet initialised
  435. explanation: "cfgfs setup" was not yet run
  436. EOF
  437. [[ $1 = -f ]] || exit 11
  438. fi
  439. shift
  440. tempd=/tmp/.cfgfs/temp
  441. mount -t tmpfs none $tempd
  442. (cd /tmp/.cfgfs/root; tar cf - .) | (cd $tempd; tar xpf - 2>/dev/null)
  443. x=$(dd if="$part" bs=4 count=1 2>/dev/null)
  444. [[ "$x" = "FWCF" ]] && cfgfs.helper -U $tempd <"$part"
  445. if test -e $tempd/.cfgfs_deleted; then
  446. while IFS= read -r file; do
  447. rm -f "$tempd/$file"
  448. done <$tempd/.cfgfs_deleted
  449. rm -f $tempd/.cfgfs_deleted
  450. fi
  451. (cd /etc; find . -type f; \
  452. cd $tempd; find . -type f \
  453. ) | grep -v -e '^./.cfgfs' -e '^./.rnd$' | sort -u | while read f; do
  454. f=${f#./}
  455. if [ ! -e "/etc/$f" ]; then
  456. echo "Deleted: /etc/$f"
  457. elif [ ! -e "$tempd/$f" ]; then
  458. echo "New: /etc/$f"
  459. else
  460. diff "$@" "$tempd/$f" "/etc/$f"
  461. fi
  462. done
  463. umount $tempd
  464. exit 0
  465. fi
  466. echo 'cfgfs: cannot be reached...'
  467. exit 255