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