|  | 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 | 
|  | } |