Skip to content

Commit 6e55f5b

Browse files
authored
Support synchronizing symbolic link itself (#230)
1 parent c903adf commit 6e55f5b

20 files changed

+320
-52
lines changed

fs/fs.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func IsNonEOF(err error) bool {
7171

7272
// GetFileTime get the creation time, last access time, last modify time of the path
7373
func GetFileTime(path string) (cTime time.Time, aTime time.Time, mTime time.Time, err error) {
74-
stat, err := os.Stat(path)
74+
stat, err := os.Lstat(path)
7575
if err != nil {
7676
return
7777
}
@@ -131,7 +131,7 @@ func IsSymlinkMode(mode fs.FileMode) bool {
131131

132132
// IsSymlinkSupported checks if the system supports symbolic links
133133
func IsSymlinkSupported() bool {
134-
symlink := filepath.Join(os.TempDir(), "symlink_detect.symlink")
134+
symlink := filepath.Join(os.TempDir(), fmt.Sprintf("%d.symlink_detect", time.Now().UnixNano()))
135135
defer os.RemoveAll(symlink)
136136
return Symlink(os.Args[0], symlink) == nil
137137
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/gin-contrib/sessions v0.0.5
1010
github.com/gin-gonic/gin v1.9.1
1111
github.com/minio/minio-go/v7 v7.0.60
12-
github.com/no-src/fsctl v0.1.1
12+
github.com/no-src/fsctl v0.1.2-0.20230808162217-242297fbaea4
1313
github.com/no-src/log v0.2.3
1414
github.com/no-src/nscache v0.1.1
1515
github.com/pkg/sftp v1.13.5

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
126126
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
127127
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
128128
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
129-
github.com/no-src/fsctl v0.1.1 h1:E4JTXDhASk6LtZLWFVpDCqVIw7DtU8IPDydamYi/wTQ=
130-
github.com/no-src/fsctl v0.1.1/go.mod h1:ObH5aEHwS6JLtV81Igj2BxciD5W/Tm40szqhm8W55q8=
129+
github.com/no-src/fsctl v0.1.2-0.20230808162217-242297fbaea4 h1:l4/bFYazcDMwBEogCeLwNRVxGtmNYBwLZEoJRqOxdf0=
130+
github.com/no-src/fsctl v0.1.2-0.20230808162217-242297fbaea4/go.mod h1:sXkup5MSxKl0br3AbtkoOhaGD0QSuieO8J3pExKegVs=
131131
github.com/no-src/log v0.2.3 h1:lCJrWeMMuC2Wkp6CKEVylgllH+k4YAm0eRsFX9nVxaQ=
132132
github.com/no-src/log v0.2.3/go.mod h1:C58ahv0WdsBL3Yxi+v6wyUXzwqk7aEPN+/1ltOsdHXg=
133133
github.com/no-src/nscache v0.1.1 h1:gs9hJ4Y5CaeLRmexvE4Rto6Gnjf5maKoTxFnssXAuJE=

integration/testdata/test/test-gofs-local-disk.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ actions:
3434
source: ./source/empty2
3535
no-newline: true
3636
- run: echo "Bye Bye"
37+
- symlink:
38+
link: ./source/hello.symlink
39+
dest: ./source/hello
40+
ignore-error: false
41+
- symlink:
42+
link: ./source/hello.symlink.bak
43+
dest: ./source/hello
44+
ignore-error: false
45+
- symlink:
46+
link: ./source/not_exist.symlink
47+
dest: ./source/not_exist
48+
ignore-error: false
49+
- rm:
50+
source: ./source/hello.symlink.bak
3751
- sleep: 10s
3852
- is-equal:
3953
source: ./integration_test.go
@@ -75,6 +89,17 @@ actions:
7589
algorithm: sha1
7690
source: ./source/hello
7791
expect: f343874b5df87e887d85df2e790df33584463162
92+
- is-symlink:
93+
link: ./dest/hello.symlink
94+
expect: true
95+
ignore-error: false
96+
- is-symlink:
97+
link: ./dest/not_exist.symlink
98+
expect: true
99+
ignore-error: false
100+
- is-exist:
101+
source: ./dest/hello.symlink.bak
102+
expect: false
78103
clear:
79104
- rm:
80105
source: ./source

integration/testdata/test/test-gofs-minio-push.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ actions:
3030
- echo:
3131
source: ./minio-push-client/source/empty2
3232
no-newline: true
33+
- symlink:
34+
link: ./minio-push-client/source/hello.symlink
35+
dest: ./minio-push-client/source/hello
36+
ignore-error: false
37+
- symlink:
38+
link: ./minio-push-client/source/hello.symlink.bak
39+
dest: ./minio-push-client/source/hello
40+
ignore-error: false
41+
- symlink:
42+
link: ./minio-push-client/source/not_exist.symlink
43+
dest: ./minio-push-client/source/not_exist
44+
ignore-error: false
45+
- rm:
46+
source: ./minio-push-client/source/hello.symlink.bak
3347
- sleep: 10s
3448
- is-equal:
3549
source: ./minio-push-client/source/integration_test.go.bak1
@@ -81,6 +95,15 @@ actions:
8195
- is-exist:
8296
source: ./minio-data-mount/integration_test.go.bak1
8397
expect: false
98+
- is-empty:
99+
source: ./minio-data-mount/hello.symlink
100+
expect: false
101+
- is-empty:
102+
source: ./minio-data-mount/not_exist.symlink
103+
expect: false
104+
- is-exist:
105+
source: ./minio-data-mount/hello.symlink.bak
106+
expect: false
84107
clear:
85108
- rm:
86109
source: ./minio-push-client

integration/testdata/test/test-gofs-remote-disk.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ actions:
4242
- echo:
4343
source: ./rs/source/empty2
4444
no-newline: true
45+
- symlink:
46+
link: ./rs/source/hello.symlink
47+
dest: ./rs/source/hello
48+
ignore-error: false
49+
- symlink:
50+
link: ./rs/source/hello.symlink.bak
51+
dest: ./rs/source/hello
52+
ignore-error: false
53+
- symlink:
54+
link: ./rs/source/not_exist.symlink
55+
dest: ./rs/source/not_exist
56+
ignore-error: false
57+
- rm:
58+
source: ./rs/source/hello.symlink.bak
4559
- sleep: 10s
4660
- hash:
4761
algorithm: md5
@@ -90,6 +104,17 @@ actions:
90104
algorithm: sha1
91105
source: ./rc/dest/hello
92106
expect: f343874b5df87e887d85df2e790df33584463162
107+
- is-symlink:
108+
link: ./rc/dest/hello.symlink
109+
expect: true
110+
ignore-error: false
111+
- is-symlink:
112+
link: ./rc/dest/not_exist.symlink
113+
expect: true
114+
ignore-error: false
115+
- is-exist:
116+
source: ./rc/dest/hello.symlink.bak
117+
expect: false
93118
clear:
94119
- rm:
95120
source: ./rs

integration/testdata/test/test-gofs-remote-push.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ actions:
4242
- echo:
4343
source: ./rc-push/source/empty2
4444
no-newline: true
45+
- symlink:
46+
link: ./rc-push/source/hello.symlink
47+
dest: ./rc-push/source/hello
48+
ignore-error: false
49+
- symlink:
50+
link: ./rc-push/source/hello.symlink.bak
51+
dest: ./rc-push/source/hello
52+
ignore-error: false
53+
- symlink:
54+
link: ./rc-push/source/not_exist.symlink
55+
dest: ./rc-push/source/not_exist
56+
ignore-error: false
57+
- rm:
58+
source: ./rc-push/source/hello.symlink.bak
4559
- sleep: 10s
4660
- hash:
4761
algorithm: md5
@@ -96,6 +110,17 @@ actions:
96110
- is-exist:
97111
source: ./rs-push/source/integration_test.go.bak1
98112
expect: false
113+
- is-symlink:
114+
link: ./rs-push/source/hello.symlink
115+
expect: true
116+
ignore-error: false
117+
- is-symlink:
118+
link: ./rs-push/source/not_exist.symlink
119+
expect: true
120+
ignore-error: false
121+
- is-exist:
122+
source: ./rs-push/source/hello.symlink.bak
123+
expect: false
99124
clear:
100125
- rm:
101126
source: ./rs-push

monitor/fsnotify_monitor.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/fsnotify/fsnotify"
1111
"github.com/no-src/gofs/core"
1212
"github.com/no-src/gofs/eventlog"
13+
nsfs "github.com/no-src/gofs/fs"
1314
"github.com/no-src/gofs/ignore"
1415
"github.com/no-src/gofs/internal/clist"
1516
"github.com/no-src/gofs/report"
@@ -167,9 +168,13 @@ func (m *fsNotifyMonitor) startProcessEvents() error {
167168
// the file "info.log" does not match the ignore rule and should be synchronized to destination directory.
168169
m.monitorDirIfCreate(event)
169170
} else if event.Op&fsnotify.Write == fsnotify.Write {
170-
m.write(event)
171+
symlink, err := nsfs.IsSymlink(event.Name)
172+
log.ErrorIf(err, "[write] check is symlink error =>%s", event.Name)
173+
if err == nil && !symlink {
174+
m.write(event)
175+
}
171176
} else if event.Op&fsnotify.Create == fsnotify.Create {
172-
m.create(event)
177+
m.symlinkOrCreate(event)
173178
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
174179
m.remove(event)
175180
} else if event.Op&fsnotify.Rename == fsnotify.Rename {
@@ -212,7 +217,7 @@ func (m *fsNotifyMonitor) create(event fsnotify.Event) {
212217
// rename a file will not trigger the Write event
213218
// rename a dir will not trigger the Write event on Linux and some Windows environments
214219
// in some cases it will trigger the Write event for the parent dir on Windows
215-
// send a Write event manually
220+
// send a Write event manually if the file is not a symbolic link
216221
log.Debug("prepare to send a [write] event after [create] event [%s]", event.Name)
217222
m.events.PushBack(fsnotify.Event{
218223
Name: event.Name,
@@ -222,6 +227,28 @@ func (m *fsNotifyMonitor) create(event fsnotify.Event) {
222227
}
223228
}
224229

230+
func (m *fsNotifyMonitor) symlink(event fsnotify.Event) {
231+
source, err := nsfs.Readlink(event.Name)
232+
if err != nil {
233+
log.Error(err, "[symlink] read link error => [%s]", event.Name)
234+
return
235+
}
236+
log.ErrorIf(m.syncer.Symlink(source, event.Name), "[symlink] event execute error => [%s]", event.Name)
237+
}
238+
239+
func (m *fsNotifyMonitor) symlinkOrCreate(event fsnotify.Event) {
240+
symlink, err := nsfs.IsSymlink(event.Name)
241+
if err != nil {
242+
log.Error(err, "[create] check is symlink error =>%s", event.Name)
243+
return
244+
}
245+
if symlink {
246+
m.symlink(event)
247+
} else {
248+
m.create(event)
249+
}
250+
}
251+
225252
func (m *fsNotifyMonitor) remove(event fsnotify.Event) {
226253
m.removeWrite(event.Name)
227254
log.ErrorIf(m.syncer.Remove(event.Name), "[remove] event execute error => [%s]", event.Name)

monitor/remote_client_monitor.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ func (m *remoteClientMonitor) execSync(msg *monitor.MonitorMessage) (err error)
209209
switch action.Action(msg.Action) {
210210
case action.CreateAction:
211211
err = m.syncer.Create(path)
212+
case action.SymlinkAction:
213+
err = m.syncer.Symlink(fi.LinkTo, path)
212214
case action.WriteAction:
213215
err = m.syncer.Create(path)
214216
// ignore is not exist error

server/README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Response field description:
6161
- `c_time` file creation time
6262
- `a_time` file last access time
6363
- `m_time` file last modify time
64+
- `link_to` link to the real file
6465

6566
##### Example
6667

@@ -128,7 +129,8 @@ Here is an example response:
128129
],
129130
"c_time": 1649431872,
130131
"a_time": 1649431873,
131-
"m_time": 1647397031
132+
"m_time": 1647397031,
133+
"link_to": ""
132134
},
133135
{
134136
"path": "hello_gofs.txt",
@@ -143,7 +145,8 @@ Here is an example response:
143145
],
144146
"c_time": 1649431542,
145147
"a_time": 1649434237,
146-
"m_time": 1649434237
148+
"m_time": 1649434237,
149+
"link_to": ""
147150
},
148151
{
149152
"path": "resource",
@@ -153,7 +156,8 @@ Here is an example response:
153156
"hash_values": null,
154157
"c_time": 1649431669,
155158
"a_time": 1649431898,
156-
"m_time": 1649431898
159+
"m_time": 1649431898,
160+
"link_to": ""
157161
}
158162
]
159163
}
@@ -174,7 +178,7 @@ Push the file changes to the [Remote Push Server](/README.md#remote-push-server)
174178
Request field description:
175179

176180
- `push_data` the request data of push api, contains basic push file info and chunk info etc
177-
- `action` the action of file change, Create(1) Write(2) Remove(3) Rename(4) Chmod(5)
181+
- `action` the action of file change, Create(1) Write(2) Remove(3) Rename(4) Chmod(5) Symlink(6)
178182
- `push_action` the file upload action, CompareFile(1) CompareChunk(2) CompareFileAndChunk(3) Write(4) Truncate(5)
179183
- `file_info` basic push file info
180184
- `path` file path
@@ -184,6 +188,7 @@ Request field description:
184188
- `c_time` file creation time
185189
- `a_time` file last access time
186190
- `m_time` file last modify time
191+
- `link_to` link to the real file
187192
- `chunk`
188193
- `offset` the offset relative to the origin of the file
189194
- `size` file chunk size of bytes, directory is always `0`
@@ -208,7 +213,7 @@ Accept-Encoding: gzip
208213
--d9e3eb63103de1c2698b0675a70567e47bed1b06c71ff9e26199967312d1
209214
Content-Disposition: form-data; name="push_data"
210215
211-
{"action":2,"push_action":4,"file_info":{"path":"hello_gofs.txt","is_dir":0,"size":5,"hash":"5d41402abc4b2a76b9719d911017c592","hash_values":null,"c_time":1651681421,"a_time":1651681421,"m_time":1651681421},"chunk":{"offset":0,"hash":"5d41402abc4b2a76b9719d911017c592","size":5},"force_checksum":false}
216+
{"action":2,"push_action":4,"file_info":{"path":"hello_gofs.txt","is_dir":0,"size":5,"hash":"5d41402abc4b2a76b9719d911017c592","hash_values":null,"c_time":1651681421,"a_time":1651681421,"m_time":1651681421,"link_to":""},"chunk":{"offset":0,"hash":"5d41402abc4b2a76b9719d911017c592","size":5},"force_checksum":false}
212217
--d9e3eb63103de1c2698b0675a70567e47bed1b06c71ff9e26199967312d1
213218
Content-Disposition: form-data; name="up_file"; filename="hello_gofs.txt"
214219
Content-Type: application/octet-stream

0 commit comments

Comments
 (0)