init commit

Signed-off-by: Jess Frazelle <jess@mesosphere.com>
This commit is contained in:
Jess Frazelle 2016-04-14 23:42:40 -07:00
commit fec7085140
13 changed files with 4205 additions and 0 deletions

47
.gitignore vendored Normal file
View file

@ -0,0 +1,47 @@
###Go###
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
*.swo
*.swp
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
###OSX###
.DS_Store
.AppleDouble
.LSOverride
# Icon must ends with two \r.
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
binctr

22
LICENSE Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Jess Frazelle
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

56
Makefile Normal file
View file

@ -0,0 +1,56 @@
.PHONY: clean all fmt vet lint build test install static
PREFIX?=$(shell pwd)
BUILDTAGS=seccomp apparmor
PROJECT := github.com/jfrazelle/binctr
VENDOR := vendor
# Variable to get the current version.
VERSION := $(shell cat VERSION)
# Variable to set the current git commit.
GITCOMMIT := $(shell git rev-parse --short HEAD)
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
GITUNTRACKEDCHANGES := $(shell git status --porcelain --untracked-files=no)
ifneq ($(GITUNTRACKEDCHANGES),)
GITCOMMIT := $(GITCOMMIT)-dirty
endif
LDFLAGS := ${LDFLAGS} \
-X $(PROJECT)/main.GITCOMMIT=${GITCOMMIT} \
-X $(PROJECT)/main.VERSION=${VERSION} \
all: clean build fmt lint test vet install
build:
@echo "+ $@"
go build -tags "$(BUILDTAGS)" -ldflags "${LDFLAGS}" .
static:
@echo "+ $@"
CGO_ENABLED=1 go build -tags "$(BUILDTAGS) cgo static_build" \
-ldflags "-w -extldflags -static ${LDFLAGS}" -o binctr .
fmt:
@echo "+ $@"
@gofmt -s -l . | grep -v $(VENDOR) | tee /dev/stderr
lint:
@echo "+ $@"
@golint ./... | grep -v $(VENDOR) | tee /dev/stderr
test: fmt lint vet
@echo "+ $@"
@go test -v -tags "$(BUILDTAGS) cgo" $(shell go list ./... | grep -v $(VENDOR))
vet:
@echo "+ $@"
@go vet $(shell go list ./... | grep -v $(VENDOR))
clean:
@echo "+ $@"
@$(RM) binctr
install:
@echo "+ $@"
@go install .

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.1.0

15
circle.yml Normal file
View file

@ -0,0 +1,15 @@
machine:
environment:
GO15VENDOREXPERIMENT: 1
dependencies:
post:
# install golint
- go get github.com/golang/lint/golint
test:
pre:
- go vet $(go list ./... | grep -v vendor)
- test -z "$(golint ./... | grep -v vendor | tee /dev/stderr)"
- test -z "$(gofmt -s -l . | grep -v vendor | tee /dev/stderr)"
override:
- go test $(go list ./... | grep -v vendor)

120
main.go Normal file
View file

@ -0,0 +1,120 @@
package main
import (
"bytes"
"encoding/base64"
"flag"
"fmt"
"os"
"runtime"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/archive"
"github.com/opencontainers/runc/libcontainer"
)
const (
// BANNER is what is printed for help/info output.
BANNER = ` _ _ _
| |__ (_)_ __ ___| |_ _ __
| '_ \| | '_ \ / __| __| '__|
| |_) | | | | | (__| |_| |
|_.__/|_|_| |_|\___|\__|_|
Fully static self-contained container including the rootfs.
Version: %s
GitCommit: %s
`
defaultRoot = "/run/binctr"
)
var (
console = os.Getenv("console")
containerID string
root string
debug bool
version bool
// GITCOMMIT is git commit the binary was compiled against.
GITCOMMIT = ""
// VERSION is the binary version.
VERSION = "v0.1.0"
// DATA is the rootfs tar that is added at compile time.
DATA = ""
)
func init() {
// Parse flags
flag.StringVar(&containerID, "id", "jessiscool", "container ID")
flag.StringVar(&console, "console", console, "the pty slave path for use with the container")
flag.StringVar(&root, "root", defaultRoot, "root directory of container state, should be tmpfs")
flag.BoolVar(&version, "version", false, "print version and exit")
flag.BoolVar(&version, "v", false, "print version and exit (shorthand)")
flag.BoolVar(&debug, "d", false, "run in debug mode")
flag.Usage = func() {
fmt.Fprint(os.Stderr, fmt.Sprintf(BANNER, VERSION, GITCOMMIT))
flag.PrintDefaults()
}
flag.Parse()
if version {
fmt.Printf("%s, commit: %s", VERSION, GITCOMMIT)
os.Exit(0)
}
// Set log level
if debug {
logrus.SetLevel(logrus.DebugLevel)
}
}
func main() {
if len(os.Args) > 1 && os.Args[1] == "init" {
runInit()
return
}
if err := unpackRootfs(); err != nil {
logrus.Fatal(err)
}
status, err := startContainer(spec, containerID)
if err != nil {
logrus.Fatal(err)
}
// exit with the container's exit status
os.Exit(status)
}
func unpackRootfs() error {
data, err := base64.StdEncoding.DecodeString(DATA)
if err != nil {
return err
}
r := bytes.NewReader(data)
if err := os.Mkdir("container", 0755); err != nil {
return err
}
return archive.Untar(r, "container", nil)
}
func runInit() {
if len(os.Args) > 1 && os.Args[1] == "init" {
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
factory, _ := libcontainer.New("")
if err := factory.StartInitialization(); err != nil {
// as the error is sent back to the parent there is no need to log
// or write it to stderr because the parent process will handle this
os.Exit(1)
}
panic("libcontainer: container init failed to exec")
}
}

