Skip to content

Commit

Permalink
Allow more control over file descriptors
Browse files Browse the repository at this point in the history
  • Loading branch information
percivalalb committed May 4, 2024
1 parent 7d375ec commit cbfc7a9
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 12 deletions.
34 changes: 34 additions & 0 deletions activation/filesmethod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2015 CoreOS, Inc.
//
// 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 activation

// Method decides what happens to the file descriptors that are passed in by systemd.
type Method int

const (
// ConsumeFiles is the default, and removes the original file descriptors passed in by
// systemd. This means that new file descriptors created by the program may use the
// file descriptors indices.
ConsumeFiles Method = iota

// ReserveFiles stores placeholder file descriptors, which point to /dev/null. This
// stops new file descriptors consuming the indices.
ReserveFiles

// CloneFiles duplicates and leaves the original file descriptors in tack. This
// stops new file descriptors consuming the indices like [ReserveFiles] but
// consider the possible secuirty risk of leaving the sockets exposed.
ConserveFiles
)
54 changes: 54 additions & 0 deletions activation/filesmethod_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.

//go:build !windows
// +build !windows

package activation

import (
"fmt"
"os"
"syscall"
)

func (m Method) Apply(f *os.File) error {
saveFd := int(f.Fd()) // get the idx before being closed.

switch m {
case ConsumeFiles:
f.Close()
case ReserveFiles:
devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0755)
if err != nil {
return fmt.Errorf("accessing /dev/null: %w", err)

Check failure on line 35 in activation/filesmethod_unix.go

View workflow job for this annotation

GitHub Actions / Build on minimum supported toolchain

Errorf format %w has unknown verb w
}

nullFd := int(devNull.Fd())

// "If oldfd equals newfd, then dup3() fails with the error EINVAL."
if saveFd == nullFd {
syscall.CloseOnExec(nullFd)
} else {
// "makes newfd be the copy of oldfd, closing newfd first if necessary"
if err := syscall.Dup3(nullFd, saveFd, syscall.O_CLOEXEC); err != nil {
return fmt.Errorf("setting %d fd to /dev/null: %w", saveFd, err)

Check failure on line 46 in activation/filesmethod_unix.go

View workflow job for this annotation

GitHub Actions / Build on minimum supported toolchain

Errorf format %w has unknown verb w
}
}
case ConserveFiles:
// no action
}

return nil
}
21 changes: 21 additions & 0 deletions activation/filesmethod_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2015 CoreOS, Inc.
//
// 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 activation

import "os"

func (m Method) Apply(f *os.File) error {
return nil
}
30 changes: 20 additions & 10 deletions activation/listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,29 @@ import (
// The order of the file descriptors is preserved in the returned slice.
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
// corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener}
func Listeners() ([]net.Listener, error) {
files := Files(true)
func Listeners(opts ...option) ([]net.Listener, error) {
o := Options(opts...)

files := Files(o.unsetEnv)
listeners := make([]net.Listener, len(files))

for i, f := range files {
if pc, err := net.FileListener(f); err == nil {
listeners[i] = pc
f.Close()

if err := o.method.Apply(f); err != nil {

}
}
}
return listeners, nil
}

// ListenersWithNames maps a listener name to a set of net.Listener instances.
func ListenersWithNames() (map[string][]net.Listener, error) {
files := Files(true)
func ListenersWithNames(opts ...option) (map[string][]net.Listener, error) {
o := Options(opts...)

files := Files(o.unsetEnv)
listeners := map[string][]net.Listener{}

for _, f := range files {
Expand All @@ -51,7 +58,10 @@ func ListenersWithNames() (map[string][]net.Listener, error) {
} else {
listeners[f.Name()] = append(current, pc)
}
f.Close()

if err := o.method.Apply(f); err != nil {

}
}
}
return listeners, nil
Expand All @@ -60,8 +70,8 @@ func ListenersWithNames() (map[string][]net.Listener, error) {
// TLSListeners returns a slice containing a net.listener for each matching TCP socket type
// passed to this process.
// It uses default Listeners func and forces TCP sockets handlers to use TLS based on tlsConfig.
func TLSListeners(tlsConfig *tls.Config) ([]net.Listener, error) {
listeners, err := Listeners()
func TLSListeners(tlsConfig *tls.Config, opts ...option) ([]net.Listener, error) {
listeners, err := Listeners(opts...)

if listeners == nil || err != nil {
return nil, err
Expand All @@ -81,8 +91,8 @@ func TLSListeners(tlsConfig *tls.Config) ([]net.Listener, error) {

// TLSListenersWithNames maps a listener name to a net.Listener with
// the associated TLS configuration.
func TLSListenersWithNames(tlsConfig *tls.Config) (map[string][]net.Listener, error) {
listeners, err := ListenersWithNames()
func TLSListenersWithNames(tlsConfig *tls.Config, opts ...option) (map[string][]net.Listener, error) {
listeners, err := ListenersWithNames(opts...)

if listeners == nil || err != nil {
return nil, err
Expand Down
30 changes: 30 additions & 0 deletions activation/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package activation

type options struct {
unsetEnv bool
method Method
}

type option func(*options)

func UnsetEnv(f bool) option {
return func(o *options) {
o.unsetEnv = f
}
}

func UseMethod(m Method) option {
return func(o *options) {
o.method = m
}
}

func Options(opts ...option) *options {
o := &options{}

for _, opt := range opts {
opt(o)
}

return o
}
9 changes: 7 additions & 2 deletions activation/packetconns.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ import (
// The order of the file descriptors is preserved in the returned slice.
// Nil values are used to fill any gaps. For example if systemd were to return file descriptors
// corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn}
func PacketConns() ([]net.PacketConn, error) {
func PacketConns(opts ...option) ([]net.PacketConn, error) {
o := Options(opts...)

files := Files(true)
conns := make([]net.PacketConn, len(files))

for i, f := range files {
if pc, err := net.FilePacketConn(f); err == nil {
conns[i] = pc
f.Close()

if err := o.method.Apply(f); err != nil {

}
}
}
return conns, nil
Expand Down

0 comments on commit cbfc7a9

Please sign in to comment.