| // Copyright 2020 The Monogon Project Authors. | 
 | // | 
 | // SPDX-License-Identifier: Apache-2.0 | 
 | // | 
 | // 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 erofs | 
 |  | 
 | import ( | 
 | 	"io" | 
 | 	"log" | 
 | 	"math/rand" | 
 | 	"os" | 
 | 	"testing" | 
 |  | 
 | 	"github.com/stretchr/testify/assert" | 
 | 	"github.com/stretchr/testify/require" | 
 | 	"golang.org/x/sys/unix" | 
 | ) | 
 |  | 
 | func TestKernelInterop(t *testing.T) { | 
 | 	if os.Getenv("IN_KTEST") != "true" { | 
 | 		t.Skip("Not in ktest") | 
 | 	} | 
 |  | 
 | 	type testCase struct { | 
 | 		name     string | 
 | 		setup    func(w *Writer) error | 
 | 		validate func(t *testing.T) error | 
 | 	} | 
 |  | 
 | 	tests := []testCase{ | 
 | 		{ | 
 | 			name: "SimpleFolder", | 
 | 			setup: func(w *Writer) error { | 
 | 				return w.Create(".", &Directory{ | 
 | 					Base:     Base{GID: 123, UID: 124, Permissions: 0753}, | 
 | 					Children: []string{}, | 
 | 				}) | 
 | 			}, | 
 | 			validate: func(t *testing.T) error { | 
 | 				var stat unix.Stat_t | 
 | 				if err := unix.Stat("/test", &stat); err != nil { | 
 | 					t.Errorf("failed to stat output: %v", err) | 
 | 				} | 
 | 				require.EqualValues(t, 124, stat.Uid, "wrong Uid") | 
 | 				require.EqualValues(t, 123, stat.Gid, "wrong Gid") | 
 | 				require.EqualValues(t, 0753, stat.Mode&^unix.S_IFMT, "wrong mode") | 
 | 				return nil | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "FolderHierarchy", | 
 | 			setup: func(w *Writer) error { | 
 | 				if err := w.Create(".", &Directory{ | 
 | 					Base:     Base{GID: 123, UID: 124, Permissions: 0753}, | 
 | 					Children: []string{"subdir"}, | 
 | 				}); err != nil { | 
 | 					return err | 
 | 				} | 
 | 				if err := w.Create("subdir", &Directory{ | 
 | 					Base:     Base{GID: 123, UID: 124, Permissions: 0753}, | 
 | 					Children: []string{}, | 
 | 				}); err != nil { | 
 | 					return err | 
 | 				} | 
 | 				return nil | 
 | 			}, | 
 | 			validate: func(t *testing.T) error { | 
 | 				dirInfo, err := os.ReadDir("/test") | 
 | 				if err != nil { | 
 | 					t.Fatalf("Failed to read top-level directory: %v", err) | 
 | 				} | 
 | 				require.Len(t, dirInfo, 1, "more subdirs than expected") | 
 | 				require.Equal(t, "subdir", dirInfo[0].Name(), "unexpected subdir") | 
 | 				require.True(t, dirInfo[0].IsDir(), "subdir not a directory") | 
 | 				subdirInfo, err := os.ReadDir("/test/subdir") | 
 | 				assert.NoError(t, err, "cannot read empty subdir") | 
 | 				require.Len(t, subdirInfo, 0, "unexpected subdirs in empty directory") | 
 | 				return nil | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "SmallFile", | 
 | 			setup: func(w *Writer) error { | 
 | 				if err := w.Create(".", &Directory{ | 
 | 					Base:     Base{GID: 123, UID: 123, Permissions: 0755}, | 
 | 					Children: []string{"test.bin"}, | 
 | 				}); err != nil { | 
 | 					return err | 
 | 				} | 
 | 				writer := w.CreateFile("test.bin", &FileMeta{ | 
 | 					Base: Base{GID: 123, UID: 124, Permissions: 0644}, | 
 | 				}) | 
 | 				r := rand.New(rand.NewSource(0)) // Random but deterministic data | 
 | 				if _, err := io.CopyN(writer, r, 128); err != nil { | 
 | 					return err | 
 | 				} | 
 | 				if err := writer.Close(); err != nil { | 
 | 					return err | 
 | 				} | 
 | 				return nil | 
 | 			}, | 
 | 			validate: func(t *testing.T) error { | 
 | 				var stat unix.Stat_t | 
 | 				err := unix.Stat("/test/test.bin", &stat) | 
 | 				assert.NoError(t, err, "failed to stat file") | 
 | 				require.EqualValues(t, 124, stat.Uid, "wrong Uid") | 
 | 				require.EqualValues(t, 123, stat.Gid, "wrong Gid") | 
 | 				require.EqualValues(t, 0644, stat.Mode&^unix.S_IFMT, "wrong mode") | 
 | 				file, err := os.Open("/test/test.bin") | 
 | 				assert.NoError(t, err, "failed to open test file") | 
 | 				defer file.Close() | 
 | 				r := io.LimitReader(rand.New(rand.NewSource(0)), 128) // Random but deterministic data | 
 | 				expected, _ := io.ReadAll(r) | 
 | 				actual, err := io.ReadAll(file) | 
 | 				assert.NoError(t, err, "failed to read test file") | 
 | 				assert.Equal(t, expected, actual, "content not identical") | 
 | 				return nil | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "Chardev", | 
 | 			setup: func(w *Writer) error { | 
 | 				if err := w.Create(".", &Directory{ | 
 | 					Base:     Base{GID: 123, UID: 123, Permissions: 0755}, | 
 | 					Children: []string{"ttyS0"}, | 
 | 				}); err != nil { | 
 | 					return err | 
 | 				} | 
 | 				err := w.Create("ttyS0", &CharacterDevice{ | 
 | 					Base:  Base{GID: 0, UID: 0, Permissions: 0600}, | 
 | 					Major: 4, | 
 | 					Minor: 64, | 
 | 				}) | 
 | 				if err != nil { | 
 | 					return err | 
 | 				} | 
 | 				return nil | 
 | 			}, | 
 | 			validate: func(t *testing.T) error { | 
 | 				var stat unix.Statx_t | 
 | 				err := unix.Statx(0, "/test/ttyS0", 0, unix.STATX_ALL, &stat) | 
 | 				assert.NoError(t, err, "failed to statx file") | 
 | 				require.EqualValues(t, 0, stat.Uid, "wrong Uid") | 
 | 				require.EqualValues(t, 0, stat.Gid, "wrong Gid") | 
 | 				require.EqualValues(t, 0600, stat.Mode&^unix.S_IFMT, "wrong mode") | 
 | 				require.EqualValues(t, unix.S_IFCHR, stat.Mode&unix.S_IFMT, "wrong file type") | 
 | 				require.EqualValues(t, 4, stat.Rdev_major, "wrong dev major") | 
 | 				require.EqualValues(t, 64, stat.Rdev_minor, "wrong dev minor") | 
 | 				return nil | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "LargeFile", | 
 | 			setup: func(w *Writer) error { | 
 | 				if err := w.Create(".", &Directory{ | 
 | 					Base:     Base{GID: 123, UID: 123, Permissions: 0755}, | 
 | 					Children: []string{"test.bin"}, | 
 | 				}); err != nil { | 
 | 					return err | 
 | 				} | 
 | 				writer := w.CreateFile("test.bin", &FileMeta{ | 
 | 					Base: Base{GID: 123, UID: 124, Permissions: 0644}, | 
 | 				}) | 
 | 				r := rand.New(rand.NewSource(1)) // Random but deterministic data | 
 | 				if _, err := io.CopyN(writer, r, 6500); err != nil { | 
 | 					return err | 
 | 				} | 
 | 				if err := writer.Close(); err != nil { | 
 | 					return err | 
 | 				} | 
 | 				return nil | 
 | 			}, | 
 | 			validate: func(t *testing.T) error { | 
 | 				var stat unix.Stat_t | 
 | 				rawContents, err := os.ReadFile("/dev/ram0") | 
 | 				assert.NoError(t, err, "failed to read test data") | 
 | 				log.Printf("%x", rawContents) | 
 | 				err = unix.Stat("/test/test.bin", &stat) | 
 | 				assert.NoError(t, err, "failed to stat file") | 
 | 				require.EqualValues(t, 124, stat.Uid, "wrong Uid") | 
 | 				require.EqualValues(t, 123, stat.Gid, "wrong Gid") | 
 | 				require.EqualValues(t, 0644, stat.Mode&^unix.S_IFMT, "wrong mode") | 
 | 				require.EqualValues(t, 6500, stat.Size, "wrong size") | 
 | 				file, err := os.Open("/test/test.bin") | 
 | 				assert.NoError(t, err, "failed to open test file") | 
 | 				defer file.Close() | 
 | 				r := io.LimitReader(rand.New(rand.NewSource(1)), 6500) // Random but deterministic data | 
 | 				expected, _ := io.ReadAll(r) | 
 | 				actual, err := io.ReadAll(file) | 
 | 				assert.NoError(t, err, "failed to read test file") | 
 | 				assert.Equal(t, expected, actual, "content not identical") | 
 | 				return nil | 
 | 			}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "MultipleMetaBlocks", | 
 | 			setup: func(w *Writer) error { | 
 | 				testFileNames := []string{"test1.bin", "test2.bin", "test3.bin"} | 
 | 				if err := w.Create(".", &Directory{ | 
 | 					Base:     Base{GID: 123, UID: 123, Permissions: 0755}, | 
 | 					Children: testFileNames, | 
 | 				}); err != nil { | 
 | 					return err | 
 | 				} | 
 | 				for i, fileName := range testFileNames { | 
 | 					writer := w.CreateFile(fileName, &FileMeta{ | 
 | 						Base: Base{GID: 123, UID: 124, Permissions: 0644}, | 
 | 					}) | 
 | 					r := rand.New(rand.NewSource(int64(i))) // Random but deterministic data | 
 | 					if _, err := io.CopyN(writer, r, 2053); err != nil { | 
 | 						return err | 
 | 					} | 
 | 					if err := writer.Close(); err != nil { | 
 | 						return err | 
 | 					} | 
 | 				} | 
 | 				return nil | 
 | 			}, | 
 | 			validate: func(t *testing.T) error { | 
 | 				testFileNames := []string{"test1.bin", "test2.bin", "test3.bin"} | 
 | 				for i, fileName := range testFileNames { | 
 | 					file, err := os.Open("/test/" + fileName) | 
 | 					assert.NoError(t, err, "failed to open test file") | 
 | 					defer file.Close() | 
 | 					r := io.LimitReader(rand.New(rand.NewSource(int64(i))), 2053) // Random but deterministic data | 
 | 					expected, _ := io.ReadAll(r) | 
 | 					actual, err := io.ReadAll(file) | 
 | 					assert.NoError(t, err, "failed to read test file") | 
 | 					require.Equal(t, expected, actual, "content not identical") | 
 | 				} | 
 | 				return nil | 
 | 			}, | 
 | 		}, | 
 | 	} | 
 |  | 
 | 	for _, test := range tests { | 
 | 		t.Run(test.name, func(t *testing.T) { | 
 | 			file, err := os.OpenFile("/dev/ram0", os.O_WRONLY, 0644) | 
 | 			if err != nil { | 
 | 				t.Fatalf("failed to create test image: %v", err) | 
 | 			} | 
 | 			defer file.Close() | 
 | 			w, err := NewWriter(file) | 
 | 			if err != nil { | 
 | 				t.Fatalf("failed to initialize EROFS writer: %v", err) | 
 | 			} | 
 | 			if err := test.setup(w); err != nil { | 
 | 				t.Fatalf("setup failed: %v", err) | 
 | 			} | 
 | 			if err := w.Close(); err != nil { | 
 | 				t.Errorf("failed close: %v", err) | 
 | 			} | 
 | 			_ = file.Close() | 
 | 			if err := os.MkdirAll("/test", 0755); err != nil { | 
 | 				t.Error(err) | 
 | 			} | 
 | 			if err := unix.Mount("/dev/ram0", "/test", "erofs", unix.MS_NOEXEC|unix.MS_NODEV, ""); err != nil { | 
 | 				t.Fatal(err) | 
 | 			} | 
 | 			if err := test.validate(t); err != nil { | 
 | 				t.Errorf("validation failure: %v", err) | 
 | 			} | 
 | 			if err := unix.Unmount("/test", 0); err != nil { | 
 | 				t.Fatalf("failed to unmount: %v", err) | 
 | 			} | 
 | 		}) | 
 |  | 
 | 	} | 
 | } |