49
rlimit_linux.go Normal file
View file

@ -0,0 +1,49 @@
package main
import "fmt"
const (
RLIMIT_CPU = iota // CPU time in sec
RLIMIT_FSIZE // Maximum filesize
RLIMIT_DATA // max data size
RLIMIT_STACK // max stack size
RLIMIT_CORE // max core file size
RLIMIT_RSS // max resident set size
RLIMIT_NPROC // max number of processes
RLIMIT_NOFILE // max number of open files
RLIMIT_MEMLOCK // max locked-in-memory address space
RLIMIT_AS // address space limit
RLIMIT_LOCKS // maximum file locks held
RLIMIT_SIGPENDING // max number of pending signals
RLIMIT_MSGQUEUE // maximum bytes in POSIX mqueues
RLIMIT_NICE // max nice prio allowed to raise to
RLIMIT_RTPRIO // maximum realtime priority
RLIMIT_RTTIME // timeout for RT tasks in us
)
var rlimitMap = map[string]int{
"RLIMIT_CPU": RLIMIT_CPU,
"RLIMIT_FSIZE": RLIMIT_FSIZE,
"RLIMIT_DATA": RLIMIT_DATA,
"RLIMIT_STACK": RLIMIT_STACK,
"RLIMIT_CORE": RLIMIT_CORE,
"RLIMIT_RSS": RLIMIT_RSS,
"RLIMIT_NPROC": RLIMIT_NPROC,
"RLIMIT_NOFILE": RLIMIT_NOFILE,
"RLIMIT_MEMLOCK": RLIMIT_MEMLOCK,
"RLIMIT_AS": RLIMIT_AS,
"RLIMIT_LOCKS": RLIMIT_LOCKS,
"RLIMIT_SIGPENDING": RLIMIT_SIGPENDING,
"RLIMIT_MSGQUEUE": RLIMIT_MSGQUEUE,
"RLIMIT_NICE": RLIMIT_NICE,
"RLIMIT_RTPRIO": RLIMIT_RTPRIO,
"RLIMIT_RTTIME": RLIMIT_RTTIME,
}
func strToRlimit(key string) (int, error) {
rl, ok := rlimitMap[key]
if !ok {
return 0, fmt.Errorf("Wrong rlimit value: %s", key)
}
return rl, nil
}

1652
seccomp.go Normal file

File diff suppressed because it is too large Load diff

1623
seccomp.json Executable file

File diff suppressed because it is too large Load diff

116
signals.go Normal file
View file

