// Copyright 2018 Google Inc.
//
// 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_test

import (
	"testing"

	"gvisor.googlesource.com/gvisor/pkg/sentry/context"
	"gvisor.googlesource.com/gvisor/pkg/sentry/context/contexttest"
	"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
	ramfstest "gvisor.googlesource.com/gvisor/pkg/sentry/fs/ramfs/test"
	"gvisor.googlesource.com/gvisor/pkg/syserror"
)

func TestLookup(t *testing.T) {
	ctx := contexttest.Context(t)
	for _, test := range []struct {
		// Test description.
		desc string

		// Lookup parameters.
		dir  *fs.Inode
		name string

		// Want from lookup.
		err      error
		found    bool
		hasUpper bool
		hasLower bool
	}{
		{
			desc: "no upper, lower has name",
			dir: fs.NewTestOverlayDir(ctx,
				nil, /* upper */
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "a",
						dir:  false,
					},
				}, nil), /* lower */
			),
			name:     "a",
			found:    true,
			hasUpper: false,
			hasLower: true,
		},
		{
			desc: "no lower, upper has name",
			dir: fs.NewTestOverlayDir(ctx,
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "a",
						dir:  false,
					},
				}, nil), /* upper */
				nil, /* lower */
			),
			name:     "a",
			found:    true,
			hasUpper: true,
			hasLower: false,
		},
		{
			desc: "upper and lower, only lower has name",
			dir: fs.NewTestOverlayDir(ctx,
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "b",
						dir:  false,
					},
				}, nil), /* upper */
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "a",
						dir:  false,
					},
				}, nil), /* lower */
			),
			name:     "a",
			found:    true,
			hasUpper: false,
			hasLower: true,
		},
		{
			desc: "upper and lower, only upper has name",
			dir: fs.NewTestOverlayDir(ctx,
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "a",
						dir:  false,
					},
				}, nil), /* upper */
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "b",
						dir:  false,
					},
				}, nil), /* lower */
			),
			name:     "a",
			found:    true,
			hasUpper: true,
			hasLower: false,
		},
		{
			desc: "upper and lower, both have file",
			dir: fs.NewTestOverlayDir(ctx,
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "a",
						dir:  false,
					},
				}, nil), /* upper */
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "a",
						dir:  false,
					},
				}, nil), /* lower */
			),
			name:     "a",
			found:    true,
			hasUpper: true,
			hasLower: false,
		},
		{
			desc: "upper and lower, both have directory",
			dir: fs.NewTestOverlayDir(ctx,
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "a",
						dir:  true,
					},
				}, nil), /* upper */
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "a",
						dir:  true,
					},
				}, nil), /* lower */
			),
			name:     "a",
			found:    true,
			hasUpper: true,
			hasLower: true,
		},
		{
			desc: "upper and lower, upper negative masks lower file",
			dir: fs.NewTestOverlayDir(ctx,
				newTestRamfsDir(ctx, nil, []string{"a"}), /* upper */
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "a",
						dir:  false,
					},
				}, nil), /* lower */
			),
			name:     "a",
			found:    false,
			hasUpper: false,
			hasLower: false,
		},
		{
			desc: "upper and lower, upper negative does not mask lower file",
			dir: fs.NewTestOverlayDir(ctx,
				newTestRamfsDir(ctx, nil, []string{"b"}), /* upper */
				newTestRamfsDir(ctx, []dirContent{
					{
						name: "a",
						dir:  false,
					},
				}, nil), /* lower */
			),
			name:     "a",
			found:    true,
			hasUpper: false,
			hasLower: true,
		},
	} {
		t.Run(test.desc, func(t *testing.T) {
			dirent, err := test.dir.Lookup(ctx, test.name)
			if err != test.err {
				t.Fatalf("lookup got error %v, want %v", err, test.err)
			}
			if test.found && dirent.IsNegative() {
				t.Fatalf("lookup expected to find %q, got negative dirent", test.name)
			}
			if !test.found {
				return
			}
			if hasUpper := dirent.Inode.TestHasUpperFS(); hasUpper != test.hasUpper {
				t.Fatalf("lookup got upper filesystem %v, want %v", hasUpper, test.hasUpper)
			}
			if hasLower := dirent.Inode.TestHasLowerFS(); hasLower != test.hasLower {
				t.Errorf("lookup got lower filesystem %v, want %v", hasLower, test.hasLower)
			}
		})
	}
}

type dir struct {
	fs.InodeOperations

	// list of negative child names.
	negative []string
}

func (d *dir) Getxattr(inode *fs.Inode, name string) ([]byte, error) {
	for _, n := range d.negative {
		if name == fs.XattrOverlayWhiteout(n) {
			return []byte("y"), nil
		}
	}
	return nil, syserror.ENOATTR
}

type dirContent struct {
	name string
	dir  bool
}

func newTestRamfsDir(ctx context.Context, contains []dirContent, negative []string) *fs.Inode {
	msrc := fs.NewCachingMountSource(nil, fs.MountSourceFlags{})
	contents := make(map[string]*fs.Inode)
	for _, c := range contains {
		if c.dir {
			contents[c.name] = newTestRamfsDir(ctx, nil, nil)
		} else {
			contents[c.name] = fs.NewInode(ramfstest.NewFile(ctx, fs.FilePermissions{}), msrc, fs.StableAttr{Type: fs.RegularFile})
		}
	}
	dops := ramfstest.NewDir(ctx, contents, fs.FilePermissions{
		User: fs.PermMask{Read: true, Execute: true},
	})
	return fs.NewInode(&dir{
		InodeOperations: dops,
		negative:        negative,
	}, msrc, fs.StableAttr{Type: fs.Directory})
}