// Copyright (c) Contributors to the Apptainer project, established as
//   Apptainer a Series of LF Projects LLC.
//   For website terms of use, trademark policy, privacy policy and other
//   project policies see https://lfprojects.org/policies
// Copyright (c) 2020, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.

package actions

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"path"
	"path/filepath"
	"testing"
	"time"

	"github.com/apptainer/apptainer/e2e/internal/e2e"
	"github.com/apptainer/apptainer/internal/pkg/cache"
	"github.com/apptainer/apptainer/internal/pkg/test/tool/require"
	"github.com/apptainer/apptainer/internal/pkg/util/fs"
)

// Check there is no file descriptor leaked in the container
// process. This test expect 4 file descriptors, 3 for stdin,
// stdout, stderr and one opened by the ls command.
func (c actionTests) issue4488(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs(c.env.ImagePath, "ls", "-1", "/proc/self/fd"),
		e2e.ExpectExit(
			0,
			e2e.ExpectOutput(e2e.ExactMatch, "0\n1\n2\n3"),
		),
	)
}

// Check that current working directory when is the user
// home directory doesn't override the custom home directory.
func (c actionTests) issue4587(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	u := e2e.UserProfile.HostUser(t)

	homeDir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "homedir-", "")
	defer cleanup(t)

	canaryFile := filepath.Join(homeDir, "canary_file")
	if err := fs.Touch(canaryFile); err != nil {
		t.Fatalf("failed to create canary file: %s", err)
	}

	homeBind := homeDir + ":" + u.Dir

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithDir(u.Dir),
		e2e.WithCommand("exec"),
		e2e.WithArgs("--home", homeBind, c.env.ImagePath, "test", "-f", filepath.Join(u.Dir, "canary_file")),
		e2e.ExpectExit(0),
	)
}

// Check that current working directory doesn't interfere
// with image content when using underlay.
func (c actionTests) issue4755(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	sandbox, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "sandbox-", "")
	defer cleanup(t)

	// convert test image to sandbox
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("build"),
		e2e.WithArgs("--force", "--sandbox", sandbox, c.env.ImagePath),
		e2e.ExpectExit(0),
	)

	// create a file in image /tmp in order to trigger the issue
	// with underlay layer
	baseDir := filepath.Join(sandbox, filepath.Dir(c.env.TestDir))
	if err := os.MkdirAll(baseDir, 0o700); err != nil {
		t.Fatalf("can't create image directory %s: %s", baseDir, err)
	}
	path := filepath.Join(baseDir, "underlay-test")
	if err := fs.Touch(path); err != nil {
		t.Fatalf("can't create %s: %s", path, err)
	}

	// use of user namespace to force runtime to use underlay
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserNamespaceProfile),
		e2e.WithDir(c.env.TestDir),
		e2e.WithCommand("exec"),
		e2e.WithArgs(sandbox, "true"),
		e2e.ExpectExit(0),
	)
}

// Check that the last element of current working directory when it's
// a symlink pointing to a relative target is correctly handled by the
// runtime.
func (c actionTests) issue4768(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	homeDir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "issue-4768-", "")
	defer cleanup(t)

	symCwdPath := filepath.Join(homeDir, "symlink")
	if err := os.Symlink(".", symCwdPath); err != nil {
		t.Fatalf("failed to create symlink %s: %s", symCwdPath, err)
	}

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithDir(symCwdPath),
		e2e.WithCommand("exec"),
		e2e.WithArgs(c.env.ImagePath, "pwd"),
		e2e.ExpectExit(
			0,
			e2e.ExpectOutput(e2e.ExactMatch, homeDir),
		),
	)
}

