mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-08 09:07:44 +00:00
ipn/ipnlocal: add LocalBackend.SetDirectFileRoot
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
64c80129f1
commit
f5742b0647
@ -94,6 +94,13 @@ type PartialFile struct {
|
|||||||
Started time.Time // time transfer started
|
Started time.Time // time transfer started
|
||||||
DeclaredSize int64 // or -1 if unknown
|
DeclaredSize int64 // or -1 if unknown
|
||||||
Received int64 // bytes copied thus far
|
Received int64 // bytes copied thus far
|
||||||
|
|
||||||
|
// FinalPath is non-empty when the final has been completely
|
||||||
|
// written and renamed into place. This is then the complete
|
||||||
|
// path to the file post-rename. This is only set in
|
||||||
|
// "direct" file mode where the peerapi isn't being used; see
|
||||||
|
// LocalBackend.SetDirectFileRoot.
|
||||||
|
FinalPath string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateKey is an opaque identifier for a set of LocalBackend state
|
// StateKey is an opaque identifier for a set of LocalBackend state
|
||||||
|
@ -118,6 +118,16 @@ type LocalBackend struct {
|
|||||||
peerAPIServer *peerAPIServer // or nil
|
peerAPIServer *peerAPIServer // or nil
|
||||||
peerAPIListeners []*peerAPIListener
|
peerAPIListeners []*peerAPIListener
|
||||||
incomingFiles map[*incomingFile]bool
|
incomingFiles map[*incomingFile]bool
|
||||||
|
// directFileRoot, if non-empty, means to write received files
|
||||||
|
// directly to this directory, without staging them in an
|
||||||
|
// intermediate buffered directory for "pick-up" later. If
|
||||||
|
// empty, the files are received in a daemon-owned location
|
||||||
|
// and the localapi is used to enumerate, download, and delete
|
||||||
|
// them. This is used on macOS where the GUI lifetime is the
|
||||||
|
// same as the Network Extension lifetime and we can thus avoid
|
||||||
|
// double-copying files by writing them to the right location
|
||||||
|
// immediately.
|
||||||
|
directFileRoot string
|
||||||
|
|
||||||
// statusLock must be held before calling statusChanged.Wait() or
|
// statusLock must be held before calling statusChanged.Wait() or
|
||||||
// statusChanged.Broadcast().
|
// statusChanged.Broadcast().
|
||||||
@ -179,6 +189,17 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, e wge
|
|||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDirectFileRoot sets the directory to download files to directly,
|
||||||
|
// without buffering them through an intermediate daemon-owned
|
||||||
|
// tailcfg.UserID-specific directory.
|
||||||
|
//
|
||||||
|
// This must be called before the LocalBackend starts being used.
|
||||||
|
func (b *LocalBackend) SetDirectFileRoot(dir string) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
b.directFileRoot = dir
|
||||||
|
}
|
||||||
|
|
||||||
// linkChange is our link monitor callback, called whenever the network changes.
|
// linkChange is our link monitor callback, called whenever the network changes.
|
||||||
// major is whether ifst is different than earlier.
|
// major is whether ifst is different than earlier.
|
||||||
func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
|
func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
|
||||||
@ -1611,6 +1632,26 @@ func tailscaleVarRoot() string {
|
|||||||
return filepath.Dir(stateFile)
|
return filepath.Dir(stateFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *LocalBackend) fileRootLocked(uid tailcfg.UserID) string {
|
||||||
|
if v := b.directFileRoot; v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
varRoot := tailscaleVarRoot()
|
||||||
|
if varRoot == "" {
|
||||||
|
b.logf("peerapi disabled; no state directory")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
baseDir := fmt.Sprintf("%s-uid-%d",
|
||||||
|
strings.ReplaceAll(b.activeLogin, "@", "-"),
|
||||||
|
uid)
|
||||||
|
dir := filepath.Join(varRoot, "files", baseDir)
|
||||||
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||||
|
b.logf("peerapi disabled; error making directory: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) initPeerAPIListener() {
|
func (b *LocalBackend) initPeerAPIListener() {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
@ -1640,17 +1681,8 @@ func (b *LocalBackend) initPeerAPIListener() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
varRoot := tailscaleVarRoot()
|
fileRoot := b.fileRootLocked(selfNode.User)
|
||||||
if varRoot == "" {
|
if fileRoot == "" {
|
||||||
b.logf("peerapi disabled; no state directory")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
baseDir := fmt.Sprintf("%s-uid-%d",
|
|
||||||
strings.ReplaceAll(b.activeLogin, "@", "-"),
|
|
||||||
selfNode.User)
|
|
||||||
dir := filepath.Join(varRoot, "files", baseDir)
|
|
||||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
||||||
b.logf("peerapi disabled; error making directory: %v", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1662,10 +1694,11 @@ func (b *LocalBackend) initPeerAPIListener() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ps := &peerAPIServer{
|
ps := &peerAPIServer{
|
||||||
b: b,
|
b: b,
|
||||||
rootDir: dir,
|
rootDir: fileRoot,
|
||||||
tunName: tunName,
|
tunName: tunName,
|
||||||
selfNode: selfNode,
|
selfNode: selfNode,
|
||||||
|
directFileMode: b.directFileRoot != "",
|
||||||
}
|
}
|
||||||
b.peerAPIServer = ps
|
b.peerAPIServer = ps
|
||||||
|
|
||||||
|
@ -39,9 +39,15 @@ type peerAPIServer struct {
|
|||||||
tunName string
|
tunName string
|
||||||
selfNode *tailcfg.Node
|
selfNode *tailcfg.Node
|
||||||
knownEmpty syncs.AtomicBool
|
knownEmpty syncs.AtomicBool
|
||||||
|
|
||||||
|
// directFileMode is whether we're writing files directly to a
|
||||||
|
// download directory (as *.partial files), rather than making
|
||||||
|
// the frontend retrieve it over localapi HTTP and write it
|
||||||
|
// somewhere itself. This is used on GUI macOS version.
|
||||||
|
directFileMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const partialSuffix = ".tspartial"
|
const partialSuffix = ".partial"
|
||||||
|
|
||||||
func (s *peerAPIServer) diskPath(baseName string) (fullPath string, ok bool) {
|
func (s *peerAPIServer) diskPath(baseName string) (fullPath string, ok bool) {
|
||||||
clean := path.Clean(baseName)
|
clean := path.Clean(baseName)
|
||||||
@ -350,6 +356,15 @@ type incomingFile struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
copied int64
|
copied int64
|
||||||
lastNotify time.Time
|
lastNotify time.Time
|
||||||
|
finalPath string // non-empty in direct mode, when file is done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *incomingFile) markAndNotifyDone(finalPath string) {
|
||||||
|
f.mu.Lock()
|
||||||
|
f.finalPath = finalPath
|
||||||
|
f.mu.Unlock()
|
||||||
|
b := f.ph.ps.b
|
||||||
|
b.sendFileNotify()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *incomingFile) Write(p []byte) (n int, err error) {
|
func (f *incomingFile) Write(p []byte) (n int, err error) {
|
||||||
@ -383,6 +398,7 @@ func (f *incomingFile) PartialFile() ipn.PartialFile {
|
|||||||
Started: f.started,
|
Started: f.started,
|
||||||
DeclaredSize: f.size,
|
DeclaredSize: f.size,
|
||||||
Received: f.copied,
|
Received: f.copied,
|
||||||
|
FinalPath: f.finalPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,6 +421,9 @@ func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "bad filename", 400)
|
http.Error(w, "bad filename", 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if h.ps.directFileMode {
|
||||||
|
dstFile += partialSuffix
|
||||||
|
}
|
||||||
f, err := os.Create(dstFile)
|
f, err := os.Create(dstFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logf("put Create error: %v", err)
|
h.logf("put Create error: %v", err)
|
||||||
@ -418,8 +437,9 @@ func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
var finalSize int64
|
var finalSize int64
|
||||||
|
var inFile *incomingFile
|
||||||
if r.ContentLength != 0 {
|
if r.ContentLength != 0 {
|
||||||
inFile := &incomingFile{
|
inFile = &incomingFile{
|
||||||
name: baseName,
|
name: baseName,
|
||||||
started: time.Now(),
|
started: time.Now(),
|
||||||
size: r.ContentLength,
|
size: r.ContentLength,
|
||||||
@ -442,6 +462,17 @@ func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if h.ps.directFileMode {
|
||||||
|
finalPath := strings.TrimSuffix(dstFile, partialSuffix)
|
||||||
|
if err := os.Rename(dstFile, finalPath); err != nil {
|
||||||
|
h.logf("Rename error: %v", err)
|
||||||
|
http.Error(w, "error renaming file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if inFile != nil { // non-zero length; TODO: notify even for zero length
|
||||||
|
inFile.markAndNotifyDone(finalPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h.logf("put of %s from %v/%v", baseName, approxSize(finalSize), h.remoteAddr.IP, h.peerNode.ComputedName)
|
h.logf("put of %s from %v/%v", baseName, approxSize(finalSize), h.remoteAddr.IP, h.peerNode.ComputedName)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user