diff --git a/Godeps/_workspace/src/github.com/docker/go-units/CONTRIBUTING.md b/Godeps/_workspace/src/github.com/docker/go-units/CONTRIBUTING.md new file mode 100644 index 0000000..9ea86d7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/go-units/CONTRIBUTING.md @@ -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 + +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`. diff --git a/Godeps/_workspace/src/github.com/docker/go-units/LICENSE b/Godeps/_workspace/src/github.com/docker/go-units/LICENSE new file mode 100644 index 0000000..b55b37b --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/go-units/LICENSE @@ -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. diff --git a/Godeps/_workspace/src/github.com/docker/go-units/MAINTAINERS b/Godeps/_workspace/src/github.com/docker/go-units/MAINTAINERS new file mode 100644 index 0000000..477be8b --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/go-units/MAINTAINERS @@ -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" diff --git a/Godeps/_workspace/src/github.com/docker/go-units/README.md b/Godeps/_workspace/src/github.com/docker/go-units/README.md new file mode 100644 index 0000000..4f70a4e --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/go-units/README.md @@ -0,0 +1,16 @@ +[![GoDoc](https://godoc.org/github.com/docker/go-units?status.svg)](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. diff --git a/Godeps/_workspace/src/github.com/docker/go-units/circle.yml b/Godeps/_workspace/src/github.com/docker/go-units/circle.yml new file mode 100644 index 0000000..9043b35 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/go-units/circle.yml @@ -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)" diff --git a/Godeps/_workspace/src/github.com/docker/go-units/duration.go b/Godeps/_workspace/src/github.com/docker/go-units/duration.go new file mode 100644 index 0000000..c219a8a --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/go-units/duration.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/docker/go-units/size.go b/Godeps/_workspace/src/github.com/docker/go-units/size.go new file mode 100644 index 0000000..989edd2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/go-units/size.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/docker/go-units/ulimit.go b/Godeps/_workspace/src/github.com/docker/go-units/ulimit.go new file mode 100644 index 0000000..5ac7fd8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/go-units/ulimit.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/MAINTAINERS b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/MAINTAINERS new file mode 100644 index 0000000..edbe200 --- /dev/null +++ b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/MAINTAINERS @@ -0,0 +1,2 @@ +Tianon Gravi (@tianon) +Aleksa Sarai (@cyphar) diff --git a/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup.go b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup.go new file mode 100644 index 0000000..ab1439f --- /dev/null +++ b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup.go @@ -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 + }) +} diff --git a/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go new file mode 100644 index 0000000..758b734 --- /dev/null +++ b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go new file mode 100644 index 0000000..7217948 --- /dev/null +++ b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/user.go b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/user.go new file mode 100644 index 0000000..43fd39e --- /dev/null +++ b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/user.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/user_test.go b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/user_test.go new file mode 100644 index 0000000..8cad2f5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/opencontainers/runc/libcontainer/user/user_test.go @@ -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) + } + } +} diff --git a/autogen.sh b/autogen.sh index b2518e1..9b604c8 100755 --- a/autogen.sh +++ b/autogen.sh @@ -13,9 +13,9 @@ die() exit 1 } -aclocal --version < /dev/null > /dev/null 2>&1 || die "You must have aclocal installed to generate the hyper." -autoconf --version < /dev/null > /dev/null 2>&1 || die "You must have autoconf installed to generate the hyper." -automake --version < /dev/null > /dev/null 2>&1 || die "You must have automake 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 gotgt." +automake --version < /dev/null > /dev/null 2>&1 || die "You must have automake installed to generate the gotgt." echo echo "Generating build-system with:" diff --git a/configure.ac b/configure.ac index 6edd1e1..5e2da20 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. 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]) # Checks for programs. diff --git a/pkg/port/iscsit/cmd.go b/pkg/port/iscsit/cmd.go index b690bd1..8bd293a 100644 --- a/pkg/port/iscsit/cmd.go +++ b/pkg/port/iscsit/cmd.go @@ -90,7 +90,11 @@ type ISCSICommand struct { Status byte SCSIResponse byte - // Data-In + // R2T + R2TSN uint32 + DesiredLength uint32 + + // Data-In/Out HasStatus bool DataSN uint32 BufferOffset uint32 @@ -110,6 +114,10 @@ func (cmd *ISCSICommand) Bytes() []byte { return cmd.textRespBytes() case OpNoopIn: return cmd.noopInBytes() + case OpSCSITaskResp: + return cmd.scsiTMFRespBytes() + case OpReady: + return cmd.r2tRespBytes() } return nil } @@ -137,7 +145,7 @@ func (m *ISCSICommand) String() string { s = append(s, fmt.Sprintf("Next Stage = %v", m.NSG)) s = append(s, fmt.Sprintf("Status Class = %d", m.StatusClass)) s = append(s, fmt.Sprintf("Status Detail = %d", m.StatusDetail)) - case OpSCSICmd: + case OpSCSICmd, OpSCSIOut: s = append(s, fmt.Sprintf("LUN = %d", m.LUN)) s = append(s, fmt.Sprintf("ExpectedDataLen = %d", m.ExpectedDataLen)) s = append(s, fmt.Sprintf("CmdSN = %d", m.CmdSN)) @@ -176,7 +184,7 @@ func parseHeader(data []byte) (*ISCSICommand, error) { m.DataLen = int(ParseUint(data[5:8])) m.TaskTag = uint32(ParseUint(data[16:20])) switch m.OpCode { - case OpSCSICmd: + case OpSCSICmd, OpSCSITaskReq: m.LUN = [8]uint8{data[9]} m.ExpectedDataLen = uint32(ParseUint(data[20:24])) m.CmdSN = uint32(ParseUint(data[24:28])) @@ -185,6 +193,11 @@ func parseHeader(data []byte) (*ISCSICommand, error) { m.CDB = data[32:48] m.ExpStatSN = uint32(ParseUint(data[28:32])) case OpSCSIResp: + case OpSCSIOut: + m.LUN = [8]uint8{data[9]} + m.ExpStatSN = uint32(ParseUint(data[28:32])) + m.DataSN = uint32(ParseUint(data[36:40])) + m.BufferOffset = uint32(ParseUint(data[40:44])) case OpLoginReq, OpTextReq, OpNoopOut, OpLogoutReq: m.Transit = m.Final m.Cont = data[1]&0x40 == 0x40 @@ -364,3 +377,59 @@ func (m *ISCSICommand) noopInBytes() []byte { buf.Write(rd) return buf.Bytes() } + +func (m *ISCSICommand) scsiTMFRespBytes() []byte { + // rfc7143 11.6 + buf := &bytes.Buffer{} + buf.WriteByte(byte(OpSCSITaskResp)) + buf.WriteByte(0x80) + buf.WriteByte(0x00) + buf.WriteByte(0x00) + + // Skip through to byte 16 + for i := 0; i < 3*4; i++ { + buf.WriteByte(0x00) + } + buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:]) + for i := 0; i < 4; i++ { + buf.WriteByte(0x00) + } + buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:]) + for i := 0; i < 3*4; i++ { + buf.WriteByte(0x00) + } + + return buf.Bytes() +} + +func (m *ISCSICommand) r2tRespBytes() []byte { + // rfc7143 11.8 + buf := &bytes.Buffer{} + buf.WriteByte(byte(OpReady)) + var b byte + if m.Final { + b |= 0x80 + } + buf.WriteByte(b) + buf.WriteByte(0x00) + buf.WriteByte(0x00) + + // Skip through to byte 16 + for i := 0; i < 3*4; i++ { + buf.WriteByte(0x00) + } + buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:]) + for i := 0; i < 4; i++ { + buf.WriteByte(0x00) + } + buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.R2TSN))[4:]) + buf.Write(util.MarshalUint64(uint64(m.BufferOffset))[4:]) + buf.Write(util.MarshalUint64(uint64(m.DesiredLength))[4:]) + + return buf.Bytes() +} diff --git a/pkg/port/iscsit/conn.go b/pkg/port/iscsit/conn.go index c02aa32..fd8ddc9 100644 --- a/pkg/port/iscsit/conn.go +++ b/pkg/port/iscsit/conn.go @@ -18,6 +18,7 @@ package iscsit import ( "net" + "sync" "github.com/gostor/gotgt/pkg/api" ) @@ -81,13 +82,15 @@ type iscsiConnection struct { txTask *iscsiTask authMethod AuthMethod + + readLock *sync.RWMutex } type taskState int const ( - taskPending taskState = 1 - taskSCSI taskState = 2 + taskPending taskState = 0 + taskSCSI taskState = 1 ) type iscsiTask struct { @@ -96,11 +99,19 @@ type iscsiTask struct { cmd *ISCSICommand scmd *api.SCSICommand state taskState + + offset int + r2tCount int + unsolCount int + expR2TSN int + + r2tSN uint32 } func (c *iscsiConnection) init() { c.state = CONN_STATE_FREE c.refcount = 1 + c.readLock = new(sync.RWMutex) c.sessionParam = []ISCSISessionParam{} for _, param := range sessionKeys { c.sessionParam = append(c.sessionParam, ISCSISessionParam{Value: param.def}) diff --git a/pkg/port/iscsit/iscsid.go b/pkg/port/iscsit/iscsid.go index 4917738..e715c93 100644 --- a/pkg/port/iscsit/iscsid.go +++ b/pkg/port/iscsit/iscsid.go @@ -21,6 +21,7 @@ import ( "fmt" "net" "os" + "runtime/debug" "github.com/golang/glog" "github.com/gostor/gotgt/pkg/api" @@ -90,15 +91,15 @@ func (s *ISCSITargetService) Run() error { func (s *ISCSITargetService) handler(events byte, conn *iscsiConnection) { if events&DATAIN != 0 { - glog.Infof("rx handler processing...") + glog.V(1).Infof("rx handler processing...") go s.rxHandler(conn) } if conn.state != CONN_STATE_CLOSE && events&DATAOUT != 0 { - glog.Infof("tx handler processing...") + glog.V(1).Infof("tx handler processing...") s.txHandler(conn) } if conn.state == CONN_STATE_CLOSE { - glog.Infof("iscsi connection[%d] closed", conn.cid) + glog.Warningf("iscsi connection[%d] closed", conn.cid) conn.close() } } @@ -110,6 +111,8 @@ func (s *ISCSITargetService) rxHandler(conn *iscsiConnection) { final bool = false cmd *ISCSICommand ) + conn.readLock.Lock() + defer conn.readLock.Unlock() if conn.state == CONN_STATE_SCSI { hdigest = conn.sessionParam[ISCSI_PARAM_HDRDGST_EN].Value & DIGEST_CRC32C ddigest = conn.sessionParam[ISCSI_PARAM_DATADGST_EN].Value & DIGEST_CRC32C @@ -124,6 +127,7 @@ func (s *ISCSITargetService) rxHandler(conn *iscsiConnection) { return } if length == 0 { + glog.Warningf("set connection to close") conn.state = CONN_STATE_CLOSE return } @@ -131,6 +135,7 @@ func (s *ISCSITargetService) rxHandler(conn *iscsiConnection) { cmd, err = parseHeader(buf) if err != nil { glog.Error(err) + glog.Warningf("set connection to close") conn.state = CONN_STATE_CLOSE return } @@ -139,8 +144,8 @@ func (s *ISCSITargetService) rxHandler(conn *iscsiConnection) { conn.rxIOState = IOSTATE_RX_INIT_AHS break } - glog.Infof("got command: \n%s", cmd.String()) - glog.Infof("got buffer: %v", buf) + glog.V(2).Infof("got command: \n%s", cmd.String()) + glog.V(2).Infof("got buffer: %v", buf) final = true case IOSTATE_RX_INIT_AHS: conn.rxIOState = IOSTATE_RX_DATA @@ -156,16 +161,24 @@ func (s *ISCSITargetService) rxHandler(conn *iscsiConnection) { return } dl := ((cmd.DataLen + DataPadding - 1) / DataPadding) * DataPadding - buf, length, err := conn.readData(dl) - if err != nil { - glog.Error(err) - return + buf := []byte{} + length := 0 + for length < dl { + b, l, err := conn.readData(dl - length) + if err != nil { + glog.Error(err) + return + } + length += l + buf = append(buf, b...) } if length != dl { + glog.V(2).Infof("get length is %d, but expected %d", length, dl) + glog.Warningf("set connection to close") conn.state = CONN_STATE_CLOSE return } - cmd.RawData = buf[:cmd.DataLen] + cmd.RawData = buf[:length] conn.rxBuffer = append(conn.rxBuffer, buf...) final = true glog.Infof("got command: \n%s", cmd.String()) @@ -189,22 +202,26 @@ func (s *ISCSITargetService) rxHandler(conn *iscsiConnection) { glog.Infof("OpLoginReq") if err := s.iscsiExecLogin(conn); err != nil { glog.Error(err) + glog.Warningf("set connection to close") conn.state = CONN_STATE_CLOSE } case OpLogoutReq: glog.Infof("OpLogoutReq") if err := iscsiExecLogout(conn); err != nil { + glog.Warningf("set connection to close") conn.state = CONN_STATE_CLOSE } case OpTextReq: glog.Infof("OpTextReq") if err := s.iscsiExecText(conn); err != nil { + glog.Warningf("set connection to close") conn.state = CONN_STATE_CLOSE } default: iscsiExecReject(conn) } - glog.Infof("%#v", conn.resp.String()) + glog.V(2).Infof("connection state is %v", conn.state) + glog.V(2).Infof("%#v", conn.resp.String()) s.handler(DATAOUT, conn) } } @@ -315,7 +332,8 @@ func (s *ISCSITargetService) iscsiExecText(conn *iscsiConnection) error { } for _, t := range list { result = append(result, util.KeyValue{"TargetName", t.Name}) - result = append(result, util.KeyValue{"TargetAddress", "127.0.0.1:3260,1"}) + result = append(result, util.KeyValue{"TargetAddress", "172.16.69.169:3260,1"}) + //result = append(result, util.KeyValue{"TargetAddress", "127.0.0.1:3260,1"}) } } } @@ -347,6 +365,20 @@ func iscsiExecNoopOut(conn *iscsiConnection) error { return nil } +func iscsiExecTMFunction(conn *iscsiConnection) error { + cmd := conn.req + conn.resp = &ISCSICommand{ + OpCode: OpSCSITaskResp, + Final: true, + NSG: FullFeaturePhase, + StatSN: cmd.ExpStatSN, + TaskTag: cmd.TaskTag, + ExpCmdSN: cmd.CmdSN + 1, + MaxCmdSN: cmd.CmdSN + 10, + } + return nil +} + func iscsiExecReject(conn *iscsiConnection) error { conn.resp = &ISCSICommand{ OpCode: OpReject, @@ -379,7 +411,9 @@ func (s *ISCSITargetService) txHandler(conn *iscsiConnection) { glog.V(2).Infof("length of RawData is %d", len(conn.resp.RawData)) glog.V(2).Infof("length of resp is %d", len(conn.resp.Bytes())) if l, err := conn.write(conn.resp.Bytes()); err != nil { + debug.PrintStack() glog.Error(err) + panic(err) return } else { conn.txIOState = IOSTATE_TX_INIT_AHS @@ -411,27 +445,30 @@ func (s *ISCSITargetService) txHandler(conn *iscsiConnection) { } } + glog.V(3).Infof("connection state: %d", conn.state) switch conn.state { case CONN_STATE_CLOSE, CONN_STATE_EXIT: + glog.Warningf("set connection to close") conn.state = CONN_STATE_CLOSE case CONN_STATE_SECURITY_LOGIN: conn.state = CONN_STATE_LOGIN - glog.Infof("CONN_STATE_LOGIN") + glog.V(3).Infof("CONN_STATE_LOGIN") case CONN_STATE_SECURITY_FULL, CONN_STATE_LOGIN_FULL: if conn.sessionType == SESSION_NORMAL { conn.state = CONN_STATE_KERNEL glog.Infof("CONN_STATE_KERNEL") conn.state = CONN_STATE_SCSI - glog.Infof("CONN_STATE_SCSI") + glog.V(3).Infof("CONN_STATE_SCSI") } else { conn.state = CONN_STATE_FULL - glog.Infof("CONN_STATE_FULL") + glog.V(3).Infof("CONN_STATE_FULL") } conn.rxIOState = IOSTATE_RX_BHS s.handler(DATAIN, conn) case CONN_STATE_SCSI: conn.txTask = nil default: + glog.Warningf("unexpected connection state: %d", conn.state) conn.rxIOState = IOSTATE_RX_BHS s.handler(DATAIN, conn) } @@ -442,9 +479,49 @@ func (s *ISCSITargetService) scsiCommandHandler(conn *iscsiConnection) (err erro req := conn.req switch req.OpCode { case OpSCSICmd: - glog.Infof("SCSI Command processing...") + glog.V(2).Infof("SCSI Command processing...") scmd := &api.SCSICommand{} task := &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: scmd} + if req.Write { + task.offset = req.DataLen + task.r2tCount = int(req.ExpectedDataLen) - req.DataLen + if !req.Final { + task.unsolCount = 1 + } + glog.V(2).Infof("SCSI write, R2T count: %d, unsol Count: %d, offset: %d", task.r2tCount, task.unsolCount, task.offset) + + if task.scmd.OutSDBBuffer.Buffer == nil { + task.scmd.OutSDBBuffer.Buffer = bytes.NewBuffer([]byte{}) + } + task.scmd.OutSDBBuffer.Buffer.Write(conn.req.RawData) + if task.r2tCount > 0 { + // prepare to receive more data + task.state = taskPending + conn.session.PendingTasks.Push(task) + //conn.rxTask = nil + conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: &api.SCSICommand{}} + conn.txIOState = IOSTATE_TX_BHS + conn.statSN += 1 + resp := &ISCSICommand{ + OpCode: OpReady, + Immediate: true, + Final: true, + StatSN: req.ExpStatSN, + TaskTag: req.TaskTag, + ExpCmdSN: conn.session.ExpCmdSN, + MaxCmdSN: conn.session.ExpCmdSN + 10, + R2TSN: task.r2tSN, + BufferOffset: uint32(task.offset), + DesiredLength: uint32(task.r2tCount), + } + if val := sessionKeys[ISCSI_PARAM_MAX_BURST].def; task.r2tCount > int(val) { + resp.DesiredLength = uint32(val) + } + conn.resp = resp + break + } + } + task.offset = 0 conn.rxTask = task if err = s.iscsiTaskQueueHandler(task); err != nil { return @@ -487,7 +564,83 @@ func (s *ISCSITargetService) scsiCommandHandler(conn *iscsiConnection) (err erro } conn.resp = resp } + case OpSCSITaskReq: + // task management function + conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, state: taskPending} + conn.txIOState = IOSTATE_TX_BHS + iscsiExecTMFunction(conn) case OpSCSIOut: + glog.V(1).Infof("iSCSI Data-out processing...") + var task *iscsiTask + for _, t := range conn.session.PendingTasks { + if t.tag == conn.req.TaskTag { + task = t + } + } + if task == nil { + err = fmt.Errorf("Cannot find iSCSI task with tag[%v]", conn.req.TaskTag) + glog.Error(err) + return + } + task.offset = task.offset + conn.req.DataLen + task.r2tCount = task.r2tCount - conn.req.DataLen + task.scmd.OutSDBBuffer.Buffer.Write(conn.req.RawData) + glog.V(2).Infof("Final: %v", conn.req.Final) + glog.V(2).Infof("r2tCount: %v", task.r2tCount) + if !conn.req.Final { + glog.V(1).Infof("Not ready to exec the task") + conn.rxIOState = IOSTATE_RX_BHS + s.handler(DATAIN, conn) + return nil + } else if task.r2tCount > 0 { + // prepare to receive more data + conn.rxTask = nil + conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: &api.SCSICommand{}} + conn.txIOState = IOSTATE_TX_BHS + conn.statSN += 1 + task.r2tSN += 1 + resp := &ISCSICommand{ + OpCode: OpReady, + Immediate: true, + Final: true, + StatSN: req.ExpStatSN, + TaskTag: req.TaskTag, + ExpCmdSN: conn.session.ExpCmdSN, + MaxCmdSN: conn.session.ExpCmdSN + 10, + R2TSN: task.r2tSN, + BufferOffset: uint32(task.offset), + DesiredLength: uint32(task.r2tCount), + } + if val := sessionKeys[ISCSI_PARAM_MAX_BURST].def; task.r2tCount > int(val) { + resp.DesiredLength = uint32(val) + } + conn.resp = resp + break + } + task.offset = 0 + glog.V(1).Infof("Process the Data-out package") + conn.rxTask = task + if err = s.iscsiExecTask(task); err != nil { + return + } else { + conn.rxTask = nil + conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: &api.SCSICommand{}, state: taskSCSI} + conn.txIOState = IOSTATE_TX_BHS + conn.statSN += 1 + resp := &ISCSICommand{ + OpCode: OpSCSIResp, + Immediate: true, + Final: true, + StatSN: req.ExpStatSN, + TaskTag: req.TaskTag, + ExpCmdSN: conn.session.ExpCmdSN, + MaxCmdSN: conn.session.ExpCmdSN + 10, + Status: 0, + SCSIResponse: 0x00, + HasStatus: true, + } + conn.resp = resp + } case OpNoopOut: conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag} conn.txIOState = IOSTATE_TX_BHS @@ -523,9 +676,9 @@ func (s *ISCSITargetService) iscsiTaskQueueHandler(task *iscsiTask) error { retry: cmdsn += 1 sess.ExpCmdSN = cmdsn - glog.Infof("session's ExpCmdSN is %d", cmdsn) + glog.V(2).Infof("session's ExpCmdSN is %d", cmdsn) - glog.Infof("process task(%d)", task.cmd.CmdSN) + glog.V(2).Infof("process task(%d)", task.cmd.CmdSN) if err := s.iscsiExecTask(task); err != nil { glog.Error(err) } @@ -546,7 +699,7 @@ func (s *ISCSITargetService) iscsiTaskQueueHandler(task *iscsiTask) error { glog.Error(err) return err } - glog.Infof("add task(%d) into task queue", task.cmd.CmdSN) + glog.V(1).Infof("add task(%d) into task queue", task.cmd.CmdSN) // add this connection into queue and set this task as pending task task.state = taskPending sess.PendingTasks.Push(task) @@ -558,7 +711,7 @@ func (s *ISCSITargetService) iscsiTaskQueueHandler(task *iscsiTask) error { func (s *ISCSITargetService) iscsiExecTask(task *iscsiTask) error { cmd := task.cmd switch cmd.OpCode { - case OpSCSICmd: + case OpSCSICmd, OpSCSIOut: if cmd.Read { if cmd.Write { task.scmd.Direction = api.SCSIDataBidirection @@ -576,7 +729,9 @@ func (s *ISCSITargetService) iscsiExecTask(task *iscsiTask) error { task.scmd.Lun = cmd.LUN task.scmd.Tag = uint64(cmd.TaskTag) task.state = taskSCSI - task.scmd.OutSDBBuffer.Buffer = bytes.NewBuffer(cmd.RawData) + if task.scmd.OutSDBBuffer.Buffer == nil { + task.scmd.OutSDBBuffer.Buffer = bytes.NewBuffer(cmd.RawData) + } // add scsi target process queue err := s.SCSI.AddCommandQueue(task.conn.session.Target.SCSITarget.TID, task.scmd) if err != nil { diff --git a/pkg/scsi/backingstore/common.go b/pkg/scsi/backingstore/common.go index 34efc2f..dcd4ff4 100644 --- a/pkg/scsi/backingstore/common.go +++ b/pkg/scsi/backingstore/common.go @@ -19,6 +19,7 @@ package backingstore import ( "bytes" "fmt" + "io" "os" "github.com/golang/glog" @@ -74,7 +75,7 @@ func (bs *FileBackingStore) CommandSubmit(cmd *api.SCSICommand) (err error) { key = scsi.ILLEGAL_REQUEST asc = scsi.ASC_INVALID_FIELD_IN_CDB wbuf []byte = []byte{} - rbuf []byte = []byte{} + rbuf = make([]byte, cmd.TL) length int doVerify bool = false doWrite bool = false @@ -98,6 +99,9 @@ func (bs *FileBackingStore) CommandSubmit(cmd *api.SCSICommand) (err error) { doWrite = true goto write case api.SYNCHRONIZE_CACHE, api.SYNCHRONIZE_CACHE_16: + if err = util.Fdatasync(lu.File); err != nil { + panic(err) + } break case api.WRITE_VERIFY, api.WRITE_VERIFY_12, api.WRITE_VERIFY_16: doVerify = true @@ -110,7 +114,7 @@ func (bs *FileBackingStore) CommandSubmit(cmd *api.SCSICommand) (err error) { break case api.READ_6, api.READ_10, api.READ_12, api.READ_16: length, err = lu.File.ReadAt(rbuf, int64(offset)) - if err != nil { + if err != nil && err != io.EOF { key = scsi.MEDIUM_ERROR asc = scsi.ASC_READ_ERROR break diff --git a/pkg/scsi/lun.go b/pkg/scsi/lun.go index 1a2849e..53396aa 100644 --- a/pkg/scsi/lun.go +++ b/pkg/scsi/lun.go @@ -34,14 +34,14 @@ func NewSCSILu(lun uint64, target *api.SCSITarget) (*api.SCSILu, error) { PerformCommand: luPerformCommand, DeviceProtocol: sbc, Storage: backing, - BlockShift: 0, - Size: 1024 * 1024 * 1024, + BlockShift: api.DefaultBlockShift, + Size: 1024 * 1024 * 10, } // hack this - if _, err = os.Stat("/tmp/data.txt"); err != nil && os.IsExist(err) { - os.Create("/tmp/data.txt") + if _, err = os.Stat("/var/tmp/disk.img"); err != nil && os.IsExist(err) { + panic(err) } - f, err := backing.Open(lu, "/tmp/data.txt") + f, err := backing.Open(lu, "/var/tmp/disk.img") if err != nil { return nil, err }