mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-29 15:23:45 +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 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
|
||||
// avoid unnecessary churn between multiple equally-good options.
|
||||
lastSuggestedExitNode tailcfg.StableNodeID
|
||||
@ -5303,7 +5306,7 @@ func (b *LocalBackend) initPeerAPIListener() {
|
||||
Dir: fileRoot,
|
||||
DirectFileMode: b.directFileRoot != "",
|
||||
SendFileNotify: b.sendFileNotify,
|
||||
}.New(),
|
||||
}.New(b.getSafFd),
|
||||
}
|
||||
if dm, ok := b.sys.DNSManager.GetOK(); ok {
|
||||
ps.resolver = dm.Resolver()
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"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:
|
||||
return 0, ErrNotAccessible
|
||||
}
|
||||
dstPath, err := joinDir(m.opts.Dir, baseName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
var dstPath string
|
||||
var err error
|
||||
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 {
|
||||
@ -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
|
||||
|
||||
// Create (if not already) the partial file with read-write permissions.
|
||||
f, err := os.OpenFile(partialPath, os.O_CREATE|os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
return 0, redactAndLogError("Create", err)
|
||||
var f *os.File
|
||||
if m.opts.DirectFileMode && strings.HasPrefix(m.opts.Dir, "content://") {
|
||||
// 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() {
|
||||
f.Close() // best-effort to cleanup dangling file handles
|
||||
|
@ -101,6 +101,8 @@ type ManagerOptions struct {
|
||||
type Manager struct {
|
||||
opts ManagerOptions
|
||||
|
||||
getSafFd func() int
|
||||
|
||||
// incomingFiles is a map of files actively being received.
|
||||
incomingFiles syncs.Map[incomingFileKey, *incomingFile]
|
||||
// deleter managers asynchronous deletion of files.
|
||||
@ -119,14 +121,14 @@ type Manager struct {
|
||||
// New initializes a new taildrop manager.
|
||||
// It may spawn asynchronous goroutines to delete files,
|
||||
// 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 {
|
||||
opts.Logf = logger.Discard
|
||||
}
|
||||
if opts.SendFileNotify == nil {
|
||||
opts.SendFileNotify = func() {}
|
||||
}
|
||||
m := &Manager{opts: opts}
|
||||
m := &Manager{opts: opts, getSafFd: getSafFd}
|
||||
m.deleter.Init(m, func(string) {})
|
||||
m.emptySince.Store(-1) // invalidate this cache
|
||||
return m
|
||||
|
Loading…
x
Reference in New Issue
Block a user