diff --git a/filesystem/fat32/directory.go b/filesystem/fat32/directory.go index bd917b75..0aa25c78 100644 --- a/filesystem/fat32/directory.go +++ b/filesystem/fat32/directory.go @@ -1,6 +1,7 @@ package fat32 import ( + "fmt" "time" ) @@ -72,6 +73,10 @@ func (d *Directory) createEntry(name string, cluster uint32, dir bool) (*directo // createVolumeLabel create a volume label entry in the given directory, and return the handle to it func (d *Directory) createVolumeLabel(name string) (*directoryEntry, error) { + name, err := validateAndFormatVolumeLabel(name) + if err != nil { + return nil, fmt.Errorf("Unable to create a volume label entry: %w", err) + } // allocate a slot for the new filename in the existing directory entry := directoryEntry{ filenameLong: "", diff --git a/filesystem/fat32/fat32.go b/filesystem/fat32/fat32.go index 84961587..494b1b23 100644 --- a/filesystem/fat32/fat32.go +++ b/filesystem/fat32/fat32.go @@ -1,6 +1,7 @@ package fat32 import ( + "bytes" "errors" "fmt" "os" @@ -626,14 +627,36 @@ func (fs *FileSystem) Label() string { return labelEntry.filenameShort + labelEntry.fileExtension } -// SetLabel changes the filesystem label -func (fs *FileSystem) SetLabel(volumeLabel string) error { +// https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions +// https://www.cs.fsu.edu/~cop4610t/assignments/project3/spec/fatspec.pdf +func validateAndFormatVolumeLabel(volumeLabel string) (string, error) { if volumeLabel == "" { volumeLabel = "NO NAME" } + if len(volumeLabel) > 11 { + return "", fmt.Errorf("Volume label |%s| must be less than 11 characters, got %d", volumeLabel, len(volumeLabel)) + } - // ensure the volumeLabel is proper sized - volumeLabel = fmt.Sprintf("%-11.11s", volumeLabel) + // The following characters are not legal in any bytes of DIR_Name: + // • Values less than 0x20 except for the special case of 0x05 in DIR_Name[0] described above. + // TODO + // • 0x22, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D, + // and 0x7C. + invalidBytes := string([]byte{0x22, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D, 0x7C}) + idx := bytes.IndexAny([]byte(volumeLabel), invalidBytes) + if idx != -1 { + return "", fmt.Errorf("Volume label cannot have invalid character %c", volumeLabel[idx]) + } + + return fmt.Sprintf("%-11.11s", volumeLabel), nil +} + +// SetLabel changes the filesystem label +func (fs *FileSystem) SetLabel(volumeLabel string) error { + volumeLabel, err := validateAndFormatVolumeLabel(volumeLabel) + if err != nil { + return fmt.Errorf("Error validating volume label %w", err) + } // set the label in the superblock bpb := fs.bootSector.biosParameterBlock @@ -827,6 +850,9 @@ func (fs *FileSystem) readDirWithMkdir(p string, doMake bool) (*Directory, []*di // do we have an entry whose name is the same as this name? found := false for _, e := range entries { + if e.isVolumeLabel { + continue + } if e.filenameLong != subp && e.filenameShort != subp && (!e.lowercaseShortname || (e.lowercaseShortname && !strings.EqualFold(e.filenameShort, subp))) { continue } diff --git a/filesystem/fat32/fat32_internal_test.go b/filesystem/fat32/fat32_internal_test.go index 4b300061..da19b8f2 100644 --- a/filesystem/fat32/fat32_internal_test.go +++ b/filesystem/fat32/fat32_internal_test.go @@ -348,3 +348,24 @@ func TestFat32ReadDirWithMkdir(t *testing.T) { } } } + +func TestFat32ValidateAndFormatVolumeLabel(t *testing.T) { + label, err := validateAndFormatVolumeLabel("foo") + if err != nil { + t.Fatal(err) + } + if label != "foo " { + t.Fatal("invalid format") + } + + label, err = validateAndFormatVolumeLabel("ahfkjsdhfkjshdkjfhkhsdf") + if err == nil { + t.Fatal("expected error validating input over accepted length") + } + + label, err = validateAndFormatVolumeLabel("*") + if err == nil { + t.Fatal("expected error validating input with bad character") + } + +} diff --git a/filesystem/fat32/fat32_test.go b/filesystem/fat32/fat32_test.go index 5b1bca19..226dff96 100644 --- a/filesystem/fat32/fat32_test.go +++ b/filesystem/fat32/fat32_test.go @@ -106,6 +106,20 @@ func TestFat32Type(t *testing.T) { } func TestFat32Mkdir(t *testing.T) { + t.Run("Creating directory at root with the same name as volume", func(t *testing.T) { + f, err := os.CreateTemp("", "*") + label := "boot" + // create an empty filesystem + fs, err := fat32.Create(f, 1000000, 0, 512, label) + if err != nil { + t.Fatalf("error creating fat32 filesystem: %v", err) + } + err = fs.Mkdir("/" + label) + if err != nil { + t.Fatalf("error creating directory with same name as label (%s): %v", label, err) + } + }) + // only do this test if os.Getenv("TEST_IMAGE") contains a real image if intImage == "" { return