blob: 41bc2ee2e16be96fdaa233d4efe410788651a227 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package direntry
import (
"bytes"
"errors"
"strings"
"testing"
)
func TestDOSToUnix(t *testing.T) {
checkConvertDOSToUnix := func(b []byte, lower bool, goldBytes []byte) {
name := convertDOSToUnix(b, lower)
if !bytes.Equal([]byte(name), goldBytes) {
t.Fatal("Unexpected name: ", name, ", expected: ", string(goldBytes), " from ", b)
}
}
checkConvertDOSToUnix([]byte("FOOBAR TXT"), false, []byte("FOOBAR.TXT")) // Test simple case
checkConvertDOSToUnix([]byte("foobar txt"), false, []byte("FOOBAR.TXT")) // Test lowercase DOS (invalid DOS string)
checkConvertDOSToUnix([]byte("FOOBAR TXT"), true, []byte("foobar.txt")) // Test lowercase Unix
checkConvertDOSToUnix([]byte("FOO.BAR TXT"), false, []byte("FOO?BAR.TXT")) // Test embedded '.'
checkConvertDOSToUnix([]byte("FOO "), false, []byte("FOO")) // Test without extension
checkConvertDOSToUnix([]byte("..FOO "), false, []byte("??FOO")) // Test leading '.'
checkConvertDOSToUnix([]byte("/FOO "), false, []byte("?FOO")) // Test slash
checkConvertDOSToUnix([]byte("F T "), false, []byte("F.T")) // Test small name
checkConvertDOSToUnix([]byte("F "), false, []byte("F")) // Test small name (no extension)
checkConvertDOSToUnix([]byte("FOOTBARTPNG"), false, []byte("FOOTBART.PNG")) // Test full name
checkConvertDOSToUnix([]byte("FOOBAR~1 "), false, []byte("FOOBAR~1")) // Test name with gen number
checkConvertDOSToUnix([]byte("BASHRC~1SWP"), false, []byte("BASHRC~1.SWP")) // Test name with gen number and extension
checkConvertDOSToUnix([]byte(". "), false, []byte(".")) // Test '.' special case
checkConvertDOSToUnix([]byte(".. "), false, []byte("..")) // Test '..' special case
cp850inputBytesUpper := []byte{0x80, 0x90, 0xB5, 0xB0, 0xC0, 0xD1, 0x20, 0x20, 0xFE, 0x20, 0x20}
cp850inputBytesLower := []byte{0x87, 0x82, 0xA0, 0xB0, 0xC0, 0xD0, 0x20, 0x20, 0xFE, 0x20, 0x20}
checkConvertDOSToUnix(cp850inputBytesUpper, false, []byte("ÇÉÁ░└Ð.■")) // Test valid use of code page 850 (upper --> upper)
checkConvertDOSToUnix(cp850inputBytesUpper, true, []byte("çéá░└ð.■")) // Test valid use of code page 850 (upper --> lower)
checkConvertDOSToUnix(cp850inputBytesLower, true, []byte("çéá░└ð.■")) // Test valid use of code page 850 (lower --> lower)
checkConvertDOSToUnix(cp850inputBytesLower, false, []byte("ÇÉÁ░└Ð.■")) // Test valid use of code page 850 (lower --> upper)
// Test the "slot E5" special case
// "0x05" in direntryName[0] means the character should be "0xE5"
// When translating from cp850 to Unicode, 0xE5 gets mapped to 0x00D5, which is 'Õ'
checkConvertDOSToUnix(append([]byte{charE5}, []byte("ABCDEFGTXT")...), false, []byte("ÕABCDEFG.TXT"))
// Test that this swap only happens for direntryName[0]
checkConvertDOSToUnix(append([]byte{charE5, charE5, charE5}, []byte("CDEFGTXT")...), false, []byte("Õ??CDEFG.TXT"))
checkConvertDOSToUnix(append([]byte{charFree}, []byte("ABCDEFGTXT")...), false, nil) // Test charFree special case
checkConvertDOSToUnix(append([]byte{charLastFree}, []byte("ABCDEFGTXT")...), false, nil) // Test charLastFree special case
}
func TestUnixToDOSShort(t *testing.T) {
checkConvertUnixToDOS := func(nameUnix string, goldLongNeeded bool, goldName []byte) {
if len(goldName) != dosNameLen {
panic("Malformed test case; invalid gold DOS name length")
}
callback := func(i int) ([]byte, error) {
panic("The callback should not be necessary for this test case")
}
name, longnameNeeded, err := convertUnixToDOS(callback, nameUnix)
if err != nil {
t.Fatal(err)
}
if goldLongNeeded != longnameNeeded {
t.Fatal("Expectation of longname necessity did not match test results")
} else if !bytes.Equal(name, goldName) {
t.Fatal("Unexpected name: ", name, ", expected: ", goldName, " from ", nameUnix)
}
}
// Unix Filename --> Short Name
checkConvertUnixToDOS("FOOBAR.TXT", false, []byte("FOOBAR TXT")) // Test capitalization (upper)
checkConvertUnixToDOS("foobar.txt", true, []byte("FOOBAR TXT")) // Test capitalization (lower)
checkConvertUnixToDOS("FooBar.TxT", true, []byte("FOOBAR TXT")) // Test capitalization (mixed)
checkConvertUnixToDOS("FOOTBART.DOC", false, []byte("FOOTBARTDOC")) // Test max length
checkConvertUnixToDOS("FootBart.png", true, []byte("FOOTBARTPNG")) // Test max length and capitalization
checkConvertUnixToDOS("f.t", true, []byte("F T ")) // Test short length (with extension)
checkConvertUnixToDOS("f", true, []byte("F ")) // Test short length (without extension)
checkConvertUnixToDOS("F", false, []byte("F ")) // Test short length (capitalized)
checkConvertUnixToDOS("foobar", true, []byte("FOOBAR ")) // Test without . or extension
checkConvertUnixToDOS("foobar ", true, []byte("FOOBAR ")) // Test trailing spaces
checkConvertUnixToDOS("foobar.", true, []byte("FOOBAR ")) // Test without extention
checkConvertUnixToDOS("foobar......", true, []byte("FOOBAR ")) // Test trailing periods
checkConvertUnixToDOS("foo.txt.", true, []byte("FOO TXT")) // Test embedded periods
checkConvertUnixToDOS(".", false, []byte(". ")) // Test special '.' case
checkConvertUnixToDOS("..", false, []byte(".. ")) // Test special '..' case
// Test code page 850 (used by FAT)
checkConvertUnixToDOS("ÁÇÆ╬¥Ò", false, append([]byte{0xB5, 0x80, 0x92, 0xCE, 0xBE, 0xE3}, []byte(" ")...))
checkConvertUnixToDOS("áçæ╬¥ò", true, append([]byte{0xB5, 0x80, 0x92, 0xCE, 0xBE, 0xE3}, []byte(" ")...))
// Test the "slot E5" special case
checkConvertUnixToDOS("ÕABCDEFG.TXT", false, append([]byte{0x05}, []byte("ABCDEFGTXT")...))
// Test that this case only applies to direntryName[0]
checkConvertUnixToDOS("ÕÕÕCDEFG.TXT", false, append([]byte{0x05, 0xE5, 0xE5}, []byte("CDEFGTXT")...))
}
func TestUnixToDOSLong(t *testing.T) {
directoryContents := [][]byte{LastFreeDirent()}
checkConvertUnixToDOS := func(nameUnix string, goldName []byte) {
if len(goldName) != dosNameLen {
panic("Malformed test case; invalid gold DOS name length")
}
callback := func(i int) ([]byte, error) {
return directoryContents[i], nil
}
name, longnameNeeded, err := convertUnixToDOS(callback, nameUnix)
if err != nil {
t.Fatal(err)
}
if longnameNeeded != true {
t.Fatal("Expectation of longname necessity did not match test results")
} else if !bytes.Equal(name, goldName) {
t.Fatal("Unexpected name: ", name, ", expected: ", goldName, " from ", nameUnix)
}
}
// Unix Filename --> Long Name
checkConvertUnixToDOS(".bashrc.swp", []byte("BASHRC~1SWP")) // Test leading '.' with extension
checkConvertUnixToDOS("foo.bar.txt", []byte("FOOBAR~1TXT")) // Test embedded '.'
checkConvertUnixToDOS("fo..ba..tx.", []byte("FOBA~1 TX ")) // Test multiple embedded '.'
checkConvertUnixToDOS("fo..........o", []byte("FO~1 O ")) // Test multiple embedded '.' (next to extension)
checkConvertUnixToDOS("footbartpng", []byte("FOOTBA~1 ")) // Test 8.3 name without '.'
checkConvertUnixToDOS("TextFile.Mine.txt", []byte("TEXTFI~1TXT")) // Test simple case
checkConvertUnixToDOS("ver 12.txt", []byte("VER12~1 TXT")) // Test embedded space
checkConvertUnixToDOS("ver 12.txt", []byte("VER12~1 TXT")) // Test multiple embedded spaces
checkConvertUnixToDOS("ver +1.2.text", []byte("VER_12~1TEX")) // Test disallowed char "+" in base name
checkConvertUnixToDOS("ver 1.2.t+t", []byte("VER12~1 T_T")) // Test disallowed char "+" in extension
checkConvertUnixToDOS("ver +1.2.t+t", []byte("VER_12~1T_T")) // Test disallowed char "+" in both parts of name
checkConvertUnixToDOS("a,b;c=", []byte("A_B_C_~1 ")) // Test disallowed chars ",;="
checkConvertUnixToDOS("a+b[c]", []byte("A_B_C_~1 ")) // Test disallowed chars "+[]"
checkConvertUnixToDOS("AƑBƔCỠ.txt", []byte("A_B_C_~1TXT")) // Test unicode chars (NOT from page 850)
checkConvertUnixToDOS("ThisIsALongFile.txt", []byte("THISIS~1TXT")) // Test long name
checkConvertUnixToDOS("The quick brown.fox", []byte("THEQUI~1FOX")) // Test long name with spaces
checkConvertUnixToDOS("The quick 棕色.狐狸!", []byte("THEQUI~1__!")) // Test long name with spaces, embedded '.', unicode
// Utility for making a fake directory
appendEntry := func(name string) {
short := &shortDirentry{}
short.setName([]byte(name))
directoryContents = append(directoryContents, short.bytes())
}
// Test generation number in a fake directory
directoryContents = make([][]byte, 0)
appendEntry(". ")
appendEntry(".. ")
directoryContents = append(directoryContents, LastFreeDirent())
checkConvertUnixToDOS(".foobar", []byte("FOOBAR~1 "))
// Test a generation number which is already occupied
directoryContents = make([][]byte, 0)
appendEntry(". ")
appendEntry(".. ")
appendEntry("FOOBAR~1 ")
directoryContents = append(directoryContents, LastFreeDirent())
checkConvertUnixToDOS(".foobar", []byte("FOOBAR~2 "))
// Test larger generatation number
directoryContents = make([][]byte, 0)
appendEntry(". ")
appendEntry(".. ")
appendEntry("FOOBAR~1 ")
appendEntry("FOOBAR~2 ")
appendEntry("FOOBAR~3 ")
appendEntry("NOTFOO~4 ") // Not "FOOBAR~4" intentionally
appendEntry("FOOBAR~5 ")
directoryContents = append(directoryContents, LastFreeDirent())
checkConvertUnixToDOS(".foobar", []byte("FOOBAR~4 "))
// Test the same generation number, but skipping over a free spot
directoryContents = make([][]byte, 0)
appendEntry(". ")
appendEntry(".. ")
appendEntry("FOOBAR~1 ")
appendEntry("FOOBAR~2 ")
appendEntry("FOOBAR~3 ")
directoryContents = append(directoryContents, FreeDirent())
appendEntry("FOOBAR~5 ")
directoryContents = append(directoryContents, LastFreeDirent())
checkConvertUnixToDOS(".foobar", []byte("FOOBAR~4 "))
// Test multi-digit generation number
directoryContents = make([][]byte, 0)
appendEntry(". ")
appendEntry(".. ")
appendEntry("FOOBAR~1 ")
appendEntry("FOOBAR~2 ")
appendEntry("FOOBAR~3 ")
appendEntry("FOOBAR~4 ")
appendEntry("FOOBAR~5 ")
appendEntry("FOOBAR~6 ")
appendEntry("FOOBAR~7 ")
appendEntry("FOOBAR~8 ")
appendEntry("FOOBAR~9 ")
directoryContents = append(directoryContents, LastFreeDirent())
checkConvertUnixToDOS(".foobar", []byte("FOOBA~10 "))
// TODO(smklein): I'm actually not sure if this is the right behavior. Should the "~9" be at the
// right side of the "main" DOS name?
}
func TestUnixToDOSFailure(t *testing.T) {
checkConvertUnixToDOSFail := func(nameUnix string, goldErr error) {
callback := func(i int) ([]byte, error) {
panic("The callback should not be necessary for this test case")
}
_, _, err := convertUnixToDOS(callback, nameUnix)
if err != goldErr {
t.Fatalf("Expected error %s, but saw %s", goldErr, err)
}
}
// Unix Filename --> Failure case
checkConvertUnixToDOSFail("...", errInvalidFilename) // Test disallowing "only dots and spaces"
checkConvertUnixToDOSFail(". ", errInvalidFilename)
checkConvertUnixToDOSFail(" . ", errInvalidFilename)
checkConvertUnixToDOSFail(" .", errInvalidFilename)
checkConvertUnixToDOSFail("", errInvalidFilename) // Test empty string
checkConvertUnixToDOSFail("\000", errInvalidFilename) // Test NULL character
checkConvertUnixToDOSFail("foo\"", errInvalidFilename) // " character is disallowed
checkConvertUnixToDOSFail("foo?", errInvalidFilename) // ? character is disallowed
checkConvertUnixToDOSFail("foo/", errInvalidFilename) // / character is disallowed
checkConvertUnixToDOSFail("foo*", errInvalidFilename) // * character is disallowed
}
func TestUnixDOSCombo(t *testing.T) {
checkConvertUnixToDOSAndBack := func(nameUnix string, lowercase bool) {
callback := func(i int) ([]byte, error) {
panic("The callback should not be necessary for this test case")
}
nameDOS, longnameNeeded, err := convertUnixToDOS(callback, nameUnix)
if err != nil {
t.Fatal(err)
} else if !lowercase && longnameNeeded {
t.Fatal("Expected to not need a longname")
}
name := convertDOSToUnix(nameDOS, lowercase)
if name != nameUnix {
t.Fatal("Unexpected name: ", name, ", expected: ", nameUnix)
}
}
checkConvertUnixToDOSAndBack("foobar.txt", true) // Test simple case
checkConvertUnixToDOSAndBack("footbart.doc", true) // Test max length
checkConvertUnixToDOSAndBack("f.t", true) // Test short length (with extension)
checkConvertUnixToDOSAndBack("f", true) // Test short length
checkConvertUnixToDOSAndBack("foobar", true) // Test without . or extension
checkConvertUnixToDOSAndBack(".", true) // Test special '.' case
checkConvertUnixToDOSAndBack("..", true) // Test special '..' case
checkConvertUnixToDOSAndBack("áçæ╬¥ò", true) // Test code page 850 (lowercase)
checkConvertUnixToDOSAndBack("ÁÇÆ╬¥Ò", false) // Test code page 850 (uppercase)
checkConvertUnixToDOSAndBack("ÕABCDEFG.TXT", false) // Test the "charE5" special case
checkConvertUnixToDOSAndBack("ÕÕÕCDEFG.TXT", false) // Test that this case only applies to direntryName[0]
}
func TestConvertUnixToWin(t *testing.T) {
checkConvertUnixToWin := func(nameUnix string, expectedNames [][]uint16) {
// Create "nameDOS"
directoryContents := [][]byte{
LastFreeDirent(),
}
callback := func(i int) ([]byte, error) {
return directoryContents[i], nil
}
nameDOS, longnameNeeded, err := convertUnixToDOS(callback, nameUnix)
if err != nil {
t.Fatal(err)
}
if !longnameNeeded {
t.Fatal("Expected that generation number would be inserted for a long name")
}
// Create long direntries
longDirentries, err := convertUnixToWin(nameUnix, nameDOS)
if err != nil {
t.Fatal(err)
}
// Validate length
expectedLength := len(expectedNames)
if len(longDirentries) != expectedLength {
t.Fatalf("Expected %d long direntries, but saw %d", expectedLength, len(longDirentries))
}
order := uint8(0)
for i := range longDirentries {
// Validate the "Last Long Entry" bit and order
if i == 0 {
order = longDirentries[i].count & longOrdinalMask
if longDirentries[i].count&longLastEntry == 0 {
t.Fatal("The Last Entry marker should appear first in the list of Long Direntries")
}
} else {
if longDirentries[i].count&longLastEntry != 0 {
t.Fatal("The Last Entry marker should NOT appear in subsequent direntries")
}
newOrder := longDirentries[i].count & longOrdinalMask
if newOrder != order-1 {
t.Fatal("Invalid direntry order")
}
order = newOrder
}
// Validate name
rawName := longDirentries[i].nameRaw()
expectedRaw := expectedNames[i]
if len(rawName) != len(expectedRaw) {
t.Fatalf("Unexpected name length %d (expected %d)", len(rawName), len(expectedRaw))
}
for j := range rawName {
if rawName[j] != expectedRaw[j] {
t.Fatalf("Unexpected values: rawName[%d]: %x, expectedRaw[%d]: %x", j, rawName[j], j, expectedRaw[j])
}
}
}
}
// Tests simple, single-direntry case (with NULL + padding)
expectedNames := make([][]uint16, 1)
expectedNames[0] = []uint16{
0x002E, 0x0066, 0x006F, 0x006F, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
}
checkConvertUnixToWin(".foo", expectedNames)
// Test case sensitivity
expectedNames = make([][]uint16, 1)
expectedNames[0] = []uint16{
0x002E, 0x0046, 0x006F, 0x004F, 0x0062, 0x0041, 0x0072, 0x0000,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
}
checkConvertUnixToWin(".FoObAr", expectedNames)
// Tests single-direntry case (with NULL, but no padding)
expectedNames = make([][]uint16, 1)
expectedNames[0] = []uint16{
0x002E, 0x0066, 0x006F, 0x006F, 0x0062, 0x0061, 0x0072, 0x0062,
0x0061, 0x007A, 0x0062, 0x006F, 0x0000,
}
checkConvertUnixToWin(".foobarbazbo", expectedNames)
// Tests single-direntry case (with no NULL and no padding)
expectedNames = make([][]uint16, 1)
expectedNames[0] = []uint16{
0x002E, 0x0066, 0x006F, 0x006F, 0x0062, 0x0061, 0x0072, 0x0062,
0x0061, 0x007A, 0x0062, 0x006F, 0x0074,
}
checkConvertUnixToWin(".foobarbazbot", expectedNames)
// Tests example from "FAT: General Overview of On-Disk Format"
expectedNames = make([][]uint16, 2)
expectedNames[0] = []uint16{ // "wn.fox"
0x0077, 0x006E, 0x002E, 0x0066, 0x006F, 0x0078, 0x0000, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
}
expectedNames[1] = []uint16{ // "The quick bro"
0x0054, 0x0068, 0x0065, 0x0020, 0x0071, 0x0075, 0x0069, 0x0063,
0x006B, 0x0020, 0x0062, 0x0072, 0x006F,
}
checkConvertUnixToWin("The quick brown.fox", expectedNames)
// Tests longer direntry name
expectedNames = make([][]uint16, 5)
expectedNames[0] = []uint16{ // "hat a story!"
0x0068, 0x0061, 0x0074, 0x0020, 0x0061, 0x0020, 0x0073, 0x0074,
0x006F, 0x0072, 0x0079, 0x0021, 0x0000,
}
expectedNames[1] = []uint16{ // "y dog! Wow, w"
0x0079, 0x0020, 0x0064, 0x006F, 0x0067, 0x0021, 0x0020, 0x0057,
0x006F, 0x0077, 0x002C, 0x0020, 0x0077,
}
expectedNames[2] = []uint16{ // " over the laz"
0x0020, 0x006F, 0x0076, 0x0065, 0x0072, 0x0020, 0x0074, 0x0068,
0x0065, 0x0020, 0x006c, 0x0061, 0x007A,
}
expectedNames[3] = []uint16{ // "wn fox jumped"
0x0077, 0x006E, 0x0020, 0x0066, 0x006F, 0x0078, 0x0020, 0x006A,
0x0075, 0x006D, 0x0070, 0x0065, 0x0064,
}
expectedNames[4] = []uint16{ // "The quick bro"
0x0054, 0x0068, 0x0065, 0x0020, 0x0071, 0x0075, 0x0069, 0x0063,
0x006B, 0x0020, 0x0062, 0x0072, 0x006F,
}
checkConvertUnixToWin("The quick brown fox jumped over the lazy dog! Wow, what a story!", expectedNames)
// Tests longest direntry name
expectedNames = make([][]uint16, 20)
expectedNames[0] = []uint16{
0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061,
0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
}
for i := 1; i < 20; i++ {
expectedNames[i] = []uint16{
0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061,
0x0061, 0x0061, 0x0061, 0x0061, 0x0061,
}
}
checkConvertUnixToWin(strings.Repeat("a", 255), expectedNames)
// Tests use of characters which are disallowed in short filenames
expectedNames = make([][]uint16, 1)
expectedNames[0] = []uint16{
0x002B, 0x002C, 0x003B, 0x003D, 0x005B, 0x005D, 0x0000, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
}
checkConvertUnixToWin("+,;=[]", expectedNames)
// Tests valid UCS-16 unicode
expectedNames = make([][]uint16, 1)
expectedNames[0] = []uint16{
0x0191, 0x0194, 0x1EE0, 0x68D5, 0x8272, 0x0000, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
}
checkConvertUnixToWin("ƑƔỠ棕色", expectedNames)
}
func TestConvertUnixToWinFailure(t *testing.T) {
checkConvertUnixToWinFailure := func(nameUnix string, goldErr error) {
// Create "nameDOS".
directoryContents := [][]byte{
LastFreeDirent(),
}
callback := func(i int) ([]byte, error) {
return directoryContents[i], nil
}
nameDOS, longnameNeeded, err := convertUnixToDOS(callback, nameUnix)
if err != nil {
t.Fatal(err)
}
if !longnameNeeded {
t.Fatal("Expected that a long name would be necessary for this test case")
}
// Create long direntries
_, err = convertUnixToWin(nameUnix, nameDOS)
if err != goldErr {
t.Fatalf("Expected error %s, but saw %s", goldErr, err)
}
}
// Tests one longer than the longest direntry name
checkConvertUnixToWinFailure(strings.Repeat("a", 256), errTooLong)
}
func TestGetShortEntryFromWin(t *testing.T) {
checkGetShortEntryFromWin := func(buf, goldShortBuf []byte, goldNumDirentrySlots uint8) {
callback := func(i int) ([]byte, error) {
return buf[i*DirentrySize : (i+1)*DirentrySize], nil
}
shortEntry, numDirentrySlots, err := getShortEntryFromWin(callback, 0)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(shortEntry, goldShortBuf) {
t.Fatal("Invalid short entry read from LFN")
} else if numDirentrySlots != goldNumDirentrySlots {
t.Fatalf("Unexpected number of direntry slots %d (expected %d)", numDirentrySlots, goldNumDirentrySlots)
}
}
// Test order == 1
long := make([]longDirentry, 1)
long[0].count = longLastEntry | 1
short := &shortDirentry{}
short.setName([]byte("FOOBAR "))
buf := append(long[0].bytes(), short.bytes()...)
checkGetShortEntryFromWin(buf, short.bytes(), 2)
// Test order == 2
long = make([]longDirentry, 2)
long[0].count = longLastEntry | 2
long[1].count = 1
short = &shortDirentry{}
short.setName([]byte("FOOBAR BAZ"))
buf = append(long[0].bytes(), long[1].bytes()...)
buf = append(buf, short.bytes()...)
checkGetShortEntryFromWin(buf, short.bytes(), 3)
}
func TestGetShortEntryFromWinFailure(t *testing.T) {
// Test completely broken callback
callbackErr := errors.New("This callback is broken")
callbackBad := func(i int) ([]byte, error) {
return nil, callbackErr
}
_, _, err := getShortEntryFromWin(callbackBad, 0)
if err != callbackErr {
t.Fatal("Expected callback error")
}
// Test valid callback, but invalid longLastEntry (implying an invalid sequence)
long := make([]longDirentry, 2)
long[0].count = 2
long[1].count = 1 | longLastEntry
short := &shortDirentry{}
short.setName([]byte("FOOBAR BAZ"))
buf := append(long[0].bytes(), long[1].bytes()...)
buf = append(buf, short.bytes()...)
callbackGood := func(i int) ([]byte, error) {
return buf[i*DirentrySize : (i+1)*DirentrySize], nil
}
_, _, err = getShortEntryFromWin(callbackGood, 0)
if err != errLongDirentry {
t.Fatalf("Expected error %s, saw %s", errLongDirentry, err)
}
// Test valid callback, but invalid order
long = make([]longDirentry, 2)
long[0].count = 0 | longLastEntry
long[1].count = 1
short = &shortDirentry{}
short.setName([]byte("FOOBAR BAZ"))
buf = append(long[0].bytes(), long[1].bytes()...)
buf = append(buf, short.bytes()...)
callbackGood = func(i int) ([]byte, error) {
return buf[i*DirentrySize : (i+1)*DirentrySize], nil
}
_, _, err = getShortEntryFromWin(callbackGood, 0)
if err != errLongDirentry {
t.Fatalf("Expected error %s, saw %s", errLongDirentry, err)
}
// Test partially broken callback, which can access the first long direntry, but not the short
// entry.
long = make([]longDirentry, 2)
long[0].count = 2 | longLastEntry
long[1].count = 1
short = &shortDirentry{}
short.setName([]byte("FOOBAR BAZ"))
buf = append(long[0].bytes(), long[1].bytes()...)
buf = append(buf, short.bytes()...)
callbackGood = func(i int) ([]byte, error) {
if i == 0 {
return buf[i*DirentrySize : (i+1)*DirentrySize], nil
}
return nil, callbackErr
}
_, _, err = getShortEntryFromWin(callbackGood, 0)
if err != callbackErr {
t.Fatal("Expected callback err")
}
}
func TestConvertWinToUnix(t *testing.T) {
checkConvertWinToUnix := func(direntries []longDirentry, goldNameUnix string) {
callback := func(i int) ([]byte, error) {
if i < len(direntries) {
return direntries[i].bytes(), nil
}
return LastFreeDirent(), nil
}
nameDOS, _, err := convertUnixToDOS(callback, goldNameUnix)
if err != nil {
t.Fatal(err)
}
chksum := checksum(nameDOS)
for i := range direntries {
direntries[i].chksum = chksum
}
nameUnix, err := convertWinToUnix(callback, 0, chksum, len(direntries))
if err != nil {
t.Fatal(err)
} else if nameUnix != goldNameUnix {
t.Fatalf("Unexpected unix name %s (expected %s)", nameUnix, goldNameUnix)
}
}
// Test simple case (terminator in part 1 of name)
direntries := make([]longDirentry, 1)
direntries[0].count = longLastEntry | 1
copy(direntries[0].name1[:], []uint8{0x2E, 0x00, 0x66, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x00, 0x00})
copy(direntries[0].name2[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
copy(direntries[0].name3[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF})
checkConvertWinToUnix(direntries, ".foo")
// Test simple case (terminator in part 2 of name)
direntries = make([]longDirentry, 1)
direntries[0].count = longLastEntry | 1
copy(direntries[0].name1[:], []uint8{0x2E, 0x00, 0x66, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x6F, 0x00})
copy(direntries[0].name2[:], []uint8{0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
copy(direntries[0].name3[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF})
checkConvertWinToUnix(direntries, ".fooo")
// Test simple case (terminator in part 3 of name)
direntries = make([]longDirentry, 1)
direntries[0].count = longLastEntry | 1
copy(direntries[0].name1[:], []uint8{0x2E, 0x00, 0x66, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x6F, 0x00})
copy(direntries[0].name2[:], []uint8{0x6F, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x2E, 0x00})
copy(direntries[0].name3[:], []uint8{0x66, 0x00, 0x00, 0x00})
checkConvertWinToUnix(direntries, ".foooooooo.f")
// Tests example from "FAT: General Overview of On-Disk Format"
direntries = make([]longDirentry, 2)
direntries[0].count = longLastEntry | 2
copy(direntries[0].name1[:], []uint8{0x77, 0x00, 0x6E, 0x00, 0x2E, 0x00, 0x66, 0x00, 0x6F, 0x00})
copy(direntries[0].name2[:], []uint8{0x78, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
copy(direntries[0].name3[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF})
direntries[1].count = 1
copy(direntries[1].name1[:], []uint8{0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x71, 0x00})
copy(direntries[1].name2[:], []uint8{0x75, 0x00, 0x69, 0x00, 0x63, 0x00, 0x6B, 0x00, 0x20, 0x00, 0x62, 0x00})
copy(direntries[1].name3[:], []uint8{0x72, 0x00, 0x6F, 0x00})
checkConvertWinToUnix(direntries, "The quick brown.fox")
// Test unicode case
direntries = make([]longDirentry, 1)
direntries[0].count = longLastEntry | 1
copy(direntries[0].name1[:], []uint8{0x91, 0x01, 0x94, 0x01, 0xE0, 0x1E, 0xD5, 0x68, 0x72, 0x82})
copy(direntries[0].name2[:], []uint8{0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
copy(direntries[0].name3[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF})
checkConvertWinToUnix(direntries, "ƑƔỠ棕色")
// Tests longest direntry name
direntries = make([]longDirentry, 20)
direntries[0].count = longLastEntry | 20
copy(direntries[0].name1[:], []uint8{0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00})
copy(direntries[0].name2[:], []uint8{0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF})
copy(direntries[0].name3[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF})
for i := 1; i < len(direntries); i++ {
direntries[i].count = uint8(len(direntries) - i)
copy(direntries[i].name1[:], []uint8{0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00})
copy(direntries[i].name2[:], []uint8{0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00})
copy(direntries[i].name3[:], []uint8{0x61, 0x00, 0x61, 0x00})
}
checkConvertWinToUnix(direntries, strings.Repeat("a", 255))
}
func TestConvertWinToUnixFailure(t *testing.T) {
applyChecksum := func(direntries []longDirentry, chksum uint8) {
for i := range direntries {
direntries[i].chksum = chksum
}
}
checkConvertWinToUnixFailure := func(direntries []longDirentry, chksum uint8, goldErr error) {
callback := func(i int) ([]byte, error) {
return direntries[i].bytes(), nil
}
_, err := convertWinToUnix(callback, 0, chksum, len(direntries))
if err != goldErr {
t.Fatalf("Expected error %s, saw error %s", goldErr, err)
}
}
// Test direntry with a name that is one character too long
direntries := make([]longDirentry, 20)
direntries[0].count = longLastEntry | 20
copy(direntries[0].name1[:], []uint8{0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00})
copy(direntries[0].name2[:], []uint8{0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x00, 0x00, 0xFF, 0xFF})
copy(direntries[0].name3[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF})
for i := 1; i < len(direntries); i++ {
direntries[i].count = uint8(len(direntries) - i)
copy(direntries[i].name1[:], []uint8{0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00})
copy(direntries[i].name2[:], []uint8{0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00})
copy(direntries[i].name3[:], []uint8{0x61, 0x00, 0x61, 0x00})
}
chksum := uint8(5)
applyChecksum(direntries, chksum)
checkConvertWinToUnixFailure(direntries, chksum, errTooLong)
// Test variations of invalid order for a single long direntry
direntries = make([]longDirentry, 1)
copy(direntries[0].name1[:], []uint8{0x2E, 0x00, 0x66, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x00, 0x00})
copy(direntries[0].name2[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
copy(direntries[0].name3[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF})
chksum = uint8(5)
applyChecksum(direntries, chksum)
// Order too small
direntries[0].count = 0 | longLastEntry
checkConvertWinToUnixFailure(direntries, chksum, errLongDirentry)
// Order too large
direntries[0].count = maxLongDirentries + 1 | longLastEntry
checkConvertWinToUnixFailure(direntries, chksum, errLongDirentry)
// "Last entry" not first
direntries[0].count = 1
checkConvertWinToUnixFailure(direntries, chksum, errLongDirentry)
// Test variations of invalid order for a multiple long direntries
direntries = make([]longDirentry, 2)
copy(direntries[0].name1[:], []uint8{0x77, 0x00, 0x6E, 0x00, 0x2E, 0x00, 0x66, 0x00, 0x6F, 0x00})
copy(direntries[0].name2[:], []uint8{0x78, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
copy(direntries[0].name3[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF})
copy(direntries[1].name1[:], []uint8{0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x71, 0x00})
copy(direntries[1].name2[:], []uint8{0x75, 0x00, 0x69, 0x00, 0x63, 0x00, 0x6B, 0x00, 0x20, 0x00, 0x62, 0x00})
copy(direntries[1].name3[:], []uint8{0x72, 0x00, 0x6F, 0x00})
chksum = uint8(5)
applyChecksum(direntries, chksum)
// Multiple "last entries"
direntries[0].count = longLastEntry | 2
direntries[1].count = longLastEntry | 1
checkConvertWinToUnixFailure(direntries, chksum, errLongDirentry)
// Last entry not first
direntries[0].count = 2
direntries[1].count = longLastEntry | 1
checkConvertWinToUnixFailure(direntries, chksum, errLongDirentry)
// NULL terminator seen early
direntries[0].count = longLastEntry | 2
direntries[1].count = 1
copy(direntries[1].name3[:], []uint8{0x00, 0x00, 0x6F, 0x00})
checkConvertWinToUnixFailure(direntries, chksum, errLongDirentry)
// Test broken checksum
direntries = make([]longDirentry, 1)
direntries[0].count = longLastEntry | 1
copy(direntries[0].name1[:], []uint8{0x2E, 0x00, 0x66, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x00, 0x00})
copy(direntries[0].name2[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
copy(direntries[0].name3[:], []uint8{0xFF, 0xFF, 0xFF, 0xFF})
chksum = uint8(5)
applyChecksum(direntries, chksum+1) // Apply the broken checksum
checkConvertWinToUnixFailure(direntries, chksum, errChecksum)
// Test direntries which use the '/' character (0x002F), which is invalid for unix filenames
direntries = make([]longDirentry, 1)
direntries[0].count = longLastEntry | 1
chksum = uint8(5)
applyChecksum(direntries, chksum)
copy(direntries[0].name1[:], []uint8{0x2F, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00}) // '/' here
copy(direntries[0].name2[:], []uint8{0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00})
copy(direntries[0].name3[:], []uint8{0x55, 0x00, 0x00, 0x00})
checkConvertWinToUnixFailure(direntries, chksum, errInvalidFilename)
copy(direntries[0].name1[:], []uint8{0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00})
copy(direntries[0].name2[:], []uint8{0x55, 0x00, 0x2F, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00}) // '/' here
copy(direntries[0].name3[:], []uint8{0x55, 0x00, 0x00, 0x00})
checkConvertWinToUnixFailure(direntries, chksum, errInvalidFilename)
copy(direntries[0].name1[:], []uint8{0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00})
copy(direntries[0].name2[:], []uint8{0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00})
copy(direntries[0].name3[:], []uint8{0x2F, 0x00, 0x00, 0x00}) // '/' here
checkConvertWinToUnixFailure(direntries, chksum, errInvalidFilename)
}
// Convert a Unix name to a DOS name and a long name. Afterwards, convert back to the original Unix
// name. Verify that the new name matches the original.
func TestUnixWinCombo(t *testing.T) {
checkUnixToWinAndBack := func(nameUnix string) {
directoryContents := [][]byte{
LastFreeDirent(),
}
callback := func(i int) ([]byte, error) {
return directoryContents[i], nil
}
nameDOS, longnameNeeded, err := convertUnixToDOS(callback, nameUnix)
if err != nil {
t.Fatal(err)
} else if !longnameNeeded {
t.Fatalf("%s was converted to a short name, but not a long name", nameUnix)
}
direntries, err := convertUnixToWin(nameUnix, nameDOS)
if err != nil {
t.Fatal(err)
}
callback = func(i int) ([]byte, error) {
return direntries[i].bytes(), nil
}
newNameUnix, err := convertWinToUnix(callback, 0, checksum(nameDOS), len(direntries))
if err != nil {
t.Fatal(err)
}
if newNameUnix != nameUnix {
t.Fatalf("Input name %s did not match output name %s", nameUnix, newNameUnix)
}
}
checkUnixToWinAndBack("foo") // Test lowercase
checkUnixToWinAndBack("FoObAr") // Test mixed case
checkUnixToWinAndBack(".foo") // Test leading '.' (lowercase)
checkUnixToWinAndBack(".FOO") // Test leading '.' (uppercase)
checkUnixToWinAndBack("The quick brown.fox") // Test long name (mixed case)
checkUnixToWinAndBack("THEQUICKBROWN.FOX") // Test long name (uppercase)
checkUnixToWinAndBack("ƑƔỠ棕色") // Test unicode
checkUnixToWinAndBack("本") // Test short unicode
checkUnixToWinAndBack("李白《静夜思》床前明月光,疑是地上霜。举头望明月,低头思故乡。 - Thoughts in the Silent Night, by Li Bai")
checkUnixToWinAndBack(strings.Repeat("a", 255))
}