// Check that underlay layer handle relative/absolute symlinks
// when those are bind mount points.
func (c actionTests) issue4797(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	// /etc/relative-slink in the image point to ../usr/share/zoneinfo/Etc/UTC
	// /etc/absolute-slink in the image point to /usr/share/zoneinfo/Etc/UTC
	tests := []struct {
		name string
		args []string
		exit int
	}{
		{
			// check /usr/bin presence in the container
			name: "RelativeUsrBin",
			args: []string{"--bind", "/etc/passwd:/etc/relative-slink", c.env.ImagePath, "test", "-d", "/usr/bin"},
			exit: 0,
		},
		{
			// check /usr/share/zoneinfo/Etc/UTC presence in the container
			name: "RelativeUTC",
			args: []string{"--bind", "/etc/passwd:/etc/relative-slink", c.env.ImagePath, "test", "-f", "/usr/share/zoneinfo/Etc/UTC"},
			exit: 0,
		},
		{
			name: "AbsoluteUsrBin",
			args: []string{"--bind", "/etc/passwd:/etc/absolute-slink", c.env.ImagePath, "test", "-d", "/usr/bin"},
			exit: 0,
		},
		{
			name: "AbsoluteUTC",
			args: []string{"--bind", "/etc/passwd:/etc/absolute-slink", c.env.ImagePath, "test", "-f", "/usr/share/zoneinfo/Etc/UTC"},
			exit: 0,
		},
	}

	for _, tt := range tests {
		c.env.RunApptainer(
			t,
			e2e.AsSubtest(tt.name),
			e2e.WithProfile(e2e.UserNamespaceProfile),
			e2e.WithCommand("exec"),
			e2e.WithArgs(tt.args...),
			e2e.ExpectExit(tt.exit),
		)
	}
}

// Check that current working directory is correctly handled when an
// element of the path is a symlink.
func (c actionTests) issue4836(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	// $TMPDIR/issue-4836-XXXX directory
	issueDir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "issue-4836-", "")
	defer cleanup(t)

	// $TMPDIR/issue-4836-XXXX/dir/child directory
	dir := filepath.Join(issueDir, "dir", "child")
	if err := os.MkdirAll(filepath.Join(issueDir, "dir", "child"), 0o755); err != nil {
		t.Fatalf("failed to create dir %s: %s", dir, err)
	}

	// $TMPDIR/issue-4836-XXXX/symlink -> $TMPDIR/issue-4836-XXXX/dir
	symlink := filepath.Join(issueDir, "symlink")
	if err := os.Symlink(filepath.Join(issueDir, "dir"), symlink); err != nil {
		t.Fatalf("failed to create symlink %s: %s", symlink, err)
	}

	// will trigger the issue by traversing symlinked path into
	// the child directory :
	// PWD = $TMPDIR/issue-4836-XXXX/symlink/child
	cwd := filepath.Join(symlink, "child")

	// chdir will resolve the path so we check against dir, we could also
	// check $PWD content but that's enough
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithDir(cwd),
		e2e.WithArgs(c.env.ImagePath, "pwd"),
		e2e.ExpectExit(
			0,
			e2e.ExpectOutput(e2e.ExactMatch, dir),
		),
	)
}

// Check that image caching from an http source works correctly, using
// the HTTP Last-Modified header to invalidate previously pulled images,
// and that if we exec c1/v1.sif and then c1/v2.sif the latter is *not*
// run from the cached image of the former.
func (c actionTests) issue4823(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	// Copy image to a local tempdir so we can modify times on it
	issueDir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "issue-4823-", "")
	defer cleanup(t)
	issueImage := path.Join(issueDir, "test.sif")
	if err := fs.CopyFile(c.env.ImagePath, issueImage, 0o755); err != nil {
		t.Fatalf("Could not copy test image file: %v", err)
	}

	// Start an http server that always serves the same file from
	// whatever URL is requested
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, issueImage)
	}))
	defer srv.Close()

	tests := []struct {
		name         string
		urlPath      string
		touchFile    bool
		expectCached bool
	}{
		// First time we use an image at a URL it is not cached
		{
			name:         "http_c1v1.sif_uncached",
			urlPath:      "/c1/v1.sif",
			touchFile:    false,
			expectCached: false,
		},
		// Second time at same URL is cached
		{
			name:         "http_c1v1.sif_cached",
			urlPath:      "/c1/v1.sif",
			touchFile:    false,
			expectCached: true,
		},
		// Date of file at the URL is modified - not cached
		{
			name:         "http_c1v1.sif_touched_uncached",
			urlPath:      "/c1/v1.sif",
			touchFile:    true,
			expectCached: false,
		},
		// Different URL - not cached
		{
			name:         "http_c2v1.sif_uncached",
			urlPath:      "/c2/v1.sif",
			touchFile:    false,
			expectCached: false,
		},
	}

	// Share a cache dir for all of the subtests
	cacheDir, cleanup := e2e.MakeCacheDir(t, "")
	defer cleanup(t)
	_, err := cache.New(cache.Config{ParentDir: cacheDir})
	if err != nil {
		t.Fatalf("Could not create image cache handle: %v", err)
	}

	for _, tt := range tests {
		if tt.touchFile {
			// touch it into the future by a minute, in case tests
			// are running on fs with poor timestamp resolution
			newTime := time.Now().Add(time.Minute)
			err := os.Chtimes(issueImage, newTime, newTime)
			if err != nil {
				t.Fatalf("Error setting test file times: %v", err)
			}
		}

		expected := e2e.ExpectError(e2e.ContainMatch, "Downloading network image")
		if tt.expectCached {
			expected = e2e.ExpectError(e2e.ContainMatch, "Using image from cache")
		}

		c.env.UnprivCacheDir = cacheDir
		c.env.RunApptainer(
			t,
			e2e.AsSubtest(tt.name),
			e2e.WithGlobalOptions("--debug"),
			e2e.WithProfile(e2e.UserProfile),
			e2e.WithCommand("exec"),
			e2e.WithArgs(srv.URL+tt.urlPath, "/bin/true"),
			e2e.ExpectExit(
				0,
				expected,
			),
		)
	}
}