@ -0,0 +1,116 @@
// +build linux
package main
import (
"os"
"os/signal"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runc/libcontainer/utils"
)
const signalBufferSize = 2048
// newSignalHandler returns a signal handler for processing SIGCHLD and SIGWINCH signals
// while still forwarding all other signals to the process.
func newSignalHandler(tty *tty, enableSubreaper bool) *signalHandler {
if enableSubreaper {
// set us as the subreaper before registering the signal handler for the container
if err := system.SetSubreaper(1); err != nil {
logrus.Warn(err)
}
}
// ensure that we have a large buffer size so that we do not miss any signals
// incase we are not processing them fast enough.
s := make(chan os.Signal, signalBufferSize)
// handle all signals for the process.
signal.Notify(s)
return &signalHandler{
tty: tty,
signals: s,
}
}
// exit models a process exit status with the pid and
// exit status.
type exit struct {
pid int
status int
}
type signalHandler struct {
signals chan os.Signal
tty *tty
}
// forward handles the main signal event loop forwarding, resizing, or reaping depending
// on the signal received.
func (h *signalHandler) forward(process *libcontainer.Process) (int, error) {
// make sure we know the pid of our main process so that we can return
// after it dies.
pid1, err := process.Pid()
if err != nil {
return -1, err
}
// perform the initial tty resize.
h.tty.resize()
for s := range h.signals {
switch s {
case syscall.SIGWINCH:
h.tty.resize()
case syscall.SIGCHLD:
exits, err := h.reap()
if err != nil {
logrus.Error(err)
}
for _, e := range exits {
logrus.WithFields(logrus.Fields{
"pid": e.pid,
"status": e.status,
}).Debug("process exited")
if e.pid == pid1 {
// call Wait() on the process even though we already have the exit
// status because we must ensure that any of the go specific process
// fun such as flushing pipes are complete before we return.
process.Wait()
return e.status, nil
}
}
default:
logrus.Debugf("sending signal to process %s", s)
if err := syscall.Kill(pid1, s.(syscall.Signal)); err != nil {
logrus.Error(err)
}
}
}
return -1, nil
}
// reap runs wait4 in a loop until we have finished processing any existing exits
// then returns all exits to the main event loop for further processing.
func (h *signalHandler) reap() (exits []exit, err error) {
var (
ws syscall.WaitStatus
rus syscall.Rusage
)
for {
pid, err := syscall.Wait4(-1, &ws, syscall.WNOHANG, &rus)
if err != nil {
if err == syscall.ECHILD {
return exits, nil
}
return nil, err
}
if pid <= 0 {
return exits, nil
}
exits = append(exits, exit{
pid: pid,
status: utils.ExitStatus(ws),
})
}
}

133
spec.go Normal file
View file

@ -0,0 +1,133 @@
package main
import (
"runtime"
"github.com/opencontainers/runtime-spec/specs-go"
)
var (
spec = &specs.Spec{
Version: specs.Version,
Platform: specs.Platform{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
},
Root: specs.Root{
Path: "rootfs",
Readonly: true,
},
Process: specs.Process{
Terminal: true,
User: specs.User{},
Args: []string{
"sh",
},
Env: []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm",
},
Cwd: "/",
NoNewPrivileges: true,
Capabilities: []string{
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE",
},
Rlimits: []specs.Rlimit{
{
Type: "RLIMIT_NOFILE",
Hard: uint64(1024),
Soft: uint64(1024),
},
},
},
Hostname: "ctr",
Mounts: []specs.Mount{
{
Destination: "/proc",
Type: "proc",
Source: "proc",
Options: nil,
},
{
Destination: "/dev",
Type: "tmpfs",
Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
},
{
Destination: "/dev/pts",
Type: "devpts",
Source: "devpts",
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
},
{
Destination: "/dev/shm",
Type: "tmpfs",
Source: "shm",
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
},
{
Destination: "/dev/mqueue",
Type: "mqueue",
Source: "mqueue",
Options: []string{"nosuid", "noexec", "nodev"},
},
{
Destination: "/sys",
Type: "sysfs",
Source: "sysfs",
Options: []string{"nosuid", "noexec", "nodev", "ro"},
},
{
Destination: "/sys/fs/cgroup",
Type: "cgroup",
Source: "cgroup",
Options: []string{"nosuid", "noexec", "nodev", "relatime", "ro"},
},
},
Linux: specs.Linux{
MaskedPaths: []string{
"/proc/kcore",
"/proc/latency_stats",
"/proc/timer_stats",
"/proc/sched_debug",
},
ReadonlyPaths: []string{
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger",
},
Resources: &specs.Resources{
Devices: []specs.DeviceCgroup{
{
Allow: false,
Access: sPtr("rwm"),
},
},
},
Namespaces: []specs.Namespace{
{
Type: "pid",
},
{
Type: "ipc",
},
{
Type: "uts",
},
{
Type: "user",
},
{
Type: "mount",
},
},
Seccomp: defaultSeccompProfile,
},
}
)

126
tty.go Normal file
View file

