diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..230c3e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Autoconf/Automake generated files +aclocal.m4 +.deps +autom4te.cache +Makefile +Makefile.in +ar-lib +compile +config.log +config.status +configure +depcomp +install-sh +missing +config.guess +config.sub + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +# intellij idea configure +.idea +/citadm +/citd +*.out +examples/* +*.bak diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5867b62 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +sudo: required +dist: trusty + +language: go +go: + - 1.4 + - 1.5 + +install: + - true + +before_script: + - go get golang.org/x/tools/cmd/vet + - go get github.com/kr/godep + +script: + - cd ${TRAVIS_BUILD_DIR} + - ./autogen.sh + - ./configure + - make + - hack/verify-gofmt.sh diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json new file mode 100644 index 0000000..b9e9b88 --- /dev/null +++ b/Godeps/Godeps.json @@ -0,0 +1,10 @@ +{ + "ImportPath": "github.com/gostor/gotgt", + "GoVersion": "go1.4.1", + "Deps": [ + { + "ImportPath": "github.com/golang/glog", + "Rev": "fca8c8854093a154ff1eb580aae10276ad6b1b5f" + } + ] +} diff --git a/Godeps/Readme b/Godeps/Readme new file mode 100644 index 0000000..4cdaa53 --- /dev/null +++ b/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/Godeps/_workspace/.gitignore b/Godeps/_workspace/.gitignore new file mode 100644 index 0000000..f037d68 --- /dev/null +++ b/Godeps/_workspace/.gitignore @@ -0,0 +1,2 @@ +/pkg +/bin diff --git a/Godeps/_workspace/src/github.com/golang/glog/LICENSE b/Godeps/_workspace/src/github.com/golang/glog/LICENSE new file mode 100644 index 0000000..37ec93a --- /dev/null +++ b/Godeps/_workspace/src/github.com/golang/glog/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://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: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +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 +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 + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Godeps/_workspace/src/github.com/golang/glog/README b/Godeps/_workspace/src/github.com/golang/glog/README new file mode 100644 index 0000000..5f9c114 --- /dev/null +++ b/Godeps/_workspace/src/github.com/golang/glog/README @@ -0,0 +1,44 @@ +glog +==== + +Leveled execution logs for Go. + +This is an efficient pure Go implementation of leveled logs in the +manner of the open source C++ package + http://code.google.com/p/google-glog + +By binding methods to booleans it is possible to use the log package +without paying the expense of evaluating the arguments to the log. +Through the -vmodule flag, the package also provides fine-grained +control over logging at the file level. + +The comment from glog.go introduces the ideas: + + Package glog implements logging analogous to the Google-internal + C++ INFO/ERROR/V setup. It provides functions Info, Warning, + Error, Fatal, plus formatting variants such as Infof. It + also provides V-style logging controlled by the -v and + -vmodule=file=2 flags. + + Basic examples: + + glog.Info("Prepare to repel boarders") + + glog.Fatalf("Initialization failed: %s", err) + + See the documentation for the V function for an explanation + of these examples: + + if glog.V(2) { + glog.Info("Starting transaction...") + } + + glog.V(2).Infoln("Processed", nItems, "elements") + + +The repository contains an open source version of the log package +used inside Google. The master copy of the source lives inside +Google, not here. The code in this repo is for export only and is not itself +under development. Feature requests will be ignored. + +Send bug reports to golang-nuts@googlegroups.com. diff --git a/Godeps/_workspace/src/github.com/golang/glog/glog.go b/Godeps/_workspace/src/github.com/golang/glog/glog.go new file mode 100644 index 0000000..54bd7af --- /dev/null +++ b/Godeps/_workspace/src/github.com/golang/glog/glog.go @@ -0,0 +1,1180 @@ +// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ +// +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package glog implements logging analogous to the Google-internal C++ INFO/ERROR/V setup. +// It provides functions Info, Warning, Error, Fatal, plus formatting variants such as +// Infof. It also provides V-style logging controlled by the -v and -vmodule=file=2 flags. +// +// Basic examples: +// +// glog.Info("Prepare to repel boarders") +// +// glog.Fatalf("Initialization failed: %s", err) +// +// See the documentation for the V function for an explanation of these examples: +// +// if glog.V(2) { +// glog.Info("Starting transaction...") +// } +// +// glog.V(2).Infoln("Processed", nItems, "elements") +// +// Log output is buffered and written periodically using Flush. Programs +// should call Flush before exiting to guarantee all log output is written. +// +// By default, all log statements write to files in a temporary directory. +// This package provides several flags that modify this behavior. +// As a result, flag.Parse must be called before any logging is done. +// +// -logtostderr=false +// Logs are written to standard error instead of to files. +// -alsologtostderr=false +// Logs are written to standard error as well as to files. +// -stderrthreshold=ERROR +// Log events at or above this severity are logged to standard +// error as well as to files. +// -log_dir="" +// Log files will be written to this directory instead of the +// default temporary directory. +// +// Other flags provide aids to debugging. +// +// -log_backtrace_at="" +// When set to a file and line number holding a logging statement, +// such as +// -log_backtrace_at=gopherflakes.go:234 +// a stack trace will be written to the Info log whenever execution +// hits that statement. (Unlike with -vmodule, the ".go" must be +// present.) +// -v=0 +// Enable V-leveled logging at the specified level. +// -vmodule="" +// The syntax of the argument is a comma-separated list of pattern=N, +// where pattern is a literal file name (minus the ".go" suffix) or +// "glob" pattern and N is a V level. For instance, +// -vmodule=gopher*=3 +// sets the V level to 3 in all Go files whose names begin "gopher". +// +package glog + +import ( + "bufio" + "bytes" + "errors" + "flag" + "fmt" + "io" + stdLog "log" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +// severity identifies the sort of log: info, warning etc. It also implements +// the flag.Value interface. The -stderrthreshold flag is of type severity and +// should be modified only through the flag.Value interface. The values match +// the corresponding constants in C++. +type severity int32 // sync/atomic int32 + +// These constants identify the log levels in order of increasing severity. +// A message written to a high-severity log file is also written to each +// lower-severity log file. +const ( + infoLog severity = iota + warningLog + errorLog + fatalLog + numSeverity = 4 +) + +const severityChar = "IWEF" + +var severityName = []string{ + infoLog: "INFO", + warningLog: "WARNING", + errorLog: "ERROR", + fatalLog: "FATAL", +} + +// get returns the value of the severity. +func (s *severity) get() severity { + return severity(atomic.LoadInt32((*int32)(s))) +} + +// set sets the value of the severity. +func (s *severity) set(val severity) { + atomic.StoreInt32((*int32)(s), int32(val)) +} + +// String is part of the flag.Value interface. +func (s *severity) String() string { + return strconv.FormatInt(int64(*s), 10) +} + +// Get is part of the flag.Value interface. +func (s *severity) Get() interface{} { + return *s +} + +// Set is part of the flag.Value interface. +func (s *severity) Set(value string) error { + var threshold severity + // Is it a known name? + if v, ok := severityByName(value); ok { + threshold = v + } else { + v, err := strconv.Atoi(value) + if err != nil { + return err + } + threshold = severity(v) + } + logging.stderrThreshold.set(threshold) + return nil +} + +func severityByName(s string) (severity, bool) { + s = strings.ToUpper(s) + for i, name := range severityName { + if name == s { + return severity(i), true + } + } + return 0, false +} + +// OutputStats tracks the number of output lines and bytes written. +type OutputStats struct { + lines int64 + bytes int64 +} + +// Lines returns the number of lines written. +func (s *OutputStats) Lines() int64 { + return atomic.LoadInt64(&s.lines) +} + +// Bytes returns the number of bytes written. +func (s *OutputStats) Bytes() int64 { + return atomic.LoadInt64(&s.bytes) +} + +// Stats tracks the number of lines of output and number of bytes +// per severity level. Values must be read with atomic.LoadInt64. +var Stats struct { + Info, Warning, Error OutputStats +} + +var severityStats = [numSeverity]*OutputStats{ + infoLog: &Stats.Info, + warningLog: &Stats.Warning, + errorLog: &Stats.Error, +} + +// Level is exported because it appears in the arguments to V and is +// the type of the v flag, which can be set programmatically. +// It's a distinct type because we want to discriminate it from logType. +// Variables of type level are only changed under logging.mu. +// The -v flag is read only with atomic ops, so the state of the logging +// module is consistent. + +// Level is treated as a sync/atomic int32. + +// Level specifies a level of verbosity for V logs. *Level implements +// flag.Value; the -v flag is of type Level and should be modified +// only through the flag.Value interface. +type Level int32 + +// get returns the value of the Level. +func (l *Level) get() Level { + return Level(atomic.LoadInt32((*int32)(l))) +} + +// set sets the value of the Level. +func (l *Level) set(val Level) { + atomic.StoreInt32((*int32)(l), int32(val)) +} + +// String is part of the flag.Value interface. +func (l *Level) String() string { + return strconv.FormatInt(int64(*l), 10) +} + +// Get is part of the flag.Value interface. +func (l *Level) Get() interface{} { + return *l +} + +// Set is part of the flag.Value interface. +func (l *Level) Set(value string) error { + v, err := strconv.Atoi(value) + if err != nil { + return err + } + logging.mu.Lock() + defer logging.mu.Unlock() + logging.setVState(Level(v), logging.vmodule.filter, false) + return nil +} + +// moduleSpec represents the setting of the -vmodule flag. +type moduleSpec struct { + filter []modulePat +} + +// modulePat contains a filter for the -vmodule flag. +// It holds a verbosity level and a file pattern to match. +type modulePat struct { + pattern string + literal bool // The pattern is a literal string + level Level +} + +// match reports whether the file matches the pattern. It uses a string +// comparison if the pattern contains no metacharacters. +func (m *modulePat) match(file string) bool { + if m.literal { + return file == m.pattern + } + match, _ := filepath.Match(m.pattern, file) + return match +} + +func (m *moduleSpec) String() string { + // Lock because the type is not atomic. TODO: clean this up. + logging.mu.Lock() + defer logging.mu.Unlock() + var b bytes.Buffer + for i, f := range m.filter { + if i > 0 { + b.WriteRune(',') + } + fmt.Fprintf(&b, "%s=%d", f.pattern, f.level) + } + return b.String() +} + +// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the +// struct is not exported. +func (m *moduleSpec) Get() interface{} { + return nil +} + +var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N") + +// Syntax: -vmodule=recordio=2,file=1,gfs*=3 +func (m *moduleSpec) Set(value string) error { + var filter []modulePat + for _, pat := range strings.Split(value, ",") { + if len(pat) == 0 { + // Empty strings such as from a trailing comma can be ignored. + continue + } + patLev := strings.Split(pat, "=") + if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 { + return errVmoduleSyntax + } + pattern := patLev[0] + v, err := strconv.Atoi(patLev[1]) + if err != nil { + return errors.New("syntax error: expect comma-separated list of filename=N") + } + if v < 0 { + return errors.New("negative value for vmodule level") + } + if v == 0 { + continue // Ignore. It's harmless but no point in paying the overhead. + } + // TODO: check syntax of filter? + filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)}) + } + logging.mu.Lock() + defer logging.mu.Unlock() + logging.setVState(logging.verbosity, filter, true) + return nil +} + +// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters +// that require filepath.Match to be called to match the pattern. +func isLiteral(pattern string) bool { + return !strings.ContainsAny(pattern, `\*?[]`) +} + +// traceLocation represents the setting of the -log_backtrace_at flag. +type traceLocation struct { + file string + line int +} + +// isSet reports whether the trace location has been specified. +// logging.mu is held. +func (t *traceLocation) isSet() bool { + return t.line > 0 +} + +// match reports whether the specified file and line matches the trace location. +// The argument file name is the full path, not the basename specified in the flag. +// logging.mu is held. +func (t *traceLocation) match(file string, line int) bool { + if t.line != line { + return false + } + if i := strings.LastIndex(file, "/"); i >= 0 { + file = file[i+1:] + } + return t.file == file +} + +func (t *traceLocation) String() string { + // Lock because the type is not atomic. TODO: clean this up. + logging.mu.Lock() + defer logging.mu.Unlock() + return fmt.Sprintf("%s:%d", t.file, t.line) +} + +// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the +// struct is not exported +func (t *traceLocation) Get() interface{} { + return nil +} + +var errTraceSyntax = errors.New("syntax error: expect file.go:234") + +// Syntax: -log_backtrace_at=gopherflakes.go:234 +// Note that unlike vmodule the file extension is included here. +func (t *traceLocation) Set(value string) error { + if value == "" { + // Unset. + t.line = 0 + t.file = "" + } + fields := strings.Split(value, ":") + if len(fields) != 2 { + return errTraceSyntax + } + file, line := fields[0], fields[1] + if !strings.Contains(file, ".") { + return errTraceSyntax + } + v, err := strconv.Atoi(line) + if err != nil { + return errTraceSyntax + } + if v <= 0 { + return errors.New("negative or zero value for level") + } + logging.mu.Lock() + defer logging.mu.Unlock() + t.line = v + t.file = file + return nil +} + +// flushSyncWriter is the interface satisfied by logging destinations. +type flushSyncWriter interface { + Flush() error + Sync() error + io.Writer +} + +func init() { + flag.BoolVar(&logging.toStderr, "logtostderr", false, "log to standard error instead of files") + flag.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files") + flag.Var(&logging.verbosity, "v", "log level for V logs") + flag.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr") + flag.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") + flag.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace") + + // Default stderrThreshold is ERROR. + logging.stderrThreshold = errorLog + + logging.setVState(0, nil, false) + go logging.flushDaemon() +} + +// Flush flushes all pending log I/O. +func Flush() { + logging.lockAndFlushAll() +} + +// loggingT collects all the global state of the logging setup. +type loggingT struct { + // Boolean flags. Not handled atomically because the flag.Value interface + // does not let us avoid the =true, and that shorthand is necessary for + // compatibility. TODO: does this matter enough to fix? Seems unlikely. + toStderr bool // The -logtostderr flag. + alsoToStderr bool // The -alsologtostderr flag. + + // Level flag. Handled atomically. + stderrThreshold severity // The -stderrthreshold flag. + + // freeList is a list of byte buffers, maintained under freeListMu. + freeList *buffer + // freeListMu maintains the free list. It is separate from the main mutex + // so buffers can be grabbed and printed to without holding the main lock, + // for better parallelization. + freeListMu sync.Mutex + + // mu protects the remaining elements of this structure and is + // used to synchronize logging. + mu sync.Mutex + // file holds writer for each of the log types. + file [numSeverity]flushSyncWriter + // pcs is used in V to avoid an allocation when computing the caller's PC. + pcs [1]uintptr + // vmap is a cache of the V Level for each V() call site, identified by PC. + // It is wiped whenever the vmodule flag changes state. + vmap map[uintptr]Level + // filterLength stores the length of the vmodule filter chain. If greater + // than zero, it means vmodule is enabled. It may be read safely + // using sync.LoadInt32, but is only modified under mu. + filterLength int32 + // traceLocation is the state of the -log_backtrace_at flag. + traceLocation traceLocation + // These flags are modified only under lock, although verbosity may be fetched + // safely using atomic.LoadInt32. + vmodule moduleSpec // The state of the -vmodule flag. + verbosity Level // V logging level, the value of the -v flag/ +} + +// buffer holds a byte Buffer for reuse. The zero value is ready for use. +type buffer struct { + bytes.Buffer + tmp [64]byte // temporary byte array for creating headers. + next *buffer +} + +var logging loggingT + +// setVState sets a consistent state for V logging. +// l.mu is held. +func (l *loggingT) setVState(verbosity Level, filter []modulePat, setFilter bool) { + // Turn verbosity off so V will not fire while we are in transition. + logging.verbosity.set(0) + // Ditto for filter length. + atomic.StoreInt32(&logging.filterLength, 0) + + // Set the new filters and wipe the pc->Level map if the filter has changed. + if setFilter { + logging.vmodule.filter = filter + logging.vmap = make(map[uintptr]Level) + } + + // Things are consistent now, so enable filtering and verbosity. + // They are enabled in order opposite to that in V. + atomic.StoreInt32(&logging.filterLength, int32(len(filter))) + logging.verbosity.set(verbosity) +} + +// getBuffer returns a new, ready-to-use buffer. +func (l *loggingT) getBuffer() *buffer { + l.freeListMu.Lock() + b := l.freeList + if b != nil { + l.freeList = b.next + } + l.freeListMu.Unlock() + if b == nil { + b = new(buffer) + } else { + b.next = nil + b.Reset() + } + return b +} + +// putBuffer returns a buffer to the free list. +func (l *loggingT) putBuffer(b *buffer) { + if b.Len() >= 256 { + // Let big buffers die a natural death. + return + } + l.freeListMu.Lock() + b.next = l.freeList + l.freeList = b + l.freeListMu.Unlock() +} + +var timeNow = time.Now // Stubbed out for testing. + +/* +header formats a log header as defined by the C++ implementation. +It returns a buffer containing the formatted header and the user's file and line number. +The depth specifies how many stack frames above lives the source line to be identified in the log message. + +Log lines have this form: + Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... +where the fields are defined as follows: + L A single character, representing the log level (eg 'I' for INFO) + mm The month (zero padded; ie May is '05') + dd The day (zero padded) + hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds + threadid The space-padded thread ID as returned by GetTID() + file The file name + line The line number + msg The user-supplied message +*/ +func (l *loggingT) header(s severity, depth int) (*buffer, string, int) { + _, file, line, ok := runtime.Caller(3 + depth) + if !ok { + file = "???" + line = 1 + } else { + slash := strings.LastIndex(file, "/") + if slash >= 0 { + file = file[slash+1:] + } + } + return l.formatHeader(s, file, line), file, line +} + +// formatHeader formats a log header using the provided file name and line number. +func (l *loggingT) formatHeader(s severity, file string, line int) *buffer { + now := timeNow() + if line < 0 { + line = 0 // not a real line number, but acceptable to someDigits + } + if s > fatalLog { + s = infoLog // for safety. + } + buf := l.getBuffer() + + // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand. + // It's worth about 3X. Fprintf is hard. + _, month, day := now.Date() + hour, minute, second := now.Clock() + // Lmmdd hh:mm:ss.uuuuuu threadid file:line] + buf.tmp[0] = severityChar[s] + buf.twoDigits(1, int(month)) + buf.twoDigits(3, day) + buf.tmp[5] = ' ' + buf.twoDigits(6, hour) + buf.tmp[8] = ':' + buf.twoDigits(9, minute) + buf.tmp[11] = ':' + buf.twoDigits(12, second) + buf.tmp[14] = '.' + buf.nDigits(6, 15, now.Nanosecond()/1000, '0') + buf.tmp[21] = ' ' + buf.nDigits(7, 22, pid, ' ') // TODO: should be TID + buf.tmp[29] = ' ' + buf.Write(buf.tmp[:30]) + buf.WriteString(file) + buf.tmp[0] = ':' + n := buf.someDigits(1, line) + buf.tmp[n+1] = ']' + buf.tmp[n+2] = ' ' + buf.Write(buf.tmp[:n+3]) + return buf +} + +// Some custom tiny helper functions to print the log header efficiently. + +const digits = "0123456789" + +// twoDigits formats a zero-prefixed two-digit integer at buf.tmp[i]. +func (buf *buffer) twoDigits(i, d int) { + buf.tmp[i+1] = digits[d%10] + d /= 10 + buf.tmp[i] = digits[d%10] +} + +// nDigits formats an n-digit integer at buf.tmp[i], +// padding with pad on the left. +// It assumes d >= 0. +func (buf *buffer) nDigits(n, i, d int, pad byte) { + j := n - 1 + for ; j >= 0 && d > 0; j-- { + buf.tmp[i+j] = digits[d%10] + d /= 10 + } + for ; j >= 0; j-- { + buf.tmp[i+j] = pad + } +} + +// someDigits formats a zero-prefixed variable-width integer at buf.tmp[i]. +func (buf *buffer) someDigits(i, d int) int { + // Print into the top, then copy down. We know there's space for at least + // a 10-digit number. + j := len(buf.tmp) + for { + j-- + buf.tmp[j] = digits[d%10] + d /= 10 + if d == 0 { + break + } + } + return copy(buf.tmp[i:], buf.tmp[j:]) +} + +func (l *loggingT) println(s severity, args ...interface{}) { + buf, file, line := l.header(s, 0) + fmt.Fprintln(buf, args...) + l.output(s, buf, file, line, false) +} + +func (l *loggingT) print(s severity, args ...interface{}) { + l.printDepth(s, 1, args...) +} + +func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) { + buf, file, line := l.header(s, depth) + fmt.Fprint(buf, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, false) +} + +func (l *loggingT) printf(s severity, format string, args ...interface{}) { + buf, file, line := l.header(s, 0) + fmt.Fprintf(buf, format, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, false) +} + +// printWithFileLine behaves like print but uses the provided file and line number. If +// alsoLogToStderr is true, the log message always appears on standard error; it +// will also appear in the log file unless --logtostderr is set. +func (l *loggingT) printWithFileLine(s severity, file string, line int, alsoToStderr bool, args ...interface{}) { + buf := l.formatHeader(s, file, line) + fmt.Fprint(buf, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, alsoToStderr) +} + +// output writes the data to the log files and releases the buffer. +func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoToStderr bool) { + l.mu.Lock() + if l.traceLocation.isSet() { + if l.traceLocation.match(file, line) { + buf.Write(stacks(false)) + } + } + data := buf.Bytes() + if !flag.Parsed() { + os.Stderr.Write([]byte("ERROR: logging before flag.Parse: ")) + os.Stderr.Write(data) + } else if l.toStderr { + os.Stderr.Write(data) + } else { + if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() { + os.Stderr.Write(data) + } + if l.file[s] == nil { + if err := l.createFiles(s); err != nil { + os.Stderr.Write(data) // Make sure the message appears somewhere. + l.exit(err) + } + } + switch s { + case fatalLog: + l.file[fatalLog].Write(data) + fallthrough + case errorLog: + l.file[errorLog].Write(data) + fallthrough + case warningLog: + l.file[warningLog].Write(data) + fallthrough + case infoLog: + l.file[infoLog].Write(data) + } + } + if s == fatalLog { + // If we got here via Exit rather than Fatal, print no stacks. + if atomic.LoadUint32(&fatalNoStacks) > 0 { + l.mu.Unlock() + timeoutFlush(10 * time.Second) + os.Exit(1) + } + // Dump all goroutine stacks before exiting. + // First, make sure we see the trace for the current goroutine on standard error. + // If -logtostderr has been specified, the loop below will do that anyway + // as the first stack in the full dump. + if !l.toStderr { + os.Stderr.Write(stacks(false)) + } + // Write the stack trace for all goroutines to the files. + trace := stacks(true) + logExitFunc = func(error) {} // If we get a write error, we'll still exit below. + for log := fatalLog; log >= infoLog; log-- { + if f := l.file[log]; f != nil { // Can be nil if -logtostderr is set. + f.Write(trace) + } + } + l.mu.Unlock() + timeoutFlush(10 * time.Second) + os.Exit(255) // C++ uses -1, which is silly because it's anded with 255 anyway. + } + l.putBuffer(buf) + l.mu.Unlock() + if stats := severityStats[s]; stats != nil { + atomic.AddInt64(&stats.lines, 1) + atomic.AddInt64(&stats.bytes, int64(len(data))) + } +} + +// timeoutFlush calls Flush and returns when it completes or after timeout +// elapses, whichever happens first. This is needed because the hooks invoked +// by Flush may deadlock when glog.Fatal is called from a hook that holds +// a lock. +func timeoutFlush(timeout time.Duration) { + done := make(chan bool, 1) + go func() { + Flush() // calls logging.lockAndFlushAll() + done <- true + }() + select { + case <-done: + case <-time.After(timeout): + fmt.Fprintln(os.Stderr, "glog: Flush took longer than", timeout) + } +} + +// stacks is a wrapper for runtime.Stack that attempts to recover the data for all goroutines. +func stacks(all bool) []byte { + // We don't know how big the traces are, so grow a few times if they don't fit. Start large, though. + n := 10000 + if all { + n = 100000 + } + var trace []byte + for i := 0; i < 5; i++ { + trace = make([]byte, n) + nbytes := runtime.Stack(trace, all) + if nbytes < len(trace) { + return trace[:nbytes] + } + n *= 2 + } + return trace +} + +// logExitFunc provides a simple mechanism to override the default behavior +// of exiting on error. Used in testing and to guarantee we reach a required exit +// for fatal logs. Instead, exit could be a function rather than a method but that +// would make its use clumsier. +var logExitFunc func(error) + +// exit is called if there is trouble creating or writing log files. +// It flushes the logs and exits the program; there's no point in hanging around. +// l.mu is held. +func (l *loggingT) exit(err error) { + fmt.Fprintf(os.Stderr, "log: exiting because of error: %s\n", err) + // If logExitFunc is set, we do that instead of exiting. + if logExitFunc != nil { + logExitFunc(err) + return + } + l.flushAll() + os.Exit(2) +} + +// syncBuffer joins a bufio.Writer to its underlying file, providing access to the +// file's Sync method and providing a wrapper for the Write method that provides log +// file rotation. There are conflicting methods, so the file cannot be embedded. +// l.mu is held for all its methods. +type syncBuffer struct { + logger *loggingT + *bufio.Writer + file *os.File + sev severity + nbytes uint64 // The number of bytes written to this file +} + +func (sb *syncBuffer) Sync() error { + return sb.file.Sync() +} + +func (sb *syncBuffer) Write(p []byte) (n int, err error) { + if sb.nbytes+uint64(len(p)) >= MaxSize { + if err := sb.rotateFile(time.Now()); err != nil { + sb.logger.exit(err) + } + } + n, err = sb.Writer.Write(p) + sb.nbytes += uint64(n) + if err != nil { + sb.logger.exit(err) + } + return +} + +// rotateFile closes the syncBuffer's file and starts a new one. +func (sb *syncBuffer) rotateFile(now time.Time) error { + if sb.file != nil { + sb.Flush() + sb.file.Close() + } + var err error + sb.file, _, err = create(severityName[sb.sev], now) + sb.nbytes = 0 + if err != nil { + return err + } + + sb.Writer = bufio.NewWriterSize(sb.file, bufferSize) + + // Write header. + var buf bytes.Buffer + fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05")) + fmt.Fprintf(&buf, "Running on machine: %s\n", host) + fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH) + fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n") + n, err := sb.file.Write(buf.Bytes()) + sb.nbytes += uint64(n) + return err +} + +// bufferSize sizes the buffer associated with each log file. It's large +// so that log records can accumulate without the logging thread blocking +// on disk I/O. The flushDaemon will block instead. +const bufferSize = 256 * 1024 + +// createFiles creates all the log files for severity from sev down to infoLog. +// l.mu is held. +func (l *loggingT) createFiles(sev severity) error { + now := time.Now() + // Files are created in decreasing severity order, so as soon as we find one + // has already been created, we can stop. + for s := sev; s >= infoLog && l.file[s] == nil; s-- { + sb := &syncBuffer{ + logger: l, + sev: s, + } + if err := sb.rotateFile(now); err != nil { + return err + } + l.file[s] = sb + } + return nil +} + +const flushInterval = 30 * time.Second + +// flushDaemon periodically flushes the log file buffers. +func (l *loggingT) flushDaemon() { + for _ = range time.NewTicker(flushInterval).C { + l.lockAndFlushAll() + } +} + +// lockAndFlushAll is like flushAll but locks l.mu first. +func (l *loggingT) lockAndFlushAll() { + l.mu.Lock() + l.flushAll() + l.mu.Unlock() +} + +// flushAll flushes all the logs and attempts to "sync" their data to disk. +// l.mu is held. +func (l *loggingT) flushAll() { + // Flush from fatal down, in case there's trouble flushing. + for s := fatalLog; s >= infoLog; s-- { + file := l.file[s] + if file != nil { + file.Flush() // ignore error + file.Sync() // ignore error + } + } +} + +// CopyStandardLogTo arranges for messages written to the Go "log" package's +// default logs to also appear in the Google logs for the named and lower +// severities. Subsequent changes to the standard log's default output location +// or format may break this behavior. +// +// Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not +// recognized, CopyStandardLogTo panics. +func CopyStandardLogTo(name string) { + sev, ok := severityByName(name) + if !ok { + panic(fmt.Sprintf("log.CopyStandardLogTo(%q): unrecognized severity name", name)) + } + // Set a log format that captures the user's file and line: + // d.go:23: message + stdLog.SetFlags(stdLog.Lshortfile) + stdLog.SetOutput(logBridge(sev)) +} + +// logBridge provides the Write method that enables CopyStandardLogTo to connect +// Go's standard logs to the logs provided by this package. +type logBridge severity + +// Write parses the standard logging line and passes its components to the +// logger for severity(lb). +func (lb logBridge) Write(b []byte) (n int, err error) { + var ( + file = "???" + line = 1 + text string + ) + // Split "d.go:23: message" into "d.go", "23", and "message". + if parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 { + text = fmt.Sprintf("bad log format: %s", b) + } else { + file = string(parts[0]) + text = string(parts[2][1:]) // skip leading space + line, err = strconv.Atoi(string(parts[1])) + if err != nil { + text = fmt.Sprintf("bad line number: %s", b) + line = 1 + } + } + // printWithFileLine with alsoToStderr=true, so standard log messages + // always appear on standard error. + logging.printWithFileLine(severity(lb), file, line, true, text) + return len(b), nil +} + +// setV computes and remembers the V level for a given PC +// when vmodule is enabled. +// File pattern matching takes the basename of the file, stripped +// of its .go suffix, and uses filepath.Match, which is a little more +// general than the *? matching used in C++. +// l.mu is held. +func (l *loggingT) setV(pc uintptr) Level { + fn := runtime.FuncForPC(pc) + file, _ := fn.FileLine(pc) + // The file is something like /a/b/c/d.go. We want just the d. + if strings.HasSuffix(file, ".go") { + file = file[:len(file)-3] + } + if slash := strings.LastIndex(file, "/"); slash >= 0 { + file = file[slash+1:] + } + for _, filter := range l.vmodule.filter { + if filter.match(file) { + l.vmap[pc] = filter.level + return filter.level + } + } + l.vmap[pc] = 0 + return 0 +} + +// Verbose is a boolean type that implements Infof (like Printf) etc. +// See the documentation of V for more information. +type Verbose bool + +// V reports whether verbosity at the call site is at least the requested level. +// The returned value is a boolean of type Verbose, which implements Info, Infoln +// and Infof. These methods will write to the Info log if called. +// Thus, one may write either +// if glog.V(2) { glog.Info("log this") } +// or +// glog.V(2).Info("log this") +// The second form is shorter but the first is cheaper if logging is off because it does +// not evaluate its arguments. +// +// Whether an individual call to V generates a log record depends on the setting of +// the -v and --vmodule flags; both are off by default. If the level in the call to +// V is at least the value of -v, or of -vmodule for the source file containing the +// call, the V call will log. +func V(level Level) Verbose { + // This function tries hard to be cheap unless there's work to do. + // The fast path is two atomic loads and compares. + + // Here is a cheap but safe test to see if V logging is enabled globally. + if logging.verbosity.get() >= level { + return Verbose(true) + } + + // It's off globally but it vmodule may still be set. + // Here is another cheap but safe test to see if vmodule is enabled. + if atomic.LoadInt32(&logging.filterLength) > 0 { + // Now we need a proper lock to use the logging structure. The pcs field + // is shared so we must lock before accessing it. This is fairly expensive, + // but if V logging is enabled we're slow anyway. + logging.mu.Lock() + defer logging.mu.Unlock() + if runtime.Callers(2, logging.pcs[:]) == 0 { + return Verbose(false) + } + v, ok := logging.vmap[logging.pcs[0]] + if !ok { + v = logging.setV(logging.pcs[0]) + } + return Verbose(v >= level) + } + return Verbose(false) +} + +// Info is equivalent to the global Info function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Info(args ...interface{}) { + if v { + logging.print(infoLog, args...) + } +} + +// Infoln is equivalent to the global Infoln function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Infoln(args ...interface{}) { + if v { + logging.println(infoLog, args...) + } +} + +// Infof is equivalent to the global Infof function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Infof(format string, args ...interface{}) { + if v { + logging.printf(infoLog, format, args...) + } +} + +// Info logs to the INFO log. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Info(args ...interface{}) { + logging.print(infoLog, args...) +} + +// InfoDepth acts as Info but uses depth to determine which call frame to log. +// InfoDepth(0, "msg") is the same as Info("msg"). +func InfoDepth(depth int, args ...interface{}) { + logging.printDepth(infoLog, depth, args...) +} + +// Infoln logs to the INFO log. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Infoln(args ...interface{}) { + logging.println(infoLog, args...) +} + +// Infof logs to the INFO log. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Infof(format string, args ...interface{}) { + logging.printf(infoLog, format, args...) +} + +// Warning logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Warning(args ...interface{}) { + logging.print(warningLog, args...) +} + +// WarningDepth acts as Warning but uses depth to determine which call frame to log. +// WarningDepth(0, "msg") is the same as Warning("msg"). +func WarningDepth(depth int, args ...interface{}) { + logging.printDepth(warningLog, depth, args...) +} + +// Warningln logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Warningln(args ...interface{}) { + logging.println(warningLog, args...) +} + +// Warningf logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Warningf(format string, args ...interface{}) { + logging.printf(warningLog, format, args...) +} + +// Error logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Error(args ...interface{}) { + logging.print(errorLog, args...) +} + +// ErrorDepth acts as Error but uses depth to determine which call frame to log. +// ErrorDepth(0, "msg") is the same as Error("msg"). +func ErrorDepth(depth int, args ...interface{}) { + logging.printDepth(errorLog, depth, args...) +} + +// Errorln logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Errorln(args ...interface{}) { + logging.println(errorLog, args...) +} + +// Errorf logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Errorf(format string, args ...interface{}) { + logging.printf(errorLog, format, args...) +} + +// Fatal logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Fatal(args ...interface{}) { + logging.print(fatalLog, args...) +} + +// FatalDepth acts as Fatal but uses depth to determine which call frame to log. +// FatalDepth(0, "msg") is the same as Fatal("msg"). +func FatalDepth(depth int, args ...interface{}) { + logging.printDepth(fatalLog, depth, args...) +} + +// Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Fatalln(args ...interface{}) { + logging.println(fatalLog, args...) +} + +// Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Fatalf(format string, args ...interface{}) { + logging.printf(fatalLog, format, args...) +} + +// fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks. +// It allows Exit and relatives to use the Fatal logs. +var fatalNoStacks uint32 + +// Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Exit(args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.print(fatalLog, args...) +} + +// ExitDepth acts as Exit but uses depth to determine which call frame to log. +// ExitDepth(0, "msg") is the same as Exit("msg"). +func ExitDepth(depth int, args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.printDepth(fatalLog, depth, args...) +} + +// Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +func Exitln(args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.println(fatalLog, args...) +} + +// Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Exitf(format string, args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.printf(fatalLog, format, args...) +} diff --git a/Godeps/_workspace/src/github.com/golang/glog/glog_file.go b/Godeps/_workspace/src/github.com/golang/glog/glog_file.go new file mode 100644 index 0000000..65075d2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/golang/glog/glog_file.go @@ -0,0 +1,124 @@ +// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ +// +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// File I/O for logs. + +package glog + +import ( + "errors" + "flag" + "fmt" + "os" + "os/user" + "path/filepath" + "strings" + "sync" + "time" +) + +// MaxSize is the maximum size of a log file in bytes. +var MaxSize uint64 = 1024 * 1024 * 1800 + +// logDirs lists the candidate directories for new log files. +var logDirs []string + +// If non-empty, overrides the choice of directory in which to write logs. +// See createLogDirs for the full list of possible destinations. +var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory") + +func createLogDirs() { + if *logDir != "" { + logDirs = append(logDirs, *logDir) + } + logDirs = append(logDirs, os.TempDir()) +} + +var ( + pid = os.Getpid() + program = filepath.Base(os.Args[0]) + host = "unknownhost" + userName = "unknownuser" +) + +func init() { + h, err := os.Hostname() + if err == nil { + host = shortHostname(h) + } + + current, err := user.Current() + if err == nil { + userName = current.Username + } + + // Sanitize userName since it may contain filepath separators on Windows. + userName = strings.Replace(userName, `\`, "_", -1) +} + +// shortHostname returns its argument, truncating at the first period. +// For instance, given "www.google.com" it returns "www". +func shortHostname(hostname string) string { + if i := strings.Index(hostname, "."); i >= 0 { + return hostname[:i] + } + return hostname +} + +// logName returns a new log file name containing tag, with start time t, and +// the name for the symlink for tag. +func logName(tag string, t time.Time) (name, link string) { + name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d", + program, + host, + userName, + tag, + t.Year(), + t.Month(), + t.Day(), + t.Hour(), + t.Minute(), + t.Second(), + pid) + return name, program + "." + tag +} + +var onceLogDirs sync.Once + +// create creates a new log file and returns the file and its filename, which +// contains tag ("INFO", "FATAL", etc.) and t. If the file is created +// successfully, create also attempts to update the symlink for that tag, ignoring +// errors. +func create(tag string, t time.Time) (f *os.File, filename string, err error) { + onceLogDirs.Do(createLogDirs) + if len(logDirs) == 0 { + return nil, "", errors.New("log: no log dirs") + } + name, link := logName(tag, t) + var lastErr error + for _, dir := range logDirs { + fname := filepath.Join(dir, name) + f, err := os.Create(fname) + if err == nil { + symlink := filepath.Join(dir, link) + os.Remove(symlink) // ignore err + os.Symlink(name, symlink) // ignore err + return f, fname, nil + } + lastErr = err + } + return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr) +} diff --git a/Godeps/_workspace/src/github.com/golang/glog/glog_test.go b/Godeps/_workspace/src/github.com/golang/glog/glog_test.go new file mode 100644 index 0000000..0fb376e --- /dev/null +++ b/Godeps/_workspace/src/github.com/golang/glog/glog_test.go @@ -0,0 +1,415 @@ +// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ +// +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package glog + +import ( + "bytes" + "fmt" + stdLog "log" + "path/filepath" + "runtime" + "strconv" + "strings" + "testing" + "time" +) + +// Test that shortHostname works as advertised. +func TestShortHostname(t *testing.T) { + for hostname, expect := range map[string]string{ + "": "", + "host": "host", + "host.google.com": "host", + } { + if got := shortHostname(hostname); expect != got { + t.Errorf("shortHostname(%q): expected %q, got %q", hostname, expect, got) + } + } +} + +// flushBuffer wraps a bytes.Buffer to satisfy flushSyncWriter. +type flushBuffer struct { + bytes.Buffer +} + +func (f *flushBuffer) Flush() error { + return nil +} + +func (f *flushBuffer) Sync() error { + return nil +} + +// swap sets the log writers and returns the old array. +func (l *loggingT) swap(writers [numSeverity]flushSyncWriter) (old [numSeverity]flushSyncWriter) { + l.mu.Lock() + defer l.mu.Unlock() + old = l.file + for i, w := range writers { + logging.file[i] = w + } + return +} + +// newBuffers sets the log writers to all new byte buffers and returns the old array. +func (l *loggingT) newBuffers() [numSeverity]flushSyncWriter { + return l.swap([numSeverity]flushSyncWriter{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)}) +} + +// contents returns the specified log value as a string. +func contents(s severity) string { + return logging.file[s].(*flushBuffer).String() +} + +// contains reports whether the string is contained in the log. +func contains(s severity, str string, t *testing.T) bool { + return strings.Contains(contents(s), str) +} + +// setFlags configures the logging flags how the test expects them. +func setFlags() { + logging.toStderr = false +} + +// Test that Info works as advertised. +func TestInfo(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + Info("test") + if !contains(infoLog, "I", t) { + t.Errorf("Info has wrong character: %q", contents(infoLog)) + } + if !contains(infoLog, "test", t) { + t.Error("Info failed") + } +} + +func TestInfoDepth(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + + f := func() { InfoDepth(1, "depth-test1") } + + // The next three lines must stay together + _, _, wantLine, _ := runtime.Caller(0) + InfoDepth(0, "depth-test0") + f() + + msgs := strings.Split(strings.TrimSuffix(contents(infoLog), "\n"), "\n") + if len(msgs) != 2 { + t.Fatalf("Got %d lines, expected 2", len(msgs)) + } + + for i, m := range msgs { + if !strings.HasPrefix(m, "I") { + t.Errorf("InfoDepth[%d] has wrong character: %q", i, m) + } + w := fmt.Sprintf("depth-test%d", i) + if !strings.Contains(m, w) { + t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m) + } + + // pull out the line number (between : and ]) + msg := m[strings.LastIndex(m, ":")+1:] + x := strings.Index(msg, "]") + if x < 0 { + t.Errorf("InfoDepth[%d]: missing ']': %q", i, m) + continue + } + line, err := strconv.Atoi(msg[:x]) + if err != nil { + t.Errorf("InfoDepth[%d]: bad line number: %q", i, m) + continue + } + wantLine++ + if wantLine != line { + t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine) + } + } +} + +func init() { + CopyStandardLogTo("INFO") +} + +// Test that CopyStandardLogTo panics on bad input. +func TestCopyStandardLogToPanic(t *testing.T) { + defer func() { + if s, ok := recover().(string); !ok || !strings.Contains(s, "LOG") { + t.Errorf(`CopyStandardLogTo("LOG") should have panicked: %v`, s) + } + }() + CopyStandardLogTo("LOG") +} + +// Test that using the standard log package logs to INFO. +func TestStandardLog(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + stdLog.Print("test") + if !contains(infoLog, "I", t) { + t.Errorf("Info has wrong character: %q", contents(infoLog)) + } + if !contains(infoLog, "test", t) { + t.Error("Info failed") + } +} + +// Test that the header has the correct format. +func TestHeader(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + defer func(previous func() time.Time) { timeNow = previous }(timeNow) + timeNow = func() time.Time { + return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local) + } + pid = 1234 + Info("test") + var line int + format := "I0102 15:04:05.067890 1234 glog_test.go:%d] test\n" + n, err := fmt.Sscanf(contents(infoLog), format, &line) + if n != 1 || err != nil { + t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(infoLog)) + } + // Scanf treats multiple spaces as equivalent to a single space, + // so check for correct space-padding also. + want := fmt.Sprintf(format, line) + if contents(infoLog) != want { + t.Errorf("log format error: got:\n\t%q\nwant:\t%q", contents(infoLog), want) + } +} + +// Test that an Error log goes to Warning and Info. +// Even in the Info log, the source character will be E, so the data should +// all be identical. +func TestError(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + Error("test") + if !contains(errorLog, "E", t) { + t.Errorf("Error has wrong character: %q", contents(errorLog)) + } + if !contains(errorLog, "test", t) { + t.Error("Error failed") + } + str := contents(errorLog) + if !contains(warningLog, str, t) { + t.Error("Warning failed") + } + if !contains(infoLog, str, t) { + t.Error("Info failed") + } +} + +// Test that a Warning log goes to Info. +// Even in the Info log, the source character will be W, so the data should +// all be identical. +func TestWarning(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + Warning("test") + if !contains(warningLog, "W", t) { + t.Errorf("Warning has wrong character: %q", contents(warningLog)) + } + if !contains(warningLog, "test", t) { + t.Error("Warning failed") + } + str := contents(warningLog) + if !contains(infoLog, str, t) { + t.Error("Info failed") + } +} + +// Test that a V log goes to Info. +func TestV(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + logging.verbosity.Set("2") + defer logging.verbosity.Set("0") + V(2).Info("test") + if !contains(infoLog, "I", t) { + t.Errorf("Info has wrong character: %q", contents(infoLog)) + } + if !contains(infoLog, "test", t) { + t.Error("Info failed") + } +} + +// Test that a vmodule enables a log in this file. +func TestVmoduleOn(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + logging.vmodule.Set("glog_test=2") + defer logging.vmodule.Set("") + if !V(1) { + t.Error("V not enabled for 1") + } + if !V(2) { + t.Error("V not enabled for 2") + } + if V(3) { + t.Error("V enabled for 3") + } + V(2).Info("test") + if !contains(infoLog, "I", t) { + t.Errorf("Info has wrong character: %q", contents(infoLog)) + } + if !contains(infoLog, "test", t) { + t.Error("Info failed") + } +} + +// Test that a vmodule of another file does not enable a log in this file. +func TestVmoduleOff(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + logging.vmodule.Set("notthisfile=2") + defer logging.vmodule.Set("") + for i := 1; i <= 3; i++ { + if V(Level(i)) { + t.Errorf("V enabled for %d", i) + } + } + V(2).Info("test") + if contents(infoLog) != "" { + t.Error("V logged incorrectly") + } +} + +// vGlobs are patterns that match/don't match this file at V=2. +var vGlobs = map[string]bool{ + // Easy to test the numeric match here. + "glog_test=1": false, // If -vmodule sets V to 1, V(2) will fail. + "glog_test=2": true, + "glog_test=3": true, // If -vmodule sets V to 1, V(3) will succeed. + // These all use 2 and check the patterns. All are true. + "*=2": true, + "?l*=2": true, + "????_*=2": true, + "??[mno]?_*t=2": true, + // These all use 2 and check the patterns. All are false. + "*x=2": false, + "m*=2": false, + "??_*=2": false, + "?[abc]?_*t=2": false, +} + +// Test that vmodule globbing works as advertised. +func testVmoduleGlob(pat string, match bool, t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + defer logging.vmodule.Set("") + logging.vmodule.Set(pat) + if V(2) != Verbose(match) { + t.Errorf("incorrect match for %q: got %t expected %t", pat, V(2), match) + } +} + +// Test that a vmodule globbing works as advertised. +func TestVmoduleGlob(t *testing.T) { + for glob, match := range vGlobs { + testVmoduleGlob(glob, match, t) + } +} + +func TestRollover(t *testing.T) { + setFlags() + var err error + defer func(previous func(error)) { logExitFunc = previous }(logExitFunc) + logExitFunc = func(e error) { + err = e + } + defer func(previous uint64) { MaxSize = previous }(MaxSize) + MaxSize = 512 + + Info("x") // Be sure we have a file. + info, ok := logging.file[infoLog].(*syncBuffer) + if !ok { + t.Fatal("info wasn't created") + } + if err != nil { + t.Fatalf("info has initial error: %v", err) + } + fname0 := info.file.Name() + Info(strings.Repeat("x", int(MaxSize))) // force a rollover + if err != nil { + t.Fatalf("info has error after big write: %v", err) + } + + // Make sure the next log file gets a file name with a different + // time stamp. + // + // TODO: determine whether we need to support subsecond log + // rotation. C++ does not appear to handle this case (nor does it + // handle Daylight Savings Time properly). + time.Sleep(1 * time.Second) + + Info("x") // create a new file + if err != nil { + t.Fatalf("error after rotation: %v", err) + } + fname1 := info.file.Name() + if fname0 == fname1 { + t.Errorf("info.f.Name did not change: %v", fname0) + } + if info.nbytes >= MaxSize { + t.Errorf("file size was not reset: %d", info.nbytes) + } +} + +func TestLogBacktraceAt(t *testing.T) { + setFlags() + defer logging.swap(logging.newBuffers()) + // The peculiar style of this code simplifies line counting and maintenance of the + // tracing block below. + var infoLine string + setTraceLocation := func(file string, line int, ok bool, delta int) { + if !ok { + t.Fatal("could not get file:line") + } + _, file = filepath.Split(file) + infoLine = fmt.Sprintf("%s:%d", file, line+delta) + err := logging.traceLocation.Set(infoLine) + if err != nil { + t.Fatal("error setting log_backtrace_at: ", err) + } + } + { + // Start of tracing block. These lines know about each other's relative position. + _, file, line, ok := runtime.Caller(0) + setTraceLocation(file, line, ok, +2) // Two lines between Caller and Info calls. + Info("we want a stack trace here") + } + numAppearances := strings.Count(contents(infoLog), infoLine) + if numAppearances < 2 { + // Need 2 appearances, one in the log header and one in the trace: + // log_test.go:281: I0511 16:36:06.952398 02238 log_test.go:280] we want a stack trace here + // ... + // github.com/glog/glog_test.go:280 (0x41ba91) + // ... + // We could be more precise but that would require knowing the details + // of the traceback format, which may not be dependable. + t.Fatal("got no trace back; log is ", contents(infoLog)) + } +} + +func BenchmarkHeader(b *testing.B) { + for i := 0; i < b.N; i++ { + buf, _, _ := logging.header(infoLog, 0) + logging.putBuffer(buf) + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8f71f43 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..e0e4a51 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,13 @@ +export GOPATH:=$(abs_top_srcdir)/Godeps/_workspace:$(GOPATH) + +all-local: build-cli build-daemon +clean-local: + -rm -f citadm citd +install-exec-local: + $(INSTALL_PROGRAM) citadm $(bindir) + $(INSTALL_PROGRAM) citd $(bindir) + +build-cli: + go build citadm.go +build-daemon: + go build citd.go diff --git a/README.md b/README.md index f5b0617..d79a313 100644 --- a/README.md +++ b/README.md @@ -1 +1,66 @@ -# gotgt +## gotgt [![Build Status](https://travis-ci.org/gostor/gotgt.svg)](https://travis-ci.org/gostor/gotgt) + +Cloud Integrated Target framework, this includes two binaries, one is `citadm` which is command line to config and control, the other is `citd` which is a target daemon. + +``` +# citadm --help +Linux SCSI Target administration utility, version 0.1 +Usage: ./citadm [OPTIONS] + +Application Options: + --lld --mode target --op new --tid --targetname + add a new target with and . must not be zero. + --lld --mode target --op delete [--force] --tid + delete the specific target with . + With force option, the specific target is deleted + even if there is an activity. + --lld --mode target --op show + show all the targets. + --lld --mode target --op show --tid + show the specific target's parameters. + --lld --mode target --op update --tid --name --value + change the target parameters of the target with . + --lld --mode target --op bind --tid --initiator-address
+ --lld --mode target --op bind --tid --initiator-name + enable the target to accept the specific initiators. + --lld --mode target --op unbind --tid --initiator-address
+ --lld --mode target --op unbind --tid --initiator-name + disable the specific permitted initiators. + --lld --mode logicalunit --op new --tid --lun + --backing-store --bstype --bsopts --bsoflags + add a new logical unit with to the specific + target with . The logical unit is offered + to the initiators. must be block device files + (including LVM and RAID devices) or regular files. + bstype option is optional. + bsopts are specific to the bstype. + bsoflags supported options are sync and direct + (sync:direct for both). + --lld --mode logicalunit --op delete --tid --lun + delete the specific logical unit with that + the target with has. + --lld --mode account --op new --user --password + add a new account with and . + --lld --mode account --op delete --user + delete the specific account having . + --lld --mode account --op bind --tid --user [--outgoing] + add the specific account having to + the specific target with . + could be or . + If you use --outgoing option, the account will + be added as an outgoing account. + --lld --mode account --op unbind --tid --user [--outgoing] + delete the specific account having from specific + target. The --outgoing option must be added if you + delete an outgoing account. + --lld --mode lld --op start + Start the specified lld without restarting the tgtd process. + --control-port use control port + +Help Options: + --help + display this help and exit + +Report bugs via . + +``` diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..b2518e1 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +srcdir=`dirname $0` +test -z "$srcdir" && srcidr=. + +cd $srcdir + +die() +{ + echo + echo "$1" + echo + 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." + +echo +echo "Generating build-system with:" +echo " aclocal: $(aclocal --version | head -1)" +echo " autoconf: $(autoconf --version | head -1)" +echo " automake: $(automake --version | head -1)" +echo + +rm -rf autom4te.cache + +aclocal +autoconf +automake --add-missing + +echo +echo "type '$srcdir/configure' and 'make' to compile hyper." +echo diff --git a/citadm.go b/citadm.go new file mode 100644 index 0000000..d8e648e --- /dev/null +++ b/citadm.go @@ -0,0 +1,200 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// SCSI target command line +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/gostor/gotgt/pkg/version" +) + +type AdminMode int +type AdminOperation int + +const ( + OP_NEW = iota + OP_DELETE + OP_SHOW + OP_BIND + OP_UNBIND + OP_UPDATE + OP_STATS + OP_START + OP_STOP +) + +const ( + MODE_SYSTEM = iota + MODE_TARGET + MODE_DEVICE + MODE_PORTAL + MODE_LLD + + MODE_SESSION + MODE_CONNECTION + MODE_ACCOUNT +) + +type AdminRequest struct { + Mode AdminMode + Operation AdminOperation + LLD string + Length uint32 + TID int32 + SID uint64 + Lun uint64 + Cid uint64 + host_no uint32 + device_type uint32 + ac_dir uint32 + pack uint32 + force uint32 +} + +func main() { + // define options + //var req AdminRequest + flDebug := flag.Bool("debug", false, "Debug mode") + flHelp := flag.Bool("help", false, "Help Message") + flVersion := flag.Bool("version", false, "Version message") + flLLD := flag.String("lld", "", "Low level device") + flOperation := flag.String("op", "", "Operation") + flMode := flag.String("mode", "", "") + flTID := flag.String("tid", "", "") + flSID := flag.String("sid", "", "") + flCID := flag.String("cid", "", "") + flLUN := flag.String("lun", "", "") + flName := flag.String("name", "", "") + flValue := flag.String("value", "", "") + flBS := flag.String("backing-store", "", "") + flTarget := flag.String("target", "", "") + flInitiatorName := flag.String("initiator-name", "", "") + flInitiatorAddress := flag.String("initiator-address", "", "") + flUser := flag.String("user", "", "") + flPassword := flag.String("password", "", "") + flHost := flag.String("host", "", "") + flForce := flag.Bool("force", false, "") + flDeviceType := flag.String("devicetype", "", "") + + flag.Usage = func() { usage(0) } + flag.Parse() + if *flHelp { + usage(0) + } + if *flVersion { + showVersion() + } + + _ = flDebug + _ = flLLD + _ = flOperation + _ = flMode + _ = flTID + _ = flSID + _ = flCID + _ = flLUN + _ = flName + _ = flValue + _ = flBS + _ = flTarget + _ = flInitiatorName + _ = flInitiatorAddress + _ = flUser + _ = flPassword + _ = flHost + _ = flForce + _ = flDeviceType +} + +func usage(status int) { + if status != 0 { + fmt.Fprintf(os.Stderr, "Try `%s --help' for more information.\n", os.Args[0]) + os.Exit(status) + } + + var helpMessage = `Linux SCSI Target administration utility, version %s +Usage: %s [OPTIONS] + +Application Options: + --lld --mode target --op new --tid --targetname + add a new target with and . must not be zero. + --lld --mode target --op delete [--force] --tid + delete the specific target with . + With force option, the specific target is deleted + even if there is an activity. + --lld --mode target --op show + show all the targets. + --lld --mode target --op show --tid + show the specific target's parameters. + --lld --mode target --op update --tid --name --value + change the target parameters of the target with . + --lld --mode target --op bind --tid --initiator-address
+ --lld --mode target --op bind --tid --initiator-name + enable the target to accept the specific initiators. + --lld --mode target --op unbind --tid --initiator-address
+ --lld --mode target --op unbind --tid --initiator-name + disable the specific permitted initiators. + --lld --mode logicalunit --op new --tid --lun + --backing-store --bstype --bsopts --bsoflags + add a new logical unit with to the specific + target with . The logical unit is offered + to the initiators. must be block device files + (including LVM and RAID devices) or regular files. + bstype option is optional. + bsopts are specific to the bstype. + bsoflags supported options are sync and direct + (sync:direct for both). + --lld --mode logicalunit --op delete --tid --lun + delete the specific logical unit with that + the target with has. + --lld --mode account --op new --user --password + add a new account with and . + --lld --mode account --op delete --user + delete the specific account having . + --lld --mode account --op bind --tid --user [--outgoing] + add the specific account having to + the specific target with . + could be or . + If you use --outgoing option, the account will + be added as an outgoing account. + --lld --mode account --op unbind --tid --user [--outgoing] + delete the specific account having from specific + target. The --outgoing option must be added if you + delete an outgoing account. + --lld --mode lld --op start + Start the specified lld without restarting the tgtd process. + --control-port use control port + +Help Options: + --help + display this help and exit + +Report bugs via . + +` + + fmt.Printf(helpMessage, version.VERSION, os.Args[0]) + os.Exit(0) +} + +func showVersion() { + fmt.Printf("%s\n", version.VERSION) + os.Exit(0) +} diff --git a/citd.go b/citd.go new file mode 100644 index 0000000..5df3aef --- /dev/null +++ b/citd.go @@ -0,0 +1,31 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// SCSI target daemon +package main + +import ( + "net" + + "github.com/golang/glog" +) + +func main() { + _, err := net.Listen("tcp", ":3260") + if err != nil { + glog.Error(err) + } +} diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..6edd1e1 --- /dev/null +++ b/configure.ac @@ -0,0 +1,60 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.69]) +AC_INIT([runv], [0.4.0], [hyper.sh]) +AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) + +# Checks for programs. +AC_PROG_CC +AM_PROG_AR +AC_PROG_RANLIB +AC_CANONICAL_BUILD + +case $build_os in + darwin*) + AM_CONDITIONAL([ON_DARWIN], [ true ]) + ;; + *) + AM_CONDITIONAL([ON_DARWIN], [ false ]) + ;; +esac + +# Checks for go tool chain +AC_CHECK_PROG([has_go], [go], [yes], [no]) +if test "x$has_go" != "xyes" ; then + AC_MSG_ERROR(Unable to find go binary in PATH) +fi + +# Platform specific setup +AC_CANONICAL_HOST + +case $host_os in + linux*) AM_CONDITIONAL([ON_LINUX], [ true ]) ;; + *) AM_CONDITIONAL([ON_LINUX], [ false ]) ;; +esac + +# Checks for header files. +AC_CHECK_HEADERS([stdlib.h string.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_CHECK_HEADER_STDBOOL +AC_TYPE_PID_T +AC_TYPE_UINT32_T +AC_TYPE_UINT8_T + +# Checks for library functions. +AC_FUNC_MALLOC +AC_CHECK_FUNCS([strdup]) + +AC_CONFIG_FILES([Makefile]) + +AC_OUTPUT + +AC_MSG_RESULT([ + ${PACKAGE} ${VERSION} + build OS: ${build_os} + prefix: ${prefix} + + has go: ${has_go} +]) diff --git a/hack/verify-gofmt.sh b/hack/verify-gofmt.sh new file mode 100755 index 0000000..7bf16bb --- /dev/null +++ b/hack/verify-gofmt.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -o errexit +set -o nounset +set -o pipefail + +find_files() { + find . -not \( \ + \( \ + -wholename '*/Godeps/*' \ + \) -prune \ + \) -name '*.go' +} + +GOFMT="gofmt -s" +bad_files=$(find_files | xargs $GOFMT -l) +if [[ -n "${bad_files}" ]]; then + echo "!!! '$GOFMT' needs to be run on the following files: " + echo "${bad_files}" + exit 1 +fi diff --git a/pkg/apiserver/api_installer.go b/pkg/apiserver/api_installer.go new file mode 100644 index 0000000..4f00fa2 --- /dev/null +++ b/pkg/apiserver/api_installer.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package apiserver contains the code that provides a rest.ful api service. +package apiserver diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go new file mode 100644 index 0000000..4f00fa2 --- /dev/null +++ b/pkg/apiserver/apiserver.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package apiserver contains the code that provides a rest.ful api service. +package apiserver diff --git a/pkg/apiserver/doc.go b/pkg/apiserver/doc.go new file mode 100644 index 0000000..4f00fa2 --- /dev/null +++ b/pkg/apiserver/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package apiserver contains the code that provides a rest.ful api service. +package apiserver diff --git a/pkg/apiserver/handlers.go b/pkg/apiserver/handlers.go new file mode 100644 index 0000000..2b4b0fe --- /dev/null +++ b/pkg/apiserver/handlers.go @@ -0,0 +1,17 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package apiserver diff --git a/pkg/port/iscsit/auth.go b/pkg/port/iscsit/auth.go new file mode 100644 index 0000000..96aa347 --- /dev/null +++ b/pkg/port/iscsit/auth.go @@ -0,0 +1,24 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package iscsit + +type AuthMethod int + +const ( + AuthNone = iota + AuthChap +) diff --git a/pkg/port/iscsit/iscsit.go b/pkg/port/iscsit/iscsit.go new file mode 100644 index 0000000..3165bb8 --- /dev/null +++ b/pkg/port/iscsit/iscsit.go @@ -0,0 +1,84 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// iSCSI Target Driver +package iscsit + +type ISCSIDiscoveryMethod string + +var ( + ISCSIDiscoverySendTargets ISCSIDiscoveryMethod = "sendtargets" + ISCSIDiscoveryStaticConfig ISCSIDiscoveryMethod = "static" + ISCSIDiscoveryISNS ISCSIDiscoveryMethod = "isns" +) + +type ISCSIRedirectInfo struct { + Address string + Port int + Reason uint8 + Callback string +} + +type ISCSITarget struct { + Sessions []*ISCSISession + SessionParam []ISCSISessionParam + TID int + Alias string + MaxSessions int + RedirectInfo ISCSIRedirectInfo + Rdma int + NopInterval int + NopCount int +} + +type ISCSITargetDriver struct { + SCSITargetDriver +} + +func (tgt *ISCSITargetDriver) Init() error { + return nil +} + +func (tgt *ISCSITargetDriver) Exit() error { + return nil +} + +func (tgt *ISCSITargetDriver) CreateTarget(target *SCSITarget) error { + return nil +} + +func (tgt *ISCSITargetDriver) DestroyTarget(target *SCSITarget) error { + return nil +} + +func (tgt *ISCSITargetDriver) CreatePortal(name string) error { + return nil +} + +func (tgt *ISCSITargetDriver) DestroyPortal(name string) error { + return nil +} + +func (tgt *ISCSITargetDriver) CreateLu(lu *SCSILu) error { + return nil +} + +func (tgt *ISCSITargetDriver) GetLun(lun uint8) (uint64, error) { + return 0, nil +} +func (tgt *ISCSITargetDriver) CommandNotify(nid uint64, result int, cmd *SCSICommand) error { + return nil +} diff --git a/pkg/port/iscsit/login.go b/pkg/port/iscsit/login.go new file mode 100644 index 0000000..b30d08e --- /dev/null +++ b/pkg/port/iscsit/login.go @@ -0,0 +1,85 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package iscsit + +import "bytes" + +func (m *Message) loginRespBytes() []byte { + // rfc7143 11.13 + buf := &bytes.Buffer{} + // byte 0 + buf.WriteByte(byte(OpLoginResp)) + var b byte + if m.Transit { + b |= 0x80 + } + if m.Cont { + b |= 0x40 + } + b |= byte(m.CSG&0xff) << 2 + b |= byte(m.NSG & 0xff) + // byte 1 + buf.WriteByte(b) + + b = 0 + buf.WriteByte(b) // version-max + buf.WriteByte(b) // version-active + buf.WriteByte(b) // ahsLen + buf.Write(MarshalUint64(uint64(len(m.RawData)))[5:]) // data segment length, no padding + buf.Write(MarshalUint64(m.ISID)[2:]) + buf.Write(MarshalUint64(uint64(m.TSIH))[6:]) + buf.Write(MarshalUint64(uint64(m.TaskTag))[4:]) + buf.WriteByte(b) + buf.WriteByte(b) + buf.WriteByte(b) + buf.WriteByte(b) // "reserved" + buf.Write(MarshalUint64(uint64(m.StatSN))[4:]) + buf.Write(MarshalUint64(uint64(m.ExpCmdSN))[4:]) + buf.Write(MarshalUint64(uint64(m.MaxCmdSN))[4:]) + buf.WriteByte(byte(m.StatusClass)) + buf.WriteByte(byte(m.StatusDetail)) + buf.WriteByte(b) + buf.WriteByte(b) // "reserved" + var bs [8]byte + buf.Write(bs[:]) + rd := m.RawData + for len(rd)%4 != 0 { + rd = append(rd, 0) + } + buf.Write(rd) + return buf.Bytes() +} + +type Stage int + +const ( + SecurityNegotiation Stage = 0 + LoginOperationalNegotiation = 1 + FullFeaturePhase = 3 +) + +func (s Stage) String() string { + switch s { + case SecurityNegotiation: + return "Security Negotiation" + case LoginOperationalNegotiation: + return "Login Operational Negotiation" + case FullFeaturePhase: + return "Full Feature Phase" + } + return "Unknown Stage" +} diff --git a/pkg/port/iscsit/logout.go b/pkg/port/iscsit/logout.go new file mode 100644 index 0000000..a28e48c --- /dev/null +++ b/pkg/port/iscsit/logout.go @@ -0,0 +1,41 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package iscsit + +import "bytes" + +func (m *Message) logoutRespBytes() []byte { + buf := &bytes.Buffer{} + buf.WriteByte(byte(OpLogoutResp)) + buf.WriteByte(0x80) + buf.WriteByte(0x00) // response + buf.WriteByte(0x00) + for i := 4; i < 16; i++ { + buf.WriteByte(0x00) + } + buf.Write(MarshalUint64(uint64(m.TaskTag))[4:]) + for i := 20; i < 24; i++ { + buf.WriteByte(0x00) + } + buf.Write(MarshalUint64(uint64(m.StatSN))[4:]) + buf.Write(MarshalUint64(uint64(m.ExpCmdSN))[4:]) + buf.Write(MarshalUint64(uint64(m.MaxCmdSN))[4:]) + for i := 36; i < 48; i++ { + buf.WriteByte(0x00) + } + return buf.Bytes() +} diff --git a/pkg/port/iscsit/packet.go b/pkg/port/iscsit/packet.go new file mode 100644 index 0000000..e502051 --- /dev/null +++ b/pkg/port/iscsit/packet.go @@ -0,0 +1,276 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package packet implements the iSCSI PDU packet format as specified in +// rfc7143 section 11. +package iscsit + +import ( + "fmt" + "io" + "strings" +) + +type OpCode int + +const ( + // Defined on the initiator. + OpNoopOut OpCode = 0x00 + OpSCSICmd = 0x01 + OpSCSITaskReq = 0x02 + OpLoginReq = 0x03 + OpTextReq = 0x04 + OpSCSIOut = 0x05 + OpLogoutReq = 0x06 + OpSNACKReq = 0x10 + // Defined on the target. + OpNoopIn OpCode = 0x20 + OpSCSIResp = 0x21 + OpSCSITaskResp = 0x22 + OpLoginResp = 0x23 + OpTextResp = 0x24 + OpSCSIIn = 0x25 + OpLogoutResp = 0x26 + OpReady = 0x31 + OpAsync = 0x32 + OpReject = 0x3f +) + +var opCodeMap = map[OpCode]string{ + OpNoopOut: "NOP-Out", + OpSCSICmd: "SCSI Command", + OpSCSITaskReq: "SCSI Task Management FunctionRequest", + OpLoginReq: "Login Request", + OpTextReq: "Text Request", + OpSCSIOut: "SCSI Data-Out (write)", + OpLogoutReq: "Logout Request", + OpSNACKReq: "SNACK Request", + OpNoopIn: "NOP-In", + OpSCSIResp: "SCSI Response", + OpSCSITaskResp: "SCSI Task Management Function Response", + OpLoginResp: "Login Response", + OpTextResp: "Text Response", + OpSCSIIn: "SCSI Data-In (read)", + OpLogoutResp: "Logout Response", + OpReady: "Ready To Transfer (R2T)", + OpAsync: "Asynchronous Message", + OpReject: "Reject", +} + +func (c OpCode) String() string { + s := opCodeMap[c] + if s == "" { + s = fmt.Sprintf("Unknown Code: %x", int(c)) + } + return s +} + +type Message struct { + OpCode OpCode + RawHeader []byte + DataLen int + RawData []byte + Final bool + Immediate bool + TaskTag uint32 + ExpCmdSN, MaxCmdSN uint32 + AHSLen int + + ConnID uint16 // Connection ID. + CmdSN uint32 // Command serial number. + ExpStatSN uint32 // Expected status serial. + + Read, Write bool + LUN uint8 + Transit bool // Transit bit. + Cont bool // Continue bit. + CSG, NSG Stage // Current Stage, Next Stage. + ISID uint64 // Initiator part of the SSID. + TSIH uint16 // Target-assigned Session Identifying Handle. + StatSN uint32 // Status serial number. + + // For login response. + StatusClass uint8 + StatusDetail uint8 + + // SCSI commands + ExpectedDataLen uint32 + CDB []byte + Status Status + SCSIResponse Response + + // Data-In + HasStatus bool + DataSN uint32 + BufferOffset uint32 +} + +func (m *Message) Bytes() []byte { + switch m.OpCode { + case OpLoginResp: + return m.loginRespBytes() + case OpLogoutResp: + return m.logoutRespBytes() + case OpSCSIResp: + return m.scsiCmdRespBytes() + case OpSCSIIn: + return m.dataInBytes() + } + return nil +} + +func (m *Message) String() string { + var s []string + s = append(s, fmt.Sprintf("Op: %v", m.OpCode)) + s = append(s, fmt.Sprintf("Final = %v", m.Final)) + s = append(s, fmt.Sprintf("Immediate = %v", m.Immediate)) + s = append(s, fmt.Sprintf("Data Segment Length = %d", m.DataLen)) + s = append(s, fmt.Sprintf("Task Tag = %x", m.TaskTag)) + s = append(s, fmt.Sprintf("AHS Length = %d", m.AHSLen)) + switch m.OpCode { + case OpLoginReq: + s = append(s, fmt.Sprintf("ISID = %x", m.ISID)) + s = append(s, fmt.Sprintf("Transit = %v", m.Transit)) + s = append(s, fmt.Sprintf("Continue = %v", m.Cont)) + s = append(s, fmt.Sprintf("Current Stage = %v", m.CSG)) + s = append(s, fmt.Sprintf("Next Stage = %v", m.NSG)) + case OpLoginResp: + s = append(s, fmt.Sprintf("ISID = %x", m.ISID)) + s = append(s, fmt.Sprintf("Transit = %v", m.Transit)) + s = append(s, fmt.Sprintf("Continue = %v", m.Cont)) + s = append(s, fmt.Sprintf("Current Stage = %v", m.CSG)) + 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: + 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)) + s = append(s, fmt.Sprintf("ExpStatSN = %d", m.ExpStatSN)) + s = append(s, fmt.Sprintf("Read = %v", m.Read)) + s = append(s, fmt.Sprintf("Write = %v", m.Write)) + s = append(s, fmt.Sprintf("CDB = %x", m.CDB)) + case OpSCSIResp: + s = append(s, fmt.Sprintf("StatSN = %d", m.StatSN)) + s = append(s, fmt.Sprintf("ExpCmdSN = %d", m.ExpCmdSN)) + s = append(s, fmt.Sprintf("MaxCmdSN = %d", m.MaxCmdSN)) + } + return strings.Join(s, "\n") +} + +// Response composes a reply to the given message with the appropriate bits set. +func (m *Message) Response(r *Message) { + r.TaskTag = m.TaskTag + r.ConnID = m.ConnID + r.ISID = m.ISID +} + +func Next(r io.Reader) (*Message, error) { + buf := make([]byte, 48) // TODO: sync.Pool + if _, err := io.ReadFull(r, buf); err != nil { + return nil, err + } + m, err := parseHeader(buf) + if err != nil { + return nil, err + } + m.RawHeader = buf + if m.DataLen > 0 { + dl := m.DataLen + for dl%4 > 0 { + dl++ + } + data := make([]byte, dl) + if _, err := io.ReadFull(r, data); err != nil { + return nil, err + } + m.RawData = data[:m.DataLen] + } + return m, nil +} + +// parseUint parses the given slice as a network-byte-ordered integer. If +// there are more than 8 bytes in data, it overflows. +func ParseUint(data []byte) uint64 { + var out uint64 + for i := 0; i < len(data); i++ { + out += uint64(data[len(data)-i-1]) << uint(8*i) + } + return out +} + +func MarshalUint64(i uint64) []byte { + var data []byte + for j := 0; j < 8; j++ { + b := byte(i >> uint(8*(7-j)) & 0xff) + data = append(data, b) + } + return data +} + +func parseHeader(data []byte) (*Message, error) { + if len(data) != 48 { + return nil, fmt.Errorf("garbled header") + } + // TODO: sync.Pool + m := &Message{} + m.Immediate = 0x40&data[0] == 0x40 + m.OpCode = OpCode(data[0] & 0x3f) + m.Final = 0x80&data[1] == 0x80 + m.AHSLen = int(data[4]) * 4 + m.DataLen = int(ParseUint(data[5:8])) + m.TaskTag = uint32(ParseUint(data[16:20])) + switch m.OpCode { + case OpSCSICmd: + m.LUN = uint8(data[9]) + m.ExpectedDataLen = uint32(ParseUint(data[20:24])) + m.CmdSN = uint32(ParseUint(data[24:28])) + m.Read = data[1]&0x40 == 0x40 + m.Write = data[1]&0x20 == 0x20 + m.CDB = data[32:48] + m.ExpStatSN = uint32(ParseUint(data[28:32])) + case OpSCSIResp: + case OpLoginReq: + m.Transit = m.Final + m.Cont = data[1]&0x40 == 0x40 + if m.Cont && m.Transit { + // rfc7143 11.12.2 + return nil, fmt.Errorf("transit and continue bits set in same login request") + } + m.CSG = Stage(data[1]&0xc) >> 2 + m.NSG = Stage(data[1] & 0x3) + m.ISID = uint64(ParseUint(data[8:14])) + m.TSIH = uint16(ParseUint(data[14:16])) + m.ConnID = uint16(ParseUint(data[20:22])) + m.CmdSN = uint32(ParseUint(data[24:28])) + m.ExpStatSN = uint32(ParseUint(data[28:32])) + case OpLoginResp: + m.Transit = m.Final + m.Cont = data[1]&0x40 == 0x40 + if m.Cont && m.Transit { + // rfc7143 11.12.2 + return nil, fmt.Errorf("transit and continue bits set in same login request") + } + m.CSG = Stage(data[1]&0xc) >> 2 + m.NSG = Stage(data[1] & 0x3) + m.StatSN = uint32(ParseUint(data[24:28])) + m.ExpCmdSN = uint32(ParseUint(data[28:32])) + m.MaxCmdSN = uint32(ParseUint(data[32:36])) + m.StatusClass = uint8(data[36]) + m.StatusDetail = uint8(data[37]) + } + return m, nil +} diff --git a/pkg/port/iscsit/portal.go b/pkg/port/iscsit/portal.go new file mode 100644 index 0000000..2d8033e --- /dev/null +++ b/pkg/port/iscsit/portal.go @@ -0,0 +1,25 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package iscsit + +type ISCSIPortal struct { + Address string + Port int + Tpgt int + Fd int + Af int +} diff --git a/pkg/port/iscsit/session.go b/pkg/port/iscsit/session.go new file mode 100644 index 0000000..8705f11 --- /dev/null +++ b/pkg/port/iscsit/session.go @@ -0,0 +1,84 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package iscsit + +import "crypto/rand" + +type ISCSISessionParam struct { + State int + Value uint +} + +// Session is an iSCSI session. +type ISCSISession struct { + Refcount int + Initiator string + InitiatorAlias string + Target *ISCSITarget + Isid uint64 + Tsih uint16 + // only one connection per session + Connections []*ISCSIConnection + Commands []*ISCSICommand + PendingCommands []*ISCSICommand + ExpectionCommandSN uint32 + MaxQueueCommand uint32 + SessionParam []ISCSISessionParam + Info string + Rdma int +} + +type ISCSIPdu struct { + Bhs ISCSIHeader + AhsSize uint + DataSize uint +} + +type ISCSIConnection struct { + State int + RxIostate int + TxIostate int + Refcount int + + Session *ISCSISession + + TID int + CID int + Auth AuthMethod + StatSN uint32 + ExpectionStatSN uint32 + CommandSN uint32 + ExpectionCommandSN uint32 + MaxCommandSN uint32 + Request ISCSIPdu + Response ISCSIPdu +} + +// New creates a new session. +func NewISCSISession() (*ISCSISession, error) { + var tsih uint16 + b := make([]byte, 2) + if _, err := rand.Read(b); err != nil { + return nil, err + } + tsih += uint16(b[0]) << 8 + tsih += uint16(b[1]) + + return &Session{ + Tsih: tsih, + }, nil +} diff --git a/pkg/scsi/backingstore.go b/pkg/scsi/backingstore.go new file mode 100644 index 0000000..6747ad8 --- /dev/null +++ b/pkg/scsi/backingstore.go @@ -0,0 +1,76 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scsi + +import "fmt" + +type BaseBackingStore struct { + Name string + DataSize int + OflagsSupported int +} + +type BackingStore interface { + Open(dev *SCSILu, path string, fd *int, size *uint64) error + Close(dev *SCSILu) error + Init(dev *SCSILu, Opts string) error + Exit(dev *SCSILu) error + CommandSubmit(cmd *SCSICommand) error +} + +type BackingStoreFunc func() (BackingStore, error) + +var registeredPlugins = map[name](BackingStoreFunc){} + +func RegisterBackingStore(name string, f BackingStoreFunc) { + registeredPlugins[name] = f +} + +func NewBackingStore(name string) (BackingStore, error) { + if name == "" { + return nil, nil + } + f, ok := registeredPlugins[name] + if !ok { + return nil, fmt.Errorf("BackingStore %s is not found.", name) + } + return f() +} + +type fakeBackingStore struct { + BaseBackingStore +} + +func (fake *fakeBackingStore) Open(dev *SCSILu, path string, fd *int, size *uint64) error { + return nil +} + +func (fake *fakeBackingStore) Close(dev *SCSILu) error { + return nil +} + +func (fake *fakeBackingStore) Init(dev *SCSILu, Opts string) error { + return nil +} + +func (fake *fakeBackingStore) Exit(dev *SCSILu) error { + return nil +} + +func (fake *fakeBackingStore) CommandSubmit(cmd *SCSICommand) error { + return nil +} diff --git a/pkg/scsi/backingstore/common.go b/pkg/scsi/backingstore/common.go new file mode 100644 index 0000000..411793e --- /dev/null +++ b/pkg/scsi/backingstore/common.go @@ -0,0 +1,55 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package backingstore + +import "github.com/gostor/gotgt/pkg/scsi" + +func init() { + scsi.RegisterBackingStore("file", new) +} + +type FileBackingStore struct { + BaseBackingStore +} + +func new() (scsi.BackingStore, error) { + return NullBackingStore{ + Name: "file", + DataSize: 0, + OflagsSupported: 0, + }, nil +} + +func (bs *FileBackingStore) Open(dev *SCSILu, path string, fd *int, size *uint64) error { + return nil +} + +func (bs *FileBackingStore) Close(dev *SCSILu) error { + return nil +} + +func (bs *FileBackingStore) Init(dev *SCSILu, Opts string) error { + return nil +} + +func (bs *FileBackingStore) Exit(dev *SCSILu) error { + return nil +} + +func (bs *FileBackingStore) CommandSubmit(cmd *SCSICommand) error { + return nil +} diff --git a/pkg/scsi/backingstore/null.go b/pkg/scsi/backingstore/null.go new file mode 100644 index 0000000..c2fb4bb --- /dev/null +++ b/pkg/scsi/backingstore/null.go @@ -0,0 +1,55 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package backingstore + +import "github.com/gostor/gotgt/pkg/scsi" + +func init() { + scsi.RegisterBackingStore("null", new) +} + +type NullBackingStore struct { + BaseBackingStore +} + +func new() (scsi.BackingStore, error) { + return NullBackingStore{ + Name: "null", + DataSize: 0, + OflagsSupported: 0, + }, nil +} + +func (bs *NullBackingStore) Open(dev *SCSILu, path string, fd *int, size *uint64) error { + return nil +} + +func (bs *NullBackingStore) Close(dev *SCSILu) error { + return nil +} + +func (bs *NullBackingStore) Init(dev *SCSILu, Opts string) error { + return nil +} + +func (bs *NullBackingStore) Exit(dev *SCSILu) error { + return nil +} + +func (bs *NullBackingStore) CommandSubmit(cmd *SCSICommand) error { + return nil +} diff --git a/pkg/scsi/cmd.go b/pkg/scsi/cmd.go new file mode 100644 index 0000000..f492d0e --- /dev/null +++ b/pkg/scsi/cmd.go @@ -0,0 +1,192 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scsi + +type SCSICommandType byte + +var ( + TEST_UNIT_READY SCSICommandType = 0x00 + REZERO_UNIT SCSICommandType = 0x01 + REQUEST_SENSE SCSICommandType = 0x03 + FORMAT_UNIT SCSICommandType = 0x04 + READ_BLOCK_LIMITS SCSICommandType = 0x05 + REASSIGN_BLOCKS SCSICommandType = 0x07 + INITIALIZE_ELEMENT_STATUS SCSICommandType = 0x07 + READ_6 SCSICommandType = 0x08 + WRITE_6 SCSICommandType = 0x0a + SEEK_6 SCSICommandType = 0x0b + READ_REVERSE SCSICommandType = 0x0f + WRITE_FILEMARKS SCSICommandType = 0x10 + SPACE SCSICommandType = 0x11 + INQUIRY SCSICommandType = 0x12 + RECOVER_BUFFERED_DATA SCSICommandType = 0x14 + MODE_SELECT SCSICommandType = 0x15 + RESERVE SCSICommandType = 0x16 + RELEASE SCSICommandType = 0x17 + COPY SCSICommandType = 0x18 + ERASE SCSICommandType = 0x19 + MODE_SENSE SCSICommandType = 0x1a + START_STOP SCSICommandType = 0x1b + RECEIVE_DIAGNOSTIC SCSICommandType = 0x1c + SEND_DIAGNOSTIC SCSICommandType = 0x1d + ALLOW_MEDIUM_REMOVAL SCSICommandType = 0x1e + + SET_WINDOW SCSICommandType = 0x24 + READ_CAPACITY SCSICommandType = 0x25 + READ_10 SCSICommandType = 0x28 + WRITE_10 SCSICommandType = 0x2a + SEEK_10 SCSICommandType = 0x2b + POSITION_TO_ELEMENT SCSICommandType = 0x2b + WRITE_VERIFY SCSICommandType = 0x2e + VERIFY_10 SCSICommandType = 0x2f + SEARCH_HIGH SCSICommandType = 0x30 + SEARCH_EQUAL SCSICommandType = 0x31 + SEARCH_LOW SCSICommandType = 0x32 + SET_LIMITS SCSICommandType = 0x33 + PRE_FETCH_10 SCSICommandType = 0x34 + READ_POSITION SCSICommandType = 0x34 + SYNCHRONIZE_CACHE SCSICommandType = 0x35 + LOCK_UNLOCK_CACHE SCSICommandType = 0x36 + READ_DEFECT_DATA SCSICommandType = 0x37 + INITIALIZE_ELEMENT_STATUS_WITH_RANGE SCSICommandType = 0x37 + MEDIUM_SCAN SCSICommandType = 0x38 + COMPARE SCSICommandType = 0x39 + COPY_VERIFY SCSICommandType = 0x3a + WRITE_BUFFER SCSICommandType = 0x3b + READ_BUFFER SCSICommandType = 0x3c + UPDATE_BLOCK SCSICommandType = 0x3d + READ_LONG SCSICommandType = 0x3e + WRITE_LONG SCSICommandType = 0x3f + CHANGE_DEFINITION SCSICommandType = 0x40 + WRITE_SAME SCSICommandType = 0x41 + UNMAP SCSICommandType = 0x42 + READ_TOC SCSICommandType = 0x43 + GET_CONFIGURATION SCSICommandType = 0x46 + LOG_SELECT SCSICommandType = 0x4c + LOG_SENSE SCSICommandType = 0x4d + READ_DISK_INFO SCSICommandType = 0x51 + READ_TRACK_INFO SCSICommandType = 0x52 + MODE_SELECT_10 SCSICommandType = 0x55 + RESERVE_10 SCSICommandType = 0x56 + RELEASE_10 SCSICommandType = 0x57 + MODE_SENSE_10 SCSICommandType = 0x5a + CLOSE_TRACK SCSICommandType = 0x5b + READ_BUFFER_CAP SCSICommandType = 0x5c + PERSISTENT_RESERVE_IN SCSICommandType = 0x5e + PERSISTENT_RESERVE_OUT SCSICommandType = 0x5f + VARLEN_CDB SCSICommandType = 0x7f + READ_16 SCSICommandType = 0x88 + COMPARE_AND_WRITE SCSICommandType = 0x89 + WRITE_16 SCSICommandType = 0x8a + ORWRITE_16 SCSICommandType = 0x8b + WRITE_VERIFY_16 SCSICommandType = 0x8e + VERIFY_16 SCSICommandType = 0x8f + PRE_FETCH_16 SCSICommandType = 0x90 + SYNCHRONIZE_CACHE_16 SCSICommandType = 0x91 + WRITE_SAME_16 SCSICommandType = 0x93 + SERVICE_ACTION_IN SCSICommandType = 0x9e + SAI_READ_CAPACITY_16 SCSICommandType = 0x10 + SAI_GET_LBA_STATUS SCSICommandType = 0x12 + REPORT_LUNS SCSICommandType = 0xa0 + MAINT_PROTOCOL_IN SCSICommandType = 0xa3 + MOVE_MEDIUM SCSICommandType = 0xa5 + EXCHANGE_MEDIUM SCSICommandType = 0xa6 + READ_12 SCSICommandType = 0xa8 + WRITE_12 SCSICommandType = 0xaa + GET_PERFORMACE SCSICommandType = 0xac + READ_DVD_STRUCTURE SCSICommandType = 0xad + WRITE_VERIFY_12 SCSICommandType = 0xae + VERIFY_12 SCSICommandType = 0xaf + SEARCH_HIGH_12 SCSICommandType = 0xb0 + SEARCH_EQUAL_12 SCSICommandType = 0xb1 + SEARCH_LOW_12 SCSICommandType = 0xb2 + READ_ELEMENT_STATUS SCSICommandType = 0xb8 + SEND_VOLUME_TAG SCSICommandType = 0xb6 + SET_STREAMING SCSICommandType = 0xb6 + SET_CD_SPEED SCSICommandType = 0xbb + WRITE_LONG_2 SCSICommandType = 0xea +) + +type SCSIPRServiceAction byte +type SCSIPRType byte + +var ( + /* PERSISTENT_RESERVE_IN service action codes */ + PR_IN_READ_KEYS SCSIPRServiceAction = 0x00 + PR_IN_READ_RESERVATION SCSIPRServiceAction = 0x01 + PR_IN_REPORT_CAPABILITIES SCSIPRServiceAction = 0x02 + PR_IN_READ_FULL_STATUS SCSIPRServiceAction = 0x03 + + /* PERSISTENT_RESERVE_OUT service action codes */ + PR_OUT_REGISTER SCSIPRServiceAction = 0x00 + PR_OUT_RESERVE SCSIPRServiceAction = 0x01 + PR_OUT_RELEASE SCSIPRServiceAction = 0x02 + PR_OUT_CLEAR SCSIPRServiceAction = 0x03 + PR_OUT_PREEMPT SCSIPRServiceAction = 0x04 + PR_OUT_PREEMPT_AND_ABORT SCSIPRServiceAction = 0x05 + PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY SCSIPRServiceAction = 0x06 + PR_OUT_REGISTER_AND_MOVE SCSIPRServiceAction = 0x07 + + /* Persistent Reservation scope */ + PR_LU_SCOPE byte = 0x00 + + /* Persistent Reservation Type Mask format */ + PR_TYPE_WRITE_EXCLUSIVE SCSIPRType = 0x01 + PR_TYPE_EXCLUSIVE_ACCESS SCSIPRType = 0x03 + PR_TYPE_WRITE_EXCLUSIVE_REGONLY SCSIPRType = 0x05 + PR_TYPE_EXCLUSIVE_ACCESS_REGONLY SCSIPRType = 0x06 + PR_TYPE_WRITE_EXCLUSIVE_ALLREG SCSIPRType = 0x07 + PR_TYPE_EXCLUSIVE_ACCESS_ALLREG SCSIPRType = 0x08 +) + +type SCSIDataDirection int + +const ( + SCSIDataNone = iota + SCSIDataWrite + SCSIDataRead + SCSIDataBidirection +) + +type SCSIDataBuffer struct { + Buffer uint64 + Length uint64 + TransferLength uint32 + Resid int32 +} + +type SCSICommand struct { + Target *SCSITarget + DeviceID uint64 + Device *SCSILu + State uint64 + Direction SCSIDataDirection + InSDBBuffer *SCSIDataBuffer + OutSDBBuffer *SCSIDataBuffer + // Command ITN ID + CommandITNID uint64 + Offset uint64 + TL uint32 + SCB *[]byte + SCBLength int + Lun []uint8 + Attribute int + Tag uint64 + Result int + SenseBuffer []byte + SenseLength int +} diff --git a/pkg/scsi/drivers.go b/pkg/scsi/drivers.go new file mode 100644 index 0000000..a8808b6 --- /dev/null +++ b/pkg/scsi/drivers.go @@ -0,0 +1,89 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Target Driver Interface +package scsi + +var SCSITargetDriverState int + +const ( + // just registered + SCSI_DRIVER_REGD = iota + // initialized ok + SCSI_DRIVER_INIT + // failed to initialize + SCSI_DRIVER_ERR + // exited + SCSI_DRIVER_EXIT +) + +type SCSITargetDriver struct { + Name string + State SCSITargetDriverState + DefaultBST string + Targets []*SCSITarget +} + +type SCSITargetDriverOps interface { + Init() error + Exit() error + + CreateTarget(target *SCSITarget) error + DestroyTarget(target *SCSITarget) error + CreatePortal(name string) error + DestroyPortal(name string) error + CreateLu(lu *SCSILu) error + + GetLun(lun uint8) (uint64, error) + CommandNotify(nid uint64, result int, cmd *SCSICommand) error +} + +var fakeSCSITargetDriver SCSITargetDriver + +func (fake *fakeSCSITargetDriver) Init() error { + return nil +} + +func (fake *fakeSCSITargetDriver) Exit() error { + return nil +} + +func (fake *fakeSCSITargetDriver) CreateTarget(target *SCSITarget) error { + return nil +} + +func (fake *fakeSCSITargetDriver) DestroyTarget(target *SCSITarget) error { + return nil +} + +func (fake *fakeSCSITargetDriver) CreatePortal(name string) error { + return nil +} + +func (fake *fakeSCSITargetDriver) DestroyPortal(name string) error { + return nil +} + +func (fake *fakeSCSITargetDriver) CreateLu(lu *SCSILu) error { + return nil +} + +func (fake *fakeSCSITargetDriver) GetLun(lun uint8) (uint64, error) { + return 0, nil +} +func (fake *fakeSCSITargetDriver) CommandNotify(nid uint64, result int, cmd *SCSICommand) error { + return nil +} diff --git a/pkg/scsi/error.go b/pkg/scsi/error.go new file mode 100644 index 0000000..357f9c9 --- /dev/null +++ b/pkg/scsi/error.go @@ -0,0 +1,123 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scsi + +type SCSIError byte + +var ( + NO_SENSE SCSIError = 0x00 + RECOVERED_ERROR SCSIError = 0x01 + NOT_READY SCSIError = 0x02 + MEDIUM_ERROR SCSIError = 0x03 + HARDWARE_ERROR SCSIError = 0x04 + ILLEGAL_REQUEST SCSIError = 0x05 + UNIT_ATTENTION SCSIError = 0x06 + DATA_PROTECT SCSIError = 0x07 + BLANK_CHECK SCSIError = 0x08 + COPY_ABORTED SCSIError = 0x0a + ABORTED_COMMAND SCSIError = 0x0b + VOLUME_OVERFLOW SCSIError = 0x0d + MISCOMPARE SCSIError = 0x0e +) + +type SCSISubError uint16 + +var ( + /* Key 0: No Sense Errors */ + NO_ADDITIONAL_SENSE SCSISubError = 0x0000 + ASC_MARK SCSISubError = 0x0001 + ASC_EOM SCSISubError = 0x0002 + ASC_BOM SCSISubError = 0x0004 + ASC_END_OF_DATA SCSISubError = 0x0005 + ASC_OP_IN_PROGRESS SCSISubError = 0x0016 + ASC_DRIVE_REQUIRES_CLEANING SCSISubError = 0x8282 + + /* Key 1: Recovered Errors */ + ASC_WRITE_ERROR SCSISubError = 0x0c00 + ASC_READ_ERROR SCSISubError = 0x1100 + ASC_RECOVERED_WITH_RETRYS SCSISubError = 0x1701 + ASC_MEDIA_LOAD_EJECT_ERROR SCSISubError = 0x5300 + ASC_FAILURE_PREDICTION SCSISubError = 0x5d00 + + /* Key 2: Not ready */ + ASC_CAUSE_NOT_REPORTABLE SCSISubError = 0x0400 + ASC_BECOMING_READY SCSISubError = 0x0401 + ASC_INITIALIZING_REQUIRED SCSISubError = 0x0402 + ASC_CLEANING_CART_INSTALLED SCSISubError = 0x3003 + ASC_CLEANING_FAILURE SCSISubError = 0x3007 + ASC_MEDIUM_NOT_PRESENT SCSISubError = 0x3a00 + ASC_LOGICAL_UNIT_NOT_CONFIG SCSISubError = 0x3e00 + + /* Key 3: Medium Errors */ + ASC_WRITE_ERROR SCSISubError = 0x0c00 + ASC_UNRECOVERED_READ SCSISubError = 0x1100 + ASC_RECORDED_ENTITY_NOT_FOUND SCSISubError = 0x1400 + ASC_UNKNOWN_FORMAT SCSISubError = 0x3001 + ASC_IMCOMPATIBLE_FORMAT SCSISubError = 0x3002 + ASC_MEDIUM_FORMAT_CORRUPT SCSISubError = 0x3100 + ASC_SEQUENTIAL_POSITION_ERR SCSISubError = 0x3b00 + ASC_WRITE_APPEND_ERR SCSISubError = 0x5000 + ASC_CARTRIDGE_FAULT SCSISubError = 0x5200 + ASC_MEDIA_LOAD_OR_EJECT_FAILED SCSISubError = 0x5300 + + /* Key 4: Hardware Failure */ + ASC_COMPRESSION_CHECK SCSISubError = 0x0c04 + ASC_DECOMPRESSION_CRC SCSISubError = 0x110d + ASC_MECHANICAL_POSITIONING_ERROR SCSISubError = 0x1501 + ASC_MANUAL_INTERVENTION_REQ SCSISubError = 0x0403 + ASC_HARDWARE_FAILURE SCSISubError = 0x4000 + ASC_INTERNAL_TGT_FAILURE SCSISubError = 0x4400 + ASC_ERASE_FAILURE SCSISubError = 0x5100 + + /* Key 5: Illegal Request */ + ASC_PARAMETER_LIST_LENGTH_ERR SCSISubError = 0x1a00 + ASC_INVALID_OP_CODE SCSISubError = 0x2000 + ASC_LBA_OUT_OF_RANGE SCSISubError = 0x2100 + ASC_INVALID_FIELD_IN_CDB SCSISubError = 0x2400 + ASC_LUN_NOT_SUPPORTED SCSISubError = 0x2500 + ASC_INVALID_FIELD_IN_PARMS SCSISubError = 0x2600 + ASC_INVALID_RELEASE_OF_PERSISTENT_RESERVATION SCSISubError = 0x2604 + ASC_INCOMPATIBLE_FORMAT SCSISubError = 0x3005 + ASC_SAVING_PARMS_UNSUP SCSISubError = 0x3900 + ASC_MEDIUM_DEST_FULL SCSISubError = 0x3b0d + ASC_MEDIUM_SRC_EMPTY SCSISubError = 0x3b0e + ASC_POSITION_PAST_BOM SCSISubError = 0x3b0c + ASC_MEDIUM_REMOVAL_PREVENTED SCSISubError = 0x5302 + ASC_INSUFFICENT_REGISTRATION_RESOURCES SCSISubError = 0x5504 + ASC_BAD_MICROCODE_DETECTED SCSISubError = 0x8283 + + /* Key 6: Unit Attention */ + ASC_NOT_READY_TO_TRANSITION SCSISubError = 0x2800 + ASC_POWERON_RESET SCSISubError = 0x2900 + ASC_I_T_NEXUS_LOSS_OCCURRED SCSISubError = 0x2907 + ASC_MODE_PARAMETERS_CHANGED SCSISubError = 0x2a01 + ASC_RESERVATIONS_PREEMPTED SCSISubError = 0x2a03 + ASC_RESERVATIONS_RELEASED SCSISubError = 0x2a04 + ASC_INSUFFICIENT_TIME_FOR_OPERATION SCSISubError = 0x2e00 + ASC_CMDS_CLEARED_BY_ANOTHER_INI SCSISubError = 0x2f00 + ASC_MICROCODE_DOWNLOADED SCSISubError = 0x3f01 + ASC_INQUIRY_DATA_HAS_CHANGED SCSISubError = 0x3f03 + ASC_REPORTED_LUNS_DATA_HAS_CHANGED SCSISubError = 0x3f0e + ASC_FAILURE_PREDICTION_FALSE SCSISubError = 0x5dff + + /* Data Protect */ + ASC_WRITE_PROTECT SCSISubError = 0x2700 + ASC_MEDIUM_OVERWRITE_ATTEMPTED SCSISubError = 0x300c + + /* Miscompare */ + ASC_MISCOMPARE_DURING_VERIFY_OPERATION SCSISubError = 0x1d00 +) diff --git a/pkg/scsi/lun.go b/pkg/scsi/lun.go new file mode 100644 index 0000000..d85dfe8 --- /dev/null +++ b/pkg/scsi/lun.go @@ -0,0 +1,29 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scsi + +type SCSILu struct { + FD int + Address uint64 + Size uint64 + Lun uint64 + Path string + BsoFlags int + BlockShift uint + ReserveID uint64 + Target *SCSITarget +} diff --git a/pkg/scsi/sbc.go b/pkg/scsi/sbc.go new file mode 100644 index 0000000..54f7e1e --- /dev/null +++ b/pkg/scsi/sbc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// SCSI block command processing +package scsi diff --git a/pkg/scsi/scsi.go b/pkg/scsi/scsi.go new file mode 100644 index 0000000..fd2e558 --- /dev/null +++ b/pkg/scsi/scsi.go @@ -0,0 +1,60 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scsi + +var ( + DefaultBlockShift int = 9 + DefaultSenseBufferSize int = 252 +) + +type SAMStat byte +type SCSIDeviceType byte + +var ( + SAM_STAT_GOOD SAMStat = 0x00 + SAM_STAT_CHECK_CONDITION SAMStat = 0x02 + SAM_STAT_CONDITION_MET SAMStat = 0x04 + SAM_STAT_BUSY SAMStat = 0x08 + SAM_STAT_INTERMEDIATE SAMStat = 0x10 + SAM_STAT_INTERMEDIATE_CONDITION_MET SAMStat = 0x14 + SAM_STAT_RESERVATION_CONFLICT SAMStat = 0x18 + SAM_STAT_COMMAND_TERMINATED SAMStat = 0x22 + SAM_STAT_TASK_SET_FULL SAMStat = 0x28 + SAM_STAT_ACA_ACTIVE SAMStat = 0x30 + SAM_STAT_TASK_ABORTED SAMStat = 0x40 +) + +var ( + TYPE_DISK SCSIDeviceType = 0x00 + TYPE_TAPE SCSIDeviceType = 0x01 + TYPE_PRINTER SCSIDeviceType = 0x02 + TYPE_PROCESSOR SCSIDeviceType = 0x03 + TYPE_WORM SCSIDeviceType = 0x04 + TYPE_MMC SCSIDeviceType = 0x05 + TYPE_SCANNER SCSIDeviceType = 0x06 + TYPE_MOD SCSIDeviceType = 0x07 + + TYPE_MEDIUM_CHANGER SCSIDeviceType = 0x08 + TYPE_COMM SCSIDeviceType = 0x09 + TYPE_RAID SCSIDeviceType = 0x0c + TYPE_ENCLOSURE SCSIDeviceType = 0x0d + TYPE_RBC SCSIDeviceType = 0x0e + TYPE_OSD SCSIDeviceType = 0x11 + TYPE_NO_LUN SCSIDeviceType = 0x7f + + TYPE_PT SCSIDeviceType = 0xff +) diff --git a/pkg/scsi/spc.go b/pkg/scsi/spc.go new file mode 100644 index 0000000..d7c2136 --- /dev/null +++ b/pkg/scsi/spc.go @@ -0,0 +1,103 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// SCSI primary command processing +package scsi + +/* + * Protocol Identifier Values + * + * 0 Fibre Channel (FCP-2) + * 1 Parallel SCSI (SPI-5) + * 2 SSA (SSA-S3P) + * 3 IEEE 1394 (SBP-3) + * 4 SCSI Remote Direct Memory Access (SRP) + * 5 iSCSI + * 6 SAS Serial SCSI Protocol (SAS) + * 7 Automation/Drive Interface (ADT) + * 8 AT Attachment Interface (ATA/ATAPI-7) + */ +type ProtocolIdentifier int + +var ( + PIV_FCP ProtocolIdentifier = iota + PIV_SPI ProtocolIdentifier + PIV_S3P ProtocolIdentifier + PIV_SBP ProtocolIdentifier + PIV_SRP ProtocolIdentifier + PIV_ISCSI ProtocolIdentifier + PIV_SAS ProtocolIdentifier + PIV_ADT ProtocolIdentifier + PIV_ATA ProtocolIdentifier +) + +/* + * Code Set + * + * 1 - Designator fild contains binary values + * 2 - Designator field contains ASCII printable chars + * 3 - Designaotor field contains UTF-8 + */ +type CodeSet int + +var ( + INQ_CODE_BIN CodeSet = 1 + INQ_CODE_ASCII CodeSet = 2 + INQ_CODE_UTF8 CodeSet = 3 +) + +/* + * Association field + * + * 00b - Associated with Logical Unit + * 01b - Associated with target port + * 10b - Associated with SCSI Target device + * 11b - Reserved + */ +type AssociationField int + +var ( + ASS_LU AssociationField = 0 + ASS_TGT_PORT AssociationField = 0x10 + ASS_TGT_DEV AssociationField = 0x20 +) + +/* + * Designator type - SPC-4 Reference + * + * 0 - Vendor specific - 7.6.3.3 + * 1 - T10 vendor ID - 7.6.3.4 + * 2 - EUI-64 - 7.6.3.5 + * 3 - NAA - 7.6.3.6 + * 4 - Relative Target port identifier - 7.6.3.7 + * 5 - Target Port group - 7.6.3.8 + * 6 - Logical Unit group - 7.6.3.9 + * 7 - MD5 logical unit identifier - 7.6.3.10 + * 8 - SCSI name string - 7.6.3.11 + */ +type DesignatorType int + +var ( + DESG_VENDOR DesignatorType = iota + DESG_T10 DesignatorType + DESG_EUI64 DesignatorType + DESG_NAA DesignatorType + DESG_REL_TGT_PORT DesignatorType + DESG_TGT_PORT_GRP DesignatorType + DESG_LU_GRP DesignatorType + DESG_MD5 DesignatorType + DESG_SCSI DesignatorType +) diff --git a/pkg/scsi/target.go b/pkg/scsi/target.go new file mode 100644 index 0000000..e167c77 --- /dev/null +++ b/pkg/scsi/target.go @@ -0,0 +1,31 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scsi + +var SCSITargetState int + +var ( + TargetOnline SCSITargetState = 1 + TargetReady SCSITargetState = 2 +) + +type SCSITarget struct { + Name string + TID int + LID int + State SCSITargetState +} diff --git a/pkg/util/util.go b/pkg/util/util.go new file mode 100644 index 0000000..afdbae3 --- /dev/null +++ b/pkg/util/util.go @@ -0,0 +1,50 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +// ParseKVText parses iSCSI key value data. +func ParseKVText(txt []byte) map[string]string { + m := make(map[string]string) + var kv, sep int + var key string + for i := 0; i < len(txt); i++ { + if txt[i] == '=' { + if key == "" { + sep = i + key = string(txt[kv:sep]) + } + continue + } + if txt[i] == 0 && key != "" { + m[key] = string(txt[sep+1 : i]) + key = "" + kv = i + 1 + } + } + return m +} + +func MarshalKVText(kv map[string]string) []byte { + var data []byte + for k, v := range kv { + data = append(data, []byte(k)...) + data = append(data, '=') + data = append(data, []byte(v)...) + data = append(data, 0) + } + return data +} diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 0000000..c66d9f9 --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,21 @@ +/* +Copyright 2015 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package version + +const ( + VERSION = "0.1" +)