...

Source file src/github.com/coreos/go-systemd/v22/journal/journal_unix.go

Documentation: github.com/coreos/go-systemd/v22/journal

     1  // Copyright 2015 CoreOS, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build !windows
    16  // +build !windows
    17  
    18  // Package journal provides write bindings to the local systemd journal.
    19  // It is implemented in pure Go and connects to the journal directly over its
    20  // unix socket.
    21  //
    22  // To read from the journal, see the "sdjournal" package, which wraps the
    23  // sd-journal a C API.
    24  //
    25  // http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html
    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  	// This can be overridden at build-time:
    47  	// https://github.com/golang/go/wiki/GcToolchainTricks#including-build-information-in-the-executable
    48  	journalSocket = "/run/systemd/journal/socket"
    49  
    50  	// unixConnPtr atomically holds the local unconnected Unix-domain socket.
    51  	// Concrete safe pointer type: *net.UnixConn
    52  	unixConnPtr unsafe.Pointer
    53  	// onceConn ensures that unixConnPtr is initialized exactly once.
    54  	onceConn sync.Once
    55  )
    56  
    57  // Enabled checks whether the local systemd journal is available for logging.
    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  // StderrIsJournalStream returns whether the process stderr is connected
    73  // to the Journal's stream transport.
    74  //
    75  // This can be used for automatic protocol upgrading described in [Journal Native Protocol].
    76  //
    77  // Returns true if JOURNAL_STREAM environment variable is present,
    78  // and stderr's device and inode numbers match it.
    79  //
    80  // Error is returned if unexpected error occurs: e.g. if JOURNAL_STREAM environment variable
    81  // is present, but malformed, fstat syscall fails, etc.
    82  //
    83  // [Journal Native Protocol]: https://systemd.io/JOURNAL_NATIVE_PROTOCOL/#automatic-protocol-upgrading
    84  func StderrIsJournalStream() (bool, error) {
    85  	return fdIsJournalStream(syscall.Stderr)
    86  }
    87  
    88  // StdoutIsJournalStream returns whether the process stdout is connected
    89  // to the Journal's stream transport.
    90  //
    91  // Returns true if JOURNAL_STREAM environment variable is present,
    92  // and stdout's device and inode numbers match it.
    93  //
    94  // Error is returned if unexpected error occurs: e.g. if JOURNAL_STREAM environment variable
    95  // is present, but malformed, fstat syscall fails, etc.
    96  //
    97  // Most users should probably use [StderrIsJournalStream].
    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  // Send a message to the local systemd journal. vars is a map of journald
   125  // fields to values.  Fields must be composed of uppercase letters, numbers,
   126  // and underscores, but must not start with an underscore. Within these
   127  // restrictions, any arbitrary field name may be used.  Some names have special
   128  // significance: see the journalctl documentation
   129  // (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
   130  // for more details.  vars may be nil.
   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  	// Large log entry, send it via tempfile and ancillary-fd.
   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  // getOrInitConn attempts to get the global `unixConnPtr` socket, initializing if necessary
   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  		/* When the value contains a newline, we write:
   192  		 * - the variable name, followed by a newline
   193  		 * - the size (in 64bit little endian format)
   194  		 * - the data, followed by a newline
   195  		 */
   196  		fmt.Fprintln(w, name)
   197  		binary.Write(w, binary.LittleEndian, uint64(len(value)))
   198  		fmt.Fprintln(w, value)
   199  	} else {
   200  		/* just write the variable and value all on one line */
   201  		fmt.Fprintf(w, "%s=%s\n", name, value)
   202  	}
   203  }
   204  
   205  // validVarName validates a variable name to make sure journald will accept it.
   206  // The variable name must be in uppercase and consist only of characters,
   207  // numbers and underscores, and may not begin with an underscore:
   208  // https://www.freedesktop.org/software/systemd/man/sd_journal_print.html
   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  // isSocketSpaceError checks whether the error is signaling
   225  // an "overlarge message" condition.
   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  // tempFd creates a temporary, unlinked file under `/dev/shm`.
   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  // initConn initializes the global `unixConnPtr` socket.
   254  // It is automatically called when needed.
   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