From 963644c19058829f74bd7d19a484c0786d6777cd Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Tue, 27 Dec 2016 21:59:17 -0700 Subject: Documentation improvements. Also, I renamed a couple of the sd_id128 functions for better clarity. But since I haven't pushed since originally implementing them, I'm not labeling it as a breaking change. --- readme.go | 7 +- sd_daemon/booted.go | 4 + sd_daemon/doc.go | 8 +- sd_id128/get.go | 152 +++++++++++++++++++++++++++++++++++ sd_id128/sd_id128.go | 222 --------------------------------------------------- sd_id128/types.go | 184 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 351 insertions(+), 226 deletions(-) create mode 100644 sd_id128/get.go delete mode 100644 sd_id128/sd_id128.go create mode 100644 sd_id128/types.go diff --git a/readme.go b/readme.go index 88719f0..aac1aa7 100644 --- a/readme.go +++ b/readme.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package libsystemd provides libraries for working with systemd. -// Right now it only has a mostly-complete sd_daemon. +// Package libsystemd implements the client-side of generic system +// APIs. +// +// Several of these APIs originated with systemd, but they are all +// very simple, and may be implemented with a variety of systems. package libsystemd diff --git a/sd_daemon/booted.go b/sd_daemon/booted.go index b833b45..18fd4fb 100644 --- a/sd_daemon/booted.go +++ b/sd_daemon/booted.go @@ -23,6 +23,10 @@ import "os" // Please do not use this function. All of the other functionality in // this package uses interfaces that are not systemd-specific. func SdBooted() bool { + // BUG(lukeshu): SdBooted is systemd-specific, and isn't of + // particular value to daemons. It doesn't really belong in a + // library of generic daemon utilities. But, we defer to its + // inclusion in the C-language libsystemd. fi, err := os.Lstat("/run/systemd/system") return err != nil && fi.IsDir() } diff --git a/sd_daemon/doc.go b/sd_daemon/doc.go index 90616f6..927ac7f 100644 --- a/sd_daemon/doc.go +++ b/sd_daemon/doc.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package sd_daemon provides functions for writing "new-style" +// Package sd_daemon implements utilities for writing "new-style" // daemons. // // The daemon(7) manual page has historically documented the very long @@ -24,7 +24,11 @@ // systemd, they are all very simple mechanisms which can easily be // implemented with a variety of service managers. // -// [daemon(7)]: https://www.freedesktop.org/software/systemd/man/daemon.html +// daemon(7): https://www.freedesktop.org/software/systemd/man/daemon.html +// +// BUG(lukeshu): Logically, sd_id128.GetInvocationID might belong in +// this package, but we defer to the C-language libsystemd's placement +// of sd_id128_get_invocation() in sd-id128.h. package sd_daemon import "errors" diff --git a/sd_id128/get.go b/sd_id128/get.go new file mode 100644 index 0000000..d8cdcfa --- /dev/null +++ b/sd_id128/get.go @@ -0,0 +1,152 @@ +// Copyright 2016 Luke Shumaker +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sd_id128 + +import ( + "crypto/rand" + "os" + "strings" +) + +// GetRandomUUID generates a random UUID (122 random bits). +// +// For convenience when a valid UUID is needed, rather than returning +// a totally random 128-bit number, this sets the UUID type +// ("variant") to RFC 4122 format, and the sub-type ("version") to +// "4", which is (pseudo) random generation. This leaves 122 random +// bits. +// +// The pseudorandom number generator used is cryptographically secure. +func GetRandomUUID() (ID128, error) { + var id ID128 + _, err := rand.Read(id[:]) + if err != nil { + return ID128{}, err + } + + // Set the type ("variant") to RFC 4122 format + id[8] = (id[8] & 0x3F) | 0x80 + // Set the sub-type ("version") to 4 (random generation). + id[6] = (id[6] & 0x0F) | 0x40 + + return id, nil +} + +var cachedMachineID ID128 + +// GetMachineID returns a unique identifier for this operating system +// installation. +// +// This is read as a plain hexadecimal number (with an optional +// trailing newline) from "/etc/machine-id". +// +// Note that unlike every other "Get*ID" function, the ID returned by +// GetMachineID is not guaranteed to be a valid UUID, though values +// generated by systemd v30 and later are valid UUIDs. +// +// See machine-id(5) for more information. +// +// machine-id(5): https://www.freedesktop.org/software/systemd/man/machine-id.html +func GetMachineID() (ID128, error) { + if cachedMachineID == (ID128{}) { + str, err := readfile("/etc/machine-id") + if err != nil { + return ID128{}, err + } + id, err := ParsePlain(strings.TrimSuffix(str, "\n")) + if err != nil { + return ID128{}, err + } + if id == (ID128{}) { + return ID128{}, ErrInvalid + } + cachedMachineID = id + } + return cachedMachineID, nil +} + +var cachedBootID ID128 + +// GetBootUUID returns a UUID unique to this boot; it is generated by +// the kernel once very early in the boot process. +// +// This is read as a UUID (with trailing newline) from +// "/proc/sys/kernel/random/boot_id". +// +// It may be relied on that the Linux kernel sets boot_id to a UUID +// with the UUID type ("variant") set to RFC 4122 format, and the +// sub-type ("version") set appropriately. +// +// See random(4) for more information. +// +// random(4): http://man7.org/linux/man-pages/man4/random.4.html +func GetBootUUID() (ID128, error) { + if cachedBootID == (ID128{}) { + str, err := readfile("/proc/sys/kernel/random/boot_id") + if err != nil { + return ID128{}, err + } + id, err := ParseUUID(strings.TrimSuffix(str, "\n")) + if err != nil { + return ID128{}, err + } + cachedBootID = id + } + return cachedBootID, nil +} + +var cachedInvocationID ID128 + +// GetInvocationUUID returns a UUID unique to this invocation of the +// currently executed service. +// +// This is read from the "INVOCATION_ID" environment variable, which +// is expected to be set by the service manager. +// +// It may be relied on that the service manager sets this to a UUID +// with the UUID type ("variant") set to RFC 4122 format, and the +// sub-type ("version") set appropriately. +// +// The error returned is ErrNone if the service manager did not set +// INVOCATION_ID, or ErrInvalid if the value of INVOCATION_ID could +// not be parsed. +// +// See sd_id128_get_invocation(3) for more information. +// +// sd_id128_get_invocation(3): https://www.freedesktop.org/software/systemd/man/sd_id128_get_invocation.html +// +// BUG(lukeshu): GetInvocationUUID uses a mechanism specified by "what +// systemd currently does", rather than a stable specification. +func GetInvocationUUID() (ID128, error) { + // BUG(lukeshu): GetInvocationUUID might logically belong in the + // sd_daemon package, but we defer to the C-language + // libsystemd's placement of sd_id128_get_invocation() in + // sd-id128.h. + if cachedInvocationID == (ID128{}) { + // BUG(lukeshu): GetInvocationUUID should distrust the + // environment in certain situations, similarly to GNU + // libc secure_getenv. + str, have := os.LookupEnv("INVOCATION_ID") + if !have { + return ID128{}, ErrNone + } + t, err := Parse(str) + if err != nil { + return ID128{}, err + } + cachedInvocationID = t + } + return cachedInvocationID, nil +} diff --git a/sd_id128/sd_id128.go b/sd_id128/sd_id128.go deleted file mode 100644 index 063a4d4..0000000 --- a/sd_id128/sd_id128.go +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2016 Luke Shumaker -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package sd_id128 provides functions for dealing with 128-bit IDs. -package sd_id128 - -import ( - "crypto/rand" - "errors" - "os" - "strings" -) - -var ( - ErrInvalid = errors.New("Invalid ID") - ErrNone = errors.New("No Invocation ID set") -) - -type ID128 [16]byte - -// String formats the ID as a hexadecimal number. -func (id ID128) String() string { - var s [32]byte - for n := range id { - s[n*2] = hexchar(id[n] >> 4) - s[n*2+1] = hexchar(id[n] & 0xF) - } - return string(s[:]) -} - -// UUID formats the ID as a UUID string. -func (id ID128) UUID() string { - var s [36]byte - k := 0 - for n := range id { - switch n { - case 4, 6, 8, 10: - s[k] = '-' - k++ - } - s[k] = hexchar(id[n] >> 4) - k++ - s[k] = hexchar(id[n] & 0xF) - k++ - } - return string(s[:]) -} - -// Parse parses a 128-bit ID represented either as a hexadecimal -// number, or as a UUID string (that is, a hexadecimal number with -// hyphens inserted at positions 8, 12, 18, and 23). -func Parse(s string) (ID128, error) { - ret := ID128{} - isGuid := false - - i := 0 - n := 0 - for n < 16 { - if s[i] == '-' { - if i == 8 { - isGuid = true - } else if i == 13 || i == 18 || i == 23 { - if !isGuid { - return ID128{}, ErrInvalid - } - } else { - return ID128{}, ErrInvalid - } - i++ - } else { - if i+2 >= len(s) { - return ID128{}, ErrInvalid - } - - a, err := unhexchar(s[i]) - i++ - if err != nil { - return ID128{}, err - } - - b, err := unhexchar(s[i]) - i++ - if err != nil { - return ID128{}, err - } - - ret[n] = (a << 4) | b - n++ - } - } - - if !((i == 36 && isGuid) || (i == 32 && !isGuid)) { - return ID128{}, ErrInvalid - } - if i != len(s) { - return ID128{}, ErrInvalid - } - - return ret, nil -} - -// ParsePlain is like Parse, but only accepts hexadecimal numbers, not -// UUID-formatted strings. -func ParsePlain(s string) (ID128, error) { - if len(s) != 32 { - return ID128{}, ErrInvalid - } - return Parse(s) -} - -// ParseUUID is like Parse, but only accepts UUID-formatted strings, -// not hexadecimal numbers. -func ParseUUID(s string) (ID128, error) { - if len(s) != 36 { - return ID128{}, ErrInvalid - } - return Parse(s) -} - -// GetRandomID generates a random v4 UUID (122 random bits). -func GetRandomID() (ID128, error) { - var id ID128 - _, err := rand.Read(id[:]) - if err != nil { - return ID128{}, err - } - - // Turn this in to a valid v4 UUID, to be nice. - - // Set UUID version to 4 --- truly random generation - id[6] = (id[6] & 0x0F) | 0x40 - - // Set the UUID variant to DCE - id[8] = (id[8] & 0x3F) | 0x80 - - return id, nil -} - -var cachedMachineID ID128 - -// GetMachineID returns a unique identifier for this machine. -// -// This is read as a plain hexadecimal number (with an optional -// trailing newline) from "/etc/machine-id". -func GetMachineID() (ID128, error) { - if cachedMachineID == (ID128{}) { - str, err := readfile("/etc/machine-id") - if err != nil { - return ID128{}, err - } - id, err := ParsePlain(strings.TrimSuffix(str, "\n")) - if err != nil { - return ID128{}, err - } - if id == (ID128{}) { - return ID128{}, ErrInvalid - } - cachedMachineID = id - } - return cachedMachineID, nil -} - -var cachedBootID ID128 - -// GetBootID returns a unique identifier for this boot. -// -// This is read as a UUID (with trailing newline) from -// "/proc/sys/kernel/random/boot_id". -func GetBootID() (ID128, error) { - if cachedBootID == (ID128{}) { - str, err := readfile("/proc/sys/kernel/random/boot_id") - if err != nil { - return ID128{}, err - } - id, err := ParseUUID(strings.TrimSuffix(str, "\n")) - if err != nil { - return ID128{}, err - } - cachedBootID = id - } - return cachedBootID, nil -} - -var cachedInvocationID ID128 - -// GetInvocationID returns a unique identifier for this invocation of -// the currently executed service. -// -// This is read from the "INVOCATION_ID" environment variable, which -// is expected to be set by the service manager. -// -// The error returned is ErrNone if the service manager did not set -// INVOCATION_ID, or ErrInvalid if the value of INVOCATION_ID could -// not be parsed. -func GetInvocationID() (ID128, error) { - if cachedInvocationID == (ID128{}) { - // BUG(lukeshu): GetInvocationID should distrust the - // environment in certain situations, similarly to GNU - // libc secure_getenv. - str, have := os.LookupEnv("INVOCATION_ID") - if !have { - return ID128{}, ErrNone - } - t, err := Parse(str) - if err != nil { - return ID128{}, err - } - cachedInvocationID = t - } - return cachedInvocationID, nil -} diff --git a/sd_id128/types.go b/sd_id128/types.go new file mode 100644 index 0000000..3b5228c --- /dev/null +++ b/sd_id128/types.go @@ -0,0 +1,184 @@ +// Copyright 2016 Luke Shumaker +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package sd_id128 reads, generates, and processes 128-bit ID values. +// +// These 128-bit IDs are a generalization of DCE UUIDs (of which +// Microsoft GUIDs are one implementation), but use a simpler string +// format. This package imposes no structure on the used IDs, unlike +// UUIDs, but are fully compatible with those types of IDs. +// +// DCE UUID: https://tools.ietf.org/html/rfc4122 +// +// Microsoft GUID: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx +// +// The UUID string representation is a (zero-padded) 32-digit +// hexadecimal number with a hyphen after the 8th, 12th, 16th, and +// 20th digits (for a total length of 36 glyphs). This package +// supports this format, but the primary ("plain") string +// representation is simply a (zero-padded) 32-digit hexadecimal +// number. +// +// For example: +// +// Plain: 6ba7b8109dad11d180b400c04fd430c8 +// UUID : 6ba7b810-9dad-11d1-80b4-00c04fd430c8 +// +// Several restrictions are imposed on the structure of UUIDs. Most +// notably, the most significant 1-3 bits of the 8th octet specify the +// UUID type ("variant"); unless dealing with legacy UUIDs, the most +// significant 2 bits of the octet will be "10", specifying a RFC 4122 +// type UUID. Assuming RFC 4122 type, the most significant 4 bits of +// the 6th octet then specify the sub-type ("version"), which could +// impose complicated requirements on the rest of the UUID. +// +// This package doesn't really care about all of these UUID structure +// requirements. Nothing in this package ever inspects an ID's +// structure. However, because it is anticipated that the IDs +// generated by this package might be used in a context where a UUID +// is required, many of the functions that emit an ID ensure that the +// ID emitted is structured to be a valid UUID. +package sd_id128 + +import ( + "errors" +) + +var ( + ErrInvalid = errors.New("Invalid ID") + ErrNone = errors.New("No Invocation ID set") +) + +type ID128 [16]byte + +// String formats the ID as a plain zero-padded 32-digit hexadecimal +// number. +func (id ID128) String() string { + var s [32]byte + for n := range id { + s[n*2] = hexchar(id[n] >> 4) + s[n*2+1] = hexchar(id[n] & 0xF) + } + return string(s[:]) +} + +// UUID formats the ID as a UUID string. +func (id ID128) UUID() string { + var s [36]byte + k := 0 + for n := range id { + switch n { + case 4, 6, 8, 10: + s[k] = '-' + k++ + } + s[k] = hexchar(id[n] >> 4) + k++ + s[k] = hexchar(id[n] & 0xF) + k++ + } + return string(s[:]) +} + +// Parse parses a 128-bit ID represented either in the plain string +// format, or in the UUID string format. +// +// If you need to validate that the string is in one format or the +// other, then you should use the ParsePlain or ParseUUID functions. +// +// If the input string does not match either of these formats, then +// ErrInvalid is returned. +func Parse(s string) (ID128, error) { + ret := ID128{} + isGuid := false + + i := 0 + n := 0 + for n < 16 { + if s[i] == '-' { + if i == 8 { + isGuid = true + } else if i == 13 || i == 18 || i == 23 { + if !isGuid { + return ID128{}, ErrInvalid + } + } else { + return ID128{}, ErrInvalid + } + i++ + } else { + if i+2 >= len(s) { + return ID128{}, ErrInvalid + } + + a, err := unhexchar(s[i]) + i++ + if err != nil { + return ID128{}, err + } + + b, err := unhexchar(s[i]) + i++ + if err != nil { + return ID128{}, err + } + + ret[n] = (a << 4) | b + n++ + } + } + + if !((i == 36 && isGuid) || (i == 32 && !isGuid)) { + return ID128{}, ErrInvalid + } + if i != len(s) { + return ID128{}, ErrInvalid + } + + return ret, nil +} + +// ParsePlain parses a 128-bit ID represented in the plain string +// format. +// +// If you would like more flexibility, and would like to be accept +// either the plain string format or the UUID string format, then you +// should use the Parse function. +// +// If the input string is not a 32-digit hexadecimal number, then +// ErrInvalid is returned. +func ParsePlain(s string) (ID128, error) { + if len(s) != 32 { + return ID128{}, ErrInvalid + } + return Parse(s) +} + +// ParsePlain parses a 128-bit ID represented in the UUID string +// format. +// +// If you would like more flexibility, and would like to be accept +// either the plain string format or the UUID string format, then you +// should use the Parse function. +// +// If the input string is not a 36-character UUID string, then +// ErrInvalid is returned. This function does not validate the +// variant or version bit fields, nor does it validate any structure +// implied by them. +func ParseUUID(s string) (ID128, error) { + if len(s) != 36 { + return ID128{}, ErrInvalid + } + return Parse(s) +} -- cgit v1.1-4-g5e80