chunked: add tests for filesystem operations

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
Giuseppe Scrivano 2024-06-06 13:51:45 +02:00
parent c4ba01f635
commit 42801b27de
No known key found for this signature in database
GPG Key ID: 67E38F7A8BA21772
1 changed files with 339 additions and 0 deletions

View File

@ -0,0 +1,339 @@
package chunked
import (
"bytes"
"io"
"os"
"path"
"syscall"
"testing"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/chunked/internal"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type nopCloser struct {
*bytes.Reader
}
func (nopCloser) Close() error {
return nil
}
func TestSeekableFileGetBlobAt(t *testing.T) {
content := []byte("Hello, World!")
br := bytes.NewReader(content)
reader := nopCloser{br}
sf := newSeekableFile(reader)
chunks := []ImageSourceChunk{
{Offset: 0, Length: 5},
{Offset: 7, Length: 5},
}
streams, errs, err := sf.GetBlobAt(chunks)
assert.NoError(t, err)
expectedContents := [][]byte{
[]byte("Hello"),
[]byte("World"),
}
i := 0
for stream := range streams {
buf := new(bytes.Buffer)
_, err := buf.ReadFrom(stream)
require.NoError(t, err)
require.Equal(t, expectedContents[i], buf.Bytes())
i++
}
err, ok := <-errs
assert.NoError(t, err)
assert.False(t, ok)
}
func TestDoHardLink(t *testing.T) {
tmpDir := t.TempDir()
srcFile := createTempFile(t, tmpDir, "source")
defer srcFile.Close()
srcFd := int(srcFile.Fd())
destDir := t.TempDir()
destDirFd, err := syscall.Open(destDir, syscall.O_RDONLY|syscall.O_CLOEXEC, 0)
require.NoError(t, err)
defer syscall.Close(destDirFd)
destBase := "dest-file"
err = doHardLink(srcFd, destDirFd, destBase)
require.NoError(t, err)
// an existing file is unlinked first
err = doHardLink(srcFd, destDirFd, destBase)
assert.NoError(t, err)
err = doHardLink(-1, destDirFd, destBase)
assert.Error(t, err)
err = doHardLink(srcFd, -1, destBase)
assert.Error(t, err)
}
func TestAppendHole(t *testing.T) {
tmpDir := t.TempDir()
tmpFile := createTempFile(t, tmpDir, "file-with-holes")
defer tmpFile.Close()
fd := int(tmpFile.Fd())
size := int64(1024)
err := appendHole(fd, size)
assert.NoError(t, err, "Appending hole failed")
fileSize, err := syscall.Seek(fd, 0, io.SeekEnd)
assert.NoError(t, err)
assert.Equal(t, size, fileSize, "File size is not as expected")
}
func TestSafeMkdir(t *testing.T) {
rootDir := t.TempDir()
dirName := "../dir"
rootFile, err := os.Open(rootDir)
require.NoError(t, err)
defer rootFile.Close()
rootFd := int(rootFile.Fd())
metadata := fileMetadata{
FileMetadata: internal.FileMetadata{
Type: internal.TypeDir,
Mode: 0o755,
},
}
options := &archive.TarOptions{
// Allow the test to run without privileges
IgnoreChownErrors: true,
}
err = safeMkdir(rootFd, 0o755, dirName, &metadata, options)
require.NoError(t, err)
dir, err := openFileUnderRoot(rootFd, dirName, syscall.O_DIRECTORY, 0)
assert.NoError(t, err)
err = dir.Close()
assert.NoError(t, err)
}
func TestSafeLink(t *testing.T) {
linkName := "a-hard-link"
rootDir := t.TempDir()
rootFile, err := os.Open(rootDir)
require.NoError(t, err)
defer rootFile.Close()
rootFd := int(rootFile.Fd())
file := createTempFile(t, rootDir, "an-existing-file")
existingFile := path.Base(file.Name())
err = file.Close()
assert.NoError(t, err)
metadata := fileMetadata{
FileMetadata: internal.FileMetadata{
Name: linkName,
// try to create outside the root
Linkname: "../../" + existingFile,
Type: internal.TypeReg,
Mode: 0o755,
},
}
options := &archive.TarOptions{
// Allow the test to run without privileges
IgnoreChownErrors: true,
}
err = safeLink(rootFd, 0o755, &metadata, options)
require.NoError(t, err)
// validate it was created
newFile, err := openFileUnderRoot(rootFd, linkName, syscall.O_RDONLY, 0)
assert.NoError(t, err)
st := syscall.Stat_t{}
err = syscall.Fstat(int(newFile.Fd()), &st)
assert.NoError(t, err)
assert.Equal(t, st.Nlink, uint64(2))
err = newFile.Close()
assert.NoError(t, err)
}
func TestSafeSymlink(t *testing.T) {
linkName := "a-hard-link"
rootDir := t.TempDir()
rootFile, err := os.Open(rootDir)
require.NoError(t, err)
defer rootFile.Close()
rootFd := int(rootFile.Fd())
file := createTempFile(t, rootDir, "an-existing-file")
st := syscall.Stat_t{}
err = syscall.Fstat(int(file.Fd()), &st)
assert.NoError(t, err)
err = file.Close()
assert.NoError(t, err)
existingFile := path.Base(file.Name())
metadata := fileMetadata{
FileMetadata: internal.FileMetadata{
Name: linkName,
// try to create outside the root
Linkname: "../../" + existingFile,
Type: internal.TypeReg,
Mode: 0o755,
},
}
options := &archive.TarOptions{
// Allow the test to run without privileges
IgnoreChownErrors: true,
}
err = safeSymlink(rootFd, 0o755, &metadata, options)
require.NoError(t, err)
// validate it was created
newFile, err := openFileUnderRoot(rootFd, linkName, syscall.O_RDONLY, 0)
assert.NoError(t, err)
st2 := syscall.Stat_t{}
err = syscall.Fstat(int(newFile.Fd()), &st2)
require.NoError(t, err)
// validate that the opened file is the same as the original file that was
// created earlier. Compare the inode and device numbers.
assert.Equal(t, st.Dev, st2.Dev)
assert.Equal(t, st.Ino, st2.Ino)
err = newFile.Close()
assert.NoError(t, err)
}
func TestOpenOrCreateDirUnderRoot(t *testing.T) {
rootDir := t.TempDir()
dirName := "dir"
rootFile, err := os.Open(rootDir)
require.NoError(t, err)
defer rootFile.Close()
rootFd := int(rootFile.Fd())
// try to create a directory outside the root
dir, err := openOrCreateDirUnderRoot(rootFd, "../../"+dirName, 0o755)
require.NoError(t, err)
err = dir.Close()
assert.NoError(t, err)
dir, err = openFileUnderRoot(rootFd, dirName, syscall.O_DIRECTORY, 0)
require.NoError(t, err)
err = dir.Close()
require.NoError(t, err)
}
func TestCopyFileContent(t *testing.T) {
rootDir := t.TempDir()
rootFile, err := os.Open(rootDir)
require.NoError(t, err)
defer rootFile.Close()
rootFd := int(rootFile.Fd())
file := createTempFile(t, rootDir, "an-existing-file")
defer file.Close()
size, err := file.Write([]byte("Hello, World!"))
require.NoError(t, err)
_, err = file.Seek(0, io.SeekStart)
assert.NoError(t, err)
st := syscall.Stat_t{}
err = syscall.Fstat(int(file.Fd()), &st)
require.NoError(t, err)
metadata := fileMetadata{
FileMetadata: internal.FileMetadata{
Name: "new-file",
Type: internal.TypeDir,
Mode: 0o755,
},
}
newFile, newSize, err := copyFileContent(int(file.Fd()), &metadata, rootFd, 0o755, false)
require.NoError(t, err)
assert.Equal(t, size, int(newSize))
st2 := syscall.Stat_t{}
err = syscall.Fstat(int(newFile.Fd()), &st2)
require.NoError(t, err)
err = newFile.Close()
require.NoError(t, err)
// the file was copied without hard links, the inodes must be different
assert.Equal(t, st.Dev, st2.Dev)
assert.NotEqual(t, st.Ino, st2.Ino)
metadataCopyHardLinks := fileMetadata{
FileMetadata: internal.FileMetadata{
Name: "new-file2",
Type: internal.TypeDir,
Mode: 0o755,
},
}
newFile, newSize, err = copyFileContent(int(file.Fd()), &metadataCopyHardLinks, rootFd, 0o755, true)
require.NoError(t, err)
assert.Nil(t, newFile)
// validate it was created as an inode
newFile, err = openFileUnderRoot(rootFd, metadataCopyHardLinks.FileMetadata.Name, syscall.O_RDONLY, 0)
require.NoError(t, err)
assert.Equal(t, size, int(newSize))
st2 = syscall.Stat_t{}
err = syscall.Fstat(int(newFile.Fd()), &st2)
require.NoError(t, err)
err = newFile.Close()
require.NoError(t, err)
// the file was copied with hard links, the inodes must be equal
assert.Equal(t, st.Dev, st2.Dev)
assert.Equal(t, st.Ino, st2.Ino)
}
func createTempFile(t *testing.T, dir, name string) *os.File {
tmpFile, err := os.CreateTemp(dir, name)
require.NoError(t, err)
return tmpFile
}