2013-01-18 19:13:39 -05:00
|
|
|
package docker
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2013-03-21 03:54:54 -04:00
|
|
|
"errors"
|
2013-03-21 03:52:43 -04:00
|
|
|
"fmt"
|
2013-03-22 14:44:12 -04:00
|
|
|
"github.com/dotcloud/docker/rcli"
|
2013-03-31 05:02:01 -04:00
|
|
|
"index/suffixarray"
|
2013-01-18 19:13:39 -05:00
|
|
|
"io"
|
2013-03-30 13:33:10 -04:00
|
|
|
"io/ioutil"
|
2013-03-21 03:54:54 -04:00
|
|
|
"net/http"
|
2013-02-13 16:58:28 -05:00
|
|
|
"os"
|
2013-01-27 03:59:49 -05:00
|
|
|
"os/exec"
|
2013-02-13 16:58:28 -05:00
|
|
|
"path/filepath"
|
2013-04-19 00:57:58 -04:00
|
|
|
"regexp"
|
2013-03-25 04:29:01 -04:00
|
|
|
"runtime"
|
|
|
|
"strings"
|
2013-01-28 17:30:05 -05:00
|
|
|
"sync"
|
2013-03-21 03:52:43 -04:00
|
|
|
"time"
|
2013-01-18 19:13:39 -05:00
|
|
|
)
|
|
|
|
|
2013-03-21 04:13:55 -04:00
|
|
|
// Go is a basic promise implementation: it wraps calls a function in a goroutine,
|
|
|
|
// and returns a channel which will later return the function's return value.
|
|
|
|
func Go(f func() error) chan error {
|
|
|
|
ch := make(chan error)
|
|
|
|
go func() {
|
|
|
|
ch <- f()
|
|
|
|
}()
|
|
|
|
return ch
|
|
|
|
}
|
|
|
|
|
2013-03-21 03:54:54 -04:00
|
|
|
// Request a given URL and return an io.Reader
|
|
|
|
func Download(url string, stderr io.Writer) (*http.Response, error) {
|
|
|
|
var resp *http.Response
|
|
|
|
var err error = nil
|
|
|
|
if resp, err = http.Get(url); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if resp.StatusCode >= 400 {
|
|
|
|
return nil, errors.New("Got HTTP status code >= 400: " + resp.Status)
|
|
|
|
}
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
2013-03-22 14:44:12 -04:00
|
|
|
// Debug function, if the debug flag is set, then display. Do nothing otherwise
|
|
|
|
// If Docker is in damon mode, also send the debug info on the socket
|
|
|
|
func Debugf(format string, a ...interface{}) {
|
2013-04-02 13:58:16 -04:00
|
|
|
if os.Getenv("DEBUG") != "" {
|
2013-03-25 04:29:01 -04:00
|
|
|
|
|
|
|
// Retrieve the stack infos
|
|
|
|
_, file, line, ok := runtime.Caller(1)
|
|
|
|
if !ok {
|
|
|
|
file = "<unknown>"
|
|
|
|
line = -1
|
|
|
|
} else {
|
|
|
|
file = file[strings.LastIndex(file, "/")+1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...)
|
2013-03-22 14:44:12 -04:00
|
|
|
if rcli.CLIENT_SOCKET != nil {
|
2013-03-25 04:29:01 -04:00
|
|
|
fmt.Fprintf(rcli.CLIENT_SOCKET, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...)
|
2013-03-22 14:44:12 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-21 03:54:54 -04:00
|
|
|
// Reader with progress bar
|
|
|
|
type progressReader struct {
|
2013-03-28 20:12:23 -04:00
|
|
|
reader io.ReadCloser // Stream to read from
|
|
|
|
output io.Writer // Where to send progress bar to
|
|
|
|
readTotal int // Expected stream length (bytes)
|
|
|
|
readProgress int // How much has been read so far (bytes)
|
|
|
|
lastUpdate int // How many bytes read at least update
|
2013-04-21 18:29:26 -04:00
|
|
|
template string // Template to print. Default "%v/%v (%v)"
|
2013-03-21 03:54:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *progressReader) Read(p []byte) (n int, err error) {
|
|
|
|
read, err := io.ReadCloser(r.reader).Read(p)
|
2013-03-28 20:12:23 -04:00
|
|
|
r.readProgress += read
|
2013-03-21 03:54:54 -04:00
|
|
|
|
2013-04-21 18:29:26 -04:00
|
|
|
updateEvery := 4096
|
|
|
|
if r.readTotal > 0 {
|
|
|
|
// Only update progress for every 1% read
|
|
|
|
if increment := int(0.01 * float64(r.readTotal)); increment > updateEvery {
|
|
|
|
updateEvery = increment
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if r.readProgress-r.lastUpdate > updateEvery || err != nil {
|
|
|
|
if r.readTotal > 0 {
|
|
|
|
fmt.Fprintf(r.output, r.template+"\r", r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(r.output, r.template+"\r", r.readProgress, "?", "n/a")
|
|
|
|
}
|
2013-03-28 20:12:23 -04:00
|
|
|
r.lastUpdate = r.readProgress
|
2013-03-21 03:54:54 -04:00
|
|
|
}
|
|
|
|
// Send newline when complete
|
2013-04-21 18:29:26 -04:00
|
|
|
if err != nil {
|
2013-03-21 03:54:54 -04:00
|
|
|
fmt.Fprintf(r.output, "\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
return read, err
|
|
|
|
}
|
|
|
|
func (r *progressReader) Close() error {
|
|
|
|
return io.ReadCloser(r.reader).Close()
|
|
|
|
}
|
2013-04-21 18:29:26 -04:00
|
|
|
func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string) *progressReader {
|
|
|
|
if template == "" {
|
|
|
|
template = "%v/%v (%v)"
|
|
|
|
}
|
|
|
|
return &progressReader{r, output, size, 0, 0, template}
|
2013-03-21 03:54:54 -04:00
|
|
|
}
|
|
|
|
|
2013-03-21 03:52:43 -04:00
|
|
|
// HumanDuration returns a human-readable approximation of a duration
|
|
|
|
// (eg. "About a minute", "4 hours ago", etc.)
|
|
|
|
func HumanDuration(d time.Duration) string {
|
|
|
|
if seconds := int(d.Seconds()); seconds < 1 {
|
|
|
|
return "Less than a second"
|
|
|
|
} else if seconds < 60 {
|
|
|
|
return fmt.Sprintf("%d seconds", seconds)
|
|
|
|
} else if minutes := int(d.Minutes()); minutes == 1 {
|
|
|
|
return "About a minute"
|
|
|
|
} else if minutes < 60 {
|
|
|
|
return fmt.Sprintf("%d minutes", minutes)
|
|
|
|
} else if hours := int(d.Hours()); hours == 1 {
|
|
|
|
return "About an hour"
|
|
|
|
} else if hours < 48 {
|
|
|
|
return fmt.Sprintf("%d hours", hours)
|
|
|
|
} else if hours < 24*7*2 {
|
|
|
|
return fmt.Sprintf("%d days", hours/24)
|
|
|
|
} else if hours < 24*30*3 {
|
|
|
|
return fmt.Sprintf("%d weeks", hours/24/7)
|
|
|
|
} else if hours < 24*365*2 {
|
|
|
|
return fmt.Sprintf("%d months", hours/24/30)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%d years", d.Hours()/24/365)
|
|
|
|
}
|
|
|
|
|
2013-01-29 06:18:07 -05:00
|
|
|
func Trunc(s string, maxlen int) string {
|
|
|
|
if len(s) <= maxlen {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
return s[:maxlen]
|
|
|
|
}
|
|
|
|
|
2013-02-13 16:58:28 -05:00
|
|
|
// Figure out the absolute path of our own binary
|
|
|
|
func SelfPath() string {
|
|
|
|
path, err := exec.LookPath(os.Args[0])
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
path, err = filepath.Abs(path)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
2013-01-26 18:56:42 -05:00
|
|
|
type nopWriteCloser struct {
|
|
|
|
io.Writer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *nopWriteCloser) Close() error { return nil }
|
|
|
|
|
|
|
|
func NopWriteCloser(w io.Writer) io.WriteCloser {
|
|
|
|
return &nopWriteCloser{w}
|
|
|
|
}
|
|
|
|
|
2013-01-18 19:13:39 -05:00
|
|
|
type bufReader struct {
|
|
|
|
buf *bytes.Buffer
|
|
|
|
reader io.Reader
|
|
|
|
err error
|
|
|
|
l sync.Mutex
|
|
|
|
wait sync.Cond
|
|
|
|
}
|
|
|
|
|
|
|
|
func newBufReader(r io.Reader) *bufReader {
|
|
|
|
reader := &bufReader{
|
|
|
|
buf: &bytes.Buffer{},
|
|
|
|
reader: r,
|
|
|
|
}
|
|
|
|
reader.wait.L = &reader.l
|
|
|
|
go reader.drain()
|
|
|
|
return reader
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *bufReader) drain() {
|
|
|
|
buf := make([]byte, 1024)
|
|
|
|
for {
|
|
|
|
n, err := r.reader.Read(buf)
|
2013-03-29 06:00:50 -04:00
|
|
|
r.l.Lock()
|
2013-01-18 19:13:39 -05:00
|
|
|
if err != nil {
|
|
|
|
r.err = err
|
|
|
|
} else {
|
|
|
|
r.buf.Write(buf[0:n])
|
|
|
|
}
|
|
|
|
r.wait.Signal()
|
|
|
|
r.l.Unlock()
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *bufReader) Read(p []byte) (n int, err error) {
|
2013-03-29 06:00:50 -04:00
|
|
|
r.l.Lock()
|
|
|
|
defer r.l.Unlock()
|
2013-01-18 19:13:39 -05:00
|
|
|
for {
|
|
|
|
n, err = r.buf.Read(p)
|
|
|
|
if n > 0 {
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
if r.err != nil {
|
|
|
|
return 0, r.err
|
|
|
|
}
|
|
|
|
r.wait.Wait()
|
|
|
|
}
|
2013-04-03 05:18:23 -04:00
|
|
|
panic("unreachable")
|
2013-01-18 19:13:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *bufReader) Close() error {
|
|
|
|
closer, ok := r.reader.(io.ReadCloser)
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return closer.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
type writeBroadcaster struct {
|
2013-03-30 09:37:06 -04:00
|
|
|
mu sync.Mutex
|
2013-03-30 09:44:00 -04:00
|
|
|
writers map[io.WriteCloser]struct{}
|
2013-01-18 19:13:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (w *writeBroadcaster) AddWriter(writer io.WriteCloser) {
|
2013-03-30 09:37:06 -04:00
|
|
|
w.mu.Lock()
|
2013-03-30 09:44:00 -04:00
|
|
|
w.writers[writer] = struct{}{}
|
2013-03-30 09:37:06 -04:00
|
|
|
w.mu.Unlock()
|
2013-01-18 19:13:39 -05:00
|
|
|
}
|
|
|
|
|
2013-03-29 12:31:47 -04:00
|
|
|
// FIXME: Is that function used?
|
2013-03-30 09:37:06 -04:00
|
|
|
// FIXME: This relies on the concrete writer type used having equality operator
|
2013-01-18 19:13:39 -05:00
|
|
|
func (w *writeBroadcaster) RemoveWriter(writer io.WriteCloser) {
|
2013-03-30 09:37:06 -04:00
|
|
|
w.mu.Lock()
|
2013-03-30 09:44:00 -04:00
|
|
|
delete(w.writers, writer)
|
|
|
|
w.mu.Unlock()
|
2013-01-18 19:13:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (w *writeBroadcaster) Write(p []byte) (n int, err error) {
|
2013-03-30 09:37:06 -04:00
|
|
|
w.mu.Lock()
|
|
|
|
defer w.mu.Unlock()
|
2013-03-30 09:44:00 -04:00
|
|
|
for writer := range w.writers {
|
2013-01-18 19:13:39 -05:00
|
|
|
if n, err := writer.Write(p); err != nil || n != len(p) {
|
|
|
|
// On error, evict the writer
|
2013-03-30 09:44:00 -04:00
|
|
|
delete(w.writers, writer)
|
2013-01-18 19:13:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return len(p), nil
|
|
|
|
}
|
|
|
|
|
2013-04-02 04:45:17 -04:00
|
|
|
func (w *writeBroadcaster) CloseWriters() error {
|
2013-03-30 09:37:06 -04:00
|
|
|
w.mu.Lock()
|
|
|
|
defer w.mu.Unlock()
|
2013-03-30 09:44:00 -04:00
|
|
|
for writer := range w.writers {
|
2013-01-18 19:13:39 -05:00
|
|
|
writer.Close()
|
|
|
|
}
|
2013-03-30 09:44:00 -04:00
|
|
|
w.writers = make(map[io.WriteCloser]struct{})
|
2013-01-18 19:13:39 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newWriteBroadcaster() *writeBroadcaster {
|
2013-03-30 09:44:00 -04:00
|
|
|
return &writeBroadcaster{writers: make(map[io.WriteCloser]struct{})}
|
2013-01-18 19:13:39 -05:00
|
|
|
}
|
2013-03-30 13:33:10 -04:00
|
|
|
|
|
|
|
func getTotalUsedFds() int {
|
|
|
|
if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
|
|
|
|
Debugf("Error opening /proc/%d/fd: %s", os.Getpid(), err)
|
|
|
|
} else {
|
|
|
|
return len(fds)
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
2013-03-31 05:02:01 -04:00
|
|
|
|
|
|
|
// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
|
|
|
|
// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
|
|
|
|
type TruncIndex struct {
|
|
|
|
index *suffixarray.Index
|
|
|
|
ids map[string]bool
|
|
|
|
bytes []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewTruncIndex() *TruncIndex {
|
|
|
|
return &TruncIndex{
|
|
|
|
index: suffixarray.New([]byte{' '}),
|
|
|
|
ids: make(map[string]bool),
|
|
|
|
bytes: []byte{' '},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (idx *TruncIndex) Add(id string) error {
|
|
|
|
if strings.Contains(id, " ") {
|
|
|
|
return fmt.Errorf("Illegal character: ' '")
|
|
|
|
}
|
|
|
|
if _, exists := idx.ids[id]; exists {
|
|
|
|
return fmt.Errorf("Id already exists: %s", id)
|
|
|
|
}
|
|
|
|
idx.ids[id] = true
|
|
|
|
idx.bytes = append(idx.bytes, []byte(id+" ")...)
|
|
|
|
idx.index = suffixarray.New(idx.bytes)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (idx *TruncIndex) Delete(id string) error {
|
|
|
|
if _, exists := idx.ids[id]; !exists {
|
|
|
|
return fmt.Errorf("No such id: %s", id)
|
|
|
|
}
|
|
|
|
before, after, err := idx.lookup(id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
delete(idx.ids, id)
|
|
|
|
idx.bytes = append(idx.bytes[:before], idx.bytes[after:]...)
|
|
|
|
idx.index = suffixarray.New(idx.bytes)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (idx *TruncIndex) lookup(s string) (int, int, error) {
|
|
|
|
offsets := idx.index.Lookup([]byte(" "+s), -1)
|
|
|
|
//log.Printf("lookup(%s): %v (index bytes: '%s')\n", s, offsets, idx.index.Bytes())
|
|
|
|
if offsets == nil || len(offsets) == 0 || len(offsets) > 1 {
|
|
|
|
return -1, -1, fmt.Errorf("No such id: %s", s)
|
|
|
|
}
|
|
|
|
offsetBefore := offsets[0] + 1
|
|
|
|
offsetAfter := offsetBefore + strings.Index(string(idx.bytes[offsetBefore:]), " ")
|
|
|
|
return offsetBefore, offsetAfter, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (idx *TruncIndex) Get(s string) (string, error) {
|
|
|
|
before, after, err := idx.lookup(s)
|
|
|
|
//log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(idx.bytes[before:after]), err
|
|
|
|
}
|
2013-04-01 01:11:55 -04:00
|
|
|
|
|
|
|
// TruncateId returns a shorthand version of a string identifier for convenience.
|
|
|
|
// A collision with other shorthands is very unlikely, but possible.
|
|
|
|
// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
|
|
|
|
// will need to use a langer prefix, or the full-length Id.
|
|
|
|
func TruncateId(id string) string {
|
|
|
|
shortLen := 12
|
|
|
|
if len(id) < shortLen {
|
|
|
|
shortLen = len(id)
|
|
|
|
}
|
|
|
|
return id[:shortLen]
|
|
|
|
}
|
2013-04-04 15:55:24 -04:00
|
|
|
|
|
|
|
// Code c/c from io.Copy() modified to handle escape sequence
|
|
|
|
func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
|
|
|
|
buf := make([]byte, 32*1024)
|
|
|
|
for {
|
|
|
|
nr, er := src.Read(buf)
|
|
|
|
if nr > 0 {
|
|
|
|
// ---- Docker addition
|
2013-04-04 21:13:43 -04:00
|
|
|
// char 16 is C-p
|
|
|
|
if nr == 1 && buf[0] == 16 {
|
2013-04-04 15:55:24 -04:00
|
|
|
nr, er = src.Read(buf)
|
2013-04-04 21:13:43 -04:00
|
|
|
// char 17 is C-q
|
|
|
|
if nr == 1 && buf[0] == 17 {
|
2013-04-04 15:55:24 -04:00
|
|
|
if err := src.Close(); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return 0, io.EOF
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// ---- End of docker
|
|
|
|
nw, ew := dst.Write(buf[0:nr])
|
|
|
|
if nw > 0 {
|
|
|
|
written += int64(nw)
|
|
|
|
}
|
|
|
|
if ew != nil {
|
|
|
|
err = ew
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if nr != nw {
|
|
|
|
err = io.ErrShortWrite
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if er == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if er != nil {
|
|
|
|
err = er
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return written, err
|
|
|
|
}
|
2013-04-18 23:47:24 -04:00
|
|
|
|
|
|
|
type KernelVersionInfo struct {
|
2013-04-21 21:19:38 -04:00
|
|
|
Kernel int
|
|
|
|
Major int
|
|
|
|
Minor int
|
|
|
|
Flavor string
|
2013-04-18 23:47:24 -04:00
|
|
|
}
|
|
|
|
|
2013-04-21 18:29:26 -04:00
|
|
|
// FIXME: this doens't build on Darwin
|
2013-04-18 23:47:24 -04:00
|
|
|
func GetKernelVersion() (*KernelVersionInfo, error) {
|
2013-04-22 15:08:59 -04:00
|
|
|
return getKernelVersion()
|
2013-04-18 23:47:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (k *KernelVersionInfo) String() string {
|
2013-04-21 21:19:38 -04:00
|
|
|
return fmt.Sprintf("%d.%d.%d-%s", k.Kernel, k.Major, k.Minor, k.Flavor)
|
2013-04-18 23:47:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Compare two KernelVersionInfo struct.
|
|
|
|
// Returns -1 if a < b, = if a == b, 1 it a > b
|
|
|
|
func CompareKernelVersion(a, b *KernelVersionInfo) int {
|
|
|
|
if a.Kernel < b.Kernel {
|
|
|
|
return -1
|
|
|
|
} else if a.Kernel > b.Kernel {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if a.Major < b.Major {
|
|
|
|
return -1
|
|
|
|
} else if a.Major > b.Major {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if a.Minor < b.Minor {
|
|
|
|
return -1
|
|
|
|
} else if a.Minor > b.Minor {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
2013-04-19 00:57:58 -04:00
|
|
|
|
|
|
|
func FindCgroupMountpoint(cgroupType string) (string, error) {
|
|
|
|
output, err := exec.Command("mount").CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
Update FindCgroupMountpoint to be more forgiving
On Gentoo, the memory cgroup is mounted at /sys/fs/cgroup/memory, but the mount line looks like the following:
memory on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
(note that the first word on the line is "memory", not "cgroup", but the other essentials are there, namely the type of cgroup and the memory mount option)
2013-04-23 03:09:29 -04:00
|
|
|
reg := regexp.MustCompile(`^.* on (.*) type cgroup \(.*` + cgroupType + `[,\)]`)
|
2013-04-19 00:57:58 -04:00
|
|
|
for _, line := range strings.Split(string(output), "\n") {
|
|
|
|
r := reg.FindStringSubmatch(line)
|
|
|
|
if len(r) == 2 {
|
|
|
|
return r[1], nil
|
|
|
|
}
|
|
|
|
}
|
2013-04-22 00:44:57 -04:00
|
|
|
return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType)
|
2013-04-19 00:57:58 -04:00
|
|
|
}
|