@ -0,0 +1,126 @@
// +build linux
package main
import (
"fmt"
"io"
"os"
"sync"
"github.com/docker/docker/pkg/term"
"github.com/opencontainers/runc/libcontainer"
)
// setup standard pipes so that the TTY of the calling runc process
// is not inherited by the container.
func createStdioPipes(p *libcontainer.Process, rootuid int) (*tty, error) {
i, err := p.InitializeIO(rootuid)
if err != nil {
return nil, err
}
t := &tty{
closers: []io.Closer{
i.Stdin,
i.Stdout,
i.Stderr,
},
}
// add the process's io to the post start closers if they support close
for _, cc := range []interface{}{
p.Stdin,
p.Stdout,
p.Stderr,
} {
if c, ok := cc.(io.Closer); ok {
t.postStart = append(t.postStart, c)
}
}
go func() {
io.Copy(i.Stdin, os.Stdin)
i.Stdin.Close()
}()
t.wg.Add(2)
go t.copyIO(os.Stdout, i.Stdout)
go t.copyIO(os.Stderr, i.Stderr)
return t, nil
}
func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
defer t.wg.Done()
io.Copy(w, r)
r.Close()
}
func createTty(p *libcontainer.Process, rootuid int, consolePath string) (*tty, error) {
if consolePath != "" {
if err := p.ConsoleFromPath(consolePath); err != nil {
return nil, err
}
return &tty{}, nil
}
console, err := p.NewConsole(rootuid)
if err != nil {
return nil, err
}
go io.Copy(console, os.Stdin)
go io.Copy(os.Stdout, console)
state, err := term.SetRawTerminal(os.Stdin.Fd())
if err != nil {
return nil, fmt.Errorf("failed to set the terminal from the stdin: %v", err)
}
return &tty{
console: console,
state: state,
closers: []io.Closer{
console,
},
}, nil
}
type tty struct {
console libcontainer.Console
state *term.State
closers []io.Closer
postStart []io.Closer
wg sync.WaitGroup
}
// ClosePostStart closes any fds that are provided to the container and dup2'd
// so that we no longer have copy in our process.
func (t *tty) ClosePostStart() error {
for _, c := range t.postStart {
c.Close()
}
return nil
}
// Close closes all open fds for the tty and/or restores the orignal
// stdin state to what it was prior to the container execution
func (t *tty) Close() error {
// ensure that our side of the fds are always closed
for _, c := range t.postStart {
c.Close()
}
// wait for the copy routines to finish before closing the fds
t.wg.Wait()
for _, c := range t.closers {
c.Close()
}
if t.state != nil {
term.RestoreTerminal(os.Stdin.Fd(), t.state)
}
return nil
}
func (t *tty) resize() error {
if t.console == nil {
return nil
}
ws, err := term.GetWinsize(os.Stdin.Fd())
if err != nil {
return err
}
return term.SetWinsize(t.console.Fd(), ws)
}

245
utils.go Normal file
View file

