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