diff options
Diffstat (limited to 'fs/udf')
-rw-r--r-- | fs/udf/inode.c | 9 | ||||
-rw-r--r-- | fs/udf/partition.c | 55 | ||||
-rw-r--r-- | fs/udf/super.c | 190 | ||||
-rw-r--r-- | fs/udf/udf_sb.h | 16 | ||||
-rw-r--r-- | fs/udf/udfdecl.h | 2 |
5 files changed, 258 insertions, 14 deletions
diff --git a/fs/udf/inode.c b/fs/udf/inode.c index c150b6df6261..6e151f170c08 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c | |||
@@ -1301,6 +1301,15 @@ static void udf_fill_inode(struct inode *inode, struct buffer_head *bh) | |||
1301 | inode->i_op = &page_symlink_inode_operations; | 1301 | inode->i_op = &page_symlink_inode_operations; |
1302 | inode->i_mode = S_IFLNK | S_IRWXUGO; | 1302 | inode->i_mode = S_IFLNK | S_IRWXUGO; |
1303 | break; | 1303 | break; |
1304 | case ICBTAG_FILE_TYPE_MAIN: | ||
1305 | udf_debug("METADATA FILE-----\n"); | ||
1306 | break; | ||
1307 | case ICBTAG_FILE_TYPE_MIRROR: | ||
1308 | udf_debug("METADATA MIRROR FILE-----\n"); | ||
1309 | break; | ||
1310 | case ICBTAG_FILE_TYPE_BITMAP: | ||
1311 | udf_debug("METADATA BITMAP FILE-----\n"); | ||
1312 | break; | ||
1304 | default: | 1313 | default: |
1305 | printk(KERN_ERR "udf: udf_fill_inode(ino %ld) failed unknown " | 1314 | printk(KERN_ERR "udf: udf_fill_inode(ino %ld) failed unknown " |
1306 | "file type=%d\n", inode->i_ino, | 1315 | "file type=%d\n", inode->i_ino, |
diff --git a/fs/udf/partition.c b/fs/udf/partition.c index b2e6e1eddb90..2dfe4be2eeb2 100644 --- a/fs/udf/partition.c +++ b/fs/udf/partition.c | |||
@@ -266,3 +266,58 @@ int udf_relocate_blocks(struct super_block *sb, long old_block, long *new_block) | |||
266 | 266 | ||
267 | return 0; | 267 | return 0; |
268 | } | 268 | } |
269 | |||
270 | static uint32_t udf_try_read_meta(struct inode *inode, uint32_t block, | ||
271 | uint16_t partition, uint32_t offset) | ||
272 | { | ||
273 | struct super_block *sb = inode->i_sb; | ||
274 | struct udf_part_map *map; | ||
275 | kernel_lb_addr eloc; | ||
276 | uint32_t elen; | ||
277 | sector_t ext_offset; | ||
278 | struct extent_position epos = {}; | ||
279 | uint32_t phyblock; | ||
280 | |||
281 | if (inode_bmap(inode, block, &epos, &eloc, &elen, &ext_offset) != | ||
282 | (EXT_RECORDED_ALLOCATED >> 30)) | ||
283 | phyblock = 0xFFFFFFFF; | ||
284 | else { | ||
285 | map = &UDF_SB(sb)->s_partmaps[partition]; | ||
286 | /* map to sparable/physical partition desc */ | ||
287 | phyblock = udf_get_pblock(sb, eloc.logicalBlockNum, | ||
288 | map->s_partition_num, ext_offset + offset); | ||
289 | } | ||
290 | |||
291 | brelse(epos.bh); | ||
292 | return phyblock; | ||
293 | } | ||
294 | |||
295 | uint32_t udf_get_pblock_meta25(struct super_block *sb, uint32_t block, | ||
296 | uint16_t partition, uint32_t offset) | ||
297 | { | ||
298 | struct udf_sb_info *sbi = UDF_SB(sb); | ||
299 | struct udf_part_map *map; | ||
300 | struct udf_meta_data *mdata; | ||
301 | uint32_t retblk; | ||
302 | struct inode *inode; | ||
303 | |||
304 | udf_debug("READING from METADATA\n"); | ||
305 | |||
306 | map = &sbi->s_partmaps[partition]; | ||
307 | mdata = &map->s_type_specific.s_metadata; | ||
308 | inode = mdata->s_metadata_fe ? : mdata->s_mirror_fe; | ||
309 | |||
310 | /* We shouldn't mount such media... */ | ||
311 | BUG_ON(!inode); | ||
312 | retblk = udf_try_read_meta(inode, block, partition, offset); | ||
313 | if (retblk == 0xFFFFFFFF) { | ||
314 | udf_warning(sb, __func__, "error reading from METADATA, " | ||
315 | "trying to read from MIRROR"); | ||
316 | inode = mdata->s_mirror_fe; | ||
317 | if (!inode) | ||
318 | return 0xFFFFFFFF; | ||
319 | retblk = udf_try_read_meta(inode, block, partition, offset); | ||
320 | } | ||
321 | |||
322 | return retblk; | ||
323 | } | ||
diff --git a/fs/udf/super.c b/fs/udf/super.c index 787cedf6cc07..29b19678327a 100644 --- a/fs/udf/super.c +++ b/fs/udf/super.c | |||
@@ -950,6 +950,101 @@ static int udf_load_pvoldesc(struct super_block *sb, sector_t block) | |||
950 | return 0; | 950 | return 0; |
951 | } | 951 | } |
952 | 952 | ||
953 | static int udf_load_metadata_files(struct super_block *sb, int partition) | ||
954 | { | ||
955 | struct udf_sb_info *sbi = UDF_SB(sb); | ||
956 | struct udf_part_map *map; | ||
957 | struct udf_meta_data *mdata; | ||
958 | kernel_lb_addr addr; | ||
959 | int fe_error = 0; | ||
960 | |||
961 | map = &sbi->s_partmaps[partition]; | ||
962 | mdata = &map->s_type_specific.s_metadata; | ||
963 | |||
964 | /* metadata address */ | ||
965 | addr.logicalBlockNum = mdata->s_meta_file_loc; | ||
966 | addr.partitionReferenceNum = map->s_partition_num; | ||
967 | |||
968 | udf_debug("Metadata file location: block = %d part = %d\n", | ||
969 | addr.logicalBlockNum, addr.partitionReferenceNum); | ||
970 | |||
971 | mdata->s_metadata_fe = udf_iget(sb, addr); | ||
972 | |||
973 | if (mdata->s_metadata_fe == NULL) { | ||
974 | udf_warning(sb, __func__, "metadata inode efe not found, " | ||
975 | "will try mirror inode."); | ||
976 | fe_error = 1; | ||
977 | } else if (UDF_I(mdata->s_metadata_fe)->i_alloc_type != | ||
978 | ICBTAG_FLAG_AD_SHORT) { | ||
979 | udf_warning(sb, __func__, "metadata inode efe does not have " | ||
980 | "short allocation descriptors!"); | ||
981 | fe_error = 1; | ||
982 | iput(mdata->s_metadata_fe); | ||
983 | mdata->s_metadata_fe = NULL; | ||
984 | } | ||
985 | |||
986 | /* mirror file entry */ | ||
987 | addr.logicalBlockNum = mdata->s_mirror_file_loc; | ||
988 | addr.partitionReferenceNum = map->s_partition_num; | ||
989 | |||
990 | udf_debug("Mirror metadata file location: block = %d part = %d\n", | ||
991 | addr.logicalBlockNum, addr.partitionReferenceNum); | ||
992 | |||
993 | mdata->s_mirror_fe = udf_iget(sb, addr); | ||
994 | |||
995 | if (mdata->s_mirror_fe == NULL) { | ||
996 | if (fe_error) { | ||
997 | udf_error(sb, __func__, "mirror inode efe not found " | ||
998 | "and metadata inode is missing too, exiting..."); | ||
999 | goto error_exit; | ||
1000 | } else | ||
1001 | udf_warning(sb, __func__, "mirror inode efe not found," | ||
1002 | " but metadata inode is OK"); | ||
1003 | } else if (UDF_I(mdata->s_mirror_fe)->i_alloc_type != | ||
1004 | ICBTAG_FLAG_AD_SHORT) { | ||
1005 | udf_warning(sb, __func__, "mirror inode efe does not have " | ||
1006 | "short allocation descriptors!"); | ||
1007 | iput(mdata->s_mirror_fe); | ||
1008 | mdata->s_mirror_fe = NULL; | ||
1009 | if (fe_error) | ||
1010 | goto error_exit; | ||
1011 | } | ||
1012 | |||
1013 | /* | ||
1014 | * bitmap file entry | ||
1015 | * Note: | ||
1016 | * Load only if bitmap file location differs from 0xFFFFFFFF (DCN-5102) | ||
1017 | */ | ||
1018 | if (mdata->s_bitmap_file_loc != 0xFFFFFFFF) { | ||
1019 | addr.logicalBlockNum = mdata->s_bitmap_file_loc; | ||
1020 | addr.partitionReferenceNum = map->s_partition_num; | ||
1021 | |||
1022 | udf_debug("Bitmap file location: block = %d part = %d\n", | ||
1023 | addr.logicalBlockNum, addr.partitionReferenceNum); | ||
1024 | |||
1025 | mdata->s_bitmap_fe = udf_iget(sb, addr); | ||
1026 | |||
1027 | if (mdata->s_bitmap_fe == NULL) { | ||
1028 | if (sb->s_flags & MS_RDONLY) | ||
1029 | udf_warning(sb, __func__, "bitmap inode efe " | ||
1030 | "not found but it's ok since the disc" | ||
1031 | " is mounted read-only"); | ||
1032 | else { | ||
1033 | udf_error(sb, __func__, "bitmap inode efe not " | ||
1034 | "found and attempted read-write mount"); | ||
1035 | goto error_exit; | ||
1036 | } | ||
1037 | } | ||
1038 | } | ||
1039 | |||
1040 | udf_debug("udf_load_metadata_files Ok\n"); | ||
1041 | |||
1042 | return 0; | ||
1043 | |||
1044 | error_exit: | ||
1045 | return 1; | ||
1046 | } | ||
1047 | |||
953 | static void udf_load_fileset(struct super_block *sb, struct buffer_head *bh, | 1048 | static void udf_load_fileset(struct super_block *sb, struct buffer_head *bh, |
954 | kernel_lb_addr *root) | 1049 | kernel_lb_addr *root) |
955 | { | 1050 | { |
@@ -1169,7 +1264,7 @@ static int udf_load_partdesc(struct super_block *sb, sector_t block) | |||
1169 | p = (struct partitionDesc *)bh->b_data; | 1264 | p = (struct partitionDesc *)bh->b_data; |
1170 | partitionNumber = le16_to_cpu(p->partitionNumber); | 1265 | partitionNumber = le16_to_cpu(p->partitionNumber); |
1171 | 1266 | ||
1172 | /* First scan for TYPE1 and SPARABLE partitions */ | 1267 | /* First scan for TYPE1, SPARABLE and METADATA partitions */ |
1173 | for (i = 0; i < sbi->s_partitions; i++) { | 1268 | for (i = 0; i < sbi->s_partitions; i++) { |
1174 | map = &sbi->s_partmaps[i]; | 1269 | map = &sbi->s_partmaps[i]; |
1175 | udf_debug("Searching map: (%d == %d)\n", | 1270 | udf_debug("Searching map: (%d == %d)\n", |
@@ -1189,8 +1284,8 @@ static int udf_load_partdesc(struct super_block *sb, sector_t block) | |||
1189 | ret = udf_fill_partdesc_info(sb, p, i); | 1284 | ret = udf_fill_partdesc_info(sb, p, i); |
1190 | 1285 | ||
1191 | /* | 1286 | /* |
1192 | * Now rescan for VIRTUAL partitions when TYPE1 partitions are | 1287 | * Now rescan for VIRTUAL or METADATA partitions when SPARABLE and |
1193 | * already set up | 1288 | * PHYSICAL partitions are already set up |
1194 | */ | 1289 | */ |
1195 | type1_idx = i; | 1290 | type1_idx = i; |
1196 | for (i = 0; i < sbi->s_partitions; i++) { | 1291 | for (i = 0; i < sbi->s_partitions; i++) { |
@@ -1198,7 +1293,8 @@ static int udf_load_partdesc(struct super_block *sb, sector_t block) | |||
1198 | 1293 | ||
1199 | if (map->s_partition_num == partitionNumber && | 1294 | if (map->s_partition_num == partitionNumber && |
1200 | (map->s_partition_type == UDF_VIRTUAL_MAP15 || | 1295 | (map->s_partition_type == UDF_VIRTUAL_MAP15 || |
1201 | map->s_partition_type == UDF_VIRTUAL_MAP20)) | 1296 | map->s_partition_type == UDF_VIRTUAL_MAP20 || |
1297 | map->s_partition_type == UDF_METADATA_MAP25)) | ||
1202 | break; | 1298 | break; |
1203 | } | 1299 | } |
1204 | 1300 | ||
@@ -1208,16 +1304,28 @@ static int udf_load_partdesc(struct super_block *sb, sector_t block) | |||
1208 | ret = udf_fill_partdesc_info(sb, p, i); | 1304 | ret = udf_fill_partdesc_info(sb, p, i); |
1209 | if (ret) | 1305 | if (ret) |
1210 | goto out_bh; | 1306 | goto out_bh; |
1211 | /* | ||
1212 | * Mark filesystem read-only if we have a partition with virtual map | ||
1213 | * since we don't handle writing to it (we overwrite blocks instead of | ||
1214 | * relocating them). | ||
1215 | */ | ||
1216 | sb->s_flags |= MS_RDONLY; | ||
1217 | printk(KERN_NOTICE "UDF-fs: Filesystem marked read-only because " | ||
1218 | "writing to pseudooverwrite partition is not implemented.\n"); | ||
1219 | 1307 | ||
1220 | ret = udf_load_vat(sb, i, type1_idx); | 1308 | if (map->s_partition_type == UDF_METADATA_MAP25) { |
1309 | ret = udf_load_metadata_files(sb, i); | ||
1310 | if (ret) { | ||
1311 | printk(KERN_ERR "UDF-fs: error loading MetaData " | ||
1312 | "partition map %d\n", i); | ||
1313 | goto out_bh; | ||
1314 | } | ||
1315 | } else { | ||
1316 | ret = udf_load_vat(sb, i, type1_idx); | ||
1317 | if (ret) | ||
1318 | goto out_bh; | ||
1319 | /* | ||
1320 | * Mark filesystem read-only if we have a partition with | ||
1321 | * virtual map since we don't handle writing to it (we | ||
1322 | * overwrite blocks instead of relocating them). | ||
1323 | */ | ||
1324 | sb->s_flags |= MS_RDONLY; | ||
1325 | printk(KERN_NOTICE "UDF-fs: Filesystem marked read-only " | ||
1326 | "because writing to pseudooverwrite partition is " | ||
1327 | "not implemented.\n"); | ||
1328 | } | ||
1221 | out_bh: | 1329 | out_bh: |
1222 | /* In case loading failed, we handle cleanup in udf_fill_super */ | 1330 | /* In case loading failed, we handle cleanup in udf_fill_super */ |
1223 | brelse(bh); | 1331 | brelse(bh); |
@@ -1316,6 +1424,50 @@ static int udf_load_logicalvol(struct super_block *sb, sector_t block, | |||
1316 | } | 1424 | } |
1317 | } | 1425 | } |
1318 | map->s_partition_func = udf_get_pblock_spar15; | 1426 | map->s_partition_func = udf_get_pblock_spar15; |
1427 | } else if (!strncmp(upm2->partIdent.ident, | ||
1428 | UDF_ID_METADATA, | ||
1429 | strlen(UDF_ID_METADATA))) { | ||
1430 | struct udf_meta_data *mdata = | ||
1431 | &map->s_type_specific.s_metadata; | ||
1432 | struct metadataPartitionMap *mdm = | ||
1433 | (struct metadataPartitionMap *) | ||
1434 | &(lvd->partitionMaps[offset]); | ||
1435 | udf_debug("Parsing Logical vol part %d " | ||
1436 | "type %d id=%s\n", i, type, | ||
1437 | UDF_ID_METADATA); | ||
1438 | |||
1439 | map->s_partition_type = UDF_METADATA_MAP25; | ||
1440 | map->s_partition_func = udf_get_pblock_meta25; | ||
1441 | |||
1442 | mdata->s_meta_file_loc = | ||
1443 | le32_to_cpu(mdm->metadataFileLoc); | ||
1444 | mdata->s_mirror_file_loc = | ||
1445 | le32_to_cpu(mdm->metadataMirrorFileLoc); | ||
1446 | mdata->s_bitmap_file_loc = | ||
1447 | le32_to_cpu(mdm->metadataBitmapFileLoc); | ||
1448 | mdata->s_alloc_unit_size = | ||
1449 | le32_to_cpu(mdm->allocUnitSize); | ||
1450 | mdata->s_align_unit_size = | ||
1451 | le16_to_cpu(mdm->alignUnitSize); | ||
1452 | mdata->s_dup_md_flag = | ||
1453 | mdm->flags & 0x01; | ||
1454 | |||
1455 | udf_debug("Metadata Ident suffix=0x%x\n", | ||
1456 | (le16_to_cpu( | ||
1457 | ((__le16 *) | ||
1458 | mdm->partIdent.identSuffix)[0]))); | ||
1459 | udf_debug("Metadata part num=%d\n", | ||
1460 | le16_to_cpu(mdm->partitionNum)); | ||
1461 | udf_debug("Metadata part alloc unit size=%d\n", | ||
1462 | le32_to_cpu(mdm->allocUnitSize)); | ||
1463 | udf_debug("Metadata file loc=%d\n", | ||
1464 | le32_to_cpu(mdm->metadataFileLoc)); | ||
1465 | udf_debug("Mirror file loc=%d\n", | ||
1466 | le32_to_cpu(mdm->metadataMirrorFileLoc)); | ||
1467 | udf_debug("Bitmap file loc=%d\n", | ||
1468 | le32_to_cpu(mdm->metadataBitmapFileLoc)); | ||
1469 | udf_debug("Duplicate Flag: %d %d\n", | ||
1470 | mdata->s_dup_md_flag, mdm->flags); | ||
1319 | } else { | 1471 | } else { |
1320 | udf_debug("Unknown ident: %s\n", | 1472 | udf_debug("Unknown ident: %s\n", |
1321 | upm2->partIdent.ident); | 1473 | upm2->partIdent.ident); |
@@ -1677,6 +1829,7 @@ static void udf_sb_free_bitmap(struct udf_bitmap *bitmap) | |||
1677 | static void udf_free_partition(struct udf_part_map *map) | 1829 | static void udf_free_partition(struct udf_part_map *map) |
1678 | { | 1830 | { |
1679 | int i; | 1831 | int i; |
1832 | struct udf_meta_data *mdata; | ||
1680 | 1833 | ||
1681 | if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_TABLE) | 1834 | if (map->s_partition_flags & UDF_PART_FLAG_UNALLOC_TABLE) |
1682 | iput(map->s_uspace.s_table); | 1835 | iput(map->s_uspace.s_table); |
@@ -1689,6 +1842,17 @@ static void udf_free_partition(struct udf_part_map *map) | |||
1689 | if (map->s_partition_type == UDF_SPARABLE_MAP15) | 1842 | if (map->s_partition_type == UDF_SPARABLE_MAP15) |
1690 | for (i = 0; i < 4; i++) | 1843 | for (i = 0; i < 4; i++) |
1691 | brelse(map->s_type_specific.s_sparing.s_spar_map[i]); | 1844 | brelse(map->s_type_specific.s_sparing.s_spar_map[i]); |
1845 | else if (map->s_partition_type == UDF_METADATA_MAP25) { | ||
1846 | mdata = &map->s_type_specific.s_metadata; | ||
1847 | iput(mdata->s_metadata_fe); | ||
1848 | mdata->s_metadata_fe = NULL; | ||
1849 | |||
1850 | iput(mdata->s_mirror_fe); | ||
1851 | mdata->s_mirror_fe = NULL; | ||
1852 | |||
1853 | iput(mdata->s_bitmap_fe); | ||
1854 | mdata->s_bitmap_fe = NULL; | ||
1855 | } | ||
1692 | } | 1856 | } |
1693 | 1857 | ||
1694 | static int udf_fill_super(struct super_block *sb, void *options, int silent) | 1858 | static int udf_fill_super(struct super_block *sb, void *options, int silent) |
diff --git a/fs/udf/udf_sb.h b/fs/udf/udf_sb.h index 8308a12e1232..1c1c514a9725 100644 --- a/fs/udf/udf_sb.h +++ b/fs/udf/udf_sb.h | |||
@@ -6,7 +6,7 @@ | |||
6 | /* Since UDF 2.01 is ISO 13346 based... */ | 6 | /* Since UDF 2.01 is ISO 13346 based... */ |
7 | #define UDF_SUPER_MAGIC 0x15013346 | 7 | #define UDF_SUPER_MAGIC 0x15013346 |
8 | 8 | ||
9 | #define UDF_MAX_READ_VERSION 0x0201 | 9 | #define UDF_MAX_READ_VERSION 0x0250 |
10 | #define UDF_MAX_WRITE_VERSION 0x0201 | 10 | #define UDF_MAX_WRITE_VERSION 0x0201 |
11 | 11 | ||
12 | #define UDF_FLAG_USE_EXTENDED_FE 0 | 12 | #define UDF_FLAG_USE_EXTENDED_FE 0 |
@@ -46,9 +46,22 @@ | |||
46 | #define UDF_VIRTUAL_MAP15 0x1512U | 46 | #define UDF_VIRTUAL_MAP15 0x1512U |
47 | #define UDF_VIRTUAL_MAP20 0x2012U | 47 | #define UDF_VIRTUAL_MAP20 0x2012U |
48 | #define UDF_SPARABLE_MAP15 0x1522U | 48 | #define UDF_SPARABLE_MAP15 0x1522U |
49 | #define UDF_METADATA_MAP25 0x2511U | ||
49 | 50 | ||
50 | #pragma pack(1) /* XXX(hch): Why? This file just defines in-core structures */ | 51 | #pragma pack(1) /* XXX(hch): Why? This file just defines in-core structures */ |
51 | 52 | ||
53 | struct udf_meta_data { | ||
54 | __u32 s_meta_file_loc; | ||
55 | __u32 s_mirror_file_loc; | ||
56 | __u32 s_bitmap_file_loc; | ||
57 | __u32 s_alloc_unit_size; | ||
58 | __u16 s_align_unit_size; | ||
59 | __u8 s_dup_md_flag; | ||
60 | struct inode *s_metadata_fe; | ||
61 | struct inode *s_mirror_fe; | ||
62 | struct inode *s_bitmap_fe; | ||
63 | }; | ||
64 | |||
52 | struct udf_sparing_data { | 65 | struct udf_sparing_data { |
53 | __u16 s_packet_len; | 66 | __u16 s_packet_len; |
54 | struct buffer_head *s_spar_map[4]; | 67 | struct buffer_head *s_spar_map[4]; |
@@ -82,6 +95,7 @@ struct udf_part_map { | |||
82 | union { | 95 | union { |
83 | struct udf_sparing_data s_sparing; | 96 | struct udf_sparing_data s_sparing; |
84 | struct udf_virtual_data s_virtual; | 97 | struct udf_virtual_data s_virtual; |
98 | struct udf_meta_data s_metadata; | ||
85 | } s_type_specific; | 99 | } s_type_specific; |
86 | __u32 (*s_partition_func)(struct super_block *, __u32, __u16, __u32); | 100 | __u32 (*s_partition_func)(struct super_block *, __u32, __u16, __u32); |
87 | __u16 s_volumeseqnum; | 101 | __u16 s_volumeseqnum; |
diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index 2cb2f5de4245..30f664ab0cd9 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h | |||
@@ -177,6 +177,8 @@ extern uint32_t udf_get_pblock_virt20(struct super_block *, uint32_t, uint16_t, | |||
177 | uint32_t); | 177 | uint32_t); |
178 | extern uint32_t udf_get_pblock_spar15(struct super_block *, uint32_t, uint16_t, | 178 | extern uint32_t udf_get_pblock_spar15(struct super_block *, uint32_t, uint16_t, |
179 | uint32_t); | 179 | uint32_t); |
180 | extern uint32_t udf_get_pblock_meta25(struct super_block *, uint32_t, uint16_t, | ||
181 | uint32_t); | ||
180 | extern int udf_relocate_blocks(struct super_block *, long, long *); | 182 | extern int udf_relocate_blocks(struct super_block *, long, long *); |
181 | 183 | ||
182 | /* unicode.c */ | 184 | /* unicode.c */ |