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 package journal
27
28 import (
29 "bytes"
30 "encoding/binary"
31 "errors"
32 "fmt"
33 "io"
34 "io/ioutil"
35 "net"
36 "os"
37 "strconv"
38 "strings"
39 "sync"
40 "sync/atomic"
41 "syscall"
42 "unsafe"
43 )
44
45 var (
46
47
48 journalSocket = "/run/systemd/journal/socket"
49
50
51
52 unixConnPtr unsafe.Pointer
53
54 onceConn sync.Once
55 )
56
57
58 func Enabled() bool {
59 if c := getOrInitConn(); c == nil {
60 return false
61 }
62
63 conn, err := net.Dial("unixgram", journalSocket)
64 if err != nil {
65 return false
66 }
67 defer conn.Close()
68
69 return true
70 }
71
72
73
74
75
76
77
78
79
80
81
82
83
84 func StderrIsJournalStream() (bool, error) {
85 return fdIsJournalStream(syscall.Stderr)
86 }
87
88
89
90
91
92
93
94
95
96
97
98 func StdoutIsJournalStream() (bool, error) {
99 return fdIsJournalStream(syscall.Stdout)
100 }
101
102 func fdIsJournalStream(fd int) (bool, error) {
103 journalStream := os.Getenv("JOURNAL_STREAM")
104 if journalStream == "" {
105 return false, nil
106 }
107
108 var expectedStat syscall.Stat_t
109 _, err := fmt.Sscanf(journalStream, "%d:%d", &expectedStat.Dev, &expectedStat.Ino)
110 if err != nil {
111 return false, fmt.Errorf("failed to parse JOURNAL_STREAM=%q: %v", journalStream, err)
112 }
113
114 var stat syscall.Stat_t
115 err = syscall.Fstat(fd, &stat)
116 if err != nil {
117 return false, err
118 }
119
120 match := stat.Dev == expectedStat.Dev && stat.Ino == expectedStat.Ino
121 return match, nil
122 }
123
124
125
126
127
128
129
130
131 func Send(message string, priority Priority, vars map[string]string) error {
132 conn := getOrInitConn()
133 if conn == nil {
134 return errors.New("could not initialize socket to journald")
135 }
136
137 socketAddr := &net.UnixAddr{
138 Name: journalSocket,
139 Net: "unixgram",
140 }
141
142 data := new(bytes.Buffer)
143 appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
144 appendVariable(data, "MESSAGE", message)
145 for k, v := range vars {
146 appendVariable(data, k, v)
147 }
148
149 _, _, err := conn.WriteMsgUnix(data.Bytes(), nil, socketAddr)
150 if err == nil {
151 return nil
152 }
153 if !isSocketSpaceError(err) {
154 return err
155 }
156
157
158 file, err := tempFd()
159 if err != nil {
160 return err
161 }
162 defer file.Close()
163 _, err = io.Copy(file, data)
164 if err != nil {
165 return err
166 }
167 rights := syscall.UnixRights(int(file.Fd()))
168 _, _, err = conn.WriteMsgUnix([]byte{}, rights, socketAddr)
169 if err != nil {
170 return err
171 }
172
173 return nil
174 }
175
176
177 func getOrInitConn() *net.UnixConn {
178 conn := (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr))
179 if conn != nil {
180 return conn
181 }
182 onceConn.Do(initConn)
183 return (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr))
184 }
185
186 func appendVariable(w io.Writer, name, value string) {
187 if err := validVarName(name); err != nil {
188 fmt.Fprintf(os.Stderr, "variable name %s contains invalid character, ignoring\n", name)
189 }
190 if strings.ContainsRune(value, '\n') {
191
196 fmt.Fprintln(w, name)
197 binary.Write(w, binary.LittleEndian, uint64(len(value)))
198 fmt.Fprintln(w, value)
199 } else {
200
201 fmt.Fprintf(w, "%s=%s\n", name, value)
202 }
203 }
204
205
206
207
208
209 func validVarName(name string) error {
210 if name == "" {
211 return errors.New("Empty variable name")
212 } else if name[0] == '_' {
213 return errors.New("Variable name begins with an underscore")
214 }
215
216 for _, c := range name {
217 if !(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_') {
218 return errors.New("Variable name contains invalid characters")
219 }
220 }
221 return nil
222 }
223
224
225
226 func isSocketSpaceError(err error) bool {
227 opErr, ok := err.(*net.OpError)
228 if !ok || opErr == nil {
229 return false
230 }
231
232 sysErr, ok := opErr.Err.(*os.SyscallError)
233 if !ok || sysErr == nil {
234 return false
235 }
236
237 return sysErr.Err == syscall.EMSGSIZE || sysErr.Err == syscall.ENOBUFS
238 }
239
240
241 func tempFd() (*os.File, error) {
242 file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
243 if err != nil {
244 return nil, err
245 }
246 err = syscall.Unlink(file.Name())
247 if err != nil {
248 return nil, err
249 }
250 return file, nil
251 }
252
253
254
255 func initConn() {
256 autobind, err := net.ResolveUnixAddr("unixgram", "")
257 if err != nil {
258 return
259 }
260
261 sock, err := net.ListenUnixgram("unixgram", autobind)
262 if err != nil {
263 return
264 }
265
266 atomic.StorePointer(&unixConnPtr, unsafe.Pointer(sock))
267 }
268
View as plain text