mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-30 07:43:42 +00:00
ipnlocal, taildrop: use SAF to open Android files
This commit is contained in:
parent
8d7033fe7f
commit
ca50599c95
@ -406,6 +406,9 @@ type LocalBackend struct {
|
|||||||
// outgoingFiles keeps track of Taildrop outgoing files keyed to their OutgoingFile.ID
|
// outgoingFiles keeps track of Taildrop outgoing files keyed to their OutgoingFile.ID
|
||||||
outgoingFiles map[string]*ipn.OutgoingFile
|
outgoingFiles map[string]*ipn.OutgoingFile
|
||||||
|
|
||||||
|
// getSafFd gets the Storage Access Framework file descriptor for writing Taildrop files to
|
||||||
|
GetSafFd func(filename string) int32
|
||||||
|
|
||||||
// lastSuggestedExitNode stores the last suggested exit node suggestion to
|
// lastSuggestedExitNode stores the last suggested exit node suggestion to
|
||||||
// avoid unnecessary churn between multiple equally-good options.
|
// avoid unnecessary churn between multiple equally-good options.
|
||||||
lastSuggestedExitNode tailcfg.StableNodeID
|
lastSuggestedExitNode tailcfg.StableNodeID
|
||||||
@ -5303,7 +5306,7 @@ func (b *LocalBackend) initPeerAPIListener() {
|
|||||||
Dir: fileRoot,
|
Dir: fileRoot,
|
||||||
DirectFileMode: b.directFileRoot != "",
|
DirectFileMode: b.directFileRoot != "",
|
||||||
SendFileNotify: b.sendFileNotify,
|
SendFileNotify: b.sendFileNotify,
|
||||||
}.New(),
|
}.New(b.getSafFd),
|
||||||
}
|
}
|
||||||
if dm, ok := b.sys.DNSManager.GetOK(); ok {
|
if dm, ok := b.sys.DNSManager.GetOK(); ok {
|
||||||
ps.resolver = dm.Resolver()
|
ps.resolver = dm.Resolver()
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -82,9 +83,17 @@ func (m *Manager) PutFile(id ClientID, baseName string, r io.Reader, offset, len
|
|||||||
case distro.Get() == distro.Unraid && !m.opts.DirectFileMode:
|
case distro.Get() == distro.Unraid && !m.opts.DirectFileMode:
|
||||||
return 0, ErrNotAccessible
|
return 0, ErrNotAccessible
|
||||||
}
|
}
|
||||||
dstPath, err := joinDir(m.opts.Dir, baseName)
|
var dstPath string
|
||||||
if err != nil {
|
var err error
|
||||||
return 0, err
|
if !(m.opts.DirectFileMode && strings.HasPrefix(m.opts.Dir, "content://")) {
|
||||||
|
dstPath, err = joinDir(m.opts.Dir, baseName)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For Android which uses SAF mode, we can simply use the baseName as our destination "path"
|
||||||
|
// (since we won't be using traditional file paths).
|
||||||
|
dstPath = baseName
|
||||||
}
|
}
|
||||||
|
|
||||||
redactAndLogError := func(action string, err error) error {
|
redactAndLogError := func(action string, err error) error {
|
||||||
@ -116,9 +125,21 @@ func (m *Manager) PutFile(id ClientID, baseName string, r io.Reader, offset, len
|
|||||||
m.deleter.Remove(filepath.Base(partialPath)) // avoid deleting the partial file while receiving
|
m.deleter.Remove(filepath.Base(partialPath)) // avoid deleting the partial file while receiving
|
||||||
|
|
||||||
// Create (if not already) the partial file with read-write permissions.
|
// Create (if not already) the partial file with read-write permissions.
|
||||||
f, err := os.OpenFile(partialPath, os.O_CREATE|os.O_RDWR, 0666)
|
var f *os.File
|
||||||
if err != nil {
|
if m.opts.DirectFileMode && strings.HasPrefix(m.opts.Dir, "content://") {
|
||||||
return 0, redactAndLogError("Create", err)
|
// SAF mode: open the file using Android's SAF.
|
||||||
|
fd := m.GetSafFd(m.opts.Dir, baseName)
|
||||||
|
if err != nil {
|
||||||
|
return 0, redactAndLogError("Create (SAF)", err)
|
||||||
|
}
|
||||||
|
f = os.NewFile(uintptr(fd), baseName)
|
||||||
|
// In SAF mode, we don't have a traditional filesystem path; partialPath remains only for bookkeeping.
|
||||||
|
} else {
|
||||||
|
// Non-SAF (traditional filesystem) mode.
|
||||||
|
f, err = os.OpenFile(partialPath, os.O_CREATE|os.O_RDWR, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return 0, redactAndLogError("Create", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
f.Close() // best-effort to cleanup dangling file handles
|
f.Close() // best-effort to cleanup dangling file handles
|
||||||
|
@ -101,6 +101,8 @@ type ManagerOptions struct {
|
|||||||
type Manager struct {
|
type Manager struct {
|
||||||
opts ManagerOptions
|
opts ManagerOptions
|
||||||
|
|
||||||
|
getSafFd func() int
|
||||||
|
|
||||||
// incomingFiles is a map of files actively being received.
|
// incomingFiles is a map of files actively being received.
|
||||||
incomingFiles syncs.Map[incomingFileKey, *incomingFile]
|
incomingFiles syncs.Map[incomingFileKey, *incomingFile]
|
||||||
// deleter managers asynchronous deletion of files.
|
// deleter managers asynchronous deletion of files.
|
||||||
@ -119,14 +121,14 @@ type Manager struct {
|
|||||||
// New initializes a new taildrop manager.
|
// New initializes a new taildrop manager.
|
||||||
// It may spawn asynchronous goroutines to delete files,
|
// It may spawn asynchronous goroutines to delete files,
|
||||||
// so the Shutdown method must be called for resource cleanup.
|
// so the Shutdown method must be called for resource cleanup.
|
||||||
func (opts ManagerOptions) New() *Manager {
|
func (opts ManagerOptions) New(getSafFd func() int) *Manager {
|
||||||
if opts.Logf == nil {
|
if opts.Logf == nil {
|
||||||
opts.Logf = logger.Discard
|
opts.Logf = logger.Discard
|
||||||
}
|
}
|
||||||
if opts.SendFileNotify == nil {
|
if opts.SendFileNotify == nil {
|
||||||
opts.SendFileNotify = func() {}
|
opts.SendFileNotify = func() {}
|
||||||
}
|
}
|
||||||
m := &Manager{opts: opts}
|
m := &Manager{opts: opts, getSafFd: getSafFd}
|
||||||
m.deleter.Init(m, func(string) {})
|
m.deleter.Init(m, func(string) {})
|
||||||
m.emptySince.Store(-1) // invalidate this cache
|
m.emptySince.Store(-1) // invalidate this cache
|
||||||
return m
|
return m
|
||||||
|
Loading…
x
Reference in New Issue
Block a user