// Check that we can run a container when the home mount is '/' as it is for 'nobody'
// We should just not do that mount which would clobber the whole container fs
func (c actionTests) issue5228(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	u := e2e.UserProfile.HostUser(t)

	// We don't actually switch user to one with `/` - we put this mount in using `--home`
	// which has the same effect.
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithDir(u.Dir),
		e2e.WithCommand("exec"),
		e2e.WithArgs("--home", "/", c.env.ImagePath, "/bin/true"),
		e2e.ExpectExit(0),
	)
}

// Check that home directory is not mounted under `/root` when --fakeroot and
// --contain are both given.
func (c actionTests) issue5211(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	u := e2e.UserProfile.HostUser(t)

	// Make non-hidden file in host home to check if it was mounted in container
	canaryDir, cleanup := e2e.MakeTempDir(t, u.Dir, "apptainer-issue5211-dir-", "")
	defer cleanup(t)

	canaryBasename := filepath.Base(canaryDir)

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.FakerootProfile),
		e2e.WithDir(u.Dir),
		e2e.WithCommand("exec"),
		e2e.WithArgs("--contain", c.env.ImagePath, "test", "!", "-d", filepath.Join("/root", canaryBasename)),
		e2e.ExpectExit(0),
	)

	// Check we preserve `$HOME` as /root even when we `--contain` with `--fakeroot`
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.FakerootProfile),
		e2e.WithDir(u.Dir),
		e2e.WithCommand("exec"),
		e2e.WithArgs("--contain", c.env.ImagePath, "sh", "-c", "echo $HOME"),
		e2e.ExpectExit(
			0,
			e2e.ExpectOutput(e2e.ExactMatch, "/root"),
		),
	)
}

// Check that we can create a directory in container image with --writable-tmpfs.
func (c actionTests) issue5271(t *testing.T) {
	require.Filesystem(t, "overlay")

	e2e.EnsureImage(t, c.env)

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs("--writable-tmpfs", c.env.ImagePath, "mkdir", "/e2e-dir"),
		e2e.ExpectExit(0),
	)
}

// Check we can fakeroot exec an image containing a system xattr, which we may
// not be able to set in the SIF -> sandbox extraction.
func (c actionTests) issue5399(t *testing.T) {
	dir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "issue5399-", "")
	defer e2e.Privileged(cleanup)(t)
	image := filepath.Join(dir, "issue_5399.sif")

	// Build as root to guarantee no issue setting the system xattr
	// Certain config may not allow us to do it as fakeroot e.g. it failed
	// in Ubuntu1604 CI.
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.RootProfile),
		e2e.WithCommand("build"),
		e2e.WithArgs(image, "testdata/regressions/issue_5399.def"),
		e2e.ExpectExit(0),
	)

	// Fakeroot will extract to a sandbox using mksquashfs as the user.
	// Should succeed, though it can't set a system xattr.
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.FakerootProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs(image, "/bin/true"),
		e2e.ExpectExit(0),
	)
}

