From 4c813fc7bb6e1bc19e425c9587e4c49bf6e67014 Mon Sep 17 00:00:00 2001 From: Cosmin Cojocar Date: Fri, 19 Oct 2018 22:38:06 +0200 Subject: [PATCH] Update the go-uuid library (#546) Signed-off-by: Cosmin Cojocar --- Gopkg.lock | 6 +- Gopkg.toml | 2 +- pkg/config/config.go | 3 +- pkg/plugin/driver/utils/utils.go | 2 +- .../imdario/mergo/testdata/license.yml | 4 + vendor/github.com/satori/go.uuid/.travis.yml | 6 +- vendor/github.com/satori/go.uuid/README.md | 20 +- vendor/github.com/satori/go.uuid/generator.go | 186 +++++++++------- .../cobra/cobra/cmd/testdata/LICENSE.golden | 202 ++++++++++++++++++ .../tarx/tests/input/symlink.txt | 1 + 10 files changed, 337 insertions(+), 95 deletions(-) create mode 100644 vendor/github.com/imdario/mergo/testdata/license.yml create mode 100644 vendor/github.com/spf13/cobra/cobra/cmd/testdata/LICENSE.golden create mode 120000 vendor/github.com/viniciuschiele/tarx/tests/input/symlink.txt diff --git a/Gopkg.lock b/Gopkg.lock index ebeb5665a..425a43a61 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -308,12 +308,12 @@ version = "v2.3" [[projects]] - digest = "1:274f67cb6fed9588ea2521ecdac05a6d62a8c51c074c1fccc6a49a40ba80e925" + branch = "master" + digest = "1:dd6ba1917df517806c9dcee5c87f15643c9b1ca6260d5b3f25eb863c6fe092ce" name = "github.com/satori/go.uuid" packages = ["."] pruneopts = "UT" - revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3" - version = "v1.2.0" + revision = "8ccf5352a842c034b1a69f28c863aff9b1cdb116" [[projects]] branch = "master" diff --git a/Gopkg.toml b/Gopkg.toml index f2c63c8d5..b57bfcd30 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -43,7 +43,7 @@ [[constraint]] name = "github.com/satori/go.uuid" - version = "1.2.0" + branch = "master" [[constraint]] branch = "master" diff --git a/pkg/config/config.go b/pkg/config/config.go index a45e7c13c..a9efa76e0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -270,7 +270,8 @@ func (c SizeOrTimeLimitConfig) timeLimitDuration() (val time.Duration, defaulted // New returns a newly-constructed Config object with default values. func New() *Config { var cfg Config - cfg.UUID = uuid.NewV4().String() + cfgUuid, _ := uuid.NewV4() + cfg.UUID = cfgUuid.String() cfg.Description = "DEFAULT" cfg.ResultsDir = "/tmp/sonobuoy" cfg.Version = buildinfo.Version diff --git a/pkg/plugin/driver/utils/utils.go b/pkg/plugin/driver/utils/utils.go index e931f7191..be525bfc3 100644 --- a/pkg/plugin/driver/utils/utils.go +++ b/pkg/plugin/driver/utils/utils.go @@ -31,7 +31,7 @@ import ( // GetSessionID generates a new session id. // This is essentially an instance of a running plugin. func GetSessionID() string { - uuid := gouuid.NewV4() + uuid, _ := gouuid.NewV4() ret := make([]byte, hex.EncodedLen(8)) hex.Encode(ret, uuid.Bytes()[0:8]) return string(ret) diff --git a/vendor/github.com/imdario/mergo/testdata/license.yml b/vendor/github.com/imdario/mergo/testdata/license.yml new file mode 100644 index 000000000..2f1ad0082 --- /dev/null +++ b/vendor/github.com/imdario/mergo/testdata/license.yml @@ -0,0 +1,4 @@ +import: ../../../../fossene/db/schema/thing.yml +fields: + site: string + author: root diff --git a/vendor/github.com/satori/go.uuid/.travis.yml b/vendor/github.com/satori/go.uuid/.travis.yml index 20dd53b8d..03ba40dde 100644 --- a/vendor/github.com/satori/go.uuid/.travis.yml +++ b/vendor/github.com/satori/go.uuid/.travis.yml @@ -1,14 +1,12 @@ language: go sudo: false go: - - 1.2 - - 1.3 - - 1.4 - - 1.5 - 1.6 - 1.7 - 1.8 - 1.9 + - "1.10" + - 1.11 - tip matrix: allow_failures: diff --git a/vendor/github.com/satori/go.uuid/README.md b/vendor/github.com/satori/go.uuid/README.md index 7b1a722df..362b27067 100644 --- a/vendor/github.com/satori/go.uuid/README.md +++ b/vendor/github.com/satori/go.uuid/README.md @@ -1,8 +1,8 @@ # UUID package for Go language -[![Build Status](https://travis-ci.org/satori/go.uuid.png?branch=master)](https://travis-ci.org/satori/go.uuid) +[![Build Status](https://travis-ci.org/satori/go.uuid.svg?branch=master)](https://travis-ci.org/satori/go.uuid) [![Coverage Status](https://coveralls.io/repos/github/satori/go.uuid/badge.svg?branch=master)](https://coveralls.io/github/satori/go.uuid) -[![GoDoc](http://godoc.org/github.com/satori/go.uuid?status.png)](http://godoc.org/github.com/satori/go.uuid) +[![GoDoc](http://godoc.org/github.com/satori/go.uuid?status.svg)](http://godoc.org/github.com/satori/go.uuid) This package provides pure Go implementation of Universally Unique Identifier (UUID). Supported both creation and parsing of UUIDs. @@ -23,7 +23,7 @@ Use the `go` command: ## Requirements -UUID package requires Go >= 1.2. +UUID package tested against Go >= 1.6. ## Example @@ -37,13 +37,23 @@ import ( func main() { // Creating UUID Version 4 - u1 := uuid.NewV4() + // panic on error + u1 := uuid.Must(uuid.NewV4()) fmt.Printf("UUIDv4: %s\n", u1) + // or error handling + u2, err := uuid.NewV4() + if err != nil { + fmt.Printf("Something went wrong: %s", err) + return + } + fmt.Printf("UUIDv4: %s\n", u2) + // Parsing UUID from string input u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") if err != nil { - fmt.Printf("Something gone wrong: %s", err) + fmt.Printf("Something went wrong: %s", err) + return } fmt.Printf("Successfully parsed: %s", u2) } diff --git a/vendor/github.com/satori/go.uuid/generator.go b/vendor/github.com/satori/go.uuid/generator.go index 3f2f1da2d..c50d33cab 100644 --- a/vendor/github.com/satori/go.uuid/generator.go +++ b/vendor/github.com/satori/go.uuid/generator.go @@ -26,7 +26,9 @@ import ( "crypto/rand" "crypto/sha1" "encoding/binary" + "fmt" "hash" + "io" "net" "os" "sync" @@ -37,21 +39,23 @@ import ( // UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). const epochStart = 122192928000000000 +type epochFunc func() time.Time +type hwAddrFunc func() (net.HardwareAddr, error) + var ( - global = newDefaultGenerator() + global = newRFC4122Generator() - epochFunc = unixTimeFunc - posixUID = uint32(os.Getuid()) - posixGID = uint32(os.Getgid()) + posixUID = uint32(os.Getuid()) + posixGID = uint32(os.Getgid()) ) // NewV1 returns UUID based on current timestamp and MAC address. -func NewV1() UUID { +func NewV1() (UUID, error) { return global.NewV1() } // NewV2 returns DCE Security UUID based on POSIX UID/GID. -func NewV2(domain byte) UUID { +func NewV2(domain byte) (UUID, error) { return global.NewV2(domain) } @@ -61,7 +65,7 @@ func NewV3(ns UUID, name string) UUID { } // NewV4 returns random generated UUID. -func NewV4() UUID { +func NewV4() (UUID, error) { return global.NewV4() } @@ -72,74 +76,85 @@ func NewV5(ns UUID, name string) UUID { // Generator provides interface for generating UUIDs. type Generator interface { - NewV1() UUID - NewV2(domain byte) UUID + NewV1() (UUID, error) + NewV2(domain byte) (UUID, error) NewV3(ns UUID, name string) UUID - NewV4() UUID + NewV4() (UUID, error) NewV5(ns UUID, name string) UUID } // Default generator implementation. -type generator struct { - storageOnce sync.Once - storageMutex sync.Mutex +type rfc4122Generator struct { + clockSequenceOnce sync.Once + hardwareAddrOnce sync.Once + storageMutex sync.Mutex + + rand io.Reader + epochFunc epochFunc + hwAddrFunc hwAddrFunc lastTime uint64 clockSequence uint16 hardwareAddr [6]byte } -func newDefaultGenerator() Generator { - return &generator{} +func newRFC4122Generator() Generator { + return &rfc4122Generator{ + epochFunc: time.Now, + hwAddrFunc: defaultHWAddrFunc, + rand: rand.Reader, + } } // NewV1 returns UUID based on current timestamp and MAC address. -func (g *generator) NewV1() UUID { +func (g *rfc4122Generator) NewV1() (UUID, error) { u := UUID{} - timeNow, clockSeq, hardwareAddr := g.getStorage() - + timeNow, clockSeq, err := g.getClockSequence() + if err != nil { + return Nil, err + } binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) binary.BigEndian.PutUint16(u[8:], clockSeq) + hardwareAddr, err := g.getHardwareAddr() + if err != nil { + return Nil, err + } copy(u[10:], hardwareAddr) u.SetVersion(V1) u.SetVariant(VariantRFC4122) - return u + return u, nil } // NewV2 returns DCE Security UUID based on POSIX UID/GID. -func (g *generator) NewV2(domain byte) UUID { - u := UUID{} - - timeNow, clockSeq, hardwareAddr := g.getStorage() +func (g *rfc4122Generator) NewV2(domain byte) (UUID, error) { + u, err := g.NewV1() + if err != nil { + return Nil, err + } switch domain { case DomainPerson: - binary.BigEndian.PutUint32(u[0:], posixUID) + binary.BigEndian.PutUint32(u[:], posixUID) case DomainGroup: - binary.BigEndian.PutUint32(u[0:], posixGID) + binary.BigEndian.PutUint32(u[:], posixGID) } - binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) - binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) - binary.BigEndian.PutUint16(u[8:], clockSeq) u[9] = domain - copy(u[10:], hardwareAddr) - u.SetVersion(V2) u.SetVariant(VariantRFC4122) - return u + return u, nil } // NewV3 returns UUID based on MD5 hash of namespace UUID and name. -func (g *generator) NewV3(ns UUID, name string) UUID { +func (g *rfc4122Generator) NewV3(ns UUID, name string) UUID { u := newFromHash(md5.New(), ns, name) u.SetVersion(V3) u.SetVariant(VariantRFC4122) @@ -148,17 +163,19 @@ func (g *generator) NewV3(ns UUID, name string) UUID { } // NewV4 returns random generated UUID. -func (g *generator) NewV4() UUID { +func (g *rfc4122Generator) NewV4() (UUID, error) { u := UUID{} - g.safeRandom(u[:]) + if _, err := io.ReadFull(g.rand, u[:]); err != nil { + return Nil, err + } u.SetVersion(V4) u.SetVariant(VariantRFC4122) - return u + return u, nil } // NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. -func (g *generator) NewV5(ns UUID, name string) UUID { +func (g *rfc4122Generator) NewV5(ns UUID, name string) UUID { u := newFromHash(sha1.New(), ns, name) u.SetVersion(V5) u.SetVariant(VariantRFC4122) @@ -166,66 +183,61 @@ func (g *generator) NewV5(ns UUID, name string) UUID { return u } -func (g *generator) initStorage() { - g.initClockSequence() - g.initHardwareAddr() -} - -func (g *generator) initClockSequence() { - buf := make([]byte, 2) - g.safeRandom(buf) - g.clockSequence = binary.BigEndian.Uint16(buf) -} - -func (g *generator) initHardwareAddr() { - interfaces, err := net.Interfaces() - if err == nil { - for _, iface := range interfaces { - if len(iface.HardwareAddr) >= 6 { - copy(g.hardwareAddr[:], iface.HardwareAddr) - return - } +// Returns epoch and clock sequence. +func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) { + var err error + g.clockSequenceOnce.Do(func() { + buf := make([]byte, 2) + if _, err = io.ReadFull(g.rand, buf); err != nil { + return } + g.clockSequence = binary.BigEndian.Uint16(buf) + }) + if err != nil { + return 0, 0, err } - // Initialize hardwareAddr randomly in case - // of real network interfaces absence - g.safeRandom(g.hardwareAddr[:]) - - // Set multicast bit as recommended in RFC 4122 - g.hardwareAddr[0] |= 0x01 -} - -func (g *generator) safeRandom(dest []byte) { - if _, err := rand.Read(dest); err != nil { - panic(err) - } -} - -// Returns UUID v1/v2 storage state. -// Returns epoch timestamp, clock sequence, and hardware address. -func (g *generator) getStorage() (uint64, uint16, []byte) { - g.storageOnce.Do(g.initStorage) - g.storageMutex.Lock() defer g.storageMutex.Unlock() - timeNow := epochFunc() - // Clock changed backwards since last UUID generation. + timeNow := g.getEpoch() + // Clock didn't change since last UUID generation. // Should increase clock sequence. if timeNow <= g.lastTime { g.clockSequence++ } g.lastTime = timeNow - return timeNow, g.clockSequence, g.hardwareAddr[:] + return timeNow, g.clockSequence, nil +} + +// Returns hardware address. +func (g *rfc4122Generator) getHardwareAddr() ([]byte, error) { + var err error + g.hardwareAddrOnce.Do(func() { + if hwAddr, err := g.hwAddrFunc(); err == nil { + copy(g.hardwareAddr[:], hwAddr) + return + } + + // Initialize hardwareAddr randomly in case + // of real network interfaces absence. + if _, err = io.ReadFull(g.rand, g.hardwareAddr[:]); err != nil { + return + } + // Set multicast bit as recommended by RFC 4122 + g.hardwareAddr[0] |= 0x01 + }) + if err != nil { + return []byte{}, err + } + return g.hardwareAddr[:], nil } // Returns difference in 100-nanosecond intervals between // UUID epoch (October 15, 1582) and current time. -// This is default epoch calculation function. -func unixTimeFunc() uint64 { - return epochStart + uint64(time.Now().UnixNano()/100) +func (g *rfc4122Generator) getEpoch() uint64 { + return epochStart + uint64(g.epochFunc().UnixNano()/100) } // Returns UUID based on hashing of namespace UUID and name. @@ -237,3 +249,17 @@ func newFromHash(h hash.Hash, ns UUID, name string) UUID { return u } + +// Returns hardware address. +func defaultHWAddrFunc() (net.HardwareAddr, error) { + ifaces, err := net.Interfaces() + if err != nil { + return []byte{}, err + } + for _, iface := range ifaces { + if len(iface.HardwareAddr) >= 6 { + return iface.HardwareAddr, nil + } + } + return []byte{}, fmt.Errorf("uuid: no HW address found") +} diff --git a/vendor/github.com/spf13/cobra/cobra/cmd/testdata/LICENSE.golden b/vendor/github.com/spf13/cobra/cobra/cmd/testdata/LICENSE.golden new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/spf13/cobra/cobra/cmd/testdata/LICENSE.golden @@ -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/vendor/github.com/viniciuschiele/tarx/tests/input/symlink.txt b/vendor/github.com/viniciuschiele/tarx/tests/input/symlink.txt new file mode 120000 index 000000000..8d14cbf98 --- /dev/null +++ b/vendor/github.com/viniciuschiele/tarx/tests/input/symlink.txt @@ -0,0 +1 @@ +a.txt \ No newline at end of file