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