offset_names = bytes_os_size \ bytes_mbr_start \ bytes_mbr_end \ bytes_header_32k_start \ bytes_header_32k_end \ bytes_iso_start \ bytes_iso_end \ bytes_fat12_start \ bytes_fat12_end \ bytes_gpt_mirror_start \ bytes_gpt_mirror_end \ bytes_zip_start \ bytes_zip_end more_offset_names = ${offset_names} \ bytes_fat12_size \ bytes_gpt_mirror_size \ bytes_header_32k_size \ bytes_iso_size \ bytes_zip_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 \ ${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 ${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,}} # 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_fat12_end} + $${bytes_gpt_mirror_size}, bytes_fat12_end bytes_gpt_mirror_size,}} ${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,}} 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 ${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 ${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}/os.zip: ${bld}/os.32k ${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 (cd ${bld}/os.32k.tmp/ && zip -X ../os.zip os.32k) rm ${bld}/os.32k.tmp/os.32k rmdir ${bld}/os.32k.tmp ${bld}/os.zip.adjusted: ${bld}/os.zip ${dep_bytes_zip_start} ${bld}/check_makefile # TODO: the ZIP file can end with a variable-length comment, this would allow us to hide the GPT mirrors. set -x; dd if=/dev/zero bs=1 count=${bytes_zip_start} of=$@ cat $< >> $@ zip --adjust-sfx $@ gdisk_pipe_commands_slowly=while read str; do echo "$$str"; printf "\033[1;33m%s\033[m\n" "$$str" >&2; sleep 0.01; done commit_hash_as_guid=$$((git log -1 --pretty=format:%H || echo 0000000000000000000000000000000000000000) | sed -e 's/^\(.\{8\}\)\(.\{4\}\)\(.\{4\}\)\(.\{4\}\)\(.\{12\}\).*$$/\1-\2-\3-\4-\5/' | tr '[:lower:]' '[:upper:]') git_dirty=test -n "$$(git diff --shortstat)" gpt_disk_guid=${commit_hash_as_guid}$$(if $$git_dirty; then printf '0'; else printf '2'; fi) gpt_partition_guid=${commit_hash_as_guid}$$(if $$git_dirty; then printf '1'; else printf '3'; fi) ${os_filename}: ${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=$@ # pad with zeroes to prepare for GPT table set -x; dd if=/dev/zero seek=$$((${bytes_gpt_mirror_end} - 1 )) bs=1 count=1 conv=notrunc 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\n${gpt_disk_guid}\n"; \ printf "c\n${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 # 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=$@ 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/^[^ ]\+ \+[^ ]\+ \+//' $< > $@