@ -19,10 +19,10 @@ import (
"tailscale.com/logtail/backoff"
"tailscale.com/logtail/backoff"
)
)
// HasFilesWaiting reports whether any files are buffered in the
// HasFilesWaiting reports whether any files are buffered in [Handler.Dir].
// tailscaled daemon storag e.
// This always returns false when [Handler.DirectFileMode] is fals e.
func ( s * Handler ) HasFilesWaiting ( ) bool {
func ( s * Handler ) HasFilesWaiting ( ) bool {
if s == nil || s . Root Dir == "" || s . DirectFileMode {
if s == nil || s . Dir == "" || s . DirectFileMode {
return false
return false
}
}
if s . knownEmpty . Load ( ) {
if s . knownEmpty . Load ( ) {
@ -33,7 +33,7 @@ func (s *Handler) HasFilesWaiting() bool {
// keep this negative cache.
// keep this negative cache.
return false
return false
}
}
f , err := os . Open ( s . Root Dir)
f , err := os . Open ( s . Dir)
if err != nil {
if err != nil {
return false
return false
}
}
@ -42,7 +42,7 @@ func (s *Handler) HasFilesWaiting() bool {
des , err := f . ReadDir ( 10 )
des , err := f . ReadDir ( 10 )
for _ , de := range des {
for _ , de := range des {
name := de . Name ( )
name := de . Name ( )
if strings . HasSuffix ( name , P artialSuffix) {
if strings . HasSuffix ( name , p artialSuffix) {
continue
continue
}
}
if name , ok := strings . CutSuffix ( name , deletedSuffix ) ; ok { // for Windows + tests
if name , ok := strings . CutSuffix ( name , deletedSuffix ) ; ok { // for Windows + tests
@ -51,16 +51,16 @@ func (s *Handler) HasFilesWaiting() bool {
// as the OS may return "foo.jpg.deleted" before "foo.jpg"
// as the OS may return "foo.jpg.deleted" before "foo.jpg"
// and we don't want to delete the ".deleted" file before
// and we don't want to delete the ".deleted" file before
// enumerating to the "foo.jpg" file.
// enumerating to the "foo.jpg" file.
defer tryDeleteAgain ( filepath . Join ( s . Root Dir, name ) )
defer tryDeleteAgain ( filepath . Join ( s . Dir, name ) )
continue
continue
}
}
if de . Type ( ) . IsRegular ( ) {
if de . Type ( ) . IsRegular ( ) {
_ , err := os . Stat ( filepath . Join ( s . Root Dir, name + deletedSuffix ) )
_ , err := os . Stat ( filepath . Join ( s . Dir, name + deletedSuffix ) )
if os . IsNotExist ( err ) {
if os . IsNotExist ( err ) {
return true
return true
}
}
if err == nil {
if err == nil {
tryDeleteAgain ( filepath . Join ( s . Root Dir, name ) )
tryDeleteAgain ( filepath . Join ( s . Dir, name ) )
continue
continue
}
}
}
}
@ -76,22 +76,19 @@ func (s *Handler) HasFilesWaiting() bool {
}
}
// WaitingFiles returns the list of files that have been sent by a
// WaitingFiles returns the list of files that have been sent by a
// peer that are waiting in the buffered "pick up" directory owned by
// peer that are waiting in [Handler.Dir].
// the Tailscale daemon.
// This always returns nil when [Handler.DirectFileMode] is false.
//
// As a side effect, it also does any lazy deletion of files as
// required by Windows.
func ( s * Handler ) WaitingFiles ( ) ( ret [ ] apitype . WaitingFile , err error ) {
func ( s * Handler ) WaitingFiles ( ) ( ret [ ] apitype . WaitingFile , err error ) {
if s == nil {
if s == nil {
return nil , errNilHandler
return nil , errNilHandler
}
}
if s . Root Dir == "" {
if s . Dir == "" {
return nil , E rrNoTaildrop
return nil , e rrNoTaildrop
}
}
if s . DirectFileMode {
if s . DirectFileMode {
return nil , nil
return nil , nil
}
}
f , err := os . Open ( s . Root Dir)
f , err := os . Open ( s . Dir)
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
@ -101,7 +98,7 @@ func (s *Handler) WaitingFiles() (ret []apitype.WaitingFile, err error) {
des , err := f . ReadDir ( 10 )
des , err := f . ReadDir ( 10 )
for _ , de := range des {
for _ , de := range des {
name := de . Name ( )
name := de . Name ( )
if strings . HasSuffix ( name , P artialSuffix) {
if strings . HasSuffix ( name , p artialSuffix) {
continue
continue
}
}
if name , ok := strings . CutSuffix ( name , deletedSuffix ) ; ok { // for Windows + tests
if name , ok := strings . CutSuffix ( name , deletedSuffix ) ; ok { // for Windows + tests
@ -143,7 +140,7 @@ func (s *Handler) WaitingFiles() (ret []apitype.WaitingFile, err error) {
// Maybe Windows is done virus scanning the file we tried
// Maybe Windows is done virus scanning the file we tried
// to delete a long time ago and will let us delete it now.
// to delete a long time ago and will let us delete it now.
for name := range deleted {
for name := range deleted {
tryDeleteAgain ( filepath . Join ( s . Root Dir, name ) )
tryDeleteAgain ( filepath . Join ( s . Dir, name ) )
}
}
}
}
sort . Slice ( ret , func ( i , j int ) bool { return ret [ i ] . Name < ret [ j ] . Name } )
sort . Slice ( ret , func ( i , j int ) bool { return ret [ i ] . Name < ret [ j ] . Name } )
@ -164,17 +161,19 @@ func tryDeleteAgain(fullPath string) {
}
}
}
}
// DeleteFile deletes a file of the given baseName from [Handler.Dir].
// This method is only allowed when [Handler.DirectFileMode] is false.
func ( s * Handler ) DeleteFile ( baseName string ) error {
func ( s * Handler ) DeleteFile ( baseName string ) error {
if s == nil {
if s == nil {
return errNilHandler
return errNilHandler
}
}
if s . Root Dir == "" {
if s . Dir == "" {
return E rrNoTaildrop
return e rrNoTaildrop
}
}
if s . DirectFileMode {
if s . DirectFileMode {
return errors . New ( "deletes not allowed in direct mode" )
return errors . New ( "deletes not allowed in direct mode" )
}
}
path , ok := s . D iskPath( baseName )
path , ok := s . d iskPath( baseName )
if ! ok {
if ! ok {
return errors . New ( "bad filename" )
return errors . New ( "bad filename" )
}
}
@ -184,7 +183,7 @@ func (s *Handler) DeleteFile(baseName string) error {
for {
for {
err := os . Remove ( path )
err := os . Remove ( path )
if err != nil && ! os . IsNotExist ( err ) {
if err != nil && ! os . IsNotExist ( err ) {
err = R edactErr( err )
err = r edactErr( err )
// Put a retry loop around deletes on Windows. Windows
// Put a retry loop around deletes on Windows. Windows
// file descriptor closes are effectively asynchronous,
// file descriptor closes are effectively asynchronous,
// as a bunch of hooks run on/after close, and we can't
// as a bunch of hooks run on/after close, and we can't
@ -203,7 +202,7 @@ func (s *Handler) DeleteFile(baseName string) error {
bo . BackOff ( context . Background ( ) , err )
bo . BackOff ( context . Background ( ) , err )
continue
continue
}
}
if err := T ouchFile( path + deletedSuffix ) ; err != nil {
if err := t ouchFile( path + deletedSuffix ) ; err != nil {
logf ( "peerapi: failed to leave deleted marker: %v" , err )
logf ( "peerapi: failed to leave deleted marker: %v" , err )
}
}
}
}
@ -214,25 +213,27 @@ func (s *Handler) DeleteFile(baseName string) error {
}
}
}
}
func T ouchFile( path string ) error {
func t ouchFile( path string ) error {
f , err := os . OpenFile ( path , os . O_RDWR | os . O_CREATE , 0666 )
f , err := os . OpenFile ( path , os . O_RDWR | os . O_CREATE , 0666 )
if err != nil {
if err != nil {
return R edactErr( err )
return r edactErr( err )
}
}
return f . Close ( )
return f . Close ( )
}
}
// OpenFile opens a file of the given baseName from [Handler.Dir].
// This method is only allowed when [Handler.DirectFileMode] is false.
func ( s * Handler ) OpenFile ( baseName string ) ( rc io . ReadCloser , size int64 , err error ) {
func ( s * Handler ) OpenFile ( baseName string ) ( rc io . ReadCloser , size int64 , err error ) {
if s == nil {
if s == nil {
return nil , 0 , errNilHandler
return nil , 0 , errNilHandler
}
}
if s . Root Dir == "" {
if s . Dir == "" {
return nil , 0 , E rrNoTaildrop
return nil , 0 , e rrNoTaildrop
}
}
if s . DirectFileMode {
if s . DirectFileMode {
return nil , 0 , errors . New ( "opens not allowed in direct mode" )
return nil , 0 , errors . New ( "opens not allowed in direct mode" )
}
}
path , ok := s . D iskPath( baseName )
path , ok := s . d iskPath( baseName )
if ! ok {
if ! ok {
return nil , 0 , errors . New ( "bad filename" )
return nil , 0 , errors . New ( "bad filename" )
}
}
@ -242,12 +243,12 @@ func (s *Handler) OpenFile(baseName string) (rc io.ReadCloser, size int64, err e
}
}
f , err := os . Open ( path )
f , err := os . Open ( path )
if err != nil {
if err != nil {
return nil , 0 , R edactErr( err )
return nil , 0 , r edactErr( err )
}
}
fi , err := f . Stat ( )
fi , err := f . Stat ( )
if err != nil {
if err != nil {
f . Close ( )
f . Close ( )
return nil , 0 , R edactErr( err )
return nil , 0 , r edactErr( err )
}
}
return f , fi . Size ( ) , nil
return f , fi . Size ( ) , nil
}
}