// Check that we can create a directory in a root owned directory
// with others write permissions in conjunction with --writable-tmpfs.
func (c actionTests) issue5455(t *testing.T) {
	require.Filesystem(t, "overlay")

	e2e.EnsureImage(t, c.env)

	dir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "issue5455-", "")
	defer e2e.Privileged(cleanup)(t)

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.RootProfile),
		e2e.WithCommand("build"),
		e2e.WithArgs("--force", "--sandbox", dir, c.env.ImagePath),
		e2e.PostRun(func(t *testing.T) {
			if t.Failed() {
				return
			}
			permDir := filepath.Join(dir, "perm")
			if err := os.Mkdir(permDir, 0o777); err != nil {
				t.Errorf("while creating %s: %s", permDir, err)
			}
			if err := os.Chmod(permDir, 0o777); err != nil {
				t.Errorf("while setting permission on %s: %s", permDir, err)
			}
		}),
		e2e.ExpectExit(0),
	)

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs("--writable-tmpfs", dir, "mkdir", "/perm/issue5455"),
		e2e.ExpectExit(0),
	)
}

// Check that we can run a container with no fuse mounts when they are disabled
// by config enable fusemount=no
func (c actionTests) issue5631(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	// Set enable fusemount = no in a custom config file
	tmpDir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "issue-5631-", "")
	defer e2e.Privileged(cleanup)(t)
	tmpConfig := path.Join(tmpDir, "apptainer.conf")
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.RootProfile),
		e2e.PreRun(
			// Custom config file must exist and be root owned with tight permissions
			func(t *testing.T) {
				err := fs.EnsureFileWithPermission(tmpConfig, 0o600)
				if err != nil {
					t.Fatalf("while creating temporary config file: %s", err)
				}
			}),
		e2e.WithCommand("config global"),
		e2e.WithGlobalOptions("--config", tmpConfig),
		e2e.WithArgs("--set", "enable fusemount", "no"),
		e2e.ExpectExit(0),
	)

	// Check we can run a bare container still against the custom config
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.RootProfile),
		e2e.WithCommand("exec"),
		e2e.WithGlobalOptions("--config", tmpConfig),
		e2e.WithArgs(c.env.ImagePath, "/bin/true"),
		e2e.ExpectExit(0),
	)
}

// Check that mount failure for /etc/hosts and /etc/localtime are not fatal
// Separate code paths for contain and non-contained, so check both
func (c actionTests) issue5465(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	sandbox, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "sandbox-", "")
	defer cleanup(t)

	// convert test image to sandbox
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("build"),
		e2e.WithArgs("--force", "--sandbox", sandbox, c.env.ImagePath),
		e2e.ExpectExit(0),
	)

	// Link /etc/localtime and /etc/hosts to a directory
	// (bind file onto dir will fail)
	if err := os.Mkdir(filepath.Join(sandbox, "dir"), 0o755); err != nil {
		t.Fatalf("couldn't create dir in sandbox: %s", err)
	}
	if err := os.Remove(filepath.Join(sandbox, "etc", "localtime")); err != nil && !os.IsNotExist(err) {
		t.Fatalf("couldn't remove sandbox localtime: %s", err)
	}
	if err := os.Remove(filepath.Join(sandbox, "etc", "hosts")); err != nil && !os.IsNotExist(err) {
		t.Fatalf("couldn't remove sandbox hosts: %s", err)
	}
	if err := os.Symlink("/dir", filepath.Join(sandbox, "etc", "localtime")); err != nil {
		t.Fatalf("couldn't symlink sandbox localtime: %s", err)
	}
	if err := os.Symlink("/dir", filepath.Join(sandbox, "etc", "hosts")); err != nil {
		t.Fatalf("couldn't symlink sandbox hosts: %s", err)
	}

	// The standard flow where the binds come from apptainer.conf
	c.env.RunApptainer(
		t,
		e2e.AsSubtest("Standard"),
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithDir(c.env.TestDir),
		e2e.WithCommand("exec"),
		e2e.WithArgs(sandbox, "true"),
		e2e.ExpectExit(0),
	)

	// With `--contain` where the binds are hard coded
	c.env.RunApptainer(
		t,
		e2e.AsSubtest("Contain"),
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithDir(c.env.TestDir),
		e2e.WithCommand("exec"),
		e2e.WithArgs("--contain", sandbox, "true"),
		e2e.ExpectExit(0),
	)
}

