mirror of
https://github.com/rclone/rclone.git
synced 2024-12-25 13:33:40 +08:00
01ccf204f4
Before this change, if writing to a local backend with --metadata and --links, if the incoming metadata contained mode or ownership information then rclone would apply the mode/ownership to the destination of the link not the link itself. This fixes the problem by using the link safe sycall variants lchown/fchmodat when --links and --metadata is in use. Note that Linux does not support setting permissions on symlinks, so rclone emits a debug message in this case. This also fixes setting times on symlinks on Windows which wasn't implemented for atime, mtime and was incorrectly setting the target of the symlink for btime. See: https://github.com/rclone/rclone/security/advisories/GHSA-hrxh-9w67-g4cv
162 lines
3.7 KiB
Go
162 lines
3.7 KiB
Go
package local
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
)
|
|
|
|
const metadataTimeFormat = time.RFC3339Nano
|
|
|
|
// system metadata keys which this backend owns
|
|
//
|
|
// not all values supported on all OSes
|
|
var systemMetadataInfo = map[string]fs.MetadataHelp{
|
|
"mode": {
|
|
Help: "File type and mode",
|
|
Type: "octal, unix style",
|
|
Example: "0100664",
|
|
},
|
|
"uid": {
|
|
Help: "User ID of owner",
|
|
Type: "decimal number",
|
|
Example: "500",
|
|
},
|
|
"gid": {
|
|
Help: "Group ID of owner",
|
|
Type: "decimal number",
|
|
Example: "500",
|
|
},
|
|
"rdev": {
|
|
Help: "Device ID (if special file)",
|
|
Type: "hexadecimal",
|
|
Example: "1abc",
|
|
},
|
|
"atime": {
|
|
Help: "Time of last access",
|
|
Type: "RFC 3339",
|
|
Example: "2006-01-02T15:04:05.999999999Z07:00",
|
|
},
|
|
"mtime": {
|
|
Help: "Time of last modification",
|
|
Type: "RFC 3339",
|
|
Example: "2006-01-02T15:04:05.999999999Z07:00",
|
|
},
|
|
"btime": {
|
|
Help: "Time of file birth (creation)",
|
|
Type: "RFC 3339",
|
|
Example: "2006-01-02T15:04:05.999999999Z07:00",
|
|
},
|
|
}
|
|
|
|
// parse a time string from metadata with key
|
|
func (o *Object) parseMetadataTime(m fs.Metadata, key string) (t time.Time, ok bool) {
|
|
value, ok := m[key]
|
|
if ok {
|
|
var err error
|
|
t, err = time.Parse(metadataTimeFormat, value)
|
|
if err != nil {
|
|
fs.Debugf(o, "failed to parse metadata %s: %q: %v", key, value, err)
|
|
ok = false
|
|
}
|
|
}
|
|
return t, ok
|
|
}
|
|
|
|
// parse am int from metadata with key and base
|
|
func (o *Object) parseMetadataInt(m fs.Metadata, key string, base int) (result int, ok bool) {
|
|
value, ok := m[key]
|
|
if ok {
|
|
var err error
|
|
parsed, err := strconv.ParseInt(value, base, 0)
|
|
if err != nil {
|
|
fs.Debugf(o, "failed to parse metadata %s: %q: %v", key, value, err)
|
|
ok = false
|
|
}
|
|
result = int(parsed)
|
|
}
|
|
return result, ok
|
|
}
|
|
|
|
// Write the metadata into the file
|
|
//
|
|
// It isn't possible to set the ctime and btime under Unix
|
|
func (o *Object) writeMetadataToFile(m fs.Metadata) (outErr error) {
|
|
var err error
|
|
atime, atimeOK := o.parseMetadataTime(m, "atime")
|
|
mtime, mtimeOK := o.parseMetadataTime(m, "mtime")
|
|
btime, btimeOK := o.parseMetadataTime(m, "btime")
|
|
if atimeOK || mtimeOK {
|
|
if atimeOK && !mtimeOK {
|
|
mtime = atime
|
|
}
|
|
if !atimeOK && mtimeOK {
|
|
atime = mtime
|
|
}
|
|
err = o.setTimes(atime, mtime)
|
|
if err != nil {
|
|
outErr = fmt.Errorf("failed to set times: %w", err)
|
|
}
|
|
}
|
|
if haveSetBTime {
|
|
if btimeOK {
|
|
if o.translatedLink {
|
|
err = lsetBTime(o.path, btime)
|
|
} else {
|
|
err = setBTime(o.path, btime)
|
|
}
|
|
if err != nil {
|
|
outErr = fmt.Errorf("failed to set birth (creation) time: %w", err)
|
|
}
|
|
}
|
|
}
|
|
uid, hasUID := o.parseMetadataInt(m, "uid", 10)
|
|
gid, hasGID := o.parseMetadataInt(m, "gid", 10)
|
|
if hasUID {
|
|
// FIXME should read UID and GID of current user and only attempt to set it if different
|
|
if !hasGID {
|
|
gid = uid
|
|
}
|
|
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
|
|
fs.Debugf(o, "Ignoring request to set ownership %o.%o on this OS", gid, uid)
|
|
} else {
|
|
if o.translatedLink {
|
|
err = os.Lchown(o.path, uid, gid)
|
|
} else {
|
|
err = os.Chown(o.path, uid, gid)
|
|
}
|
|
if err != nil {
|
|
outErr = fmt.Errorf("failed to change ownership: %w", err)
|
|
}
|
|
}
|
|
}
|
|
mode, hasMode := o.parseMetadataInt(m, "mode", 8)
|
|
if hasMode {
|
|
if mode >= 0 {
|
|
umode := uint(mode)
|
|
if umode <= math.MaxUint32 {
|
|
if o.translatedLink {
|
|
if haveLChmod {
|
|
err = lChmod(o.path, os.FileMode(umode))
|
|
} else {
|
|
fs.Debugf(o, "Unable to set mode %v on a symlink on this OS", os.FileMode(umode))
|
|
err = nil
|
|
}
|
|
} else {
|
|
err = os.Chmod(o.path, os.FileMode(umode))
|
|
}
|
|
if err != nil {
|
|
outErr = fmt.Errorf("failed to change permissions: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// FIXME not parsing rdev yet
|
|
return outErr
|
|
}
|