| package fat32 | 
 |  | 
 | import ( | 
 | 	"errors" | 
 | 	"fmt" | 
 | 	"math" | 
 | 	"regexp" | 
 | 	"strings" | 
 | ) | 
 |  | 
 | // By default, DOS names would be encoded as what Microsoft calls the OEM | 
 | // code page. This is however dependant on the code page settings of the | 
 | // OS reading the file name as it's not mentioned in FAT32 metadata. | 
 | // To get maximum compatibility and make it easy to read in hex editors | 
 | // this only encodes ASCII characters and not any specific code page. | 
 | // This can still result in garbled data when using a non-latin code page, | 
 | // but this is unavoidable. | 
 | // This is legal as there is no specific requirements for generating these | 
 | // DOS names and any semi-modern system should use the unicode filenames | 
 | // anyways. | 
 |  | 
 | var invalidDOSNameChar = regexp.MustCompile("^[^A-Z0-9!#$%&'()@^_\x60{}~-]$") | 
 |  | 
 | // validDOSName matches names which are valid and unique DOS 8.3 file names as | 
 | // well as valid ASCII | 
 | var validDOSName = regexp.MustCompile(`^^([A-Z0-9!#$%&'()@^_\x60{}~-]{0,8})(\.[A-Z0-9!#$%&'()-@^_\x60{}~-]{1,3})?$`) | 
 |  | 
 | func makeUniqueDOSNames(inodes []*Inode) error { | 
 | 	taken := make(map[[11]byte]bool) | 
 | 	var lossyNameInodes []*Inode | 
 | 	// Make two passes to ensure that names can always be passed through even | 
 | 	// if they would conflict with a generated name. | 
 | 	for _, i := range inodes { | 
 | 		for j := range i.dosName { | 
 | 			i.dosName[j] = ' ' | 
 | 		} | 
 | 		nameUpper := strings.ToUpper(i.Name) | 
 | 		dosParts := validDOSName.FindStringSubmatch(nameUpper) | 
 | 		if dosParts != nil { | 
 | 			// Name is pass-through | 
 | 			copy(i.dosName[:8], []byte(dosParts[1])) | 
 | 			if len(dosParts[2]) > 0 { | 
 | 				// Skip the dot, it is implicit | 
 | 				copy(i.dosName[8:], []byte(dosParts[2][1:])) | 
 | 			} | 
 | 			if taken[i.dosName] { | 
 | 				// Mapping is unique, complain about the actual file name, not | 
 | 				// the 8.3 one | 
 | 				return fmt.Errorf("name %q occurs more than once in the same directory", i.Name) | 
 | 			} | 
 | 			taken[i.dosName] = true | 
 | 			continue | 
 | 		} | 
 | 		lossyNameInodes = append(lossyNameInodes, i) | 
 | 	} | 
 | 	// Willfully ignore the recommended short name generation algorithm as it | 
 | 	// requires tons of bookkeeping and doesn't result in stable names so | 
 | 	// cannot be relied on anyway. | 
 | 	// A FAT32 directory is limited to 2^16 entries (in practice less than half | 
 | 	// of that because of long file name entries), so 4 hex characters | 
 | 	// guarantee uniqueness, regardless of the rest of name. | 
 | 	var nameIdx int | 
 | 	for _, i := range lossyNameInodes { | 
 | 		nameUpper := strings.ToUpper(i.Name) | 
 | 		dotParts := strings.Split(nameUpper, ".") | 
 | 		for j := range dotParts { | 
 | 			// Remove all invalid chars | 
 | 			dotParts[j] = invalidDOSNameChar.ReplaceAllString(dotParts[j], "") | 
 | 		} | 
 | 		var fileName string | 
 | 		lastDotPart := dotParts[len(dotParts)-1] | 
 | 		if len(dotParts) > 1 && len(dotParts[0]) > 0 && len(lastDotPart) > 0 { | 
 | 			// We have a valid 8.3 extension | 
 | 			copy(i.dosName[8:], lastDotPart) | 
 | 			fileName = strings.Join(dotParts[:len(dotParts)-1], "") | 
 | 		} else { | 
 | 			fileName = strings.Join(dotParts[:], "") | 
 | 		} | 
 | 		copy(i.dosName[:4], fileName) | 
 |  | 
 | 		for { | 
 | 			copy(i.dosName[4:], fmt.Sprintf("%04X", nameIdx)) | 
 | 			nameIdx++ | 
 | 			if nameIdx >= math.MaxUint16 { | 
 | 				return errors.New("invariant violated: unable to find unique name with 16 bit counter in 16 bit space") | 
 | 			} | 
 | 			if !taken[i.dosName] { | 
 | 				break | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	return nil | 
 | } |