github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/write_test.go (about)

     1  package vfs
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"io"
     7  	"os"
     8  	"runtime"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/rclone/rclone/fs"
    14  	"github.com/rclone/rclone/fstest"
    15  	"github.com/rclone/rclone/lib/random"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  // Open a file for write
    21  func writeHandleCreate(t *testing.T) (r *fstest.Run, vfs *VFS, fh *WriteFileHandle) {
    22  	r, vfs = newTestVFS(t)
    23  
    24  	h, err := vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE, 0777)
    25  	require.NoError(t, err)
    26  	fh, ok := h.(*WriteFileHandle)
    27  	require.True(t, ok)
    28  
    29  	return r, vfs, fh
    30  }
    31  
    32  // Test write when underlying storage is readonly, must be run as non-root
    33  func TestWriteFileHandleReadonly(t *testing.T) {
    34  	if runtime.GOOS == "windows" {
    35  		t.Skipf("Skipping test on %s", runtime.GOOS)
    36  	}
    37  	if *fstest.RemoteName != "" {
    38  		t.Skip("Skipping test on non local remote")
    39  	}
    40  	r, vfs, fh := writeHandleCreate(t)
    41  
    42  	// Name
    43  	assert.Equal(t, "file1", fh.Name())
    44  
    45  	// Write a file, so underlying remote will be created
    46  	_, err := fh.Write([]byte("hello"))
    47  	assert.NoError(t, err)
    48  
    49  	err = fh.Close()
    50  	assert.NoError(t, err)
    51  
    52  	var info os.FileInfo
    53  	info, err = os.Stat(r.FremoteName)
    54  	assert.NoError(t, err)
    55  
    56  	// Remove write permission
    57  	oldMode := info.Mode()
    58  	err = os.Chmod(r.FremoteName, oldMode^(oldMode&0222))
    59  	assert.NoError(t, err)
    60  
    61  	var h Handle
    62  	h, err = vfs.OpenFile("file2", os.O_WRONLY|os.O_CREATE, 0777)
    63  	require.NoError(t, err)
    64  
    65  	var ok bool
    66  	fh, ok = h.(*WriteFileHandle)
    67  	require.True(t, ok)
    68  
    69  	// error is propagated to Close()
    70  	_, err = fh.Write([]byte("hello"))
    71  	assert.NoError(t, err)
    72  
    73  	err = fh.Close()
    74  	assert.NotNil(t, err)
    75  
    76  	// Remove should fail
    77  	err = vfs.Remove("file1")
    78  	assert.NotNil(t, err)
    79  
    80  	// Only file1 should exist
    81  	_, err = vfs.Stat("file1")
    82  	assert.NoError(t, err)
    83  
    84  	_, err = vfs.Stat("file2")
    85  	assert.Equal(t, true, errors.Is(err, os.ErrNotExist))
    86  
    87  	// Restore old permission
    88  	err = os.Chmod(r.FremoteName, oldMode)
    89  	assert.NoError(t, err)
    90  }
    91  
    92  func TestWriteFileHandleMethods(t *testing.T) {
    93  	r, vfs, fh := writeHandleCreate(t)
    94  
    95  	// String
    96  	assert.Equal(t, "file1 (w)", fh.String())
    97  	assert.Equal(t, "<nil *WriteFileHandle>", (*WriteFileHandle)(nil).String())
    98  	assert.Equal(t, "<nil *WriteFileHandle.file>", new(WriteFileHandle).String())
    99  
   100  	// Node
   101  	node := fh.Node()
   102  	assert.Equal(t, "file1", node.Name())
   103  
   104  	// Offset #1
   105  	assert.Equal(t, int64(0), fh.Offset())
   106  	assert.Equal(t, int64(0), node.Size())
   107  
   108  	// Write (smoke test only since heavy lifting done in WriteAt)
   109  	n, err := fh.Write([]byte("hello"))
   110  	assert.NoError(t, err)
   111  	assert.Equal(t, 5, n)
   112  
   113  	// Offset #2
   114  	assert.Equal(t, int64(5), fh.Offset())
   115  	assert.Equal(t, int64(5), node.Size())
   116  
   117  	// Stat
   118  	var fi os.FileInfo
   119  	fi, err = fh.Stat()
   120  	assert.NoError(t, err)
   121  	assert.Equal(t, int64(5), fi.Size())
   122  	assert.Equal(t, "file1", fi.Name())
   123  
   124  	// Read
   125  	var buf = make([]byte, 16)
   126  	_, err = fh.Read(buf)
   127  	assert.Equal(t, EPERM, err)
   128  
   129  	// ReadAt
   130  	_, err = fh.ReadAt(buf, 0)
   131  	assert.Equal(t, EPERM, err)
   132  
   133  	// Sync
   134  	err = fh.Sync()
   135  	assert.NoError(t, err)
   136  
   137  	// Truncate - can only truncate where the file pointer is
   138  	err = fh.Truncate(5)
   139  	assert.NoError(t, err)
   140  	err = fh.Truncate(6)
   141  	assert.Equal(t, EPERM, err)
   142  
   143  	// Close
   144  	assert.NoError(t, fh.Close())
   145  
   146  	// Check double close
   147  	err = fh.Close()
   148  	assert.Equal(t, ECLOSED, err)
   149  
   150  	// check vfs
   151  	root, err := vfs.Root()
   152  	require.NoError(t, err)
   153  	checkListing(t, root, []string{"file1,5,false"})
   154  
   155  	// check the underlying r.Fremote but not the modtime
   156  	file1 := fstest.NewItem("file1", "hello", t1)
   157  	fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{}, fs.ModTimeNotSupported)
   158  
   159  	// Check trying to open the file now it exists then closing it
   160  	// immediately is OK
   161  	h, err := vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE, 0777)
   162  	require.NoError(t, err)
   163  	assert.NoError(t, h.Close())
   164  	checkListing(t, root, []string{"file1,5,false"})
   165  
   166  	// Check trying to open the file and writing it now it exists
   167  	// returns an error
   168  	h, err = vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE, 0777)
   169  	require.NoError(t, err)
   170  	_, err = h.Write([]byte("hello1"))
   171  	require.Equal(t, EPERM, err)
   172  	assert.NoError(t, h.Close())
   173  	checkListing(t, root, []string{"file1,5,false"})
   174  
   175  	// Check opening the file with O_TRUNC does actually truncate
   176  	// it even if we don't write to it
   177  	h, err = vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777)
   178  	require.NoError(t, err)
   179  	err = h.Close()
   180  	if !errors.Is(err, fs.ErrorCantUploadEmptyFiles) {
   181  		assert.NoError(t, err)
   182  		checkListing(t, root, []string{"file1,0,false"})
   183  	}
   184  
   185  	// Check opening the file with O_TRUNC and writing does work
   186  	h, err = vfs.OpenFile("file1", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777)
   187  	require.NoError(t, err)
   188  	_, err = h.WriteString("hello12")
   189  	require.NoError(t, err)
   190  	assert.NoError(t, h.Close())
   191  	checkListing(t, root, []string{"file1,7,false"})
   192  }
   193  
   194  func TestWriteFileHandleWriteAt(t *testing.T) {
   195  	r, vfs, fh := writeHandleCreate(t)
   196  
   197  	// Preconditions
   198  	assert.Equal(t, int64(0), fh.offset)
   199  	assert.False(t, fh.writeCalled)
   200  
   201  	// Write the data
   202  	n, err := fh.WriteAt([]byte("hello"), 0)
   203  	assert.NoError(t, err)
   204  	assert.Equal(t, 5, n)
   205  
   206  	// After write
   207  	assert.Equal(t, int64(5), fh.offset)
   208  	assert.True(t, fh.writeCalled)
   209  
   210  	// Check can't seek
   211  	n, err = fh.WriteAt([]byte("hello"), 100)
   212  	assert.Equal(t, ESPIPE, err)
   213  	assert.Equal(t, 0, n)
   214  
   215  	// Write more data
   216  	n, err = fh.WriteAt([]byte(" world"), 5)
   217  	assert.NoError(t, err)
   218  	assert.Equal(t, 6, n)
   219  
   220  	// Close
   221  	assert.NoError(t, fh.Close())
   222  
   223  	// Check can't write on closed handle
   224  	n, err = fh.WriteAt([]byte("hello"), 0)
   225  	assert.Equal(t, ECLOSED, err)
   226  	assert.Equal(t, 0, n)
   227  
   228  	// check vfs
   229  	root, err := vfs.Root()
   230  	require.NoError(t, err)
   231  	checkListing(t, root, []string{"file1,11,false"})
   232  
   233  	// check the underlying r.Fremote but not the modtime
   234  	file1 := fstest.NewItem("file1", "hello world", t1)
   235  	fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{}, fs.ModTimeNotSupported)
   236  }
   237  
   238  func TestWriteFileHandleFlush(t *testing.T) {
   239  	_, vfs, fh := writeHandleCreate(t)
   240  
   241  	// Check Flush already creates file for unwritten handles, without closing it
   242  	err := fh.Flush()
   243  	assert.NoError(t, err)
   244  	assert.False(t, fh.closed)
   245  	root, err := vfs.Root()
   246  	assert.NoError(t, err)
   247  	checkListing(t, root, []string{"file1,0,false"})
   248  
   249  	// Write some data
   250  	n, err := fh.Write([]byte("hello"))
   251  	assert.NoError(t, err)
   252  	assert.Equal(t, 5, n)
   253  
   254  	// Check Flush closes file if write called
   255  	err = fh.Flush()
   256  	assert.NoError(t, err)
   257  	assert.True(t, fh.closed)
   258  
   259  	// Check flush does nothing if called again
   260  	err = fh.Flush()
   261  	assert.NoError(t, err)
   262  	assert.True(t, fh.closed)
   263  
   264  	// Check file was written properly
   265  	root, err = vfs.Root()
   266  	assert.NoError(t, err)
   267  	checkListing(t, root, []string{"file1,5,false"})
   268  }
   269  
   270  func TestWriteFileHandleRelease(t *testing.T) {
   271  	_, _, fh := writeHandleCreate(t)
   272  
   273  	// Check Release closes file
   274  	err := fh.Release()
   275  	if errors.Is(err, fs.ErrorCantUploadEmptyFiles) {
   276  		t.Logf("skipping test: %v", err)
   277  		return
   278  	}
   279  	assert.NoError(t, err)
   280  	assert.True(t, fh.closed)
   281  
   282  	// Check Release does nothing if called again
   283  	err = fh.Release()
   284  	assert.NoError(t, err)
   285  	assert.True(t, fh.closed)
   286  }
   287  
   288  var (
   289  	canSetModTimeOnce  sync.Once
   290  	canSetModTimeValue = true
   291  )
   292  
   293  // returns whether the remote can set modtime
   294  func canSetModTime(t *testing.T, r *fstest.Run) bool {
   295  	canSetModTimeOnce.Do(func() {
   296  		mtime1 := time.Date(2008, time.November, 18, 17, 32, 31, 0, time.UTC)
   297  		_ = r.WriteObject(context.Background(), "time_test", "stuff", mtime1)
   298  		obj, err := r.Fremote.NewObject(context.Background(), "time_test")
   299  		require.NoError(t, err)
   300  		mtime2 := time.Date(2009, time.November, 18, 17, 32, 31, 0, time.UTC)
   301  		err = obj.SetModTime(context.Background(), mtime2)
   302  		switch err {
   303  		case nil:
   304  			canSetModTimeValue = true
   305  		case fs.ErrorCantSetModTime, fs.ErrorCantSetModTimeWithoutDelete:
   306  			canSetModTimeValue = false
   307  		default:
   308  			require.NoError(t, err)
   309  		}
   310  		require.NoError(t, obj.Remove(context.Background()))
   311  		fs.Debugf(nil, "Can set mod time: %v", canSetModTimeValue)
   312  	})
   313  	return canSetModTimeValue
   314  }
   315  
   316  // tests mod time on open files
   317  func TestWriteFileModTimeWithOpenWriters(t *testing.T) {
   318  	r, vfs, fh := writeHandleCreate(t)
   319  
   320  	if !canSetModTime(t, r) {
   321  		t.Skip("can't set mod time")
   322  	}
   323  
   324  	mtime := time.Date(2012, time.November, 18, 17, 32, 31, 0, time.UTC)
   325  
   326  	_, err := fh.Write([]byte{104, 105})
   327  	require.NoError(t, err)
   328  
   329  	err = fh.Node().SetModTime(mtime)
   330  	require.NoError(t, err)
   331  
   332  	err = fh.Close()
   333  	require.NoError(t, err)
   334  
   335  	info, err := vfs.Stat("file1")
   336  	require.NoError(t, err)
   337  
   338  	if r.Fremote.Precision() != fs.ModTimeNotSupported {
   339  		// avoid errors because of timezone differences
   340  		assert.Equal(t, info.ModTime().Unix(), mtime.Unix())
   341  	}
   342  }
   343  
   344  func testFileReadAt(t *testing.T, n int) {
   345  	_, vfs, fh := writeHandleCreate(t)
   346  
   347  	contents := []byte(random.String(n))
   348  	if n != 0 {
   349  		written, err := fh.Write(contents)
   350  		require.NoError(t, err)
   351  		assert.Equal(t, n, written)
   352  	}
   353  
   354  	// Close the file without writing to it if n==0
   355  	err := fh.Close()
   356  	if errors.Is(err, fs.ErrorCantUploadEmptyFiles) {
   357  		t.Logf("skipping test: %v", err)
   358  		return
   359  	}
   360  	assert.NoError(t, err)
   361  
   362  	// read the file back in using ReadAt into a buffer
   363  	// this simulates what mount does
   364  	rd, err := vfs.OpenFile("file1", os.O_RDONLY, 0)
   365  	require.NoError(t, err)
   366  
   367  	buf := make([]byte, 1024)
   368  	read, err := rd.ReadAt(buf, 0)
   369  	if err != io.EOF {
   370  		assert.NoError(t, err)
   371  	}
   372  	assert.Equal(t, read, n)
   373  	assert.Equal(t, contents, buf[:read])
   374  
   375  	err = rd.Close()
   376  	assert.NoError(t, err)
   377  }
   378  
   379  func TestFileReadAtZeroLength(t *testing.T) {
   380  	testFileReadAt(t, 0)
   381  }
   382  
   383  func TestFileReadAtNonZeroLength(t *testing.T) {
   384  	testFileReadAt(t, 100)
   385  }