// Check that flag / env var binds are passed in $APPTAINER_BIND in the
// container. Sometimes used by containers that require data to be bound in to a
// location etc., and was present in older versions of Apptainer.
func (c actionTests) issue5599(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	tmpDir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "issue-5599-", "")
	defer e2e.Privileged(cleanup)(t)
	// Binds from env var and flag are additive
	envBind := tmpDir + ":/srv"
	bindEnv := "APPTAINER_BIND=" + envBind
	flagBind := tmpDir + ":/mnt"
	expectedEnv := "/mnt,/srv"
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithEnv(append(os.Environ(), bindEnv)),
		e2e.WithArgs("--bind", flagBind, c.env.ImagePath, "/usr/bin/env"),
		e2e.ExpectExit(0,
			e2e.ExpectOutput(e2e.ContainMatch, expectedEnv),
		),
	)
}

// Check that unsquashfs (for version >= 4.4) works for non root users when image contains
// pseudo devices in /dev.
func (c actionTests) issue5690(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs(c.env.ImagePath, "/bin/true"),
		e2e.ExpectExit(0),
	)

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.FakerootProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs(c.env.ImagePath, "/bin/true"),
		e2e.ExpectExit(0),
	)
}

// Check that --mount with nonested prevents bind from being passed
// to nested containers via APPTAINER_BIND, while normal mounts still propagate.
func (c actionTests) issue3501(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	tmpDir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "issue-3501-", "")
	defer e2e.Privileged(cleanup)(t)

	// nonested mount should not appear in APPTAINER_BIND
	c.env.RunApptainer(
		t,
		e2e.AsSubtest("nonested"),
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs("--mount", "type=bind,source="+tmpDir+",destination=/hidden,nonested", c.env.ImagePath, "/usr/bin/env"),
		e2e.ExpectExit(0,
			e2e.ExpectOutput(e2e.UnwantedContainMatch, "APPTAINER_BIND=/hidden"),
		),
	)

	// normal --bind and nonested --mount combined:
	// only --bind destination should appear in APPTAINER_BIND
	c.env.RunApptainer(
		t,
		e2e.AsSubtest("mixed"),
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs("--bind", tmpDir+":/visible", "--mount", "type=bind,source="+tmpDir+",destination=/hidden,nonested", c.env.ImagePath, "/usr/bin/env"),
		e2e.ExpectExit(0,
			e2e.ExpectOutput(e2e.ContainMatch, "APPTAINER_BIND=/visible"),
			e2e.ExpectOutput(e2e.UnwantedContainMatch, "/hidden"),
		),
	)
}

// Check that fakeroot doesn't overwrite root's username in /etc/passwd
// with the host user's name.
func (c actionTests) issue3502(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.FakerootProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs(c.env.ImagePath, "whoami"),
		e2e.ExpectExit(0,
			e2e.ExpectOutput(e2e.ExactMatch, "root"),
		),
	)
}

// If an invalid remote is set, we should not pull a container from the default
// library.
// Github Security Advisories:
// GHSA-jq42-hfch-42f3 (hpcng)
// GHSA-5mv9-q7fq-9394 (sylabs)
func (c actionTests) invalidRemote(t *testing.T) {
	testEndpoint := "invalid"
	testEndpointURI := "https://cloud.example.com"
	testImage := "oras://ghcr.io/apptainer/alpine:latest"

	// Exec library image from the default remote... ensure it succeeds
	argv := []string{testImage, "/bin/true"}
	c.env.RunApptainer(
		t,
		e2e.AsSubtest("exec default"),
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs(argv...),
		e2e.ExpectExit(0),
	)

	// Add another endpoint
	argv = []string{"add", "--no-login", testEndpoint, testEndpointURI}
	c.env.RunApptainer(
		t,
		e2e.AsSubtest("remote add"),
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("remote"),
		e2e.WithArgs(argv...),
		e2e.ExpectExit(0),
	)
	// Remove test remote when we are done here
	defer func(t *testing.T) {
		argv := []string{"remove", testEndpoint}
		c.env.RunApptainer(
			t,
			e2e.AsSubtest("remote remove"),
			e2e.WithProfile(e2e.UserProfile),
			e2e.WithCommand("remote"),
			e2e.WithArgs(argv...),
			e2e.ExpectExit(0),
		)
	}(t)

	// Set as default
	argv = []string{"use", testEndpoint}
	c.env.RunApptainer(
		t,
		e2e.AsSubtest("remote use"),
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("remote"),
		e2e.WithArgs(argv...),
		e2e.ExpectExit(0),
	)

	// Exec library image from the invalid remote, should fail
	argv = []string{testImage, "/bin/true"}
	c.env.RunApptainer(
		t,
		e2e.AsSubtest("exec invalid"),
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs(argv...),
		e2e.ExpectExit(0), // e2e.ExpectExit(255), // oras protocol does not adhere to `remote use`
	)
}

