github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/socket_nix.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 //go:build !windows 5 // +build !windows 6 7 // socket_nix.go 8 9 package libkb 10 11 import ( 12 "fmt" 13 "net" 14 "os" 15 "path/filepath" 16 "strings" 17 "sync" 18 19 "github.com/keybase/client/go/logger" 20 ) 21 22 // Though I've never seen this come up in production, it definitely comes up 23 // in systests that multiple go-routines might race over the current 24 // working directory as they do the (chdir && dial) dance below. Make sure 25 // a lock is held whenever operating on sockets, so that two racing goroutines 26 // can't conflict here. 27 var bindLock sync.Mutex 28 29 func (s SocketInfo) BindToSocket() (ret net.Listener, err error) { 30 31 // Lock so that multiple goroutines can't race over current working dir. 32 // See note above. 33 bindLock.Lock() 34 defer bindLock.Unlock() 35 36 bindFile := s.bindFile 37 what := fmt.Sprintf("SocketInfo#BindToSocket(unix:%s)", bindFile) 38 defer Trace(s.log, what, &err)() 39 40 if err := MakeParentDirs(s.log, bindFile); err != nil { 41 return nil, err 42 } 43 44 // Path can't be longer than N characters. 45 // In this case Chdir to the file directory first and use a local path. 46 // On many Linuxes, N=108, on some N=106, and on macOS N=104. 47 // N=104 is the lowest I know of. 48 // It's the length of the path buffer in sockaddr_un. 49 // And there may be a null-terminator in there, not sure, so make it 103 for good luck. 50 // https://github.com/golang/go/issues/6895#issuecomment-98006662 51 // https://gist.github.com/mlsteele/16dc5b6eb3d112b914183928c9af71b8#file-un-h-L79 52 // We could always Chdir, but then this function would be non-threadsafe more of the time. 53 // Pick your poison. 54 if len(bindFile) >= 103 { 55 prevWd, err := os.Getwd() 56 if err != nil { 57 return nil, fmt.Errorf("Error getting working directory: %s", err) 58 } 59 s.log.Debug("| Changing current working directory because path for binding is too long") 60 dir := filepath.Dir(bindFile) 61 s.log.Debug("| Chdir(%s)", dir) 62 if err := os.Chdir(dir); err != nil { 63 return nil, fmt.Errorf("Path can't be longer than 108 characters (failed to chdir): %s", err) 64 } 65 66 defer func() { 67 s.log.Debug("| Chdir(%s)", prevWd) 68 err := os.Chdir(prevWd) 69 if err != nil { 70 s.log.Debug("| Chdir(%s) err=%v", prevWd, err) 71 } 72 }() 73 74 bindFile = filepath.Base(bindFile) 75 } 76 77 s.log.Info("| net.Listen on unix:%s", bindFile) 78 ret, err = net.Listen("unix", bindFile) 79 if err != nil { 80 s.log.Warning("net.Listen failed with: %s", err.Error()) 81 } 82 return ret, err 83 } 84 85 func (s SocketInfo) DialSocket() (net.Conn, error) { 86 errs := []error{} 87 for _, file := range s.dialFiles { 88 ret, err := s.dialSocket(file) 89 if err == nil { 90 return ret, nil 91 } 92 errs = append(errs, err) 93 } 94 return nil, CombineErrors(errs...) 95 } 96 97 func (s SocketInfo) dialSocket(dialFile string) (ret net.Conn, err error) { 98 99 // Lock so that multiple goroutines can't race over current working dir. 100 // See note above. 101 bindLock.Lock() 102 defer bindLock.Unlock() 103 104 what := fmt.Sprintf("SocketInfo#dialSocket(unix:%s)", dialFile) 105 defer Trace(s.log, what, &err)() 106 107 if dialFile == "" { 108 return nil, fmt.Errorf("Can't dial empty path") 109 } 110 111 // Path can't be longer than 103 characters. 112 // In this case Chdir to the file directory first. 113 // https://github.com/golang/go/issues/6895#issuecomment-98006662 114 if len(dialFile) >= 103 { 115 prevWd, err := os.Getwd() 116 if err != nil { 117 return nil, fmt.Errorf("Error getting working directory: %s", err) 118 } 119 s.log.Debug("| Changing current working directory because path for dialing is too long") 120 dir := filepath.Dir(dialFile) 121 s.log.Debug("| os.Chdir(%s)", dir) 122 if err := os.Chdir(dir); err != nil { 123 return nil, fmt.Errorf("Path can't be longer than 108 characters (failed to chdir): %s", err) 124 } 125 defer func() { 126 err := os.Chdir(prevWd) 127 if err != nil { 128 s.log.Debug("| Chdir(%s) err=%v", prevWd, err) 129 } 130 }() 131 dialFile = filepath.Base(dialFile) 132 } 133 134 s.log.Debug("| net.Dial(unix:%s)", dialFile) 135 return net.Dial("unix", dialFile) 136 } 137 138 func NewSocket(g *GlobalContext) (ret Socket, err error) { 139 var dialFiles []string 140 dialFiles, err = g.Env.GetSocketDialFiles() 141 if err != nil { 142 return 143 } 144 var bindFile string 145 bindFile, err = g.Env.GetSocketBindFile() 146 if err != nil { 147 return 148 } 149 g.Log.Debug("Connecting to socket with dialFiles=%s, bindFiles=%s", dialFiles, bindFile) 150 log := g.Log 151 if log == nil { 152 log = logger.NewNull() 153 } 154 ret = SocketInfo{ 155 log: log, 156 dialFiles: dialFiles, 157 bindFile: bindFile, 158 } 159 return 160 } 161 162 func NewSocketWithFiles( 163 log logger.Logger, bindFile string, dialFiles []string) Socket { 164 return SocketInfo{ 165 log: log, 166 bindFile: bindFile, 167 dialFiles: dialFiles, 168 } 169 } 170 171 // net.errClosing isn't exported, so do this.. UGLY! 172 func IsSocketClosedError(e error) bool { 173 return strings.HasSuffix(e.Error(), "use of closed network connection") 174 }