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 }