// Check that a bind mount without a destination is not added two times.
func (c actionTests) issue6165(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	workspace, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "issue6165-", "")
	defer e2e.Privileged(cleanup)(t)

	hostCanaryFile := filepath.Join(workspace, "file")

	if err := fs.Touch(hostCanaryFile); err != nil {
		t.Fatalf("failed to create canary_file: %s", err)
	}

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs(
			"--contain",
			"--bind", hostCanaryFile,
			c.env.ImagePath,
			"test", "-f", hostCanaryFile,
		),
		e2e.ExpectExit(
			0,
			e2e.ExpectError(
				e2e.UnwantedContainMatch,
				"destination is already in the mount point list",
			),
		),
	)
}

// Check that a nested bind separated by more than one level works with
// underlay when the lower directory does not existing in the image but
// the image has another directory that does not exist on the host
func (c actionTests) issue619(t *testing.T) {
	e2e.EnsureImage(t, c.env)

	// use of user namespace to force runtime to use underlay
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserNamespaceProfile),
		e2e.WithDir(c.env.TestDir),
		e2e.WithCommand("exec"),
		e2e.WithArgs("--bind", "/var,/var/lib/apt", c.env.ImagePath, "true"),
		e2e.ExpectExit(0),
	)
}

// Don't bind /var/tmp over /tmp where /var/tmp is a symlink to /tmp in the
// container.
func (c actionTests) issue1097(t *testing.T) {
	// If `/var/tmp` on the host is a symlink to `/tmp` then we can't test this
	// easily.
	tmpHostResolved, err := filepath.EvalSymlinks("/tmp")
	if err != nil {
		t.Error(err)
	}
	varTmpHostResolved, err := filepath.EvalSymlinks("/var/tmp")
	if err != nil {
		t.Error(err)
	}
	if varTmpHostResolved == tmpHostResolved {
		t.Skipf("/var/tmp links to /tmp on host")
	}

	// Create a canary file in the host /var/tmp
	varTmpCanary, err := e2e.WriteTempFile("/var/tmp", "issue-1950", "")
	if err != nil {
		t.Error(err)
	}
	defer os.Remove(varTmpCanary)

	// Build an image in which /var/tmp is a symlink to /tmp
	dir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "issue1097-", "")
	defer e2e.Privileged(cleanup)(t)
	image := filepath.Join(dir, "issue_1097.sif")
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.RootProfile),
		e2e.WithCommand("build"),
		e2e.WithArgs(image, "testdata/regressions/issue_1097.def"),
		e2e.ExpectExit(0),
	)

	// If /var/tmp is *not* mounted over /tmp, then our canary file should *not* be accessible.
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs(image, "cat", varTmpCanary),
		e2e.ExpectExit(1),
	)
}

// Verify the generated host user data appended to /etc/passwd
func (c actionTests) issue1848(t *testing.T) {
	user := e2e.UserProfile.HostUser(t)
	expectedPasswdLine := fmt.Sprintf("%s:x:%d:%d:%s:%s:%s",
		user.Name,
		user.UID,
		user.GID,
		user.Gecos,
		user.Dir,
		user.Shell,
	)

	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs("docker://rockylinux:8", "tail", "-1", "/etc/passwd"),
		e2e.ExpectExit(0,
			e2e.ExpectOutput(e2e.ExactMatch, expectedPasswdLine),
		),
	)
}

// Verify whether the time discrepancy issue gets resolved
// see: https://github.com/apptainer/apptainer/issues/1868
func (c actionTests) issue1868(t *testing.T) {
	// change the host timezone to JST first
	if err := os.Setenv("TZ", "JST"); err != nil {
		t.Fatalf("Can not set the timezone to JST")
	}
	c.env.RunApptainer(
		t,
		e2e.WithProfile(e2e.UserProfile),
		e2e.WithCommand("exec"),
		e2e.WithArgs("docker://ghcr.io/apptainer/centos_base:7", "bash", "-c", "TZ=UTC date --date 2020-01-01T12:00:00 +%s"),
		e2e.ExpectExit(0,
			e2e.ExpectOutput(e2e.ExactMatch, "1577880000"),
		),
	)
	if err := os.Unsetenv("TZ"); err != nil {
		t.Fatalf("Can not unset TZ")
	}
}
