lib/config: Add file inside folder marker directory (#9525)

### Purpose

Avoid the issue where the folder marker is deleted by overzealous
cleanup tools because it's just a useless, empty directory.

We create a small file containing a an admonishment to not delete the
directory, and some metadata that is just for human consumption at the
moment. (But it would parse as a valid yaml file if we wanted to read
this, at some point.)

This will only apply when _creating_ a folder marker, that is, existing
setups will not gain the file automatically. Obviously, when using a
custom folder marker none of this applies.

Also, slightly adjust the permission bits for the folder marker directory and file on Unixes, making sure the group & write bits are unset.

### Testing

I've created and deleted a few folders and it appears to behave as I
expect.

### Screenshots

```
jb@ok:~/somefolder % ls -la
total 0
drwxr-xr-x   3 jb  staff   96 May  1 08:52 ./
drwx------  12 jb  staff  384 May  1 08:52 ../
drwxr-xr-x   3 jb  staff   96 May  1 08:52 .stfolder/
jb@ok:~/somefolder % ls -l .stfolder
total 8
-rw-r--r--  1 jb  staff  122 May  1 08:52 syncthing-folder-39a4b0.txt
jb@ok:~/somefolder % cat .stfolder/syncthing-folder-39a4b0.txt
# This directory is a Syncthing folder marker.
# Do not delete.

folderID: xtdca-cudyf
created: 2024-05-01T08:52:49+02:00
```
This commit is contained in:
Jakob Borg 2024-05-24 08:51:02 +02:00 committed by GitHub
parent 4b60e86d02
commit a2b8f2361e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 38 additions and 12 deletions

View File

@ -7,6 +7,8 @@
package config package config
import ( import (
"bytes"
"crypto/sha256"
"errors" "errors"
"fmt" "fmt"
"path" "path"
@ -90,27 +92,51 @@ func (f *FolderConfiguration) CreateMarker() error {
return nil return nil
} }
permBits := fs.FileMode(0o777) ffs := f.Filesystem(nil)
if build.IsWindows {
// Windows has no umask so we must chose a safer set of bits to // Create the marker as a directory
// begin with. err := ffs.Mkdir(DefaultMarkerName, 0o755)
permBits = 0o700
}
fs := f.Filesystem(nil)
err := fs.Mkdir(DefaultMarkerName, permBits)
if err != nil { if err != nil {
return err return err
} }
if dir, err := fs.Open("."); err != nil {
// Create a file inside it, reducing the risk of the marker directory
// being removed by automated cleanup tools.
markerFile := filepath.Join(DefaultMarkerName, f.markerFilename())
if err := fs.WriteFile(ffs, markerFile, f.markerContents(), 0o644); err != nil {
return err
}
// Sync & hide the containing directory
if dir, err := ffs.Open("."); err != nil {
l.Debugln("folder marker: open . failed:", err) l.Debugln("folder marker: open . failed:", err)
} else if err := dir.Sync(); err != nil { } else if err := dir.Sync(); err != nil {
l.Debugln("folder marker: fsync . failed:", err) l.Debugln("folder marker: fsync . failed:", err)
} }
fs.Hide(DefaultMarkerName) ffs.Hide(DefaultMarkerName)
return nil return nil
} }
func (f *FolderConfiguration) RemoveMarker() error {
ffs := f.Filesystem(nil)
_ = ffs.Remove(filepath.Join(DefaultMarkerName, f.markerFilename()))
return ffs.Remove(DefaultMarkerName)
}
func (f *FolderConfiguration) markerFilename() string {
h := sha256.Sum256([]byte(f.ID))
return fmt.Sprintf("syncthing-folder-%x.txt", h[:3])
}
func (f *FolderConfiguration) markerContents() []byte {
var buf bytes.Buffer
buf.WriteString("# This directory is a Syncthing folder marker.\n# Do not delete.\n\n")
fmt.Fprintf(&buf, "folderID: %s\n", f.ID)
fmt.Fprintf(&buf, "created: %s\n", time.Now().Format(time.RFC3339))
return buf.Bytes()
}
// CheckPath returns nil if the folder root exists and contains the marker file // CheckPath returns nil if the folder root exists and contains the marker file
func (f *FolderConfiguration) CheckPath() error { func (f *FolderConfiguration) CheckPath() error {
return f.checkFilesystemPath(f.Filesystem(nil), ".") return f.checkFilesystemPath(f.Filesystem(nil), ".")

View File

@ -464,9 +464,9 @@ func (m *model) removeFolder(cfg config.FolderConfiguration) {
if isPathUnique { if isPathUnique {
// Remove (if empty and removable) or move away (if non-empty or // Remove (if empty and removable) or move away (if non-empty or
// otherwise not removable) Syncthing-specific marker files. // otherwise not removable) Syncthing-specific marker files.
fs := cfg.Filesystem(nil) if err := cfg.RemoveMarker(); err != nil && !errors.Is(err, os.ErrNotExist) {
if err := fs.Remove(config.DefaultMarkerName); err != nil {
moved := config.DefaultMarkerName + time.Now().Format(".removed-20060102-150405") moved := config.DefaultMarkerName + time.Now().Format(".removed-20060102-150405")
fs := cfg.Filesystem(nil)
_ = fs.Rename(config.DefaultMarkerName, moved) _ = fs.Rename(config.DefaultMarkerName, moved)
} }
} }