Skip to content

Commit 95778df

Browse files
author
Luca Bruno
authored
Merge pull request #279 from lucab/ups/journal-connectionless
journal: use a connection-less socket
2 parents 4248292 + 728309f commit 95778df

File tree

4 files changed

+105
-57
lines changed

4 files changed

+105
-57
lines changed

.travis.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ services:
55

66
language: go
77
go:
8-
- "1.7.x"
98
- "1.10.x"
109
- "1.11.x"
1110
go_import_path: github.com/coreos/go-systemd
@@ -32,13 +31,13 @@ before_install:
3231
- rm -f /tmp/cidfile
3332

3433
install:
35-
- docker run -d --cidfile=/tmp/cidfile --privileged -e GOPATH=${GOPATH} -v ${PWD}:${BUILD_DIR} go-systemd/container-tests /bin/systemd --system
34+
- docker run --shm-size=2gb -d --cidfile=/tmp/cidfile --privileged -e GOPATH=${GOPATH} -v ${PWD}:${BUILD_DIR} go-systemd/container-tests /bin/systemd --system
3635

3736
script:
3837
- ./scripts/travis/pr-test.sh go_fmt
3938
- ./scripts/travis/pr-test.sh build_source
4039
- ./scripts/travis/pr-test.sh build_tests
41-
- docker exec `cat /tmp/cidfile` /bin/bash -c "cd ${BUILD_DIR} && ./scripts/travis/pr-test.sh run_tests"
40+
- docker exec --privileged `cat /tmp/cidfile` /bin/bash -c "cd ${BUILD_DIR} && ./scripts/travis/pr-test.sh run_tests"
4241
- ./scripts/travis/pr-test.sh go_vet
4342
- ./scripts/travis/pr-test.sh license_check
4443

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![Build Status](https://travis-ci.org/coreos/go-systemd.png?branch=master)](https://travis-ci.org/coreos/go-systemd)
44
[![godoc](https://godoc.org/github.com/coreos/go-systemd?status.svg)](http://godoc.org/github.com/coreos/go-systemd)
5-
![minimum golang 1.7](https://img.shields.io/badge/golang-1.7%2B-orange.svg)
5+
![minimum golang 1.10](https://img.shields.io/badge/golang-1.10%2B-orange.svg)
66

77

88
Go bindings to systemd. The project has several packages:

journal/journal.go

Lines changed: 80 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ import (
3333
"os"
3434
"strconv"
3535
"strings"
36+
"sync"
37+
"sync/atomic"
3638
"syscall"
39+
"unsafe"
3740
)
3841

3942
// Priority of a journal message
@@ -50,19 +53,35 @@ const (
5053
PriDebug
5154
)
5255

53-
var conn net.Conn
56+
var (
57+
// This can be overridden at build-time:
58+
// https://github.com/golang/go/wiki/GcToolchainTricks#including-build-information-in-the-executable
59+
journalSocket = "/run/systemd/journal/socket"
60+
61+
// unixConnPtr atomically holds the local unconnected Unix-domain socket.
62+
// Concrete safe pointer type: *net.UnixConn
63+
unixConnPtr unsafe.Pointer
64+
// onceConn ensures that unixConnPtr is initialized exactly once.
65+
onceConn sync.Once
66+
)
5467

5568
func init() {
56-
var err error
57-
conn, err = net.Dial("unixgram", "/run/systemd/journal/socket")
58-
if err != nil {
59-
conn = nil
60-
}
69+
onceConn.Do(initConn)
6170
}
6271

63-
// Enabled returns true if the local systemd journal is available for logging
72+
// Enabled checks whether the local systemd journal is available for logging.
6473
func Enabled() bool {
65-
return conn != nil
74+
onceConn.Do(initConn)
75+
76+
if (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr)) == nil {
77+
return false
78+
}
79+
80+
if _, err := net.Dial("unixgram", journalSocket); err != nil {
81+
return false
82+
}
83+
84+
return true
6685
}
6786

6887
// Send a message to the local systemd journal. vars is a map of journald
@@ -73,8 +92,14 @@ func Enabled() bool {
7392
// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
7493
// for more details. vars may be nil.
7594
func Send(message string, priority Priority, vars map[string]string) error {
95+
conn := (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr))
7696
if conn == nil {
77-
return journalError("could not connect to journald socket")
97+
return errors.New("could not initialize socket to journald")
98+
}
99+
100+
socketAddr := &net.UnixAddr{
101+
Name: journalSocket,
102+
Net: "unixgram",
78103
}
79104

80105
data := new(bytes.Buffer)
@@ -84,32 +109,30 @@ func Send(message string, priority Priority, vars map[string]string) error {
84109
appendVariable(data, k, v)
85110
}
86111

87-
_, err := io.Copy(conn, data)
88-
if err != nil && isSocketSpaceError(err) {
89-
file, err := tempFd()
90-
if err != nil {
91-
return journalError(err.Error())
92-
}
93-
defer file.Close()
94-
_, err = io.Copy(file, data)
95-
if err != nil {
96-
return journalError(err.Error())
97-
}
98-
99-
rights := syscall.UnixRights(int(file.Fd()))
112+
_, _, err := conn.WriteMsgUnix(data.Bytes(), nil, socketAddr)
113+
if err == nil {
114+
return nil
115+
}
116+
if !isSocketSpaceError(err) {
117+
return err
118+
}
100119

101-
/* this connection should always be a UnixConn, but better safe than sorry */
102-
unixConn, ok := conn.(*net.UnixConn)
103-
if !ok {
104-
return journalError("can't send file through non-Unix connection")
105-
}
106-
_, _, err = unixConn.WriteMsgUnix([]byte{}, rights, nil)
107-
if err != nil {
108-
return journalError(err.Error())
109-
}
110-
} else if err != nil {
111-
return journalError(err.Error())
120+
// Large log entry, send it via tempfile and ancillary-fd.
121+
file, err := tempFd()
122+
if err != nil {
123+
return err
124+
}
125+
defer file.Close()
126+
_, err = io.Copy(file, data)
127+
if err != nil {
128+
return err
129+
}
130+
rights := syscall.UnixRights(int(file.Fd()))
131+
_, _, err = conn.WriteMsgUnix([]byte{}, rights, socketAddr)
132+
if err != nil {
133+
return err
112134
}
135+
113136
return nil
114137
}
115138

@@ -120,7 +143,7 @@ func Print(priority Priority, format string, a ...interface{}) error {
120143

121144
func appendVariable(w io.Writer, name, value string) {
122145
if err := validVarName(name); err != nil {
123-
journalError(err.Error())
146+
fmt.Fprintf(os.Stderr, "variable name %s contains invalid character, ignoring\n", name)
124147
}
125148
if strings.ContainsRune(value, '\n') {
126149
/* When the value contains a newline, we write:
@@ -137,9 +160,9 @@ func appendVariable(w io.Writer, name, value string) {
137160
}
138161
}
139162

140-
// validVarName validates a variable name to make sure it journald will accept it.
163+
// validVarName validates a variable name to make sure journald will accept it.
141164
// The variable name must be in uppercase and consist only of characters,
142-
// numbers and underscores, and may not begin with an underscore. (from the docs)
165+
// numbers and underscores, and may not begin with an underscore:
143166
// https://www.freedesktop.org/software/systemd/man/sd_journal_print.html
144167
func validVarName(name string) error {
145168
if name == "" {
@@ -156,20 +179,23 @@ func validVarName(name string) error {
156179
return nil
157180
}
158181

182+
// isSocketSpaceError checks whether the error is signaling
183+
// an "overlarge message" condition.
159184
func isSocketSpaceError(err error) bool {
160185
opErr, ok := err.(*net.OpError)
161-
if !ok {
186+
if !ok || opErr == nil {
162187
return false
163188
}
164189

165-
sysErr, ok := opErr.Err.(syscall.Errno)
166-
if !ok {
190+
sysErr, ok := opErr.Err.(*os.SyscallError)
191+
if !ok || sysErr == nil {
167192
return false
168193
}
169194

170-
return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS
195+
return sysErr.Err == syscall.EMSGSIZE || sysErr.Err == syscall.ENOBUFS
171196
}
172197

198+
// tempFd creates a temporary, unlinked file under `/dev/shm`.
173199
func tempFd() (*os.File, error) {
174200
file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
175201
if err != nil {
@@ -182,8 +208,18 @@ func tempFd() (*os.File, error) {
182208
return file, nil
183209
}
184210

185-
func journalError(s string) error {
186-
s = "journal error: " + s
187-
fmt.Fprintln(os.Stderr, s)
188-
return errors.New(s)
211+
// initConn initializes the global `unixConnPtr` socket.
212+
// It is meant to be called exactly once, at program startup.
213+
func initConn() {
214+
autobind, err := net.ResolveUnixAddr("unixgram", "")
215+
if err != nil {
216+
return
217+
}
218+
219+
sock, err := net.ListenUnixgram("unixgram", autobind)
220+
if err != nil {
221+
return
222+
}
223+
224+
atomic.StorePointer(&unixConnPtr, unsafe.Pointer(sock))
189225
}

journal/journal_test.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,20 @@ package journal
1717
import (
1818
"fmt"
1919
"io/ioutil"
20+
"runtime"
2021
"strconv"
2122
"strings"
2223
"testing"
2324
)
2425

26+
func TestJournalEnabled(t *testing.T) {
27+
enabled := Enabled()
28+
29+
if !enabled {
30+
t.Fatalf("journald socket not detected")
31+
}
32+
}
33+
2534
func TestValidVarName(t *testing.T) {
2635
validTestCases := []string{
2736
"TEST",
@@ -65,8 +74,6 @@ func TestJournalSend(t *testing.T) {
6574
largeValue = v + 1
6675
}
6776
}
68-
// See https://github.com/coreos/go-systemd/pull/221#issuecomment-276727718
69-
_ = largeValue
7077

7178
// small messages should go over normal data,
7279
// larger ones over temporary file with fd in ancillary data
@@ -82,7 +89,6 @@ func TestJournalSend(t *testing.T) {
8289
"small message",
8390
5,
8491
},
85-
/* See https://github.com/coreos/go-systemd/pull/221#issuecomment-276727718
8692
{
8793
"large message",
8894
largeValue,
@@ -91,18 +97,25 @@ func TestJournalSend(t *testing.T) {
9197
"huge message",
9298
hugeValue,
9399
},
94-
*/
95100
}
96101

102+
// This is memory intensive, so we manually trigger GC before and after each test.
97103
for i, tt := range testValues {
98104
t.Logf("journal send test #%v - %s (len=%d)", i, tt.label, tt.len)
99-
largeVars := map[string]string{
100-
"KEY": string(make([]byte, tt.len)),
101-
}
102-
103-
err := Send(fmt.Sprintf("go-systemd test #%v - %s", i, tt.label), PriCrit, largeVars)
105+
runtime.GC()
106+
err := SendAlloc(i, tt.label, tt.len)
104107
if err != nil {
105108
t.Fatalf("#%v: failed sending %s: %s", i, tt.label, err)
106109
}
110+
runtime.GC()
107111
}
108112
}
113+
114+
func SendAlloc(run int, label string, len int) error {
115+
largeVars := map[string]string{
116+
"KEY": string(make([]byte, len)),
117+
}
118+
119+
msg := fmt.Sprintf("go-systemd test #%v - %s", run, label)
120+
return Send(msg, PriCrit, largeVars)
121+
}

0 commit comments

Comments
 (0)