forked from syncthing/syncthing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
osutil.go
144 lines (129 loc) · 4.23 KB
/
osutil.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// Package osutil implements utilities for native OS support.
package osutil
import (
"path/filepath"
"strings"
"github.com/jector20/syncthing/lib/build"
"github.com/jector20/syncthing/lib/fs"
"github.com/jector20/syncthing/lib/sync"
)
// Try to keep this entire operation atomic-like. We shouldn't be doing this
// often enough that there is any contention on this lock.
var renameLock = sync.NewMutex()
// RenameOrCopy renames a file, leaving source file intact in case of failure.
// Tries hard to succeed on various systems by temporarily tweaking directory
// permissions and removing the destination file when necessary.
func RenameOrCopy(method fs.CopyRangeMethod, src, dst fs.Filesystem, from, to string) error {
renameLock.Lock()
defer renameLock.Unlock()
return withPreparedTarget(dst, from, to, func() error {
// Optimisation 1
if src.Type() == dst.Type() && src.URI() == dst.URI() {
return src.Rename(from, to)
}
// "Optimisation" 2
// Try to find a common prefix between the two filesystems, use that as the base for the new one
// and try a rename.
if src.Type() == dst.Type() {
commonPrefix := fs.CommonPrefix(src.URI(), dst.URI())
if len(commonPrefix) > 0 {
commonFs := fs.NewFilesystem(src.Type(), commonPrefix)
err := commonFs.Rename(
filepath.Join(strings.TrimPrefix(src.URI(), commonPrefix), from),
filepath.Join(strings.TrimPrefix(dst.URI(), commonPrefix), to),
)
if err == nil {
return nil
}
}
}
// Everything is sad, do a copy and delete.
if _, err := dst.Stat(to); !fs.IsNotExist(err) {
err := dst.Remove(to)
if err != nil {
return err
}
}
err := copyFileContents(method, src, dst, from, to)
if err != nil {
_ = dst.Remove(to)
return err
}
return withPreparedTarget(src, from, from, func() error {
return src.Remove(from)
})
})
}
// Copy copies the file content from source to destination.
// Tries hard to succeed on various systems by temporarily tweaking directory
// permissions and removing the destination file when necessary.
func Copy(method fs.CopyRangeMethod, src, dst fs.Filesystem, from, to string) (err error) {
return withPreparedTarget(dst, from, to, func() error {
return copyFileContents(method, src, dst, from, to)
})
}
// Tries hard to succeed on various systems by temporarily tweaking directory
// permissions and removing the destination file when necessary.
func withPreparedTarget(filesystem fs.Filesystem, from, to string, f func() error) error {
// Make sure the destination directory is writeable
toDir := filepath.Dir(to)
if info, err := filesystem.Stat(toDir); err == nil && info.IsDir() && info.Mode()&0200 == 0 {
filesystem.Chmod(toDir, 0755)
defer filesystem.Chmod(toDir, info.Mode())
}
// On Windows, make sure the destination file is writeable (or we can't delete it)
if build.IsWindows {
filesystem.Chmod(to, 0666)
if !strings.EqualFold(from, to) {
err := filesystem.Remove(to)
if err != nil && !fs.IsNotExist(err) {
return err
}
}
}
return f()
}
// copyFileContents copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all its contents will be replaced by the contents
// of the source file.
func copyFileContents(method fs.CopyRangeMethod, srcFs, dstFs fs.Filesystem, src, dst string) (err error) {
in, err := srcFs.Open(src)
if err != nil {
return
}
defer in.Close()
out, err := dstFs.Create(dst)
if err != nil {
return
}
defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
inFi, err := in.Stat()
if err != nil {
return
}
err = fs.CopyRange(method, in, out, 0, 0, inFi.Size())
return
}
func IsDeleted(ffs fs.Filesystem, name string) bool {
if _, err := ffs.Lstat(name); err != nil {
if fs.IsNotExist(err) || fs.IsErrCaseConflict(err) {
return true
}
}
switch TraversesSymlink(ffs, filepath.Dir(name)).(type) {
case *NotADirectoryError, *TraversesSymlinkError:
return true
}
return false
}