// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fs
import (
"os"
"reflect"
"runtime"
"testing"
)
func TestParseDirent(t *testing.T) {
testCases := []struct {
name string
in []byte
out []*dirEntryInfo
}{
{
// Test that type DT_DIR is translated to os.ModeDir
name: "dir",
in: []byte{
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x28, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
out: []*dirEntryInfo{
{".module_paths", os.ModeDir, true},
},
},
{
// Test that type DT_REG is translated to a regular file
name: "file",
in: []byte{
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x28, 0x00,
// unsigned char d_type;
0x08,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
out: []*dirEntryInfo{
{".module_paths", 0, true},
},
},
{
// Test that type DT_LNK is translated to a regular os.ModeSymlink
name: "symlink",
in: []byte{
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x28, 0x00,
// unsigned char d_type;
0x0a,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
out: []*dirEntryInfo{
{".module_paths", os.ModeSymlink, true},
},
},
{
// Test that type DT_UNKNOWN sets modeExists: false
name: "unknown",
in: []byte{
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x28, 0x00,
// unsigned char d_type;
0x00,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
out: []*dirEntryInfo{
{".module_paths", 0, false},
},
},
{
// Test a name with no padding after the null terminator
name: "no padding",
in: []byte{
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x20, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x00,
},
out: []*dirEntryInfo{
{".module_path", os.ModeDir, true},
},
},
{
// Test two sequential entries
name: "two entries",
in: []byte{
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x28, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x28, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x74,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
out: []*dirEntryInfo{
{".module_paths", os.ModeDir, true},
{".module_patht", os.ModeDir, true},
},
},
{
// Test two sequential entries with no padding between them
name: "two entries no padding",
in: []byte{
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x20, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x00,
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x28, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
out: []*dirEntryInfo{
{".module_path", os.ModeDir, true},
{".module_paths", os.ModeDir, true},
},
},
{
// Test an empty buffer. This shouldn't happen in practice because
// readdir doesn't call parseDirent if no bytes were returned.
name: "empty",
in: []byte{},
out: nil,
},
{
name: "missing null terminator",
in: []byte{
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x20, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,
},
out: []*dirEntryInfo{
{".module_paths", os.ModeDir, true},
},
},
{
// Test two sequential entries where the first has an incorrect d_reclen.
// Should return with no entries.
name: "two entries first malformed",
in: []byte{
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x10, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x00,
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x28, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
out: nil,
},
{
// Test two sequential entries where the second has an incorrect d_reclen.
// Should return the first entry.
name: "two entries second malformed",
in: []byte{
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x28, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x00,
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x10, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
out: []*dirEntryInfo{
{".module_path", os.ModeDir, true},
},
},
{
// Test a reclen that goes past the end of the buffer.
name: "overrun",
in: []byte{
// __ino64_t d_ino;
0xfb, 0x10, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00,
// __off64_t d_off;
0xeb, 0x85, 0x20, 0x91, 0xb9, 0x14, 0x34, 0x03,
// unsigned short int d_reclen;
0x30, 0x00,
// unsigned char d_type;
0x04,
// char d_name[];
0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x00,
},
out: nil,
},
}
if runtime.GOOS != "linux" {
t.Skip("depends on Linux definitions of syscall.Dirent")
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
entries := parseDirent(testCase.in, nil)
if !reflect.DeepEqual(testCase.out, entries) {
t.Fatalf("expected:\n %v\ngot:\n %v\n", testCase.out, entries)
}
})
}
}