add missed dependency
This commit is contained in:
67
Godeps/_workspace/src/github.com/docker/go-units/CONTRIBUTING.md
generated
vendored
Normal file
67
Godeps/_workspace/src/github.com/docker/go-units/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Contributing to go-units
|
||||||
|
|
||||||
|
Want to hack on go-units? Awesome! Here are instructions to get you started.
|
||||||
|
|
||||||
|
go-units is a part of the [Docker](https://www.docker.com) project, and follows
|
||||||
|
the same rules and principles. If you're already familiar with the way
|
||||||
|
Docker does things, you'll feel right at home.
|
||||||
|
|
||||||
|
Otherwise, go read Docker's
|
||||||
|
[contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md),
|
||||||
|
[issue triaging](https://github.com/docker/docker/blob/master/project/ISSUE-TRIAGE.md),
|
||||||
|
[review process](https://github.com/docker/docker/blob/master/project/REVIEWING.md) and
|
||||||
|
[branches and tags](https://github.com/docker/docker/blob/master/project/BRANCHES-AND-TAGS.md).
|
||||||
|
|
||||||
|
### Sign your work
|
||||||
|
|
||||||
|
The sign-off is a simple line at the end of the explanation for the patch. Your
|
||||||
|
signature certifies that you wrote the patch or otherwise have the right to pass
|
||||||
|
it on as an open-source patch. The rules are pretty simple: if you can certify
|
||||||
|
the below (from [developercertificate.org](http://developercertificate.org/)):
|
||||||
|
|
||||||
|
```
|
||||||
|
Developer Certificate of Origin
|
||||||
|
Version 1.1
|
||||||
|
|
||||||
|
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||||
|
660 York Street, Suite 102,
|
||||||
|
San Francisco, CA 94110 USA
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this
|
||||||
|
license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Developer's Certificate of Origin 1.1
|
||||||
|
|
||||||
|
By making a contribution to this project, I certify that:
|
||||||
|
|
||||||
|
(a) The contribution was created in whole or in part by me and I
|
||||||
|
have the right to submit it under the open source license
|
||||||
|
indicated in the file; or
|
||||||
|
|
||||||
|
(b) The contribution is based upon previous work that, to the best
|
||||||
|
of my knowledge, is covered under an appropriate open source
|
||||||
|
license and I have the right under that license to submit that
|
||||||
|
work with modifications, whether created in whole or in part
|
||||||
|
by me, under the same open source license (unless I am
|
||||||
|
permitted to submit under a different license), as indicated
|
||||||
|
in the file; or
|
||||||
|
|
||||||
|
(c) The contribution was provided directly to me by some other
|
||||||
|
person who certified (a), (b) or (c) and I have not modified
|
||||||
|
it.
|
||||||
|
|
||||||
|
(d) I understand and agree that this project and the contribution
|
||||||
|
are public and that a record of the contribution (including all
|
||||||
|
personal information I submit with it, including my sign-off) is
|
||||||
|
maintained indefinitely and may be redistributed consistent with
|
||||||
|
this project or the open source license(s) involved.
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you just add a line to every git commit message:
|
||||||
|
|
||||||
|
Signed-off-by: Joe Smith <joe.smith@email.com>
|
||||||
|
|
||||||
|
Use your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||||
|
|
||||||
|
If you set your `user.name` and `user.email` git configs, you can sign your
|
||||||
|
commit automatically with `git commit -s`.
|
||||||
191
Godeps/_workspace/src/github.com/docker/go-units/LICENSE
generated
vendored
Normal file
191
Godeps/_workspace/src/github.com/docker/go-units/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
https://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Copyright 2015 Docker, 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
|
||||||
|
|
||||||
|
https://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.
|
||||||
27
Godeps/_workspace/src/github.com/docker/go-units/MAINTAINERS
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/docker/go-units/MAINTAINERS
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# go-connections maintainers file
|
||||||
|
#
|
||||||
|
# This file describes who runs the docker/go-connections project and how.
|
||||||
|
# This is a living document - if you see something out of date or missing, speak up!
|
||||||
|
#
|
||||||
|
# It is structured to be consumable by both humans and programs.
|
||||||
|
# To extract its contents programmatically, use any TOML-compliant parser.
|
||||||
|
#
|
||||||
|
# This file is compiled into the MAINTAINERS file in docker/opensource.
|
||||||
|
#
|
||||||
|
[Org]
|
||||||
|
[Org."Core maintainers"]
|
||||||
|
people = [
|
||||||
|
"calavera",
|
||||||
|
]
|
||||||
|
|
||||||
|
[people]
|
||||||
|
|
||||||
|
# A reference list of all people associated with the project.
|
||||||
|
# All other sections should refer to people by their canonical key
|
||||||
|
# in the people section.
|
||||||
|
|
||||||
|
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
|
||||||
|
[people.calavera]
|
||||||
|
Name = "David Calavera"
|
||||||
|
Email = "david.calavera@gmail.com"
|
||||||
|
GitHub = "calavera"
|
||||||
16
Godeps/_workspace/src/github.com/docker/go-units/README.md
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/docker/go-units/README.md
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[](https://godoc.org/github.com/docker/go-units)
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
go-units is a library to transform human friendly measurements into machine friendly values.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
See the [docs in godoc](https://godoc.org/github.com/docker/go-units) for examples and documentation.
|
||||||
|
|
||||||
|
## Copyright and license
|
||||||
|
|
||||||
|
Copyright © 2015 Docker, Inc.
|
||||||
|
|
||||||
|
go-units is licensed under the Apache License, Version 2.0.
|
||||||
|
See [LICENSE](LICENSE) for the full text of the license.
|
||||||
11
Godeps/_workspace/src/github.com/docker/go-units/circle.yml
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/docker/go-units/circle.yml
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
dependencies:
|
||||||
|
post:
|
||||||
|
# install golint
|
||||||
|
- go get github.com/golang/lint/golint
|
||||||
|
|
||||||
|
test:
|
||||||
|
pre:
|
||||||
|
# run analysis before tests
|
||||||
|
- go vet ./...
|
||||||
|
- test -z "$(golint ./... | tee /dev/stderr)"
|
||||||
|
- test -z "$(gofmt -s -l . | tee /dev/stderr)"
|
||||||
33
Godeps/_workspace/src/github.com/docker/go-units/duration.go
generated
vendored
Normal file
33
Godeps/_workspace/src/github.com/docker/go-units/duration.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Package units provides helper function to parse and print size and time units
|
||||||
|
// in human-readable format.
|
||||||
|
package units
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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", int(d.Hours())/24/365)
|
||||||
|
}
|
||||||
95
Godeps/_workspace/src/github.com/docker/go-units/size.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/docker/go-units/size.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package units
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// See: http://en.wikipedia.org/wiki/Binary_prefix
|
||||||
|
const (
|
||||||
|
// Decimal
|
||||||
|
|
||||||
|
KB = 1000
|
||||||
|
MB = 1000 * KB
|
||||||
|
GB = 1000 * MB
|
||||||
|
TB = 1000 * GB
|
||||||
|
PB = 1000 * TB
|
||||||
|
|
||||||
|
// Binary
|
||||||
|
|
||||||
|
KiB = 1024
|
||||||
|
MiB = 1024 * KiB
|
||||||
|
GiB = 1024 * MiB
|
||||||
|
TiB = 1024 * GiB
|
||||||
|
PiB = 1024 * TiB
|
||||||
|
)
|
||||||
|
|
||||||
|
type unitMap map[string]int64
|
||||||
|
|
||||||
|
var (
|
||||||
|
decimalMap = unitMap{"k": KB, "m": MB, "g": GB, "t": TB, "p": PB}
|
||||||
|
binaryMap = unitMap{"k": KiB, "m": MiB, "g": GiB, "t": TiB, "p": PiB}
|
||||||
|
sizeRegex = regexp.MustCompile(`^(\d+(\.\d+)*) ?([kKmMgGtTpP])?[bB]?$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||||
|
var binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
|
||||||
|
|
||||||
|
// CustomSize returns a human-readable approximation of a size
|
||||||
|
// using custom format.
|
||||||
|
func CustomSize(format string, size float64, base float64, _map []string) string {
|
||||||
|
i := 0
|
||||||
|
for size >= base {
|
||||||
|
size = size / base
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(format, size, _map[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// HumanSize returns a human-readable approximation of a size
|
||||||
|
// capped at 4 valid numbers (eg. "2.746 MB", "796 KB").
|
||||||
|
func HumanSize(size float64) string {
|
||||||
|
return CustomSize("%.4g %s", size, 1000.0, decimapAbbrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesSize returns a human-readable size in bytes, kibibytes,
|
||||||
|
// mebibytes, gibibytes, or tebibytes (eg. "44kiB", "17MiB").
|
||||||
|
func BytesSize(size float64) string {
|
||||||
|
return CustomSize("%.4g %s", size, 1024.0, binaryAbbrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromHumanSize returns an integer from a human-readable specification of a
|
||||||
|
// size using SI standard (eg. "44kB", "17MB").
|
||||||
|
func FromHumanSize(size string) (int64, error) {
|
||||||
|
return parseSize(size, decimalMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAMInBytes parses a human-readable string representing an amount of RAM
|
||||||
|
// in bytes, kibibytes, mebibytes, gibibytes, or tebibytes and
|
||||||
|
// returns the number of bytes, or -1 if the string is unparseable.
|
||||||
|
// Units are case-insensitive, and the 'b' suffix is optional.
|
||||||
|
func RAMInBytes(size string) (int64, error) {
|
||||||
|
return parseSize(size, binaryMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses the human-readable size string into the amount it represents.
|
||||||
|
func parseSize(sizeStr string, uMap unitMap) (int64, error) {
|
||||||
|
matches := sizeRegex.FindStringSubmatch(sizeStr)
|
||||||
|
if len(matches) != 4 {
|
||||||
|
return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
size, err := strconv.ParseFloat(matches[1], 64)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
unitPrefix := strings.ToLower(matches[3])
|
||||||
|
if mul, ok := uMap[unitPrefix]; ok {
|
||||||
|
size *= float64(mul)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(size), nil
|
||||||
|
}
|
||||||
118
Godeps/_workspace/src/github.com/docker/go-units/ulimit.go
generated
vendored
Normal file
118
Godeps/_workspace/src/github.com/docker/go-units/ulimit.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package units
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ulimit is a human friendly version of Rlimit.
|
||||||
|
type Ulimit struct {
|
||||||
|
Name string
|
||||||
|
Hard int64
|
||||||
|
Soft int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rlimit specifies the resource limits, such as max open files.
|
||||||
|
type Rlimit struct {
|
||||||
|
Type int `json:"type,omitempty"`
|
||||||
|
Hard uint64 `json:"hard,omitempty"`
|
||||||
|
Soft uint64 `json:"soft,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// magic numbers for making the syscall
|
||||||
|
// some of these are defined in the syscall package, but not all.
|
||||||
|
// Also since Windows client doesn't get access to the syscall package, need to
|
||||||
|
// define these here
|
||||||
|
rlimitAs = 9
|
||||||
|
rlimitCore = 4
|
||||||
|
rlimitCPU = 0
|
||||||
|
rlimitData = 2
|
||||||
|
rlimitFsize = 1
|
||||||
|
rlimitLocks = 10
|
||||||
|
rlimitMemlock = 8
|
||||||
|
rlimitMsgqueue = 12
|
||||||
|
rlimitNice = 13
|
||||||
|
rlimitNofile = 7
|
||||||
|
rlimitNproc = 6
|
||||||
|
rlimitRss = 5
|
||||||
|
rlimitRtprio = 14
|
||||||
|
rlimitRttime = 15
|
||||||
|
rlimitSigpending = 11
|
||||||
|
rlimitStack = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
var ulimitNameMapping = map[string]int{
|
||||||
|
//"as": rlimitAs, // Disabled since this doesn't seem usable with the way Docker inits a container.
|
||||||
|
"core": rlimitCore,
|
||||||
|
"cpu": rlimitCPU,
|
||||||
|
"data": rlimitData,
|
||||||
|
"fsize": rlimitFsize,
|
||||||
|
"locks": rlimitLocks,
|
||||||
|
"memlock": rlimitMemlock,
|
||||||
|
"msgqueue": rlimitMsgqueue,
|
||||||
|
"nice": rlimitNice,
|
||||||
|
"nofile": rlimitNofile,
|
||||||
|
"nproc": rlimitNproc,
|
||||||
|
"rss": rlimitRss,
|
||||||
|
"rtprio": rlimitRtprio,
|
||||||
|
"rttime": rlimitRttime,
|
||||||
|
"sigpending": rlimitSigpending,
|
||||||
|
"stack": rlimitStack,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseUlimit parses and returns a Ulimit from the specified string.
|
||||||
|
func ParseUlimit(val string) (*Ulimit, error) {
|
||||||
|
parts := strings.SplitN(val, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, fmt.Errorf("invalid ulimit argument: %s", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := ulimitNameMapping[parts[0]]; !exists {
|
||||||
|
return nil, fmt.Errorf("invalid ulimit type: %s", parts[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
soft int64
|
||||||
|
hard = &soft // default to soft in case no hard was set
|
||||||
|
temp int64
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch limitVals := strings.Split(parts[1], ":"); len(limitVals) {
|
||||||
|
case 2:
|
||||||
|
temp, err = strconv.ParseInt(limitVals[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hard = &temp
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
soft, err = strconv.ParseInt(limitVals[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("too many limit value arguments - %s, can only have up to two, `soft[:hard]`", parts[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if soft > *hard {
|
||||||
|
return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: %d > %d", soft, *hard)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Ulimit{Name: parts[0], Soft: soft, Hard: *hard}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRlimit returns the RLimit corresponding to Ulimit.
|
||||||
|
func (u *Ulimit) GetRlimit() (*Rlimit, error) {
|
||||||
|
t, exists := ulimitNameMapping[u.Name]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("invalid ulimit name %s", u.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Rlimit{Type: t, Soft: uint64(u.Soft), Hard: uint64(u.Hard)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Ulimit) String() string {
|
||||||
|
return fmt.Sprintf("%s=%d:%d", u.Name, u.Soft, u.Hard)
|
||||||
|
}
|
||||||
2
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/MAINTAINERS
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/MAINTAINERS
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||||
|
Aleksa Sarai <cyphar@cyphar.com> (@cyphar)
|
||||||
110
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup.go
generated
vendored
Normal file
110
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// The current operating system does not provide the required data for user lookups.
|
||||||
|
ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data")
|
||||||
|
// No matching entries found in file.
|
||||||
|
ErrNoPasswdEntries = errors.New("no matching entries in passwd file")
|
||||||
|
ErrNoGroupEntries = errors.New("no matching entries in group file")
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupUser(filter func(u User) bool) (User, error) {
|
||||||
|
// Get operating system-specific passwd reader-closer.
|
||||||
|
passwd, err := GetPasswd()
|
||||||
|
if err != nil {
|
||||||
|
return User{}, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
|
||||||
|
// Get the users.
|
||||||
|
users, err := ParsePasswdFilter(passwd, filter)
|
||||||
|
if err != nil {
|
||||||
|
return User{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No user entries found.
|
||||||
|
if len(users) == 0 {
|
||||||
|
return User{}, ErrNoPasswdEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the first entry is the "correct" one.
|
||||||
|
return users[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentUser looks up the current user by their user id in /etc/passwd. If the
|
||||||
|
// user cannot be found (or there is no /etc/passwd file on the filesystem),
|
||||||
|
// then CurrentUser returns an error.
|
||||||
|
func CurrentUser() (User, error) {
|
||||||
|
return LookupUid(syscall.Getuid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUser looks up a user by their username in /etc/passwd. If the user
|
||||||
|
// cannot be found (or there is no /etc/passwd file on the filesystem), then
|
||||||
|
// LookupUser returns an error.
|
||||||
|
func LookupUser(username string) (User, error) {
|
||||||
|
return lookupUser(func(u User) bool {
|
||||||
|
return u.Name == username
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot
|
||||||
|
// be found (or there is no /etc/passwd file on the filesystem), then LookupId
|
||||||
|
// returns an error.
|
||||||
|
func LookupUid(uid int) (User, error) {
|
||||||
|
return lookupUser(func(u User) bool {
|
||||||
|
return u.Uid == uid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupGroup(filter func(g Group) bool) (Group, error) {
|
||||||
|
// Get operating system-specific group reader-closer.
|
||||||
|
group, err := GetGroup()
|
||||||
|
if err != nil {
|
||||||
|
return Group{}, err
|
||||||
|
}
|
||||||
|
defer group.Close()
|
||||||
|
|
||||||
|
// Get the users.
|
||||||
|
groups, err := ParseGroupFilter(group, filter)
|
||||||
|
if err != nil {
|
||||||
|
return Group{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No user entries found.
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return Group{}, ErrNoGroupEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the first entry is the "correct" one.
|
||||||
|
return groups[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentGroup looks up the current user's group by their primary group id's
|
||||||
|
// entry in /etc/passwd. If the group cannot be found (or there is no
|
||||||
|
// /etc/group file on the filesystem), then CurrentGroup returns an error.
|
||||||
|
func CurrentGroup() (Group, error) {
|
||||||
|
return LookupGid(syscall.Getgid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGroup looks up a group by its name in /etc/group. If the group cannot
|
||||||
|
// be found (or there is no /etc/group file on the filesystem), then LookupGroup
|
||||||
|
// returns an error.
|
||||||
|
func LookupGroup(groupname string) (Group, error) {
|
||||||
|
return lookupGroup(func(g Group) bool {
|
||||||
|
return g.Name == groupname
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGid looks up a group by its group id in /etc/group. If the group cannot
|
||||||
|
// be found (or there is no /etc/group file on the filesystem), then LookupGid
|
||||||
|
// returns an error.
|
||||||
|
func LookupGid(gid int) (Group, error) {
|
||||||
|
return lookupGroup(func(g Group) bool {
|
||||||
|
return g.Gid == gid
|
||||||
|
})
|
||||||
|
}
|
||||||
30
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go
generated
vendored
Normal file
30
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unix-specific path to the passwd and group formatted files.
|
||||||
|
const (
|
||||||
|
unixPasswdPath = "/etc/passwd"
|
||||||
|
unixGroupPath = "/etc/group"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPasswdPath() (string, error) {
|
||||||
|
return unixPasswdPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPasswd() (io.ReadCloser, error) {
|
||||||
|
return os.Open(unixPasswdPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroupPath() (string, error) {
|
||||||
|
return unixGroupPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroup() (io.ReadCloser, error) {
|
||||||
|
return os.Open(unixGroupPath)
|
||||||
|
}
|
||||||
21
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func GetPasswdPath() (string, error) {
|
||||||
|
return "", ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPasswd() (io.ReadCloser, error) {
|
||||||
|
return nil, ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroupPath() (string, error) {
|
||||||
|
return "", ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroup() (io.ReadCloser, error) {
|
||||||
|
return nil, ErrUnsupported
|
||||||
|
}
|
||||||
441
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/user.go
generated
vendored
Normal file
441
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/user.go
generated
vendored
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minId = 0
|
||||||
|
maxId = 1<<31 - 1 //for 32-bit systems compatibility
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minId, maxId)
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Pass string
|
||||||
|
Uid int
|
||||||
|
Gid int
|
||||||
|
Gecos string
|
||||||
|
Home string
|
||||||
|
Shell string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
Name string
|
||||||
|
Pass string
|
||||||
|
Gid int
|
||||||
|
List []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLine(line string, v ...interface{}) {
|
||||||
|
if line == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
for i, p := range parts {
|
||||||
|
// Ignore cases where we don't have enough fields to populate the arguments.
|
||||||
|
// Some configuration files like to misbehave.
|
||||||
|
if len(v) <= i {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the type of the argument to figure out how to parse it, scanf() style.
|
||||||
|
// This is legit.
|
||||||
|
switch e := v[i].(type) {
|
||||||
|
case *string:
|
||||||
|
*e = p
|
||||||
|
case *int:
|
||||||
|
// "numbers", with conversion errors ignored because of some misbehaving configuration files.
|
||||||
|
*e, _ = strconv.Atoi(p)
|
||||||
|
case *[]string:
|
||||||
|
// Comma-separated lists.
|
||||||
|
if p != "" {
|
||||||
|
*e = strings.Split(p, ",")
|
||||||
|
} else {
|
||||||
|
*e = []string{}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Someone goof'd when writing code using this function. Scream so they can hear us.
|
||||||
|
panic(fmt.Sprintf("parseLine only accepts {*string, *int, *[]string} as arguments! %#v is not a pointer!", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFile(path string) ([]User, error) {
|
||||||
|
passwd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
return ParsePasswd(passwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswd(passwd io.Reader) ([]User, error) {
|
||||||
|
return ParsePasswdFilter(passwd, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) {
|
||||||
|
passwd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
return ParsePasswdFilter(passwd, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, fmt.Errorf("nil source for passwd-formatted data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
s = bufio.NewScanner(r)
|
||||||
|
out = []User{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
line := strings.TrimSpace(s.Text())
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: man 5 passwd
|
||||||
|
// name:password:UID:GID:GECOS:directory:shell
|
||||||
|
// Name:Pass:Uid:Gid:Gecos:Home:Shell
|
||||||
|
// root:x:0:0:root:/root:/bin/bash
|
||||||
|
// adm:x:3:4:adm:/var/adm:/bin/false
|
||||||
|
p := User{}
|
||||||
|
parseLine(line, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell)
|
||||||
|
|
||||||
|
if filter == nil || filter(p) {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFile(path string) ([]Group, error) {
|
||||||
|
group, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer group.Close()
|
||||||
|
return ParseGroup(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroup(group io.Reader) ([]Group, error) {
|
||||||
|
return ParseGroupFilter(group, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
|
||||||
|
group, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer group.Close()
|
||||||
|
return ParseGroupFilter(group, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, fmt.Errorf("nil source for group-formatted data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
s = bufio.NewScanner(r)
|
||||||
|
out = []Group{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
text := s.Text()
|
||||||
|
if text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: man 5 group
|
||||||
|
// group_name:password:GID:user_list
|
||||||
|
// Name:Pass:Gid:List
|
||||||
|
// root:x:0:root
|
||||||
|
// adm:x:4:root,adm,daemon
|
||||||
|
p := Group{}
|
||||||
|
parseLine(text, &p.Name, &p.Pass, &p.Gid, &p.List)
|
||||||
|
|
||||||
|
if filter == nil || filter(p) {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecUser struct {
|
||||||
|
Uid int
|
||||||
|
Gid int
|
||||||
|
Sgids []int
|
||||||
|
Home string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the
|
||||||
|
// given file paths and uses that data as the arguments to GetExecUser. If the
|
||||||
|
// files cannot be opened for any reason, the error is ignored and a nil
|
||||||
|
// io.Reader is passed instead.
|
||||||
|
func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
|
||||||
|
passwd, err := os.Open(passwdPath)
|
||||||
|
if err != nil {
|
||||||
|
passwd = nil
|
||||||
|
} else {
|
||||||
|
defer passwd.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := os.Open(groupPath)
|
||||||
|
if err != nil {
|
||||||
|
group = nil
|
||||||
|
} else {
|
||||||
|
defer group.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetExecUser(userSpec, defaults, passwd, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExecUser parses a user specification string (using the passwd and group
|
||||||
|
// readers as sources for /etc/passwd and /etc/group data, respectively). In
|
||||||
|
// the case of blank fields or missing data from the sources, the values in
|
||||||
|
// defaults is used.
|
||||||
|
//
|
||||||
|
// GetExecUser will return an error if a user or group literal could not be
|
||||||
|
// found in any entry in passwd and group respectively.
|
||||||
|
//
|
||||||
|
// Examples of valid user specifications are:
|
||||||
|
// * ""
|
||||||
|
// * "user"
|
||||||
|
// * "uid"
|
||||||
|
// * "user:group"
|
||||||
|
// * "uid:gid
|
||||||
|
// * "user:gid"
|
||||||
|
// * "uid:group"
|
||||||
|
//
|
||||||
|
// It should be noted that if you specify a numeric user or group id, they will
|
||||||
|
// not be evaluated as usernames (only the metadata will be filled). So attempting
|
||||||
|
// to parse a user with user.Name = "1337" will produce the user with a UID of
|
||||||
|
// 1337.
|
||||||
|
func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
|
||||||
|
if defaults == nil {
|
||||||
|
defaults = new(ExecUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy over defaults.
|
||||||
|
user := &ExecUser{
|
||||||
|
Uid: defaults.Uid,
|
||||||
|
Gid: defaults.Gid,
|
||||||
|
Sgids: defaults.Sgids,
|
||||||
|
Home: defaults.Home,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sgids slice *cannot* be nil.
|
||||||
|
if user.Sgids == nil {
|
||||||
|
user.Sgids = []int{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow for userArg to have either "user" syntax, or optionally "user:group" syntax
|
||||||
|
var userArg, groupArg string
|
||||||
|
parseLine(userSpec, &userArg, &groupArg)
|
||||||
|
|
||||||
|
// Convert userArg and groupArg to be numeric, so we don't have to execute
|
||||||
|
// Atoi *twice* for each iteration over lines.
|
||||||
|
uidArg, uidErr := strconv.Atoi(userArg)
|
||||||
|
gidArg, gidErr := strconv.Atoi(groupArg)
|
||||||
|
|
||||||
|
// Find the matching user.
|
||||||
|
users, err := ParsePasswdFilter(passwd, func(u User) bool {
|
||||||
|
if userArg == "" {
|
||||||
|
// Default to current state of the user.
|
||||||
|
return u.Uid == user.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
if uidErr == nil {
|
||||||
|
// If the userArg is numeric, always treat it as a UID.
|
||||||
|
return uidArg == u.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.Name == userArg
|
||||||
|
})
|
||||||
|
|
||||||
|
// If we can't find the user, we have to bail.
|
||||||
|
if err != nil && passwd != nil {
|
||||||
|
if userArg == "" {
|
||||||
|
userArg = strconv.Itoa(user.Uid)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to find user %s: %v", userArg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchedUserName string
|
||||||
|
if len(users) > 0 {
|
||||||
|
// First match wins, even if there's more than one matching entry.
|
||||||
|
matchedUserName = users[0].Name
|
||||||
|
user.Uid = users[0].Uid
|
||||||
|
user.Gid = users[0].Gid
|
||||||
|
user.Home = users[0].Home
|
||||||
|
} else if userArg != "" {
|
||||||
|
// If we can't find a user with the given username, the only other valid
|
||||||
|
// option is if it's a numeric username with no associated entry in passwd.
|
||||||
|
|
||||||
|
if uidErr != nil {
|
||||||
|
// Not numeric.
|
||||||
|
return nil, fmt.Errorf("unable to find user %s: %v", userArg, ErrNoPasswdEntries)
|
||||||
|
}
|
||||||
|
user.Uid = uidArg
|
||||||
|
|
||||||
|
// Must be inside valid uid range.
|
||||||
|
if user.Uid < minId || user.Uid > maxId {
|
||||||
|
return nil, ErrRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay, so it's numeric. We can just roll with this.
|
||||||
|
}
|
||||||
|
|
||||||
|
// On to the groups. If we matched a username, we need to do this because of
|
||||||
|
// the supplementary group IDs.
|
||||||
|
if groupArg != "" || matchedUserName != "" {
|
||||||
|
groups, err := ParseGroupFilter(group, func(g Group) bool {
|
||||||
|
// If the group argument isn't explicit, we'll just search for it.
|
||||||
|
if groupArg == "" {
|
||||||
|
// Check if user is a member of this group.
|
||||||
|
for _, u := range g.List {
|
||||||
|
if u == matchedUserName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if gidErr == nil {
|
||||||
|
// If the groupArg is numeric, always treat it as a GID.
|
||||||
|
return gidArg == g.Gid
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Name == groupArg
|
||||||
|
})
|
||||||
|
if err != nil && group != nil {
|
||||||
|
return nil, fmt.Errorf("unable to find groups for spec %v: %v", matchedUserName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only start modifying user.Gid if it is in explicit form.
|
||||||
|
if groupArg != "" {
|
||||||
|
if len(groups) > 0 {
|
||||||
|
// First match wins, even if there's more than one matching entry.
|
||||||
|
user.Gid = groups[0].Gid
|
||||||
|
} else if groupArg != "" {
|
||||||
|
// If we can't find a group with the given name, the only other valid
|
||||||
|
// option is if it's a numeric group name with no associated entry in group.
|
||||||
|
|
||||||
|
if gidErr != nil {
|
||||||
|
// Not numeric.
|
||||||
|
return nil, fmt.Errorf("unable to find group %s: %v", groupArg, ErrNoGroupEntries)
|
||||||
|
}
|
||||||
|
user.Gid = gidArg
|
||||||
|
|
||||||
|
// Must be inside valid gid range.
|
||||||
|
if user.Gid < minId || user.Gid > maxId {
|
||||||
|
return nil, ErrRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay, so it's numeric. We can just roll with this.
|
||||||
|
}
|
||||||
|
} else if len(groups) > 0 {
|
||||||
|
// Supplementary group ids only make sense if in the implicit form.
|
||||||
|
user.Sgids = make([]int, len(groups))
|
||||||
|
for i, group := range groups {
|
||||||
|
user.Sgids[i] = group.Gid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdditionalGroups looks up a list of groups by name or group id
|
||||||
|
// against the given /etc/group formatted data. If a group name cannot
|
||||||
|
// be found, an error will be returned. If a group id cannot be found,
|
||||||
|
// or the given group data is nil, the id will be returned as-is
|
||||||
|
// provided it is in the legal range.
|
||||||
|
func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
|
||||||
|
var groups = []Group{}
|
||||||
|
if group != nil {
|
||||||
|
var err error
|
||||||
|
groups, err = ParseGroupFilter(group, func(g Group) bool {
|
||||||
|
for _, ag := range additionalGroups {
|
||||||
|
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gidMap := make(map[int]struct{})
|
||||||
|
for _, ag := range additionalGroups {
|
||||||
|
var found bool
|
||||||
|
for _, g := range groups {
|
||||||
|
// if we found a matched group either by name or gid, take the
|
||||||
|
// first matched as correct
|
||||||
|
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
|
||||||
|
if _, ok := gidMap[g.Gid]; !ok {
|
||||||
|
gidMap[g.Gid] = struct{}{}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we asked for a group but didn't find it. let's check to see
|
||||||
|
// if we wanted a numeric group
|
||||||
|
if !found {
|
||||||
|
gid, err := strconv.Atoi(ag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to find group %s", ag)
|
||||||
|
}
|
||||||
|
// Ensure gid is inside gid range.
|
||||||
|
if gid < minId || gid > maxId {
|
||||||
|
return nil, ErrRange
|
||||||
|
}
|
||||||
|
gidMap[gid] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gids := []int{}
|
||||||
|
for gid := range gidMap {
|
||||||
|
gids = append(gids, gid)
|
||||||
|
}
|
||||||
|
return gids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups
|
||||||
|
// that opens the groupPath given and gives it as an argument to
|
||||||
|
// GetAdditionalGroups.
|
||||||
|
func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
|
||||||
|
group, err := os.Open(groupPath)
|
||||||
|
if err == nil {
|
||||||
|
defer group.Close()
|
||||||
|
}
|
||||||
|
return GetAdditionalGroups(additionalGroups, group)
|
||||||
|
}
|
||||||
500
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/user_test.go
generated
vendored
Normal file
500
Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/user_test.go
generated
vendored
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserParseLine(t *testing.T) {
|
||||||
|
var (
|
||||||
|
a, b string
|
||||||
|
c []string
|
||||||
|
d int
|
||||||
|
)
|
||||||
|
|
||||||
|
parseLine("", &a, &b)
|
||||||
|
if a != "" || b != "" {
|
||||||
|
t.Fatalf("a and b should be empty ('%v', '%v')", a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("a", &a, &b)
|
||||||
|
if a != "a" || b != "" {
|
||||||
|
t.Fatalf("a should be 'a' and b should be empty ('%v', '%v')", a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("bad boys:corny cows", &a, &b)
|
||||||
|
if a != "bad boys" || b != "corny cows" {
|
||||||
|
t.Fatalf("a should be 'bad boys' and b should be 'corny cows' ('%v', '%v')", a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("", &c)
|
||||||
|
if len(c) != 0 {
|
||||||
|
t.Fatalf("c should be empty (%#v)", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("d,e,f:g:h:i,j,k", &c, &a, &b, &c)
|
||||||
|
if a != "g" || b != "h" || len(c) != 3 || c[0] != "i" || c[1] != "j" || c[2] != "k" {
|
||||||
|
t.Fatalf("a should be 'g', b should be 'h', and c should be ['i','j','k'] ('%v', '%v', '%#v')", a, b, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("::::::::::", &a, &b, &c)
|
||||||
|
if a != "" || b != "" || len(c) != 0 {
|
||||||
|
t.Fatalf("a, b, and c should all be empty ('%v', '%v', '%#v')", a, b, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("not a number", &d)
|
||||||
|
if d != 0 {
|
||||||
|
t.Fatalf("d should be 0 (%v)", d)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("b:12:c", &a, &d, &b)
|
||||||
|
if a != "b" || b != "c" || d != 12 {
|
||||||
|
t.Fatalf("a should be 'b' and b should be 'c', and d should be 12 ('%v', '%v', %v)", a, b, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserParsePasswd(t *testing.T) {
|
||||||
|
users, err := ParsePasswdFilter(strings.NewReader(`
|
||||||
|
root:x:0:0:root:/root:/bin/bash
|
||||||
|
adm:x:3:4:adm:/var/adm:/bin/false
|
||||||
|
this is just some garbage data
|
||||||
|
`), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(users) != 3 {
|
||||||
|
t.Fatalf("Expected 3 users, got %v", len(users))
|
||||||
|
}
|
||||||
|
if users[0].Uid != 0 || users[0].Name != "root" {
|
||||||
|
t.Fatalf("Expected users[0] to be 0 - root, got %v - %v", users[0].Uid, users[0].Name)
|
||||||
|
}
|
||||||
|
if users[1].Uid != 3 || users[1].Name != "adm" {
|
||||||
|
t.Fatalf("Expected users[1] to be 3 - adm, got %v - %v", users[1].Uid, users[1].Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserParseGroup(t *testing.T) {
|
||||||
|
groups, err := ParseGroupFilter(strings.NewReader(`
|
||||||
|
root:x:0:root
|
||||||
|
adm:x:4:root,adm,daemon
|
||||||
|
this is just some garbage data
|
||||||
|
`), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(groups) != 3 {
|
||||||
|
t.Fatalf("Expected 3 groups, got %v", len(groups))
|
||||||
|
}
|
||||||
|
if groups[0].Gid != 0 || groups[0].Name != "root" || len(groups[0].List) != 1 {
|
||||||
|
t.Fatalf("Expected groups[0] to be 0 - root - 1 member, got %v - %v - %v", groups[0].Gid, groups[0].Name, len(groups[0].List))
|
||||||
|
}
|
||||||
|
if groups[1].Gid != 4 || groups[1].Name != "adm" || len(groups[1].List) != 3 {
|
||||||
|
t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidGetExecUser(t *testing.T) {
|
||||||
|
const passwdContent = `
|
||||||
|
root:x:0:0:root user:/root:/bin/bash
|
||||||
|
adm:x:42:43:adm:/var/adm:/bin/false
|
||||||
|
111:x:222:333::/var/garbage
|
||||||
|
odd:x:111:112::/home/odd:::::
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
const groupContent = `
|
||||||
|
root:x:0:root
|
||||||
|
adm:x:43:
|
||||||
|
grp:x:1234:root,adm
|
||||||
|
444:x:555:111
|
||||||
|
odd:x:444:
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
defaultExecUser := ExecUser{
|
||||||
|
Uid: 8888,
|
||||||
|
Gid: 8888,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/8888",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
ref string
|
||||||
|
expected ExecUser
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ref: "root",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 0,
|
||||||
|
Sgids: []int{0, 1234},
|
||||||
|
Home: "/root",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "adm",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 42,
|
||||||
|
Gid: 43,
|
||||||
|
Sgids: []int{1234},
|
||||||
|
Home: "/var/adm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "root:adm",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 43,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: "/root",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "adm:1234",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 42,
|
||||||
|
Gid: 1234,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: "/var/adm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "42:1234",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 42,
|
||||||
|
Gid: 1234,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: "/var/adm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "1337:1234",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 1337,
|
||||||
|
Gid: 1234,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: defaultExecUser.Home,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "1337",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 1337,
|
||||||
|
Gid: defaultExecUser.Gid,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: defaultExecUser.Home,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: defaultExecUser.Uid,
|
||||||
|
Gid: defaultExecUser.Gid,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: defaultExecUser.Home,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Regression tests for #695.
|
||||||
|
{
|
||||||
|
ref: "111",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 111,
|
||||||
|
Gid: 112,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: "/home/odd",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "111:444",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 111,
|
||||||
|
Gid: 444,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: "/home/odd",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
passwd := strings.NewReader(passwdContent)
|
||||||
|
group := strings.NewReader(groupContent)
|
||||||
|
|
||||||
|
execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error())
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(test.expected, *execUser) {
|
||||||
|
t.Logf("ref: %v", test.ref)
|
||||||
|
t.Logf("got: %#v", execUser)
|
||||||
|
t.Logf("expected: %#v", test.expected)
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidGetExecUser(t *testing.T) {
|
||||||
|
const passwdContent = `
|
||||||
|
root:x:0:0:root user:/root:/bin/bash
|
||||||
|
adm:x:42:43:adm:/var/adm:/bin/false
|
||||||
|
-42:x:12:13:broken:/very/broken
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
const groupContent = `
|
||||||
|
root:x:0:root
|
||||||
|
adm:x:43:
|
||||||
|
grp:x:1234:root,adm
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
|
||||||
|
tests := []string{
|
||||||
|
// No such user/group.
|
||||||
|
"notuser",
|
||||||
|
"notuser:notgroup",
|
||||||
|
"root:notgroup",
|
||||||
|
"notuser:adm",
|
||||||
|
"8888:notgroup",
|
||||||
|
"notuser:8888",
|
||||||
|
|
||||||
|
// Invalid user/group values.
|
||||||
|
"-1:0",
|
||||||
|
"0:-3",
|
||||||
|
"-5:-2",
|
||||||
|
"-42",
|
||||||
|
"-43",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
passwd := strings.NewReader(passwdContent)
|
||||||
|
group := strings.NewReader(groupContent)
|
||||||
|
|
||||||
|
execUser, err := GetExecUser(test, nil, passwd, group)
|
||||||
|
if err == nil {
|
||||||
|
t.Logf("got unexpected success when parsing '%s': %#v", test, execUser)
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetExecUserNilSources(t *testing.T) {
|
||||||
|
const passwdContent = `
|
||||||
|
root:x:0:0:root user:/root:/bin/bash
|
||||||
|
adm:x:42:43:adm:/var/adm:/bin/false
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
const groupContent = `
|
||||||
|
root:x:0:root
|
||||||
|
adm:x:43:
|
||||||
|
grp:x:1234:root,adm
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
|
||||||
|
defaultExecUser := ExecUser{
|
||||||
|
Uid: 8888,
|
||||||
|
Gid: 8888,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/8888",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
ref string
|
||||||
|
passwd, group bool
|
||||||
|
expected ExecUser
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ref: "",
|
||||||
|
passwd: false,
|
||||||
|
group: false,
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 8888,
|
||||||
|
Gid: 8888,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/8888",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "root",
|
||||||
|
passwd: true,
|
||||||
|
group: false,
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 0,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/root",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "0",
|
||||||
|
passwd: false,
|
||||||
|
group: false,
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 8888,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/8888",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "0:0",
|
||||||
|
passwd: false,
|
||||||
|
group: false,
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 0,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/8888",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var passwd, group io.Reader
|
||||||
|
|
||||||
|
if test.passwd {
|
||||||
|
passwd = strings.NewReader(passwdContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.group {
|
||||||
|
group = strings.NewReader(groupContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error())
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(test.expected, *execUser) {
|
||||||
|
t.Logf("got: %#v", execUser)
|
||||||
|
t.Logf("expected: %#v", test.expected)
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAdditionalGroups(t *testing.T) {
|
||||||
|
const groupContent = `
|
||||||
|
root:x:0:root
|
||||||
|
adm:x:43:
|
||||||
|
grp:x:1234:root,adm
|
||||||
|
adm:x:4343:root,adm-duplicate
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
tests := []struct {
|
||||||
|
groups []string
|
||||||
|
expected []int
|
||||||
|
hasError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// empty group
|
||||||
|
groups: []string{},
|
||||||
|
expected: []int{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// single group
|
||||||
|
groups: []string{"adm"},
|
||||||
|
expected: []int{43},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// multiple groups
|
||||||
|
groups: []string{"adm", "grp"},
|
||||||
|
expected: []int{43, 1234},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// invalid group
|
||||||
|
groups: []string{"adm", "grp", "not-exist"},
|
||||||
|
expected: nil,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// group with numeric id
|
||||||
|
groups: []string{"43"},
|
||||||
|
expected: []int{43},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// group with unknown numeric id
|
||||||
|
groups: []string{"adm", "10001"},
|
||||||
|
expected: []int{43, 10001},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// groups specified twice with numeric and name
|
||||||
|
groups: []string{"adm", "43"},
|
||||||
|
expected: []int{43},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// groups with too small id
|
||||||
|
groups: []string{"-1"},
|
||||||
|
expected: nil,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// groups with too large id
|
||||||
|
groups: []string{strconv.Itoa(1 << 31)},
|
||||||
|
expected: nil,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
group := strings.NewReader(groupContent)
|
||||||
|
|
||||||
|
gids, err := GetAdditionalGroups(test.groups, group)
|
||||||
|
if test.hasError && err == nil {
|
||||||
|
t.Errorf("Parse(%#v) expects error but has none", test)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !test.hasError && err != nil {
|
||||||
|
t.Errorf("Parse(%#v) has error %v", test, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sort.Sort(sort.IntSlice(gids))
|
||||||
|
if !reflect.DeepEqual(gids, test.expected) {
|
||||||
|
t.Errorf("Gids(%v), expect %v from groups %v", gids, test.expected, test.groups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAdditionalGroupsNumeric(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
groups []string
|
||||||
|
expected []int
|
||||||
|
hasError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// numeric groups only
|
||||||
|
groups: []string{"1234", "5678"},
|
||||||
|
expected: []int{1234, 5678},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// numeric and alphabetic
|
||||||
|
groups: []string{"1234", "fake"},
|
||||||
|
expected: nil,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
gids, err := GetAdditionalGroups(test.groups, nil)
|
||||||
|
if test.hasError && err == nil {
|
||||||
|
t.Errorf("Parse(%#v) expects error but has none", test)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !test.hasError && err != nil {
|
||||||
|
t.Errorf("Parse(%#v) has error %v", test, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sort.Sort(sort.IntSlice(gids))
|
||||||
|
if !reflect.DeepEqual(gids, test.expected) {
|
||||||
|
t.Errorf("Gids(%v), expect %v from groups %v", gids, test.expected, test.groups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,9 +13,9 @@ die()
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
aclocal --version < /dev/null > /dev/null 2>&1 || die "You must have aclocal installed to generate the hyper."
|
aclocal --version < /dev/null > /dev/null 2>&1 || die "You must have aclocal installed to generate the gotgt."
|
||||||
autoconf --version < /dev/null > /dev/null 2>&1 || die "You must have autoconf installed to generate the hyper."
|
autoconf --version < /dev/null > /dev/null 2>&1 || die "You must have autoconf installed to generate the gotgt."
|
||||||
automake --version < /dev/null > /dev/null 2>&1 || die "You must have automake installed to generate the hyper."
|
automake --version < /dev/null > /dev/null 2>&1 || die "You must have automake installed to generate the gotgt."
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Generating build-system with:"
|
echo "Generating build-system with:"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Process this file with autoconf to produce a configure script.
|
# Process this file with autoconf to produce a configure script.
|
||||||
|
|
||||||
AC_PREREQ([2.69])
|
AC_PREREQ([2.69])
|
||||||
AC_INIT([runv], [0.4.0], [hyper.sh])
|
AC_INIT([gotgt], [0.1.0], [github.com/gostor])
|
||||||
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
|
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
|
||||||
|
|
||||||
# Checks for programs.
|
# Checks for programs.
|
||||||
|
|||||||
Reference in New Issue
Block a user