github.com/tetratelabs/wazero@v1.2.1/internal/gojs/testdata/writefs/main.go (about)

     1  package writefs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/fs"
     7  	"log"
     8  	"os"
     9  	"path"
    10  	"syscall"
    11  	"time"
    12  )
    13  
    14  func Main() {
    15  	// Create a test directory
    16  	dir := path.Join(os.TempDir(), "dir")
    17  	dir1 := path.Join(os.TempDir(), "dir1")
    18  	if err := os.Mkdir(dir, 0o700); err != nil {
    19  		log.Panicln(err)
    20  		return
    21  	}
    22  	defer os.Remove(dir)
    23  
    24  	// Create a test file in that directory
    25  	file := path.Join(dir, "file")
    26  	file1 := path.Join(os.TempDir(), "file1")
    27  	if err := os.WriteFile(file, []byte{}, 0o600); err != nil {
    28  		log.Panicln(err)
    29  		return
    30  	}
    31  	defer os.Remove(file)
    32  
    33  	// Ensure stat works, particularly mode.
    34  	for _, path := range []string{dir, file} {
    35  		if stat, err := os.Stat(path); err != nil {
    36  			log.Panicln(err)
    37  		} else {
    38  			fmt.Println(path, "mode", stat.Mode())
    39  		}
    40  	}
    41  
    42  	// Now, test that syscall.WriteAt works
    43  	f, err := os.OpenFile(file1, os.O_RDWR|os.O_CREATE, 0o600)
    44  	if err != nil {
    45  		log.Panicln(err)
    46  	}
    47  	defer f.Close()
    48  
    49  	// Write segments to the file, noting map iteration isn't ordered.
    50  	bytes := []byte("wazero")
    51  	for o, b := range map[int][]byte{3: bytes[3:], 0: bytes[:3]} {
    52  		n, err := f.WriteAt(b, int64(o))
    53  		if err != nil {
    54  			log.Panicln(err)
    55  		} else if n != 3 {
    56  			log.Panicln("expected 3, but wrote", n)
    57  		}
    58  	}
    59  
    60  	// Now, use ReadAt (tested in testfs package) to verify everything wrote!
    61  	if _, err = f.ReadAt(bytes, 0); err != nil {
    62  		log.Panicln(err)
    63  	} else if string(bytes) != "wazero" {
    64  		log.Panicln("unexpected contents:", string(bytes))
    65  	}
    66  
    67  	// Next, truncate it.
    68  	if err = f.Truncate(2); err != nil {
    69  		log.Panicln(err)
    70  	}
    71  	// Next, sync it.
    72  	if err = f.Sync(); err != nil {
    73  		log.Panicln(err)
    74  	}
    75  	// Next, chmod it (tests Fchmod)
    76  	if err = f.Chmod(0o400); err != nil {
    77  		log.Panicln(err)
    78  	}
    79  	if stat, err := f.Stat(); err != nil {
    80  		log.Panicln(err)
    81  	} else if mode := stat.Mode() & fs.ModePerm; mode != 0o400 {
    82  		log.Panicln("expected mode = 0o400", mode)
    83  	}
    84  
    85  	// Invoke Chown in a way nothing changes, to ensure it doesn't panic.
    86  	if err = f.Chown(-1, -1); err != nil {
    87  		log.Panicln(err)
    88  	}
    89  
    90  	// Finally, close it.
    91  	if err = f.Close(); err != nil {
    92  		log.Panicln(err)
    93  	}
    94  
    95  	// Revert to writeable
    96  	if err = syscall.Chmod(file1, 0o600); err != nil {
    97  		log.Panicln(err)
    98  	}
    99  
   100  	// Test stat
   101  	stat, err := os.Stat(file1)
   102  	if err != nil {
   103  		log.Panicln(err)
   104  	}
   105  
   106  	// Invoke Chown in a way nothing changes, to ensure it doesn't panic.
   107  	if err = os.Chown(file1, -1, -1); err != nil {
   108  		log.Panicln(err)
   109  	}
   110  
   111  	if stat.Mode().Type() != 0 {
   112  		log.Panicln("expected type = 0", stat.Mode().Type())
   113  	}
   114  	if stat.Mode().Perm() != 0o600 {
   115  		log.Panicln("expected perm = 0o600", stat.Mode().Perm())
   116  	}
   117  
   118  	// Check the file was truncated.
   119  	if bytes, err := os.ReadFile(file1); err != nil {
   120  		log.Panicln(err)
   121  	} else if string(bytes) != "wa" {
   122  		log.Panicln("unexpected contents:", string(bytes))
   123  	}
   124  
   125  	// Now, truncate it by path
   126  	if err = os.Truncate(file1, 1); err != nil {
   127  		log.Panicln(err)
   128  	} else if bytes, err := os.ReadFile(file1); err != nil {
   129  		log.Panicln(err)
   130  	} else if string(bytes) != "w" {
   131  		log.Panicln("unexpected contents:", string(bytes))
   132  	}
   133  
   134  	// create a hard link
   135  	link := file1 + "-link"
   136  	if err = os.Link(file1, link); err != nil {
   137  		log.Panicln(err)
   138  	}
   139  
   140  	// Ensure this is a hard link, so they have the same inode.
   141  	file1Stat, err := os.Lstat(file1)
   142  	if err != nil {
   143  		log.Panicln(err)
   144  	}
   145  	linkStat, err := os.Lstat(link)
   146  	if err != nil {
   147  		log.Panicln(err)
   148  	}
   149  	if !os.SameFile(file1Stat, linkStat) {
   150  		log.Panicln("expected file == link stat", file1Stat, linkStat)
   151  	}
   152  
   153  	// create a symbolic link
   154  	symlink := file1 + "-symlink"
   155  	if err = os.Symlink(file1, symlink); err != nil {
   156  		log.Panicln(err)
   157  	}
   158  
   159  	// verify we can read the symbolic link back
   160  	if dst, err := os.Readlink(symlink); err != nil {
   161  		log.Panicln(err)
   162  	} else if dst != dst {
   163  		log.Panicln("expected link dst = old value", dst, dst)
   164  	}
   165  
   166  	// Test lstat which should be about the link not its target.
   167  	symlinkStat, err := os.Lstat(symlink)
   168  	if err != nil {
   169  		log.Panicln(err)
   170  	}
   171  
   172  	if symlinkStat.Mode().Type() != fs.ModeSymlink {
   173  		log.Panicln("expected type = symlink", symlinkStat.Mode().Type())
   174  	}
   175  	if size := int64(len(file1)); symlinkStat.Size() != size {
   176  		log.Panicln("unexpected symlink size", symlinkStat.Size(), size)
   177  	}
   178  	// A symbolic link isn't the same file as what it points to.
   179  	if os.SameFile(file1Stat, symlinkStat) {
   180  		log.Panicln("expected file != link stat", file1Stat, symlinkStat)
   181  	}
   182  
   183  	// Invoke Lchown in a way nothing changes, to ensure it doesn't panic.
   184  	if err = os.Lchown(symlink, -1, -1); err != nil {
   185  		log.Panicln(err)
   186  	}
   187  
   188  	// Test removing a non-empty empty directory
   189  	if err = syscall.Rmdir(dir); err != syscall.ENOTEMPTY {
   190  		log.Panicln("unexpected error", err)
   191  	}
   192  
   193  	// Test updating the mod time of a file, noting JS has millis precision.
   194  	atime := time.Unix(123, 4*1e6)
   195  	mtime := time.Unix(567, 8*1e6)
   196  
   197  	// Ensure errors propagate
   198  	if err = os.Chtimes("noexist", atime, mtime); !errors.Is(err, syscall.ENOENT) {
   199  		log.Panicln("unexpected error", err)
   200  	}
   201  
   202  	// Now, try a real update.
   203  	if err = os.Chtimes(dir, atime, mtime); err != nil {
   204  		log.Panicln("unexpected error", err)
   205  	}
   206  
   207  	// Ensure the times translated properly.
   208  	dirAtimeNsec, dirMtimeNsec, dirDev, dirInode := statFields(dir)
   209  	fmt.Println("dir times:", dirAtimeNsec, dirMtimeNsec)
   210  
   211  	// Ensure we were able to read the dev and inode.
   212  	//
   213  	// Note: The size of syscall.Stat_t.Dev (32-bit) in js is smaller than
   214  	// linux (64-bit), so we can't compare its real value against the host.
   215  	if dirDev == 0 {
   216  		log.Panicln("expected dir dev != 0", dirDev)
   217  	}
   218  	if dirInode == 0 {
   219  		log.Panicln("expected dir inode != 0", dirInode)
   220  	}
   221  
   222  	// Test renaming a file, noting we can't verify error numbers as they
   223  	// vary per operating system.
   224  	if err = syscall.Rename(file, dir); err == nil {
   225  		log.Panicln("expected error")
   226  	}
   227  	if err = syscall.Rename(file, file1); err != nil {
   228  		log.Panicln("unexpected error", err)
   229  	}
   230  
   231  	// Test renaming a directory
   232  	if err = syscall.Rename(dir, file1); err == nil {
   233  		log.Panicln("expected error")
   234  	}
   235  	if err = syscall.Rename(dir, dir1); err != nil {
   236  		log.Panicln("unexpected error", err)
   237  	}
   238  
   239  	// Compare stat after renaming.
   240  	atimeNsec, mtimeNsec, dev, inode := statFields(dir1)
   241  	// atime shouldn't change as we didn't access (re-open) the directory.
   242  	if atimeNsec != dirAtimeNsec {
   243  		log.Panicln("expected dir atimeNsec = previous value", atimeNsec, dirAtimeNsec)
   244  	}
   245  	// mtime should change because we renamed the directory.
   246  	if mtimeNsec <= dirMtimeNsec {
   247  		log.Panicln("expected dir mtimeNsec > previous value", mtimeNsec, dirMtimeNsec)
   248  	}
   249  	// dev/inode shouldn't change during rename.
   250  	if dev != dirDev {
   251  		log.Panicln("expected dir dev = previous value", dev, dirDev)
   252  	}
   253  	if inode != dirInode {
   254  		log.Panicln("expected dir inode = previous value", dev, dirInode)
   255  	}
   256  
   257  	// Test unlinking a file
   258  	if err = syscall.Rmdir(file1); err != syscall.ENOTDIR {
   259  		log.Panicln("unexpected error", err)
   260  	}
   261  	if err = syscall.Unlink(file1); err != nil {
   262  		log.Panicln("unexpected error", err)
   263  	}
   264  
   265  	// Test removing an empty directory
   266  	if err = syscall.Unlink(dir1); err != syscall.EISDIR {
   267  		log.Panicln("unexpected error", err)
   268  	}
   269  	if err = syscall.Rmdir(dir1); err != nil {
   270  		log.Panicln("unexpected error", err)
   271  	}
   272  
   273  	// shouldn't fail
   274  	if err = os.RemoveAll(dir1); err != nil {
   275  		log.Panicln(err)
   276  		return
   277  	}
   278  
   279  	// ensure we can use zero as is used in TestRemoveReadOnlyDir
   280  	if err = os.Mkdir(dir1, 0); err != nil {
   281  		log.Panicln(err)
   282  		return
   283  	}
   284  	defer os.Remove(dir)
   285  
   286  	// Symlink and Readlink tests.
   287  	s := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
   288  	from := "/symlink.txt"
   289  	err = os.Symlink(s, from)
   290  	if err != nil {
   291  		log.Panicln(err)
   292  	}
   293  
   294  	r, err := os.Readlink(from)
   295  	if err != nil {
   296  		log.Fatalf("readlink %q failed: %v", from, err)
   297  	}
   298  	if r != s {
   299  		log.Fatalf("after symlink %q != %q", r, s)
   300  	}
   301  }