git: 1f31d4374280 - main - makefs: Fix cd9660 duplicate directory names

From: Ed Maste <emaste_at_FreeBSD.org>
Date: Mon, 30 Dec 2024 19:50:20 UTC
The branch main has been updated by emaste:

URL: https://cgit.FreeBSD.org/src/commit/?id=1f31d437428014e864bcce1223cf7017180e2608

commit 1f31d437428014e864bcce1223cf7017180e2608
Author:     Ed Maste <emaste@FreeBSD.org>
AuthorDate: 2024-12-30 15:01:06 +0000
Commit:     Ed Maste <emaste@FreeBSD.org>
CommitDate: 2024-12-30 19:48:44 +0000

    makefs: Fix cd9660 duplicate directory names
    
    Previously we could create cd9660 images with duplicate short (level 2)
    names.
    
    cd9660_level2_convert_filename used a 30-character limit (for files and
    directories), not including the '.' separator.  cd9660_rename_filename
    used a 31-character limit, including the '.'.  Directory names 31
    characters or longer (without '.') were shortened to 30 characters, and
    if a collision occurred cd9660_rename_filename uniquified them starting
    with the 31st character.  Unfortunately the directory record's name_len
    was already set, so the unique part of the name was stripped off.
    
    Directories are up to 31 d-characters (i.e., A-Z 0-9 and _); there is no
    provision for a '.' in a directory name.  Increase the name length limit
    to 31 for directories, and exclude '.'s.
    
    This name mapping and deduplication code is still fragile and convoluted
    and would beenfit from a more holistic effort.
    
    PR:             283238, 283112
    Reviewed by:    imp
    Sponsored by:   The FreeBSD Foundation
    Differential Revision: https://reviews.freebsd.org/D48251
---
 usr.sbin/makefs/cd9660.c                     |  5 ++--
 usr.sbin/makefs/tests/makefs_cd9660_tests.sh | 39 ++++++++++++++++++++++++++--
 usr.sbin/makefs/tests/makefs_tests_common.sh |  2 +-
 3 files changed, 41 insertions(+), 5 deletions(-)

diff --git a/usr.sbin/makefs/cd9660.c b/usr.sbin/makefs/cd9660.c
index 63f2e33b978b..687bfe46ac27 100644
--- a/usr.sbin/makefs/cd9660.c
+++ b/usr.sbin/makefs/cd9660.c
@@ -1638,14 +1638,15 @@ cd9660_level2_convert_filename(iso9660_disk *diskStructure, const char *oldname,
 	 * File version number (5 characters, 1-32767)
 	 * 1 <= Sum of File name and File name extension <= 30
 	 */
+	int maxlen = is_file ? 30 : 31;
 	int namelen = 0;
 	int extlen = 0;
 	int found_ext = 0;
 	char *orignewname = newname;
 
-	while (*oldname != '\0' && namelen + extlen < 30) {
+	while (*oldname != '\0' && namelen + extlen < maxlen) {
 		/* Handle period first, as it is special */
-		if (*oldname == '.') {
+		if (*oldname == '.' && is_file) {
 			if (found_ext) {
 				if (diskStructure->allow_multidot) {
 					*newname++ = '.';
diff --git a/usr.sbin/makefs/tests/makefs_cd9660_tests.sh b/usr.sbin/makefs/tests/makefs_cd9660_tests.sh
index 2fdf47f76a4c..066a9d6ec0e0 100644
--- a/usr.sbin/makefs/tests/makefs_cd9660_tests.sh
+++ b/usr.sbin/makefs/tests/makefs_cd9660_tests.sh
@@ -51,8 +51,8 @@ common_cleanup()
 check_base_iso9660_image_contents()
 {
 	# Symlinks are treated like files when rockridge support isn't
-	# specified
-	check_image_contents "$@" -X c
+	# specified, and directories cannot contain a '.'.
+	check_image_contents "$@" -X c -X .g -X _g
 
 	atf_check -e empty -o empty -s exit:0 test -L $TEST_INPUTS_DIR/c
 	atf_check -e empty -o empty -s exit:0 test -f $TEST_MOUNT_DIR/c
@@ -374,6 +374,39 @@ o_flag_rockridge_dev_nodes_cleanup()
 	common_cleanup
 }
 
+atf_test_case duplicate_names cleanup
+duplicate_names_head()
+{
+	atf_set "descr" "Ensure shortened directory names are unique (PR283238)"
+}
+duplicate_names_body()
+{
+	check_cd9660_support
+	create_test_dirs
+
+	# Create three directories which are identical in the first 31 characters.
+	dir_prefix="this_directory_name_is_31_chars"
+	mkdir -p $TEST_INPUTS_DIR/${dir_prefix}1
+	mkdir -p $TEST_INPUTS_DIR/${dir_prefix}2
+	mkdir -p $TEST_INPUTS_DIR/${dir_prefix}3
+
+	atf_check -e empty -o empty -s exit:0 \
+	    $MAKEFS -o rockridge $TEST_IMAGE $TEST_INPUTS_DIR
+
+	# Disable Rock Ridge extensions to read the plain ISO Level 2 names.
+	mount_image -r
+
+	# The specific way the short names are made unique is not important.
+	# We verify only that there are three unique names and that the unique
+	# part is at the end of the name.
+	atf_check_equal $(ls -1 $TEST_MOUNT_DIR | sort | uniq | wc -l) 3
+	atf_check_equal $(ls -1 $TEST_MOUNT_DIR | cut -c -29 | sort | uniq | wc -l) 1
+}
+duplicate_names_cleanup()
+{
+	common_cleanup
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case D_flag
@@ -392,4 +425,6 @@ atf_init_test_cases()
 	atf_add_test_case o_flag_publisher
 	atf_add_test_case o_flag_rockridge
 	atf_add_test_case o_flag_rockridge_dev_nodes
+
+	atf_add_test_case duplicate_names
 }
diff --git a/usr.sbin/makefs/tests/makefs_tests_common.sh b/usr.sbin/makefs/tests/makefs_tests_common.sh
index 12c7c7c84fb7..b418cafc90a6 100644
--- a/usr.sbin/makefs/tests/makefs_tests_common.sh
+++ b/usr.sbin/makefs/tests/makefs_tests_common.sh
@@ -138,6 +138,6 @@ mount_image()
 	atf_check -e empty -o save:$TEST_MD_DEVICE_FILE -s exit:0 \
 	    mdconfig -a -f $TEST_IMAGE
 	atf_check -e empty -o empty -s exit:0 \
-	    $MOUNT /dev/$(cat $TEST_MD_DEVICE_FILE) $TEST_MOUNT_DIR
+	    $MOUNT ${1} /dev/$(cat $TEST_MD_DEVICE_FILE) $TEST_MOUNT_DIR
 }