DF-2551 / gen_udf.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | #!/usr/bin/env python3 """gen_udf.py -- minimal mountable UDF 1.02 image for DF-2551. Satisfies exactly what DragonFly's udf_mountfs/udf_vget/udf_readdir parse: sector 256 : anchor_vdp -> main_vds_ex MVDS : logvol_desc (type-1 part map) + part_desc + term FSD : fileset_desc -> rootdir_icb root FE : file_entry file_type=4 (VDIR), short_ad -> FID stream FIDs : parent ('..'-style) + 'regfile.txt' -> FE lb reg FE : file_entry file_type=5 (VREG) <-- v_object NEVER set data : file content udf_checktag() only verifies tag.id and the byte checksum (crc NOT checked). """ import struct, sys BS = 2048 PART_START = 32 # partition begins at this image sector LB_LVD = 32 # inside partition? NO -- MVDS lives in image space PART_LEN = 4096 # image-space sectors (outside partition): MVDS at 16..19 SEC_LVD = 16 SEC_PD = 17 SEC_TERM = 18 # partition-relative LBNs LB_FSD = 0 LB_ROOTFE = 1 LB_DIRDATA = 2 LB_REGFE = 3 LB_FILEDATA = 4 img = bytearray(BS * 512) def tag(tid, loc, ver=3, crc=0, crclen=0): t = bytearray(16) struct.pack_into('<HHBBHHHI', t, 0, tid, ver, 0, 0, 0, crc, crclen, loc) return t def fix_cksum(buf, off=0): s = (sum(buf[off:off+15]) - buf[off+4]) & 0xFF buf[off+4] = s return buf def put(sec, data): img[sec*BS:sec*BS+len(data)] = data # ---------------- anchor (sector 256) ---------------- a = bytearray(BS) a[0:16] = tag(2, 256) struct.pack_into('<II', a, 16, 3*BS, SEC_LVD) # main_vds_ex len,loc struct.pack_into('<II', a, 24, 3*BS, SEC_LVD) # reserve_vds_ex put(256, fix_cksum(a)) # ---------------- logical volume descriptor ---------------- lvd = bytearray(BS) lvd[0:16] = tag(6, SEC_LVD) struct.pack_into('<I', lvd, 16, 1) # seq_num lvd[20] = 0 # charspec type=0 lvd[84:84+8] = b'LOGVOL0' # logvol_id struct.pack_into('<I', lvd, 212, BS) # lb_size lvd[216] = 0 # domain_id flags lvd[217:217+8] = b'*OSTA UDF ' # domain regid # _lvd_use.fsd_loc (long_ad) at 248 struct.pack_into('<I', lvd, 248, BS) # len struct.pack_into('<IH', lvd, 252, LB_FSD, 0) # lb_num, part_num struct.pack_into('<H', lvd, 258, 0) # ad_flags struct.pack_into('<I', lvd, 260, 0) # ad_id struct.pack_into('<I', lvd, 264, 64) # mt_l (1 x 64-byte map) struct.pack_into('<I', lvd, 268, 1) # n_pm = 1 # imp_id regid at 272 (32 bytes) zero # imp_use[128] at 304 struct.pack_into('<II', lvd, 432, 0, 0) # integrity_seq extent # maps[0] type-1 part map at 440 lvd[440] = 1; lvd[441] = 6 struct.pack_into('<HH', lvd, 442, 1, 0) # vol_seq_num, part_num put(SEC_LVD, fix_cksum(lvd)) # ---------------- partition descriptor ---------------- pd = bytearray(BS) pd[0:16] = tag(5, SEC_PD) struct.pack_into('<I', pd, 16, 1) # seq_num struct.pack_into('<HH', pd, 20, 3, 0) # flags=allocated, part_num=0 pd[24] = 0 # contents regid flags pd[25:25+6] = b'+NSR02' # contents id # contents_use[128] at 56..184 ; access_type @184 struct.pack_into('<I', pd, 184, 0) struct.pack_into('<I', pd, 188, PART_START) # start_loc struct.pack_into('<I', pd, 192, PART_LEN) # part_len put(SEC_PD, fix_cksum(pd)) # ---------------- terminating descriptor ---------------- tm = bytearray(BS) tm[0:16] = tag(8, SEC_TERM) put(SEC_TERM, fix_cksum(tm)) # ---------------- file set descriptor (partition LB 0) ---------------- fsd = bytearray(BS) fsd[0:16] = tag(256, PART_START + LB_FSD) # timestamps/ids can stay zero # rootdir_icb long_ad at offset: tag16+time12+ichg2+max2+charset4+charset4+ # fileset_num4+desc_num4+charspec64+logvol_id128+charspec64+fileset_id32+ # copyright32+abstract32 = 16+12+2+2+4+4+4+4+64+128+64+32+32+32 = 400 off = 400 struct.pack_into('<I', fsd, off, BS) # len struct.pack_into('<IH', fsd, off+4, LB_ROOTFE, 0) struct.pack_into('<H', fsd, off+10, 0) struct.pack_into('<I', fsd, off+12, 0) put(PART_START + LB_FSD, fix_cksum(fsd)) # ---------------- root directory file entry ---------------- def file_entry(lb, ftype, inf_len, ads): fe = bytearray(BS) fe[0:16] = tag(261, PART_START + lb) # icb_tag at 16: prev@16 strat_type@20 strat_param@22 max@24 res@26 # file_type@27 parent_icb@28 (lb_num@28,part_num@32) flags@34 struct.pack_into('<I', fe, 16, 0) # prev_num_dirs struct.pack_into('<H', fe, 20, 4) # strat_type struct.pack_into('<H', fe, 24, 1) # max_num_entries fe[27] = ftype # file_type struct.pack_into('<IH', fe, 28, 0, 0) # parent_icb struct.pack_into('<H', fe, 34, 0) # flags: short_ads struct.pack_into('<III', fe, 36, 0, 0, 0x1CE7) # uid gid perm(rwx all) struct.pack_into('<H', fe, 48, 1) # link_cnt struct.pack_into('<BB', fe, 50, 0, 0) # rec_format, rec_disp_attr struct.pack_into('<I', fe, 52, 0) # rec_len struct.pack_into('<Q', fe, 56, inf_len) # inf_len struct.pack_into('<Q', fe, 64, (inf_len + BS - 1)//BS) # logblks_rec # atime @72, mtime @84, attrtime @96 -- zero struct.pack_into('<I', fe, 108, 1) # ckpoint # ex_attr_icb @112 (16) ; imp_id @128 (32) ; unique_id @160 (8) struct.pack_into('<Q', fe, 160, lb) struct.pack_into('<II', fe, 168, 0, 8*len(ads)) # l_ea=0, l_ad o = 176 for (ln, pos) in ads: struct.pack_into('<II', fe, o, ln, pos) o += 8 return fix_cksum(fe) # directory FID stream def fid(parent, name, lb): nm = name if isinstance(name, bytes) else name.encode() f = bytearray(38 + len(nm)) f[0:16] = bytearray(16) # tag placeholder struct.pack_into('<H', f, 16, 0) # file_num f[18] = 0x01 | (0x08 if parent else 0x00) # file_char VIS|PAR f[19] = len(nm) # l_fi struct.pack_into('<I', f, 20, BS) # icb len struct.pack_into('<IH', f, 24, lb, 0) struct.pack_into('<H', f, 30, 0) # ad_flags struct.pack_into('<I', f, 32, 0) # ad_id struct.pack_into('<H', f, 36, 0) # l_iu f[38:38+len(nm)] = nm # real tag id + cksum struct.pack_into('<HH', f, 0, 257, 3) fix_cksum(f) # 4-byte align while len(f) % 4: f += b'\0' return f # parent FID: l_fi must be 0 (kernel skips l_fi==0 && FILE_CHAR_PAR entries) # regular FID: CS0 identifier = compID byte (8) + 8-bit chars dirdata = fid(True, b'', LB_ROOTFE) + fid(False, b'\x08regfile.txt', LB_REGFE) put(PART_START + LB_DIRDATA, dirdata) put(PART_START + LB_ROOTFE, file_entry(LB_ROOTFE, 4, len(dirdata), [(len(dirdata), LB_DIRDATA)])) # ---------------- regular file (THE TARGET: VREG, no v_object) ----------- content = b'hello-udf-file-content-12345\n' put(PART_START + LB_FILEDATA, content) put(PART_START + LB_REGFE, file_entry(LB_REGFE, 5, len(content), [(len(content), LB_FILEDATA)])) out = sys.argv[1] if len(sys.argv) > 1 else 'mini_udf.img' open(out, 'wb').write(img) print("wrote %s (%d bytes)" % (out, len(img))) |