@ -0,0 +1,245 @@
package main
import (
"fmt"
"os"
"path/filepath"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/specconv"
"github.com/opencontainers/runtime-spec/specs-go"
)
// startContainer starts the container. Returns the exit status or -1 and an
// error. Signals sent to the current process will be forwarded to container.
func startContainer(spec *specs.Spec, id string) (int, error) {
// create the libcontainer config
config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
CgroupName: id,
Spec: spec,
})
if err != nil {
return -1, err
}
if _, err := os.Stat(config.Rootfs); err != nil {
if os.IsNotExist(err) {
return -1, fmt.Errorf("rootfs (%q) does not exist", config.Rootfs)
}
return -1, err
}
factory, err := loadFactory()
if err != nil {
return -1, err
}
ctr, err := factory.Create(id, config)
if err != nil {
return -1, err
}
r := &runner{
enableSubreaper: true,
shouldDestroy: true,
container: ctr,
console: console,
detach: false,
pidFile: "",
listenFDs: []*os.File{},
}
return r.run(&spec.Process)
}
// loadFactory returns the configured factory instance for execing containers.
func loadFactory() (libcontainer.Factory, error) {
abs, err := filepath.Abs(root)
if err != nil {
return nil, err
}
cgroupManager := libcontainer.Cgroupfs
return libcontainer.New(abs, cgroupManager, func(l *libcontainer.LinuxFactory) error {
return nil
})
}
// newProcess returns a new libcontainer Process with the arguments from the
// spec and stdio from the current process.
func newProcess(p specs.Process) (*libcontainer.Process, error) {
lp := &libcontainer.Process{
Args: p.Args,
Env: p.Env,
// TODO: fix libcontainer's API to better support uid/gid in a typesafe way.
User: fmt.Sprintf("%d:%d", p.User.UID, p.User.GID),
Cwd: p.Cwd,
Capabilities: p.Capabilities,
Label: p.SelinuxLabel,
NoNewPrivileges: &p.NoNewPrivileges,
AppArmorProfile: p.ApparmorProfile,
}
for _, rlimit := range p.Rlimits {
rl, err := createLibContainerRlimit(rlimit)
if err != nil {
return nil, err
}
lp.Rlimits = append(lp.Rlimits, rl)
}
return lp, nil
}
func dupStdio(process *libcontainer.Process, rootuid int) error {
process.Stdin = os.Stdin
process.Stdout = os.Stdout
process.Stderr = os.Stderr
for _, fd := range []uintptr{
os.Stdin.Fd(),
os.Stdout.Fd(),
os.Stderr.Fd(),
} {
if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil {
return err
}
}
return nil
}
func destroy(container libcontainer.Container) {
if err := container.Destroy(); err != nil {
logrus.Error(err)
}
}
// setupIO sets the proper IO on the process depending on the configuration
// If there is a nil error then there must be a non nil tty returned
func setupIO(process *libcontainer.Process, rootuid int, console string, createTTY, detach bool) (*tty, error) {
// detach and createTty will not work unless a console path is passed
// so error out here before changing any terminal settings
if createTTY && detach && console == "" {
return nil, fmt.Errorf("cannot allocate tty if runc will detach")
}
if createTTY {
return createTty(process, rootuid, console)
}
if detach {
if err := dupStdio(process, rootuid); err != nil {
return nil, err
}
return &tty{}, nil
}
return createStdioPipes(process, rootuid)
}
// createPidFile creates a file with the processes pid inside it atomically
// it creates a temp file with the paths filename + '.' infront of it
// then renames the file
func createPidFile(path string, process *libcontainer.Process) error {
pid, err := process.Pid()
if err != nil {
return err
}
var (
tmpDir = filepath.Dir(path)
tmpName = filepath.Join(tmpDir, fmt.Sprintf(".%s", filepath.Base(path)))
)
f, err := os.OpenFile(tmpName, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666)
if err != nil {
return err
}
_, err = fmt.Fprintf(f, "%d", pid)
f.Close()
if err != nil {
return err
}
return os.Rename(tmpName, path)
}
type runner struct {
enableSubreaper bool
shouldDestroy bool
detach bool
listenFDs []*os.File
pidFile string
console string
container libcontainer.Container
}
func (r *runner) run(config *specs.Process) (int, error) {
process, err := newProcess(*config)
if err != nil {
r.destroy()
return -1, err
}
if len(r.listenFDs) > 0 {
process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(r.listenFDs)), "LISTEN_PID=1")
process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...)
}
rootuid, err := r.container.Config().HostUID()
if err != nil {
r.destroy()
return -1, err
}
tty, err := setupIO(process, rootuid, r.console, config.Terminal, r.detach)
if err != nil {
r.destroy()
return -1, err
}
handler := newSignalHandler(tty, r.enableSubreaper)
if err := r.container.Start(process); err != nil {
r.destroy()
tty.Close()
return -1, err
}
if err := tty.ClosePostStart(); err != nil {
r.terminate(process)
r.destroy()
tty.Close()
return -1, err
}
if r.pidFile != "" {
if err := createPidFile(r.pidFile, process); err != nil {
r.terminate(process)
r.destroy()
tty.Close()
return -1, err
}
}
if r.detach {
tty.Close()
return 0, nil
}
status, err := handler.forward(process)
if err != nil {
r.terminate(process)
}
r.destroy()
tty.Close()
return status, err
}
func (r *runner) destroy() {
if r.shouldDestroy {
destroy(r.container)
}
}
func (r *runner) terminate(p *libcontainer.Process) {
p.Signal(syscall.SIGKILL)
p.Wait()
}
func sPtr(s string) *string { return &s }
func createLibContainerRlimit(rlimit specs.Rlimit) (configs.Rlimit, error) {
rl, err := strToRlimit(rlimit.Type)
if err != nil {
return configs.Rlimit{}, err
}
return configs.Rlimit{
Type: rl,
Hard: uint64(rlimit.Hard),
Soft: uint64(rlimit.Soft),
}, nil
}