build-image-qemu.sh 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. #!/usr/bin/env bash
  2. set -e
  3. SCRIPT_DIR="$(dirname "${0}")"
  4. . "${SCRIPT_DIR}/shell_include.sh"
  5. USE_FUSE2FS=0
  6. if [ "$(id -u)" != 0 ]; then
  7. if [ -x "$FUSE2FS_PATH" ] && $FUSE2FS_PATH --help 2>&1 |grep fakeroot > /dev/null; then
  8. USE_FUSE2FS=1
  9. else
  10. set +e
  11. ${SUDO} -- sh -c "\"$0\" $* || exit 42"
  12. case $? in
  13. 1)
  14. die "this script needs to run as root"
  15. ;;
  16. 42)
  17. exit 1
  18. ;;
  19. *)
  20. exit 0
  21. ;;
  22. esac
  23. fi
  24. else
  25. : "${SUDO_UID:=0}" "${SUDO_GID:=0}"
  26. fi
  27. # Prepend the toolchain qemu directory so we pick up QEMU from there
  28. PATH="$SCRIPT_DIR/../Toolchain/Local/qemu/bin:$PATH"
  29. # We depend on GNU coreutils du for the --apparent-size extension.
  30. # GNU coreutils is a build dependency.
  31. if command -v gdu > /dev/null 2>&1 && gdu --version | grep -q "GNU coreutils"; then
  32. GNUDU="gdu"
  33. else
  34. GNUDU="du"
  35. fi
  36. disk_usage() {
  37. # shellcheck disable=SC2003,SC2307
  38. expr "$(${GNUDU} -sk --apparent-size "$1" | cut -f1)"
  39. }
  40. inode_usage() {
  41. find "$1" | wc -l
  42. }
  43. INODE_SIZE=128
  44. INODE_COUNT=$(($(inode_usage "$SERENITY_SOURCE_DIR/Base") + $(inode_usage Root)))
  45. INODE_COUNT=$((INODE_COUNT + 2000)) # Some additional inodes for toolchain files, could probably also be calculated
  46. DISK_SIZE_BYTES=$((($(disk_usage "$SERENITY_SOURCE_DIR/Base") + $(disk_usage Root)) * 1024))
  47. DISK_SIZE_BYTES=$((DISK_SIZE_BYTES + (INODE_COUNT * INODE_SIZE)))
  48. if [ -z "$SERENITY_DISK_SIZE_BYTES" ]; then
  49. # Try to use heuristics to guess a good disk size and inode count.
  50. # The disk must notably fit:
  51. # * Data blocks (for both files and directories),
  52. # * Indirect/doubly indirect/triply indirect blocks,
  53. # * Inodes and block bitmaps for each block group,
  54. # * Plenty of extra free space and free inodes.
  55. DISK_SIZE_BYTES=$((DISK_SIZE_BYTES * 2))
  56. INODE_COUNT=$((INODE_COUNT * 7))
  57. else
  58. if [ "$DISK_SIZE_BYTES" -gt "$SERENITY_DISK_SIZE_BYTES" ]; then
  59. die "SERENITY_DISK_SIZE_BYTES is set to $SERENITY_DISK_SIZE_BYTES but required disk size is $DISK_SIZE_BYTES bytes"
  60. fi
  61. DISK_SIZE_BYTES="$SERENITY_DISK_SIZE_BYTES"
  62. fi
  63. USE_EXISTING=0
  64. if [ -f _disk_image ]; then
  65. USE_EXISTING=1
  66. echo "checking existing image"
  67. result=0
  68. "$E2FSCK_PATH" -f -y _disk_image || result=$?
  69. if [ $result -ge 4 ]; then
  70. rm -f _disk_image
  71. USE_EXISTING=0
  72. echo "failed, not using existing image"
  73. else
  74. echo "done"
  75. fi
  76. fi
  77. if [ $USE_EXISTING -eq 1 ]; then
  78. OLD_DISK_SIZE_BYTES=$(wc -c < _disk_image)
  79. if [ "$DISK_SIZE_BYTES" -gt "$OLD_DISK_SIZE_BYTES" ]; then
  80. echo "resizing disk image..."
  81. qemu-img resize -f raw _disk_image "$DISK_SIZE_BYTES" || die "could not resize disk image"
  82. if ! "$RESIZE2FS_PATH" _disk_image; then
  83. rm -f _disk_image
  84. USE_EXISTING=0
  85. echo "failed, not using existing image"
  86. fi
  87. echo "done"
  88. fi
  89. fi
  90. if [ $USE_EXISTING -ne 1 ]; then
  91. printf "setting up disk image... "
  92. qemu-img create -q -f raw _disk_image "$DISK_SIZE_BYTES" || die "could not create disk image"
  93. chown "$SUDO_UID":"$SUDO_GID" _disk_image || die "could not adjust permissions on disk image"
  94. echo "done"
  95. printf "creating new filesystem... "
  96. if [ "$(uname -s)" = "OpenBSD" ]; then
  97. VND=$(vnconfig _disk_image)
  98. (echo "e 0"; echo 83; echo n; echo 0; echo "*"; echo "quit") | fdisk -e "$VND"
  99. newfs_ext2fs -D $INODE_SIZE -n $INODE_COUNT "/dev/r${VND}i" || die "could not create filesystem"
  100. else
  101. "${MKE2FS_PATH}" -q -I "${INODE_SIZE}" -N "${INODE_COUNT}" _disk_image || die "could not create filesystem"
  102. fi
  103. echo "done"
  104. fi
  105. printf "mounting filesystem... "
  106. mkdir -p mnt
  107. use_genext2fs=0
  108. if [ $USE_FUSE2FS -eq 1 ]; then
  109. mount_cmd="$FUSE2FS_PATH _disk_image mnt/ -o fakeroot,rw"
  110. elif [ "$(uname -s)" = "Darwin" ]; then
  111. mount_cmd="fuse-ext2 _disk_image mnt -o rw+,allow_other,uid=501,gid=20"
  112. elif [ "$(uname -s)" = "OpenBSD" ]; then
  113. VND=$(vnconfig _disk_image)
  114. mount_cmd="mount -t ext2fs "/dev/${VND}i" mnt/"
  115. elif [ "$(uname -s)" = "FreeBSD" ]; then
  116. MD=$(mdconfig _disk_image)
  117. mount_cmd="fuse-ext2 -o rw+,direct_io "/dev/${MD}" mnt/"
  118. else
  119. mount_cmd="mount _disk_image mnt/"
  120. fi
  121. if ! eval "$mount_cmd"; then
  122. if command -v genext2fs 1>/dev/null ; then
  123. echo "mount failed but genext2fs exists, use it instead"
  124. use_genext2fs=1
  125. else
  126. die "could not mount filesystem and genext2fs is missing"
  127. fi
  128. else
  129. echo "done"
  130. fi
  131. cleanup() {
  132. if [ -d mnt ]; then
  133. if [ $use_genext2fs = 0 ] ; then
  134. printf "unmounting filesystem... "
  135. if [ $USE_FUSE2FS -eq 1 ]; then
  136. fusermount -u mnt || (sleep 1 && sync && fusermount -u mnt)
  137. else
  138. umount mnt || ( sleep 1 && sync && umount mnt )
  139. fi
  140. rmdir mnt
  141. else
  142. rm -rf mnt
  143. fi
  144. if [ "$(uname -s)" = "OpenBSD" ]; then
  145. vnconfig -u "$VND"
  146. elif [ "$(uname -s)" = "FreeBSD" ]; then
  147. mdconfig -d -u "$MD"
  148. fi
  149. echo "done"
  150. fi
  151. }
  152. trap cleanup EXIT
  153. script_path=$(cd -P -- "$(dirname -- "$0")" && pwd -P)
  154. "$script_path/build-root-filesystem.sh"
  155. if [ $use_genext2fs = 1 ]; then
  156. # regenerate new image, since genext2fs is unable to reuse the previously written image.
  157. # genext2fs is very slow in generating big images, so I use a smaller image here. size can be updated
  158. # if it's not enough.
  159. # not using "-I $INODE_SIZE" since it hangs. Serenity handles whatever default this uses instead.
  160. genext2fs -B 4096 -b $((DISK_SIZE_BYTES / 4096)) -N $INODE_COUNT -d mnt _disk_image || die "try increasing image size (genext2fs -b)"
  161. # if using docker with shared mount, file is created as root, so make it writable for users
  162. chmod 0666 _disk_image
  163. fi