offset_names = bytes_os_size \ bytes_mbr_start \ bytes_mbr_end \ bytes_gpt_header_start \ bytes_header_32k_start \ bytes_header_32k_end \ bytes_iso_start \ bytes_iso_end \ bytes_fat12_start \ bytes_fat12_end \ bytes_zip_size \ bytes_zip_start \ bytes_zip_end \ zip_byte_before_gpt \ bytes_gpt_mirror_size \ bytes_gpt_mirror_start \ bytes_gpt_header_mirror_start \ bytes_gpt_mirror_end more_offset_names = ${offset_names} \ bytes_fat12_size \ bytes_header_32k_size \ bytes_iso_size \ sectors_fat12_size \ sectors_fat12_start \ sectors_gpt_mirror_size \ sectors_iso_size \ sectors_os_size \ sectors_zip_size \ tracks_fat12_size \ tracks_gpt_mirror_size \ tracks_iso_size \ tracks_os_size \ tracks_zip_size more_offset_dec = ${more_offset_names:%=${bld}/offsets/%.dec} more_offset_hex = ${more_offset_names:%=${bld}/offsets/%.hex} # + os.arm.disasm # + os.reasm.disasm built_files += ${os_filename} \ ${bld}/os.ndisasm.disasm \ ${bld}/os.reasm.asm \ ${bld}/os.reasm \ ${bld}/os.file \ ${bld}/os.gdisk \ ${bld}/os.zip \ ${bld}/os.zip.adjusted \ ${bld}/os.iso \ ${bld}/os.32k \ ${bld}/os.fat12 \ ${bld}/os.offsets.hex \ ${bld}/os.offsets.dec \ ${bld}/os.hex_with_offsets \ ${bld}/iso_files/os.zip \ ${bld}/iso_files/boot/iso_boot.sys \ ${bld}/gpt_guid1 \ ${bld}/gpt_guid2 \ ${bld}/os_with_guid_0 \ ${bld}/os_with_guid_hash \ ${bld}/os_with_guid_hash.partition_array \ ${bld}/os_with_guid_hash.gpt_header_fixed_crc \ ${bld}/os_with_guid_hash.fixed_gpt_crc \ ${bld}/zip_comment \ ${more_offset_dec} \ ${more_offset_hex} \ # Temporary copies used to adjust timestamps for reproducible builds. # These are normally created and deleted within a single target, but # could remain if make is interrupted during the build. temp_files = ${bld}/iso_files.tmp/os.zip \ ${bld}/iso_files.tmp/boot/iso_boot.sys temp_directories = ${bld}/iso_files.tmp \ ${bld}/iso_files.tmp/boot/ built_directories += ${bld}/iso_files/boot \ ${bld}/iso_files \ ${bld}/offsets os_image_size_kb = 1440 os_partition_start_sectors = 3 os_partition_size_sectors = 717 # 720 - start # CHS parameters for 1.44 MB floppy disk os_floppy_chs_h = 2 os_floppy_chs_s = 9 in-guix: ${os_filename} \ ${bld}/os.ndisasm.disasm \ ${bld}/os.reasm.asm \ ${bld}/os.file \ ${bld}/os.gdisk \ ${bld}/os.offsets.hex \ ${bld}/os.offsets.dec \ ${bld}/os.hex_with_offsets \ ${more_offset_dec} \ ${more_offset_hex} \ ${bld}/check_makefile # 32k header of the ISO9660 image ${bld}/os.32k: example-os/os.asm ${bld}/check_makefile nasm -w+macro-params -w+macro-selfref -w+orphan-labels -w+gnu-elf-extensions -o $@ $< # Circumvent the fact that faketime does not work on system binaries in macos ./utils/mformat ./utils/mcopy: ${bld}/check_makefile # TODO: depend on the relevant binary cp $$(which $$(basename $@)) $@ chmod u+x $@ ./utils/mkisofs: ${bld}/check_makefile # TODO: depend on the mkisofs binary cp $$(which $$(basename $@) || which xorriso) $@ chmod u+x $@ cp_T_option = $$(if test "$$(uname -s)" = Darwin; then echo ''; else echo '-T'; fi) ${bld}/os.iso: ${bld}/iso_files/os.zip ${bld}/iso_files/boot/iso_boot.sys ./utils/mkisofs ${bld}/check_makefile ! test -d ${bld}/iso_files.tmp cp -a ${cp_T_option} -- ${bld}/iso_files ${bld}/iso_files.tmp find ${bld}/iso_files.tmp -depth -exec touch -t ${commit_timestamp} '{}' ';' UTILS="$$PWD/utils" ./utils/faketime.sh ${real_commit_timestamp_iso_8601} \ sh -c '(cd ./${bld}/iso_files.tmp/ && "$$UTILS/mkisofs" \ --input-charset utf-8 \ -rock \ -joliet \ -eltorito-catalog boot/boot.cat \ -eltorito-boot boot/iso_boot.sys \ -no-emul-boot \ -boot-load-size 4 \ -pad \ -output ../os.iso \ .)' rm -- ${bld}/iso_files.tmp/os.zip \ ${bld}/iso_files.tmp/boot/iso_boot.sys rmdir ${bld}/iso_files.tmp/boot/ rmdir ${bld}/iso_files.tmp/ # Layout: # MBR; GPT; UNIX sh & MS-DOS batch scripts; ISO9660; FAT12; GPT mirror; ZIP define offset tmp_${1} = ${3} ${bld}/offsets/${1}.dec: $${tmp_${1}:%=${bld}/offsets/%.dec} ${4} ${bld}/check_makefile echo $$$$(( ${2} )) | tee $$@ ${1} = $$$$(cat ${bld}/offsets/${1}.dec) dep_${1} = ${bld}/offsets/${1}.dec endef define div_round_up ( ( ( ${1} ) + ( ${2} ) - 1 ) / ( ${2} ) ) endef sector_size = 512 # should be exact (TODO: make a check) ${eval ${call offset,bytes_os_size, $${os_image_size_kb} * 1024,, }} ${eval ${call offset,sectors_os_size, $${bytes_os_size} / $${sector_size}, bytes_os_size,}} ${eval ${call offset,tracks_os_size, $${sectors_os_size} / $${os_floppy_chs_s}, sectors_os_size,}} # Primary GPT ${eval ${call offset,bytes_gpt_header_start, $${sector_size},, }} # round up ${eval ${call offset,bytes_iso_size, $$$$(utils/file-length.sh -c ${bld}/os.iso), ,${bld}/os.iso}} ${eval ${call offset,sectors_iso_size, ${call div_round_up,$${bytes_iso_size},$${sector_size}}, bytes_iso_size,}} ${eval ${call offset,tracks_iso_size, ${call div_round_up,$${sectors_iso_size},$${os_floppy_chs_s}}, sectors_iso_size,}} # round up ${eval ${call offset,bytes_zip_size, $$$$(utils/file-length.sh -c ${bld}/os.zip), ,${bld}/os.zip}} ${eval ${call offset,sectors_zip_size, ${call div_round_up,$${bytes_zip_size},$${sector_size}}, bytes_zip_size,}} ${eval ${call offset,tracks_zip_size, ${call div_round_up,$${sectors_zip_size},$${os_floppy_chs_s}}, sectors_zip_size,}} # round up ${eval ${call offset,sectors_gpt_mirror_size, 33,, }} ${eval ${call offset,tracks_gpt_mirror_size, ${call div_round_up,$${sectors_gpt_mirror_size},$${os_floppy_chs_s}}, sectors_gpt_mirror_size,}} # allocate the remaining sectors to the FAT, aligned on tracks ${eval ${call offset,tracks_fat12_size, $${tracks_os_size} - $${tracks_iso_size} - $${tracks_gpt_mirror_size} - $${tracks_zip_size}, tracks_os_size tracks_iso_size tracks_gpt_mirror_size tracks_zip_size,}} ${eval ${call offset,sectors_fat12_size,$${tracks_fat12_size} * $${os_floppy_chs_s}, tracks_fat12_size,}} # zip should probably have its end aligned, not its start ${eval ${call offset,bytes_zip_start, $${bytes_os_size} - $${bytes_zip_size}, bytes_os_size bytes_zip_size,}} ${eval ${call offset,bytes_mbr_start, 0,,}} ${eval ${call offset,bytes_mbr_end, 512,,}} ${eval ${call offset,bytes_header_32k_start, 0,,}} ${eval ${call offset,bytes_header_32k_end, 32 * 1024,,}} ${eval ${call offset,bytes_header_32k_size, $${bytes_header_32k_end} - $${bytes_header_32k_start}, bytes_header_32k_end bytes_header_32k_start,}} ${eval ${call offset,bytes_iso_start, 32 * 1024,,}} ${eval ${call offset,bytes_iso_end, $${sectors_iso_size} * $${sector_size}, sectors_iso_size,}} ${eval ${call offset,bytes_fat12_start, $${tracks_iso_size} * $${os_floppy_chs_s} * $${sector_size}, tracks_iso_size,}} ${eval ${call offset,sectors_fat12_start, $${bytes_fat12_start} / $${sector_size}, bytes_fat12_start,}} ${eval ${call offset,bytes_fat12_size, $${sectors_fat12_size} * $${sector_size}, sectors_fat12_size,}} ${eval ${call offset,bytes_fat12_end, $${bytes_fat12_start} + $${bytes_fat12_size}, bytes_fat12_start bytes_fat12_size,}} # It is probably not necessary to align the GPT mirror end on a track boundary. ${eval ${call offset,bytes_gpt_mirror_size, $${sectors_gpt_mirror_size} * $${sector_size}, sectors_gpt_mirror_size,}} ${eval ${call offset,bytes_gpt_mirror_end, $${bytes_os_size}, bytes_os_size,}} ${eval ${call offset,bytes_gpt_header_mirror_start, $${bytes_gpt_mirror_end} - $${sector_size}, bytes_gpt_mirror_end,}} ${eval ${call offset,bytes_gpt_mirror_start, $${bytes_gpt_mirror_end} - $${bytes_gpt_mirror_size}, bytes_gpt_mirror_end bytes_gpt_mirror_size,}} ${eval ${call offset,bytes_zip_end, $${bytes_os_size}, bytes_os_size,}} ${eval ${call offset,zip_byte_before_gpt, $${bytes_gpt_mirror_start} - 1, bytes_gpt_mirror_start,}} os_fat12_partition = "$@@@${bytes_fat12_start}" ${bld}/os.fat12: ${bld}/os.zip ${dep_bytes_fat12_size} ${dep_bytes_fat12_start} ${dep_sectors_os_size} \ ./utils/mformat ./utils/mcopy ${bld}/check_makefile set -x; dd if=/dev/zero bs=${sector_size} count=${sectors_os_size} of=$@ ./utils/faketime.sh ${real_commit_timestamp_iso_8601} ./utils/mformat -v "Example OS" \ -T ${sectors_fat12_size} \ -h ${os_floppy_chs_h} \ -s ${os_floppy_chs_s} \ -i ${os_fat12_partition} ./utils/faketime.sh ${real_commit_timestamp_iso_8601} ./utils/mcopy -i ${os_fat12_partition} $< "::os.zip" ${bld}/iso_files/os.zip: ${bld}/os.zip ${bld}/check_makefile # TODO: make it so that the various file formats are mutual quines: # * the ISO should contain the original file # * the ZIP should contain the original file # * the FAT12 should contain the original file cp $< $@ # 4 sectors loaded when booting from optical media (CD-ROM, …): ${bld}/iso_files/boot/iso_boot.sys: ${bld}/os.32k ${bld}/check_makefile # TODO: this copy of the (or alternate) bootsector should contain a Boot Information Table, # see https://wiki.osdev.org/El-Torito#A_BareBones_Boot_Image_with_Boot_Information_Table dd if=$< bs=512 count=4 of=$@ ${bld}/zip_comment: ${dep_bytes_gpt_mirror_size} ${bld}/check_makefile # Reserve enough space for the secondary GPT. # # The X will be replaced by a 0x00 byte after the zip is created and # adjusted, because the zip utility otherwise truncates the archive # comment. # # It seems that the zip utility injects extra '\r\n' line endings after # every 256 characters, which changes the total length of the # comment. We therefore insert a \n directly (zip seems to convert line # endings from Unix \n to DOS \r\n), and aim for the exact desired size. (printf "%sX\n" '${os_name}'; \ for i in $$(seq $$((${sectors_gpt_mirror_size} * 2))); do \ printf "%254s\n" ''; \ done) > $@ ${bld}/os.zip: ${bld}/os.32k ${bld}/zip_comment ${bld}/check_makefile # We copy os.32k and alter its timestamp to ensure reproducible # builds. mkdir -p ${bld}/os.32k.tmp cp -a $< ${bld}/os.32k.tmp/os.32k touch -t ${commit_timestamp} ${bld}/os.32k.tmp/os.32k # If there is already an archive present from a previous build, zip # risks trying to update it, so we have to delete it if it exists. rm -f $@ (cd ${bld}/os.32k.tmp/ && zip -X --archive-comment ../os.zip os.32k < ../zip_comment) rm ${bld}/os.32k.tmp/os.32k rmdir ${bld}/os.32k.tmp ${bld}/os.zip.adjusted: ${bld}/os.zip ${dep_bytes_zip_start} ${dep_zip_byte_before_gpt} ${bld}/check_makefile set -x; dd if=/dev/zero bs=1 count=${bytes_zip_start} of=$@ cat $< >> $@ zip --adjust-sfx $@ # Mark the end of the real ZIP comment, so that the rest is not printed. # The rest of the comment contains the GPT mirror. set -x; dd if=/dev/zero bs=1 count=1 conv=notrunc seek=${zip_byte_before_gpt} of=$@ gdisk_pipe_commands_slowly=while read str; do echo "$$str"; printf "\033[1;33m%s\033[m\n" "$$str" >&2; sleep 0.01; done # The last digit of guid1 is 1, for guid2 it's 2, and so on. # This allows us to have a disk GUID which is different from the partition GUID ${bld}/gpt_guid%: ${bld}/os_with_guid_0 ${bld}/check_makefile sha256sum $< \ | sed -e 's/^\(.\{8\}\)\(.\{4\}\)\(.\{4\}\)\(.\{4\}\)\(.\{11\}\).*$$/\1-\2-\3-\4-\5$*/' \ | tr '[:lower:]' '[:upper:]' \ > $@ gpt_disk_guid=${bld}/gpt_guid1 gpt_partition_guid=${bld}/gpt_guid2 ${bld}/os_with_guid_0: ${bld}/os.32k ${bld}/os.iso ${bld}/os.fat12 ${bld}/os.zip.adjusted \ ${dep_bytes_header_32k_start} \ ${dep_bytes_header_32k_size} \ ${dep_bytes_fat12_start} \ ${dep_bytes_fat12_size} \ ${dep_bytes_gpt_mirror_start} \ ${dep_bytes_gpt_mirror_end} \ ${dep_sectors_fat12_start} \ ${dep_sectors_fat12_size} \ ${dep_bytes_zip_start} \ ${bld}/check_makefile rm -f $@ # start with the .iso cp ${bld}/os.iso $@ # splice in the first 32k (bootsector and partition table) set -x; dd skip=${bytes_header_32k_start} seek=${bytes_header_32k_start} bs=1 count=${bytes_header_32k_size} conv=notrunc if=${bld}/os.32k of=$@ # splice in fat12 set -x; dd skip=${bytes_fat12_start} seek=${bytes_fat12_start} bs=1 count=${bytes_fat12_size} conv=notrunc if=${bld}/os.fat12 of=$@ # splice in zip at the end set -x; dd skip=${bytes_zip_start} seek=${bytes_zip_start} bs=1 conv=notrunc if=${bld}/os.zip.adjusted of=$@ # patch the partition table # Thanks to https://wiki.gentoo.org/wiki/Hybrid_partition_table for showing that gdisk can be used to make a hybrid MBR / GPT. # gdisk commands: # * Delete (the only) partition, eXpert mode, sector aLignment = 1, back to Main menu, # * New partition (number = 1, start sector, end sector, type 0700) # * Recovery and transformation options, make Hybrid, # * add GPT partition #1 to the hybrid MBR, do Not put the EFI partition first, # * MBR partition type=0x01, bootable=Yes, do Not add extra partitions, # * back to Main menu, # * eXpert mode, # * Change partition GUID, the GUID itself, # * change disk GUID, the GUID itself, # * Print GPT, print prOtective MBR, Write, Proceed. (if test "$$(uname -o)" = "Cygwin"; then printf "Y\n"; fi; \ printf "d\nx\nl\n1\nm\n"; \ printf "n\n1\n${sectors_fat12_start}\n${sectors_fat12_size}\n0700\n"; \ printf "r\nh\n"; \ printf "1\nN\n"; \ printf "01\nY\nN\n"; \ printf "x\n"; \ printf "g\n00000000-0000-0000-0000-000000000000\n"; \ printf "c\n00000000-0000-0000-0000-000000000000\n"; \ printf "p\no\nw\nY\n") | ${gdisk_pipe_commands_slowly} | gdisk $@ ${bld}/os_with_guid_hash: ${bld}/os_with_guid_0 \ ${gpt_disk_guid} ${gpt_partition_guid} \ ${bld}/check_makefile cp $< $@ # gdisk commands: # * eXpert mode, # * Change partition GUID, the GUID itself, # * change disk GUID, the GUID itself, # * Print GPT, print prOtective MBR, Write, Proceed. (if test "$$(uname -o)" = "Cygwin"; then printf "Y\n"; fi; \ printf "x\n"; \ printf "g\n$$(cat ${gpt_disk_guid})\n"; \ printf "c\n$$(cat ${gpt_partition_guid})\n"; \ printf "p\no\nw\nY\n") | ${gdisk_pipe_commands_slowly} | gdisk $@ # Inject MS-DOS newlines (CR+LF) and comments (":: ") in the GUID field of unused partition table entries, # so that the part that is to be skipped by MS-DOS does not form a line longer than the MS-DOS maximum # line length (8192 excluding CR+LF). $i below is the partition entry number, starting from 1 # The numbers 55 and 118 are arbitrarily chosen so that the space between two CR+LF is less than 8192. for i in 55 118; do \ printf "\r\n:: %02x" $$i | dd bs=1 seek=$$(( 1024 + ( ($$i) - 1) * 128 + 16)) count=7 conv=notrunc of=$@; \ printf "\r\n:: %02x" $$i | dd bs=1 seek=$$(( ${bytes_gpt_mirror_start} + ( ($$i) - 1) * 128 + 16)) count=7 conv=notrunc of=$@; \ done %.partition_array: % ${bld}/check_makefile # Extract the partition array header dd if=$< bs=512 skip=2 count=32 of=$@ # The GPT header of the given disk image, with its CRC adjusted to the actual # data. It is used to fix the CRC after injecting MS-DOS comments in ignored # parts of the partition array. %.gpt_header_fixed_crc: % %.partition_array % ${bld}/check_makefile # Extract the GPT header dd if=$< bs=512 skip=1 count=1 of=$@ # * Compute the CRC32 of the particion array # * Convert endianness # * Convert to binary # * Inject into the GPT header crc32 $*.partition_array \ | sed -e 's/^\(..\)\(..\)\(..\)\(..\)$$/\4\3\2\1/' \ | xxd -r -ps \ | dd bs=1 seek=88 count=4 conv=notrunc of=$@ # Zero-out the CRC of the header itself printf '00000000' | xxd -r -ps | dd bs=1 seek=16 count=4 conv=notrunc of=$@ # * Compute the CRC32 of the GPT header (with the CRC field itself zeroed out) # * Convert endianness # * Convert to binary # * Inject into the GPT header crc32 $@ \ | sed -e 's/^\(..\)\(..\)\(..\)\(..\)$$/\4\3\2\1/' \ | xxd -r -ps \ | dd bs=1 seek=16 count=4 conv=notrunc of=$@ %.fixed_gpt_crc: % %.gpt_header_fixed_crc \ ${dep_bytes_gpt_header_start} ${dep_bytes_gpt_header_mirror_start} \ ${bld}/check_makefile cp $< $@ # Inject the fixed GPT header dd if=$*.gpt_header_fixed_crc bs=1 seek=${bytes_gpt_header_start} count=${sector_size} conv=notrunc of=$@ # Inject the fixed GPT header in the mirror dd if=$*.gpt_header_fixed_crc bs=1 seek=${bytes_gpt_header_mirror_start} count=${sector_size} conv=notrunc of=$@ ${os_filename}: ${bld}/os_with_guid_hash.fixed_gpt_crc ${bld}/check_makefile cp -f $< $@ chmod a+x-w $@ ${bld}/os.file: ${os_filename} ${bld}/check_makefile file -kr $< > $@ ${bld}/os.gdisk: ${os_filename} ${bld}/check_makefile # gdisk commands: # * eXpert mode # * Print partition table #  * print detailed Information about the (only) partition # * print prOtective MBR table # * Quit printf '2\nx\np\ni\nr\no\nq\n' | ${gdisk_pipe_commands_slowly} | gdisk $< | tee $@ ${bld}/os.offsets.hex: ${offset_names:%=${bld}/offsets/%.hex} ${bld}/check_makefile grep '^' ${offset_names:%=${bld}/offsets/%.hex} | sed -e 's|^.*/||' -e 's/:/: 0x/' | column -t > $@ ${bld}/os.offsets.dec: ${offset_names:%=${bld}/offsets/%.dec} ${bld}/check_makefile grep '^' ${offset_names:%=${bld}/offsets/%.dec} | sed -e 's|^.*/||' -e 's/:/: /' | column -t > $@ ${bld}/offsets/%.hex: ${bld}/offsets/%.dec printf '%x\n' $$(cat $<) > $@ ${bld}/os.hex_with_offsets: ${os_filename} ${bld}/os.offsets.hex hexdump -C $< \ | grep -E -e "($$(cat ${bld}/os.offsets.hex | cut -d '=' -f 2 | sed -e 's/^[[:space:]]*0x\(.*\).$$/^\10/' | tr '\n' '|')^)" --color=yes > $@ ${bld}/os.ndisasm.disasm: ${os_filename} utils/compact-ndisasm.sh ${bld}/check_makefile ./utils/compact-ndisasm.sh $< $@ ${bld}/os.reasm.asm: ${bld}/os.ndisasm.disasm ${bld}/check_makefile sed -e 's/^[^ ]\+ \+[^ ]\+ \+//' $< > $@