<commit_before>package bootstrap

import (
	"bytes"
	"io"
)

type Redactor struct {
	replacement []byte

	// Current offset from the start of the next input segment
	offset int

	// Minimum and maximum length of redactable string
	minlen int
	maxlen int

	// Table of Boyer-Moore skip distances, and values to redact matching this end byte
	table [255]struct {
		skip    int
		needles [][]byte
	}

	// Internal buffer for building redacted input into
	// Also holds the final portion of the previous Write call, in case of
	// sensitive values that cross Write boundaries
	outbuf []byte

	// Wrapped Writer that we'll send redacted output to
	output io.Writer
}

// Construct a new Redactor, and pre-compile the Boyer-Moore skip table
func NewRedactor(output io.Writer, replacement string, needles []string) *Redactor {
	minNeedleLen := 0
	maxNeedleLen := 0
	for _, needle := range needles {
		if len(needle) < minNeedleLen || minNeedleLen == 0 {
			minNeedleLen = len(needle)
		}
		if len(needle) > maxNeedleLen {
			maxNeedleLen = len(needle)
		}
	}

	redactor := &Redactor{
		replacement: []byte(replacement),
		output:      output,

		// Linux pipes can buffer up to 65536 bytes before flushing, so there's
		// a reasonable chance that's how much we'll get in a single Write().
		// maxNeedleLen is added since we may retain that many bytes to handle
		// matches crossing Write boundaries.
		// It's a reasonable starting capacity which hopefully means we don't
		// have to reallocate the array, but append() will grow it if necessary
		outbuf: make([]byte, 0, 65536+maxNeedleLen),

		// Since Boyer-Moore looks for the end of substrings, we can safely offset
		// processing by the length of the shortest string we're checking for
		// Since Boyer-Moore looks for the end of substrings, only bytes further
		// behind the iterator than the longest search string are guaranteed to not
		// be part of a match
		minlen: minNeedleLen,
		maxlen: maxNeedleLen,
		offset: minNeedleLen - 1,
	}

	// For bytes that don't appear in any of the substrings we're searching
	// for, it's safe to skip forward the length of the shortest search
	// string.
	// Start by setting this as a default for all bytes
	for i := range redactor.table {
		redactor.table[i].skip = minNeedleLen
	}

	for _, needle := range needles {
		for i, ch := range needle {
			// For bytes that do exist in search strings, find the shortest distance
			// between that byte appearing to the end of the same search string
			skip := len(needle) - i - 1
			if skip < redactor.table[ch].skip {
				redactor.table[ch].skip = skip
			}

			// Build a cache of which search substrings end in which bytes
			if skip == 0 {
				redactor.table[ch].needles = append(redactor.table[ch].needles, []byte(needle))
			}
		}
	}

	return redactor
}

func (redactor *Redactor) Write(input []byte) (int, error) {
	if len(input) == 0 {
		return 0, nil
	}

	// Current iterator index, which may be a safe offset from 0
	cursor := redactor.offset

	// Current index which is guaranteed to be completely redacted
	// May lag behind cursor by up to the length of the longest search string
	doneTo := 0

	for cursor < len(input) {
		ch := input[cursor]
		skip := redactor.table[ch].skip

		// If the skip table tells us that there is no search string ending in
		// the current byte, skip forward by the indicated distance.
		if skip != 0 {
			cursor += skip

			// Also copy any content behind the cursor which is guaranteed not
			// to fall under a match
			confirmedTo := cursor - redactor.maxlen - 1
			if confirmedTo > len(input) {
				confirmedTo = len(input)
			}
			if confirmedTo > doneTo {
				redactor.outbuf = append(redactor.outbuf, input[doneTo:confirmedTo]...)
				doneTo = confirmedTo
			}

			continue
		}

		// We'll check for matching search strings here, but we'll still need
		// to move the cursor forward
		// Since Go slice syntax is not inclusive of the end index, moving it
		// forward now reduces the need to use `cursor-1` everywhere
		cursor++
		for _, needle := range redactor.table[ch].needles {
			// Since we're working backwards from what may be the end of a
			// string, it's possible that the start would be out of bounds
			startSubstr := cursor - len(needle)
			var candidate []byte

			if startSubstr >= 0 {
				// If the candidate string falls entirely within input, then just slice into input
				candidate = input[startSubstr:cursor]
			} else if -startSubstr <= len(redactor.outbuf) {
				// If the candidate crosses the Write boundary, we need to
				// concatenate the two sections to compare against
				candidate = make([]byte, 0, len(needle))
				candidate = append(candidate, redactor.outbuf[-startSubstr-1:]...)
				candidate = append(candidate, input[:cursor]...)
			} else {
				// Final case is that the start index is out of bounds, and
				// it's impossible for it to match. Just move on to the next
				// search substring
				continue
			}

			if bytes.Equal(needle, candidate) {
				if startSubstr < 0 {
					// If we accepted a negative startSubstr, the output buffer
					// needs to be truncated to remove the partial match
					redactor.outbuf = redactor.outbuf[:len(redactor.outbuf)+startSubstr]
				} else if startSubstr > doneTo {
					// First, copy over anything behind the matched substring unmodified
					redactor.outbuf = append(redactor.outbuf, input[doneTo:startSubstr]...)
				}
				// Then, write a fixed string into the output, and move doneTo past the redaction
				redactor.outbuf = append(redactor.outbuf, redactor.replacement...)
				doneTo = cursor

				// The next end-of-string will be at least this far away so
				// it's safe to skip forward a bit
				cursor += redactor.minlen - 1
				break
			}
		}
	}

	// We buffer the end of the input in order to catch passwords that fall over Write boundaries.
	// In the case of line-buffered input, that means we would hold back the
	// end of the line in a user-visible way. For this reason, we push through
	// any line endings immediately rather than hold them back.
	// The \r case should help to handle progress bars/spinners that use \r to
	// overwrite the current line.
	// Technically this means that passwords containing newlines aren't
	// guarateed to get redacted, but who does that anyway?
	for i := doneTo; i < len(input); i++ {
		if input[i] == byte('\r') || input[i] == byte('\n') {
			redactor.outbuf = append(redactor.outbuf, input[doneTo:i+1]...)
			doneTo = i + 1
		}
	}

	var err error
	if doneTo > 0 {
		// Push the output buffer down
		_, err = redactor.output.Write(redactor.outbuf)

		// There will probably be a segment at the end of the input which may be a
		// partial match crossing the Write boundary. This is retained in the
		// output buffer to compare against on the next call
		// Flush() needs to be called after the final Write(), or this bit won't
		// get written
		redactor.outbuf = append(redactor.outbuf[:0], input[doneTo:]...)
	} else {
		// If nothing was done, just add what we got to the buffer to be
		// processed on the next run
		redactor.outbuf = append(redactor.outbuf, input...)
	}

	// We can offset the next Write processing by how far cursor is ahead of
	// the end of this input segment
	redactor.offset = cursor - len(input)

	return len(input), err
}

// Flush should be called after the final Write. This will Write() anything
// retained in case of a partial match and reset the output buffer.
func (redactor *Redactor) Sync() error {
	_, err := redactor.output.Write(redactor.outbuf)
	redactor.outbuf = redactor.outbuf[:0]
	return err
}
<commit_msg>Fix Write boundary test failure<commit_after>package bootstrap

import (
	"bytes"
	"io"
)

type Redactor struct {
	replacement []byte

	// Current offset from the start of the next input segment
	offset int

	// Minimum and maximum length of redactable string
	minlen int
	maxlen int

	// Table of Boyer-Moore skip distances, and values to redact matching this end byte
	table [255]struct {
		skip    int
		needles [][]byte
	}

	// Internal buffer for building redacted input into
	// Also holds the final portion of the previous Write call, in case of
	// sensitive values that cross Write boundaries
	outbuf []byte

	// Wrapped Writer that we'll send redacted output to
	output io.Writer
}

// Construct a new Redactor, and pre-compile the Boyer-Moore skip table
func NewRedactor(output io.Writer, replacement string, needles []string) *Redactor {
	minNeedleLen := 0
	maxNeedleLen := 0
	for _, needle := range needles {
		if len(needle) < minNeedleLen || minNeedleLen == 0 {
			minNeedleLen = len(needle)
		}
		if len(needle) > maxNeedleLen {
			maxNeedleLen = len(needle)
		}
	}

	redactor := &Redactor{
		replacement: []byte(replacement),
		output:      output,

		// Linux pipes can buffer up to 65536 bytes before flushing, so there's
		// a reasonable chance that's how much we'll get in a single Write().
		// maxNeedleLen is added since we may retain that many bytes to handle
		// matches crossing Write boundaries.
		// It's a reasonable starting capacity which hopefully means we don't
		// have to reallocate the array, but append() will grow it if necessary
		outbuf: make([]byte, 0, 65536+maxNeedleLen),

		// Since Boyer-Moore looks for the end of substrings, we can safely offset
		// processing by the length of the shortest string we're checking for
		// Since Boyer-Moore looks for the end of substrings, only bytes further
		// behind the iterator than the longest search string are guaranteed to not
		// be part of a match
		minlen: minNeedleLen,
		maxlen: maxNeedleLen,
		offset: minNeedleLen - 1,
	}

	// For bytes that don't appear in any of the substrings we're searching
	// for, it's safe to skip forward the length of the shortest search
	// string.
	// Start by setting this as a default for all bytes
	for i := range redactor.table {
		redactor.table[i].skip = minNeedleLen
	}

	for _, needle := range needles {
		for i, ch := range needle {
			// For bytes that do exist in search strings, find the shortest distance
			// between that byte appearing to the end of the same search string
			skip := len(needle) - i - 1
			if skip < redactor.table[ch].skip {
				redactor.table[ch].skip = skip
			}

			// Build a cache of which search substrings end in which bytes
			if skip == 0 {
				redactor.table[ch].needles = append(redactor.table[ch].needles, []byte(needle))
			}
		}
	}

	return redactor
}

func (redactor *Redactor) Write(input []byte) (int, error) {
	if len(input) == 0 {
		return 0, nil
	}

	// Current iterator index, which may be a safe offset from 0
	cursor := redactor.offset

	// Current index which is guaranteed to be completely redacted
	// May lag behind cursor by up to the length of the longest search string
	doneTo := 0

	for cursor < len(input) {
		ch := input[cursor]
		skip := redactor.table[ch].skip

		// If the skip table tells us that there is no search string ending in
		// the current byte, skip forward by the indicated distance.
		if skip != 0 {
			cursor += skip

			// Also copy any content behind the cursor which is guaranteed not
			// to fall under a match
			confirmedTo := cursor - redactor.maxlen
			if confirmedTo > len(input) {
				confirmedTo = len(input)
			}
			if confirmedTo > doneTo {
				redactor.outbuf = append(redactor.outbuf, input[doneTo:confirmedTo]...)
				doneTo = confirmedTo
			}

			continue
		}

		// We'll check for matching search strings here, but we'll still need
		// to move the cursor forward
		// Since Go slice syntax is not inclusive of the end index, moving it
		// forward now reduces the need to use `cursor-1` everywhere
		cursor++
		for _, needle := range redactor.table[ch].needles {
			// Since we're working backwards from what may be the end of a
			// string, it's possible that the start would be out of bounds
			startSubstr := cursor - len(needle)
			var candidate []byte

			if startSubstr >= 0 {
				// If the candidate string falls entirely within input, then just slice into input
				candidate = input[startSubstr:cursor]
			} else if -startSubstr <= len(redactor.outbuf) {
				// If the candidate crosses the Write boundary, we need to
				// concatenate the two sections to compare against
				candidate = make([]byte, 0, len(needle))
				candidate = append(candidate, redactor.outbuf[len(redactor.outbuf)+startSubstr:]...)
				candidate = append(candidate, input[:cursor]...)
			} else {
				// Final case is that the start index is out of bounds, and
				// it's impossible for it to match. Just move on to the next
				// search substring
				continue
			}

			if bytes.Equal(needle, candidate) {
				if startSubstr < 0 {
					// If we accepted a negative startSubstr, the output buffer
					// needs to be truncated to remove the partial match
					redactor.outbuf = redactor.outbuf[:len(redactor.outbuf)+startSubstr]
				} else if startSubstr > doneTo {
					// First, copy over anything behind the matched substring unmodified
					redactor.outbuf = append(redactor.outbuf, input[doneTo:startSubstr]...)
				}
				// Then, write a fixed string into the output, and move doneTo past the redaction
				redactor.outbuf = append(redactor.outbuf, redactor.replacement...)
				doneTo = cursor

				// The next end-of-string will be at least this far away so
				// it's safe to skip forward a bit
				cursor += redactor.minlen - 1
				break
			}
		}
	}

	// We buffer the end of the input in order to catch passwords that fall over Write boundaries.
	// In the case of line-buffered input, that means we would hold back the
	// end of the line in a user-visible way. For this reason, we push through
	// any line endings immediately rather than hold them back.
	// The \r case should help to handle progress bars/spinners that use \r to
	// overwrite the current line.
	// Technically this means that passwords containing newlines aren't
	// guarateed to get redacted, but who does that anyway?
	for i := doneTo; i < len(input); i++ {
		if input[i] == byte('\r') || input[i] == byte('\n') {
			redactor.outbuf = append(redactor.outbuf, input[doneTo:i+1]...)
			doneTo = i + 1
		}
	}

	var err error
	if doneTo > 0 {
		// Push the output buffer down
		_, err = redactor.output.Write(redactor.outbuf)

		// There will probably be a segment at the end of the input which may be a
		// partial match crossing the Write boundary. This is retained in the
		// output buffer to compare against on the next call
		// Flush() needs to be called after the final Write(), or this bit won't
		// get written
		redactor.outbuf = append(redactor.outbuf[:0], input[doneTo:]...)
	} else {
		// If nothing was done, just add what we got to the buffer to be
		// processed on the next run
		redactor.outbuf = append(redactor.outbuf, input...)
	}

	// We can offset the next Write processing by how far cursor is ahead of
	// the end of this input segment
	redactor.offset = cursor - len(input)

	return len(input), err
}

// Flush should be called after the final Write. This will Write() anything
// retained in case of a partial match and reset the output buffer.
func (redactor *Redactor) Sync() error {
	_, err := redactor.output.Write(redactor.outbuf)
	redactor.outbuf = redactor.outbuf[:0]
	return err
}
<|endoftext|>


<commit_before>// EVH is designed to be a single-use file transfer system.  Its purpose is to replace
// aging methods of sharing files such as FTP.  With the advent of services like
// DropBox, Box, Google Drive and the like, this type of service is becoming more
// commonplace EVH has some differentiating features that make it an especially
// good tool for corporations and/or home use.
//
// EVH runs in two modes: server and client.  Server hosts a web server interface for
// uploading and downloading files.  The Client is for uploading only and runs
// in a terminal.  This app is designed to run on all platforms that Go supports.
package main

import (
	"flag"
	"html/template"
	"log"
	"net/http"
	"os"
)

// Flags
var ConfigFileFlag string
var DstEmailFlag string
var ExpirationFlag string
var FileDescrFlag string
var FilesFieldFlag string
var ProgressFlag bool
var ServerFlag bool
var SrcEmailFlag string
var UrlFlag string
var Evh1ImportFlag bool

// Global Variables
var UploadUrlPath = "/upload/"
var DownloadUrlPath = "/download/"
var AdminUrlPath = "/admin/"
var Files []string
var HttpProto = "http"
var SiteDown bool
var Templates *template.Template

// Constants
const VERSION = "2.3.1"
const TimeLayout = "Jan 2, 2006 at 3:04pm (MST)"

func init() {
	flag.StringVar(&ConfigFileFlag, "c", "", "Location of the Configuration file")
	flag.BoolVar(&ServerFlag, "server", false, "Listen for incoming file uploads")

	// Client flags
	flag.StringVar(&UrlFlag, "url", "", "Remote server URL to send files to (client only)")
	flag.StringVar(&FilesFieldFlag, "field", "", "Field name of the form (client only)")
	flag.StringVar(&SrcEmailFlag, "from", "", "Email address of uploader (client only)")
	flag.StringVar(&DstEmailFlag, "to", "", "Comma separated set of email address(es) of file recipient(s) (client only)")
	flag.StringVar(&FileDescrFlag, "description", "", "File desription (use quotes) (client only)")
	flag.BoolVar(&ProgressFlag, "progress", true, "Show progress bar during upload (client only)")
	flag.StringVar(&ExpirationFlag, "expires", "", "Example 1:d for 1 day (client only)")
	flag.BoolVar(&Evh1ImportFlag, "import", false, "Import data from EVH1 instance (client only)")
}

func main() {
	flag.Parse()

	// Load in our Config
	Config = NewConfig(ConfigFileFlag)
	Config.ImportFlags()

	if ServerFlag {
		// Final sanity check
		if Config.Server.Assets == "" {
			log.Fatal("ERROR: Cannot continue without specifying assets path")
		}
		if Config.Server.Templates == "" {
			log.Fatal("ERROR: Cannot continue without specifying templates path")
		}
		if Config.Server.ListenAddr == "" {
			log.Fatal("ERROR: Cannot continue without specifying listenaddr value")
		}
		if Config.Server.Mailserver == "" {
			log.Println("WARNING: cannot send emails, mailserver not set")
		}

		// Set so all generated URLs use https if enabled
		if Config.Server.Ssl {
			HttpProto = "https"
		}

		// Setup our assets dir (if it don't already exist)
		err := os.MkdirAll(Config.Server.Assets, 0700)
		if err != nil {
			log.Fatal("Cannot setup assetdir as needed: " + err.Error())
		}

		// Parse our html templates
		go RefreshTemplates()
		go ScrubDownloads()

		// Register our handler functions
		http.HandleFunc(UploadUrlPath, SSLCheck(UploadHandler))
		http.HandleFunc(DownloadUrlPath, SSLCheck(AssetHandler))
		http.HandleFunc(AdminUrlPath, BasicAuth(SSLCheck(AdminHandler)))
		http.HandleFunc("/", Evh1Intercept(SSLCheck(HomeHandler)))

		// Listen
		log.Println("Listening...")

		// Spawn HTTPS listener in another thread
		go func() {
			if Config.Server.Ssl == false || Config.Server.SslPort == "" {
				return
			}
			var addrSsl = Config.Server.ListenAddr + ":" + Config.Server.SslPort
			listenErrSsl := http.ListenAndServeTLS(addrSsl, Config.Server.CertFile, Config.Server.KeyFile, nil)
			if listenErrSsl != nil {
				log.Fatal("ERROR: ssl listen problem: " + listenErrSsl.Error())
			}
		}()

		// Start non-SSL listener
		var addrNonSsl = Config.Server.ListenAddr + ":" + Config.Server.NonSslPort
		listenErr := http.ListenAndServe(addrNonSsl, nil)
		if listenErr != nil {
			log.Fatal("ERROR: non-ssl listen problem: " + listenErr.Error())
		}
	} else {
		// Run import if requested
		if Evh1ImportFlag {
			SpitSlurp()
			return
		}

		// Final sanity check
		if Config.Client.DestEmail == "" {
			log.Println("WARNING: no -destemail value set, cannot send reciever an email")
		}
		if Config.Client.Email == "" {
			log.Println("WARNING: no -email value set, cannot send email to uploader")
		}
		if Config.Client.Field == "" {
			log.Println("WARNING: no -field value set, using \"file\" instead")
			Config.Client.Field = "file"
		}
		if Config.Client.Url == "" {
			log.Fatal("ERROR: Cannot continue without specifying -url value")
		}

		// All filenames are unflagged arguments, loop through them and uplod the file(s)
		for _, fname := range flag.Args() {
			fi, err := os.Stat(fname)
			if err != nil {
				log.Println("WARNING: Cannot read file, skipping ", fname, ": ", err.Error())
			} else {
				if fi.Mode().IsRegular() {
					Files = append(Files, fname)
				}
			}
		}

		Upload(Files)
	}
}
<commit_msg>Version bump to 2.3.2<commit_after>// EVH is designed to be a single-use file transfer system.  Its purpose is to replace
// aging methods of sharing files such as FTP.  With the advent of services like
// DropBox, Box, Google Drive and the like, this type of service is becoming more
// commonplace EVH has some differentiating features that make it an especially
// good tool for corporations and/or home use.
//
// EVH runs in two modes: server and client.  Server hosts a web server interface for
// uploading and downloading files.  The Client is for uploading only and runs
// in a terminal.  This app is designed to run on all platforms that Go supports.
package main

import (
	"flag"
	"html/template"
	"log"
	"net/http"
	"os"
)

// Flags
var ConfigFileFlag string
var DstEmailFlag string
var ExpirationFlag string
var FileDescrFlag string
var FilesFieldFlag string
var ProgressFlag bool
var ServerFlag bool
var SrcEmailFlag string
var UrlFlag string
var Evh1ImportFlag bool

// Global Variables
var UploadUrlPath = "/upload/"
var DownloadUrlPath = "/download/"
var AdminUrlPath = "/admin/"
var Files []string
var HttpProto = "http"
var SiteDown bool
var Templates *template.Template

// Constants
const VERSION = "2.3.2"
const TimeLayout = "Jan 2, 2006 at 3:04pm (MST)"

func init() {
	flag.StringVar(&ConfigFileFlag, "c", "", "Location of the Configuration file")
	flag.BoolVar(&ServerFlag, "server", false, "Listen for incoming file uploads")

	// Client flags
	flag.StringVar(&UrlFlag, "url", "", "Remote server URL to send files to (client only)")
	flag.StringVar(&FilesFieldFlag, "field", "", "Field name of the form (client only)")
	flag.StringVar(&SrcEmailFlag, "from", "", "Email address of uploader (client only)")
	flag.StringVar(&DstEmailFlag, "to", "", "Comma separated set of email address(es) of file recipient(s) (client only)")
	flag.StringVar(&FileDescrFlag, "description", "", "File desription (use quotes) (client only)")
	flag.BoolVar(&ProgressFlag, "progress", true, "Show progress bar during upload (client only)")
	flag.StringVar(&ExpirationFlag, "expires", "", "Example 1:d for 1 day (client only)")
	flag.BoolVar(&Evh1ImportFlag, "import", false, "Import data from EVH1 instance (client only)")
}

func main() {
	flag.Parse()

	// Load in our Config
	Config = NewConfig(ConfigFileFlag)
	Config.ImportFlags()

	if ServerFlag {
		// Final sanity check
		if Config.Server.Assets == "" {
			log.Fatal("ERROR: Cannot continue without specifying assets path")
		}
		if Config.Server.Templates == "" {
			log.Fatal("ERROR: Cannot continue without specifying templates path")
		}
		if Config.Server.ListenAddr == "" {
			log.Fatal("ERROR: Cannot continue without specifying listenaddr value")
		}
		if Config.Server.Mailserver == "" {
			log.Println("WARNING: cannot send emails, mailserver not set")
		}

		// Set so all generated URLs use https if enabled
		if Config.Server.Ssl {
			HttpProto = "https"
		}

		// Setup our assets dir (if it don't already exist)
		err := os.MkdirAll(Config.Server.Assets, 0700)
		if err != nil {
			log.Fatal("Cannot setup assetdir as needed: " + err.Error())
		}

		// Parse our html templates
		go RefreshTemplates()
		go ScrubDownloads()

		// Register our handler functions
		http.HandleFunc(UploadUrlPath, SSLCheck(UploadHandler))
		http.HandleFunc(DownloadUrlPath, SSLCheck(AssetHandler))
		http.HandleFunc(AdminUrlPath, BasicAuth(SSLCheck(AdminHandler)))
		http.HandleFunc("/", Evh1Intercept(SSLCheck(HomeHandler)))

		// Listen
		log.Println("Listening...")

		// Spawn HTTPS listener in another thread
		go func() {
			if Config.Server.Ssl == false || Config.Server.SslPort == "" {
				return
			}
			var addrSsl = Config.Server.ListenAddr + ":" + Config.Server.SslPort
			listenErrSsl := http.ListenAndServeTLS(addrSsl, Config.Server.CertFile, Config.Server.KeyFile, nil)
			if listenErrSsl != nil {
				log.Fatal("ERROR: ssl listen problem: " + listenErrSsl.Error())
			}
		}()

		// Start non-SSL listener
		var addrNonSsl = Config.Server.ListenAddr + ":" + Config.Server.NonSslPort
		listenErr := http.ListenAndServe(addrNonSsl, nil)
		if listenErr != nil {
			log.Fatal("ERROR: non-ssl listen problem: " + listenErr.Error())
		}
	} else {
		// Run import if requested
		if Evh1ImportFlag {
			SpitSlurp()
			return
		}

		// Final sanity check
		if Config.Client.DestEmail == "" {
			log.Println("WARNING: no -destemail value set, cannot send reciever an email")
		}
		if Config.Client.Email == "" {
			log.Println("WARNING: no -email value set, cannot send email to uploader")
		}
		if Config.Client.Field == "" {
			log.Println("WARNING: no -field value set, using \"file\" instead")
			Config.Client.Field = "file"
		}
		if Config.Client.Url == "" {
			log.Fatal("ERROR: Cannot continue without specifying -url value")
		}

		// All filenames are unflagged arguments, loop through them and uplod the file(s)
		for _, fname := range flag.Args() {
			fi, err := os.Stat(fname)
			if err != nil {
				log.Println("WARNING: Cannot read file, skipping ", fname, ": ", err.Error())
			} else {
				if fi.Mode().IsRegular() {
					Files = append(Files, fname)
				}
			}
		}

		Upload(Files)
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 The Prometheus Authors
// 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 remote

import (
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/pkg/labels"
	"github.com/prometheus/prometheus/storage"
)

// Appender implements retrieval.Appendable.
func (s *Storage) Appender() (storage.Appender, error) {
	return s, nil
}

// Add implements storage.Appender.
func (s *Storage) Add(l labels.Labels, t int64, v float64) (uint64, error) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()
	for _, q := range s.queues {
		q.Append(&model.Sample{
			Metric:    labelsToMetric(l),
			Timestamp: model.Time(t),
			Value:     model.SampleValue(v),
		})
	}
	return 0, nil
}

func (s *Storage) AddFast(l labels.Labels, _ uint64, t int64, v float64) error {
	_, err := s.Add(l, t, v)
	return err
}

// Commit implements storage.Appender.
func (*Storage) Commit() error {
	return nil
}

// Rollback implements storage.Appender.
func (*Storage) Rollback() error {
	return nil
}
<commit_msg>Add comment.<commit_after>// Copyright 2017 The Prometheus Authors
// 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 remote

import (
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/pkg/labels"
	"github.com/prometheus/prometheus/storage"
)

// Appender implements retrieval.Appendable.
func (s *Storage) Appender() (storage.Appender, error) {
	return s, nil
}

// Add implements storage.Appender.
func (s *Storage) Add(l labels.Labels, t int64, v float64) (uint64, error) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()
	for _, q := range s.queues {
		q.Append(&model.Sample{
			Metric:    labelsToMetric(l),
			Timestamp: model.Time(t),
			Value:     model.SampleValue(v),
		})
	}
	return 0, nil
}

// AddFast implements storage.Appender.
func (s *Storage) AddFast(l labels.Labels, _ uint64, t int64, v float64) error {
	_, err := s.Add(l, t, v)
	return err
}

// Commit implements storage.Appender.
func (*Storage) Commit() error {
	return nil
}

// Rollback implements storage.Appender.
func (*Storage) Rollback() error {
	return nil
}
<|endoftext|>


<commit_before>package storage

import (
	"bytes"
	"fmt"
	"math"
	"testing/quick"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"encoding/hex"
)

const (
	testDBDir = "./testdb"
)

var testDB *DB

var _ = Describe("Storage Main Test", func() {

	BeforeEach(func() {
		var err error
		testDB, err = OpenDB(testDBDir, 1000)
		Expect(err).Should(Succeed())
	})

	AfterEach(func() {
		testDB.Close()
		err := testDB.Delete()
		Expect(err).Should(Succeed())
	})

	It("Open and reopen", func() {
		fmt.Println("Open and re-open")
		stor, err := OpenDB("./openleveldb", 1000)
		Expect(err).Should(Succeed())
		stor.Close()
		stor, err = OpenDB("./openleveldb", 1000)
		Expect(err).Should(Succeed())
		stor.Close()
		err = stor.Delete()
		Expect(err).Should(Succeed())
		fmt.Println("Open and re-open done")
	})

	//It("String metadata", func() {
	//	err := quick.Check(testStringMetadata, nil)
	//	Expect(err).Should(Succeed())
	//})

	It("String metadata negative", func() {
		ret, err := testDB.GetMetadata("NOTFOUND")
		Expect(err).Should(Succeed())
		Expect(ret).Should(BeNil())
	})

	//It("Int metadata", func() {
	//	err := quick.Check(testIntMetadata, nil)
	//	Expect(err).Should(Succeed())
	//})

	It("Int metadata negative", func() {
		ret, err := testDB.GetIntMetadata("REALLYNOTFOUND")
		Expect(err).Should(Succeed())
		Expect(ret).Should(BeEquivalentTo(0))
	})

	It("Entries", func() {
		err := quick.Check(testEntry, nil)
		Expect(err).Should(Succeed())
	})

	It("Entries same tag", func() {
		err := quick.Check(func(lsn uint64, index uint32, data []byte) bool {
			return testEntry("tag", lsn, index, data)
		}, nil)
		Expect(err).Should(Succeed())
	})

	It("Entries empty tag", func() {
		err := quick.Check(func(lsn uint64, index uint32, data []byte) bool {
			return testEntry("", lsn, index, data)
		}, nil)
		Expect(err).Should(Succeed())
	})

	It("Entries same LSN", func() {
		err := quick.Check(func(index uint32, data []byte) bool {
			return testEntry("tag", 8675309, index, data)
		}, nil)
		Expect(err).Should(Succeed())
	})

	It("Entries and metadata", func() {
		err := quick.Check(testEntryAndData, nil)
		Expect(err).Should(Succeed())
	})

	It("Read not found", func() {
		buf, err := testDB.GetEntry("foo", 0, 0)
		Expect(err).Should(Succeed())
		Expect(buf).Should(BeNil())
	})

	It("Read not found multi", func() {
		bufs, err := testDB.GetEntries("foo", 0, 0, 100, nil)
		Expect(err).Should(Succeed())
		Expect(bufs).Should(BeEmpty())
	})

	It("Reading sequences", func() {
		val1 := []byte("Hello!")
		val2 := []byte("World.")

		testDB.PutEntry("a", 0, 0, val1)
		testDB.PutEntry("a", 1, 0, val2)
		testDB.PutEntry("a", 1, 1, val1)
		testDB.PutEntry("a", 2, 0, val2)
		testDB.PutEntry("b", 3, 0, val1)
		testDB.PutEntry("c", 4, 0, val1)
		testDB.PutEntry("c", 4, 1, val2)
		testDB.PutEntry("", 10, 0, val1)
		testDB.PutEntry("", 11, 1, val2)

		// Read whole ranges
		testGetSequence("a", 0, 0, 100, [][]byte{val1, val2, val1, val2})
		testGetSequence("b", 0, 0, 100, [][]byte{val1})
		testGetSequence("c", 0, 0, 100, [][]byte{val1, val2})
		testGetSequence("", 0, 0, 100, [][]byte{val1, val2})

		// Read after start
		testGetSequence("a", 1, 0, 100, [][]byte{val2, val1, val2})
		testGetSequence("a", 1, 1, 100, [][]byte{val1, val2})
		testGetSequence("a", 2, 0, 100, [][]byte{val2})
		testGetSequence("a", 2, 1, 100, [][]byte{})
		testGetSequence("a", 3, 0, 100, [][]byte{})

		// Read with limit
		testGetSequenceFilter("a", 0, 0, 2, [][]byte{val2, val2}, val1)
		testGetSequenceFilter("a", 0, 0, 1, [][]byte{val2}, val1)

		// Read with limit and filter
		testGetSequence("a", 0, 0, 4, [][]byte{val1, val2, val1, val2})
		testGetSequence("a", 0, 0, 3, [][]byte{val1, val2, val1})
		testGetSequence("a", 0, 0, 2, [][]byte{val1, val2})
		testGetSequence("a", 0, 0, 1, [][]byte{val1})
		testGetSequence("a", 0, 0, 0, [][]byte{})

		// Read invalid range
		testGetSequence("d", 0, 0, 0, [][]byte{})

		// Read a bunch of ranges
		testGetSequences([]string{"a", "b", "c"}, 0, 0, 100,
			[][]byte{val1, val2, val1, val2, val1, val1, val2})
		// test that sorting works
		testGetSequences([]string{"c", "b", "a"}, 0, 0, 100,
			[][]byte{val1, val2, val1, val2, val1, val1, val2})
		// test that limits work
		testGetSequences([]string{"c", "b", "a"}, 0, 0, 3,
			[][]byte{val1, val2, val1})
		// test that they work when we start in the middle
		testGetSequences([]string{"c", "b", "a"}, 2, 0, 3,
			[][]byte{val2, val1, val1})
	})

	It("Purge empty database", func() {
		count, err := testDB.PurgeEntries(func(buf []byte) bool {
			return true
		})
		Expect(err).Should(Succeed())
		Expect(count).Should(BeZero())
	})

	It("Purge some entries", func() {
		val1 := []byte("Hello")
		val2 := []byte("Goodbye")

		testDB.PutEntry("a", 0, 0, val1)
		testDB.PutEntry("a", 1, 0, val2)
		testDB.PutEntry("a", 1, 1, val1)
		testDB.PutEntry("a", 2, 0, val2)
		testDB.PutEntry("b", 3, 0, val1)
		testDB.PutEntry("c", 4, 0, val1)
		testDB.PutEntry("c", 4, 1, val2)
		testDB.PutEntry("", 10, 0, val1)
		testDB.PutEntry("", 11, 1, val2)

		// Purge only one value
		count, err := testDB.PurgeEntries(func(buf []byte) bool {
			return bytes.Equal(buf, val2)
		})
		Expect(err).Should(Succeed())
		Expect(count).Should(BeEquivalentTo(4))

		// Verify that re-purge does nothing
		count, err = testDB.PurgeEntries(func(buf []byte) bool {
			return bytes.Equal(buf, val2)
		})
		Expect(err).Should(Succeed())
		Expect(count).Should(BeZero())

		found, err := testDB.GetEntry("a", 0, 0)
		Expect(err).Should(Succeed())
		Expect(bytes.Equal(found, val1)).Should(BeTrue())

		found, err = testDB.GetEntry("a", 0, 1)
		Expect(err).Should(Succeed())
		Expect(found).Should(BeNil())

		// Purge the rest of the entries
		count, err = testDB.PurgeEntries(func(buf []byte) bool {
			return true
		})
		Expect(err).Should(Succeed())
		Expect(count).Should(BeEquivalentTo(5))

		// Verify that everything is gone now
		count, err = testDB.PurgeEntries(func(buf []byte) bool {
			return true
		})
		Expect(err).Should(Succeed())
		Expect(count).Should(BeZero())
	})
})

func testGetSequence(tag string, lsn uint64,
	index uint32, limit int, expected [][]byte) {
	ret, err := testDB.GetEntries(tag, lsn, index, limit, nil)
	Expect(err).Should(Succeed())
	Expect(len(ret)).Should(Equal(len(expected)))
	for i := range expected {
		Expect(bytes.Equal(expected[i], ret[i])).Should(BeTrue())
	}
}

func testGetSequenceFilter(tag string, lsn uint64,
	index uint32, limit int, expected [][]byte, rejected []byte) {
	ret, err := testDB.GetEntries(tag, lsn, index, limit,
		func(rej []byte) bool {
			if bytes.Equal(rej, rejected) {
				return false
			}
			return true
		})
	Expect(err).Should(Succeed())
	Expect(len(ret)).Should(Equal(len(expected)))
	for i := range expected {
		Expect(bytes.Equal(expected[i], ret[i])).Should(BeTrue())
	}
}

func testGetSequences(tags []string, lsn uint64,
	index uint32, limit int, expected [][]byte) {
	ret, _, err := testDB.GetMultiEntries(tags, nil, lsn, index, limit, nil)
	Expect(err).Should(Succeed())
	Expect(len(ret)).Should(Equal(len(expected)))
	for i := range expected {
		Expect(bytes.Equal(expected[i], ret[i])).Should(BeTrue())
	}
}

func testStringMetadata(key string, val []byte) bool {
	err := testDB.SetMetadata(key, val)
	Expect(err).Should(Succeed())
	ret, err := testDB.GetMetadata(key)
	Expect(err).Should(Succeed())
	Expect(bytes.Equal(val, ret)).Should(BeTrue())
	return true
}

func testIntMetadata(key string, val int64) bool {
	err := testDB.SetIntMetadata(key, val)
	Expect(err).Should(Succeed())
	ret, err := testDB.GetIntMetadata(key)
	Expect(err).Should(Succeed())
	Expect(ret).Should(Equal(val))
	return true
}

func testEntry(key string, lsn uint64, index uint32, val []byte) bool {
	err := testDB.PutEntry(key, lsn, index, val)
	Expect(err).Should(Succeed())
	ret, err := testDB.GetEntry(key, lsn, index)
	Expect(err).Should(Succeed())
	if !bytes.Equal(val, ret) {
		fmt.Printf("Val is %d %s ret is %d %s, key is %s, lsn is %d index is %d\n", len(val), hex.Dump(val), len(ret), hex.Dump(ret), key, lsn, index)
	}
	Expect(bytes.Equal(val, ret)).Should(BeTrue())
	return true
}

func testEntryAndData(key string, lsn uint64, index uint32, val []byte,
	mkey string, mval []byte) bool {
	if key == "" {
		return true
	}
	if lsn > math.MaxInt64 {
		// TODO this does not sound right to me, but the test fails if
		// the high bit is set
		return true
	}
	fmt.Fprintf(GinkgoWriter, "EandD: key = %v lsn = %x index = %x mkey = %v val = %v mval = %v\n",
		[]byte(key), lsn, index, []byte(mkey), val, mval)
	err := testDB.PutEntryAndMetadata(key, lsn, index, val, mkey, mval)
	Expect(err).Should(Succeed())
	ret, err := testDB.GetEntry(key, lsn, index)
	Expect(err).Should(Succeed())
	Expect(bytes.Equal(val, ret)).Should(BeTrue())
	mret, err := testDB.GetMetadata(mkey)
	Expect(err).Should(Succeed())
	Expect(bytes.Equal(mval, mret)).Should(BeTrue())
	entries, err :=
		testDB.GetEntries(key, 0, 0, 100, func([]byte) bool {
			return true
		})
	Expect(err).Should(Succeed())
	Expect(len(entries)).Should(Equal(1))

	entries, datas, err :=
		testDB.GetMultiEntries([]string{key}, []string{mkey},
			0, 0, 100, func([]byte) bool {
				return true
			})
	Expect(err).Should(Succeed())
	Expect(len(entries)).Should(Equal(1))
	Expect(len(datas)).Should(Equal(1))
	return true
}
<commit_msg>Uncomment disabled tests<commit_after>package storage

import (
	"bytes"
	"fmt"
	"math"
	"testing/quick"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"encoding/hex"
)

const (
	testDBDir = "./testdb"
)

var testDB *DB

var _ = Describe("Storage Main Test", func() {

	BeforeEach(func() {
		var err error
		testDB, err = OpenDB(testDBDir, 1000)
		Expect(err).Should(Succeed())
	})

	AfterEach(func() {
		testDB.Close()
		err := testDB.Delete()
		Expect(err).Should(Succeed())
	})

	It("Open and reopen", func() {
		fmt.Println("Open and re-open")
		stor, err := OpenDB("./openleveldb", 1000)
		Expect(err).Should(Succeed())
		stor.Close()
		stor, err = OpenDB("./openleveldb", 1000)
		Expect(err).Should(Succeed())
		stor.Close()
		err = stor.Delete()
		Expect(err).Should(Succeed())
		fmt.Println("Open and re-open done")
	})

	It("String metadata", func() {
		err := quick.Check(testStringMetadata, nil)
		Expect(err).Should(Succeed())
	})

	It("String metadata negative", func() {
		ret, err := testDB.GetMetadata("NOTFOUND")
		Expect(err).Should(Succeed())
		Expect(ret).Should(BeNil())
	})

	It("Int metadata", func() {
		err := quick.Check(testIntMetadata, nil)
		Expect(err).Should(Succeed())
	})

	It("Int metadata negative", func() {
		ret, err := testDB.GetIntMetadata("REALLYNOTFOUND")
		Expect(err).Should(Succeed())
		Expect(ret).Should(BeEquivalentTo(0))
	})

	It("Entries", func() {
		err := quick.Check(testEntry, nil)
		Expect(err).Should(Succeed())
	})

	It("Entries same tag", func() {
		err := quick.Check(func(lsn uint64, index uint32, data []byte) bool {
			return testEntry("tag", lsn, index, data)
		}, nil)
		Expect(err).Should(Succeed())
	})

	It("Entries empty tag", func() {
		err := quick.Check(func(lsn uint64, index uint32, data []byte) bool {
			return testEntry("", lsn, index, data)
		}, nil)
		Expect(err).Should(Succeed())
	})

	It("Entries same LSN", func() {
		err := quick.Check(func(index uint32, data []byte) bool {
			return testEntry("tag", 8675309, index, data)
		}, nil)
		Expect(err).Should(Succeed())
	})

	It("Entries and metadata", func() {
		err := quick.Check(testEntryAndData, nil)
		Expect(err).Should(Succeed())
	})

	It("Read not found", func() {
		buf, err := testDB.GetEntry("foo", 0, 0)
		Expect(err).Should(Succeed())
		Expect(buf).Should(BeNil())
	})

	It("Read not found multi", func() {
		bufs, err := testDB.GetEntries("foo", 0, 0, 100, nil)
		Expect(err).Should(Succeed())
		Expect(bufs).Should(BeEmpty())
	})

	It("Reading sequences", func() {
		val1 := []byte("Hello!")
		val2 := []byte("World.")

		testDB.PutEntry("a", 0, 0, val1)
		testDB.PutEntry("a", 1, 0, val2)
		testDB.PutEntry("a", 1, 1, val1)
		testDB.PutEntry("a", 2, 0, val2)
		testDB.PutEntry("b", 3, 0, val1)
		testDB.PutEntry("c", 4, 0, val1)
		testDB.PutEntry("c", 4, 1, val2)
		testDB.PutEntry("", 10, 0, val1)
		testDB.PutEntry("", 11, 1, val2)

		// Read whole ranges
		testGetSequence("a", 0, 0, 100, [][]byte{val1, val2, val1, val2})
		testGetSequence("b", 0, 0, 100, [][]byte{val1})
		testGetSequence("c", 0, 0, 100, [][]byte{val1, val2})
		testGetSequence("", 0, 0, 100, [][]byte{val1, val2})

		// Read after start
		testGetSequence("a", 1, 0, 100, [][]byte{val2, val1, val2})
		testGetSequence("a", 1, 1, 100, [][]byte{val1, val2})
		testGetSequence("a", 2, 0, 100, [][]byte{val2})
		testGetSequence("a", 2, 1, 100, [][]byte{})
		testGetSequence("a", 3, 0, 100, [][]byte{})

		// Read with limit
		testGetSequenceFilter("a", 0, 0, 2, [][]byte{val2, val2}, val1)
		testGetSequenceFilter("a", 0, 0, 1, [][]byte{val2}, val1)

		// Read with limit and filter
		testGetSequence("a", 0, 0, 4, [][]byte{val1, val2, val1, val2})
		testGetSequence("a", 0, 0, 3, [][]byte{val1, val2, val1})
		testGetSequence("a", 0, 0, 2, [][]byte{val1, val2})
		testGetSequence("a", 0, 0, 1, [][]byte{val1})
		testGetSequence("a", 0, 0, 0, [][]byte{})

		// Read invalid range
		testGetSequence("d", 0, 0, 0, [][]byte{})

		// Read a bunch of ranges
		testGetSequences([]string{"a", "b", "c"}, 0, 0, 100,
			[][]byte{val1, val2, val1, val2, val1, val1, val2})
		// test that sorting works
		testGetSequences([]string{"c", "b", "a"}, 0, 0, 100,
			[][]byte{val1, val2, val1, val2, val1, val1, val2})
		// test that limits work
		testGetSequences([]string{"c", "b", "a"}, 0, 0, 3,
			[][]byte{val1, val2, val1})
		// test that they work when we start in the middle
		testGetSequences([]string{"c", "b", "a"}, 2, 0, 3,
			[][]byte{val2, val1, val1})
	})

	It("Purge empty database", func() {
		count, err := testDB.PurgeEntries(func(buf []byte) bool {
			return true
		})
		Expect(err).Should(Succeed())
		Expect(count).Should(BeZero())
	})

	It("Purge some entries", func() {
		val1 := []byte("Hello")
		val2 := []byte("Goodbye")

		testDB.PutEntry("a", 0, 0, val1)
		testDB.PutEntry("a", 1, 0, val2)
		testDB.PutEntry("a", 1, 1, val1)
		testDB.PutEntry("a", 2, 0, val2)
		testDB.PutEntry("b", 3, 0, val1)
		testDB.PutEntry("c", 4, 0, val1)
		testDB.PutEntry("c", 4, 1, val2)
		testDB.PutEntry("", 10, 0, val1)
		testDB.PutEntry("", 11, 1, val2)

		// Purge only one value
		count, err := testDB.PurgeEntries(func(buf []byte) bool {
			return bytes.Equal(buf, val2)
		})
		Expect(err).Should(Succeed())
		Expect(count).Should(BeEquivalentTo(4))

		// Verify that re-purge does nothing
		count, err = testDB.PurgeEntries(func(buf []byte) bool {
			return bytes.Equal(buf, val2)
		})
		Expect(err).Should(Succeed())
		Expect(count).Should(BeZero())

		found, err := testDB.GetEntry("a", 0, 0)
		Expect(err).Should(Succeed())
		Expect(bytes.Equal(found, val1)).Should(BeTrue())

		found, err = testDB.GetEntry("a", 0, 1)
		Expect(err).Should(Succeed())
		Expect(found).Should(BeNil())

		// Purge the rest of the entries
		count, err = testDB.PurgeEntries(func(buf []byte) bool {
			return true
		})
		Expect(err).Should(Succeed())
		Expect(count).Should(BeEquivalentTo(5))

		// Verify that everything is gone now
		count, err = testDB.PurgeEntries(func(buf []byte) bool {
			return true
		})
		Expect(err).Should(Succeed())
		Expect(count).Should(BeZero())
	})
})

func testGetSequence(tag string, lsn uint64,
	index uint32, limit int, expected [][]byte) {
	ret, err := testDB.GetEntries(tag, lsn, index, limit, nil)
	Expect(err).Should(Succeed())
	Expect(len(ret)).Should(Equal(len(expected)))
	for i := range expected {
		Expect(bytes.Equal(expected[i], ret[i])).Should(BeTrue())
	}
}

func testGetSequenceFilter(tag string, lsn uint64,
	index uint32, limit int, expected [][]byte, rejected []byte) {
	ret, err := testDB.GetEntries(tag, lsn, index, limit,
		func(rej []byte) bool {
			if bytes.Equal(rej, rejected) {
				return false
			}
			return true
		})
	Expect(err).Should(Succeed())
	Expect(len(ret)).Should(Equal(len(expected)))
	for i := range expected {
		Expect(bytes.Equal(expected[i], ret[i])).Should(BeTrue())
	}
}

func testGetSequences(tags []string, lsn uint64,
	index uint32, limit int, expected [][]byte) {
	ret, _, err := testDB.GetMultiEntries(tags, nil, lsn, index, limit, nil)
	Expect(err).Should(Succeed())
	Expect(len(ret)).Should(Equal(len(expected)))
	for i := range expected {
		Expect(bytes.Equal(expected[i], ret[i])).Should(BeTrue())
	}
}

func testStringMetadata(key string, val []byte) bool {
	err := testDB.SetMetadata(key, val)
	Expect(err).Should(Succeed())
	ret, err := testDB.GetMetadata(key)
	Expect(err).Should(Succeed())
	Expect(bytes.Equal(val, ret)).Should(BeTrue())
	return true
}

func testIntMetadata(key string, val int64) bool {
	err := testDB.SetIntMetadata(key, val)
	Expect(err).Should(Succeed())
	ret, err := testDB.GetIntMetadata(key)
	Expect(err).Should(Succeed())
	Expect(ret).Should(Equal(val))
	return true
}

func testEntry(key string, lsn uint64, index uint32, val []byte) bool {
	err := testDB.PutEntry(key, lsn, index, val)
	Expect(err).Should(Succeed())
	ret, err := testDB.GetEntry(key, lsn, index)
	Expect(err).Should(Succeed())
	if !bytes.Equal(val, ret) {
		fmt.Printf("Val is %d %s ret is %d %s, key is %s, lsn is %d index is %d\n", len(val), hex.Dump(val), len(ret), hex.Dump(ret), key, lsn, index)
	}
	Expect(bytes.Equal(val, ret)).Should(BeTrue())
	return true
}

func testEntryAndData(key string, lsn uint64, index uint32, val []byte,
	mkey string, mval []byte) bool {
	if key == "" {
		return true
	}
	if lsn > math.MaxInt64 {
		// TODO this does not sound right to me, but the test fails if
		// the high bit is set
		return true
	}
	fmt.Fprintf(GinkgoWriter, "EandD: key = %v lsn = %x index = %x mkey = %v val = %v mval = %v\n",
		[]byte(key), lsn, index, []byte(mkey), val, mval)
	err := testDB.PutEntryAndMetadata(key, lsn, index, val, mkey, mval)
	Expect(err).Should(Succeed())
	ret, err := testDB.GetEntry(key, lsn, index)
	Expect(err).Should(Succeed())
	Expect(bytes.Equal(val, ret)).Should(BeTrue())
	mret, err := testDB.GetMetadata(mkey)
	Expect(err).Should(Succeed())
	Expect(bytes.Equal(mval, mret)).Should(BeTrue())
	entries, err :=
		testDB.GetEntries(key, 0, 0, 100, func([]byte) bool {
			return true
		})
	Expect(err).Should(Succeed())
	Expect(len(entries)).Should(Equal(1))

	entries, datas, err :=
		testDB.GetMultiEntries([]string{key}, []string{mkey},
			0, 0, 100, func([]byte) bool {
				return true
			})
	Expect(err).Should(Succeed())
	Expect(len(entries)).Should(Equal(1))
	Expect(len(datas)).Should(Equal(1))
	return true
}
<|endoftext|>


<commit_before>package conf

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"unicode"
)

type hasPrintf interface {
	Printf(fmt string, v ...interface{})
}

type sortByCommitId struct {
	data   []string
	logger hasPrintf
}

func (s sortByCommitId) Len() int {
	return len(s.data)
}
func (s sortByCommitId) Swap(i, j int) {
	s.data[i], s.data[j] = s.data[j], s.data[i]
}
func (s sortByCommitId) Less(i, j int) bool {
	s1 := s.data[i]
	id1, err1 := ExtractCommitIdFromFilename(s1)
	if err1 != nil {
		s.logger.Printf("sortByCommitId.Less: error parsing config file path: '%s': %v", s1, err1)
	}
	s2 := s.data[j]
	id2, err2 := ExtractCommitIdFromFilename(s2)
	if err2 != nil {
		s.logger.Printf("sortByCommitId.Less: error parsing config file path: '%s': %v", s2, err2)
	}
	return id1 < id2
}

func ExtractCommitIdFromFilename(filename string) (int, error) {
	lastDot := strings.LastIndexByte(filename, '.')
	commitId := filename[lastDot+1:]
	id, err := strconv.Atoi(commitId)
	if err != nil {
		return -1, fmt.Errorf("extractCommitIdFromFilename: error parsing filename [%s]: %v", filename, err)
	}

	return id, nil
}

func FindLastConfig(configPathPrefix string, logger hasPrintf) (string, error) {

	lastIdPath := getLastIdPath(configPathPrefix)
	f, openErr := os.Open(lastIdPath)
	if openErr != nil {
		defer f.Close()
		r := bufio.NewReader(f)
		line, _, readErr := r.ReadLine()
		if readErr == nil {
			id := string(line[:])
			path := getConfigPath(configPathPrefix, id)
			_, statErr := os.Stat(path)
			if statErr == nil {
				logger.Printf("FindLastConfig: found from shortcut: '%s'", path)
				return path, nil
			} else {
				logger.Printf("FindLastConfig: stat failure '%s': %v", lastIdPath, statErr)
			}
		} else {
			logger.Printf("FindLastConfig: read failure '%s': %v", lastIdPath, readErr)
		}
	}
	logger.Printf("FindLastConfig: last id file not found '%s': %v", lastIdPath, openErr)

	// search filesystem directory

	dirname, matches, err := ListConfig(configPathPrefix, logger)
	if err != nil {
		return "", err
	}

	size := len(matches)

	logger.Printf("FindLastConfig: found %d matching files: %v", size, matches)

	if size < 1 {
		return "", fmt.Errorf("FindLastConfig: no config file found for prefix: %s", configPathPrefix)
	}

	maxId := -1
	last := ""
	for _, m := range matches {
		id, idErr := ExtractCommitIdFromFilename(m)
		if idErr != nil {
			return "", fmt.Errorf("FindLastConfig: bad commit id: %s: %v", m, idErr)
		}
		if id >= maxId {
			maxId = id
			last = m
		}
	}

	lastPath := filepath.Join(dirname, last)

	logger.Printf("FindLastConfig: found: %s", lastPath)

	return lastPath, nil
}

func ListConfigSorted(configPathPrefix string, reverse bool, logger hasPrintf) (string, []string, error) {

	dirname, matches, err := ListConfig(configPathPrefix, logger)
	if err != nil {
		return dirname, matches, err
	}

	if reverse {
		sort.Sort(sort.Reverse(sortByCommitId{data: matches, logger: logger}))
	} else {
		sort.Sort(sortByCommitId{data: matches, logger: logger})
	}

	return dirname, matches, nil
}

func ListConfig(configPathPrefix string, logger hasPrintf) (string, []string, error) {

	dirname := filepath.Dir(configPathPrefix)

	dir, err := os.Open(dirname)
	if err != nil {
		return "", nil, fmt.Errorf("ListConfig: error opening dir '%s': %v", dirname, err)
	}

	names, e := dir.Readdirnames(0)
	if e != nil {
		return "", nil, fmt.Errorf("ListConfig: error reading dir '%s': %v", dirname, e)
	}

	dir.Close()

	basename := filepath.Base(configPathPrefix)

	// filter prefix
	matches := names[:0] // slice trick: Filtering without allocating
	for _, x := range names {
		lastByte := rune(x[len(x)-1])
		if unicode.IsDigit(lastByte) && strings.HasPrefix(x, basename) {
			matches = append(matches, x)
		}
	}

	return dirname, matches, nil
}

type HasWrite interface {
	Write(p []byte) (int, error)
}

func getLastIdPath(configPathPrefix string) string {
	return fmt.Sprintf("%slast", configPathPrefix)
}

func getConfigPath(configPathPrefix, id string) string {
	return fmt.Sprintf("%s%s", configPathPrefix, id)
}

func SaveNewConfig(configPathPrefix string, maxFiles int, logger hasPrintf, writeFunc func(HasWrite) error) (string, error) {

	lastConfig, err1 := FindLastConfig(configPathPrefix, logger)
	if err1 != nil {
		logger.Printf("SaveNewConfig: error reading config: [%s]: %v", configPathPrefix, err1)
	}

	id, err2 := ExtractCommitIdFromFilename(lastConfig)
	if err2 != nil {
		logger.Printf("SaveNewConfig: error parsing config path: [%s]: %v", lastConfig, err2)
	}

	newCommitId := id + 1
	newFilepath := getConfigPath(configPathPrefix, strconv.Itoa(newCommitId))

	logger.Printf("SaveNewConfig: newPath=[%s]", newFilepath)

	if _, err := os.Stat(newFilepath); err == nil {
		return "", fmt.Errorf("SaveNewConfig: new file exists: [%s]", newFilepath)
	}

	f, err3 := os.Create(newFilepath)
	if err3 != nil {
		return "", fmt.Errorf("SaveNewConfig: error creating file: [%s]: %v", newFilepath, err3)
	}

	w := bufio.NewWriter(f)

	if err := writeFunc(w); err != nil {
		return "", fmt.Errorf("SaveNewConfig: writeFunc error: [%s]: %v", newFilepath, err)
	}

	if err := w.Flush(); err != nil {
		return "", fmt.Errorf("SaveNewConfig: error flushing file: [%s]: %v", newFilepath, err)
	}

	if err := f.Close(); err != nil {
		return "", fmt.Errorf("SaveNewConfig: error closing file: [%s]: %v", newFilepath, err)
	}

	// write last id into shortcut file
	lastIdPath := getLastIdPath(configPathPrefix)
	if err := ioutil.WriteFile(lastIdPath, []byte(strconv.Itoa(newCommitId)), 0700); err != nil {
		logger.Printf("SaveNewConfig: error writing last id file '%s': %v", lastIdPath, err)
	}

	eraseOldFiles(configPathPrefix, maxFiles, logger)

	return newFilepath, nil
}

func eraseOldFiles(configPathPrefix string, maxFiles int, logger hasPrintf) {

	if maxFiles < 1 {
		return
	}

	dirname, matches, err := ListConfigSorted(configPathPrefix, false, logger)
	if err != nil {
		logger.Printf("eraseOldFiles: %v", err)
		return
	}

	totalFiles := len(matches)

	toDelete := totalFiles - maxFiles
	if toDelete < 1 {
		logger.Printf("eraseOldFiles: nothing to delete existing=%d <= max=%d", totalFiles, maxFiles)
		return
	}

	for i := 0; i < toDelete; i++ {
		path := filepath.Join(dirname, matches[i])
		logger.Printf("eraseOldFiles: delete: [%s]", path)
		if err := os.Remove(path); err != nil {
			logger.Printf("eraseOldFiles: delete: error: [%s]: %v", path, err)
		}
	}
}
<commit_msg>Fix last id shortcut.<commit_after>package conf

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"unicode"
)

type hasPrintf interface {
	Printf(fmt string, v ...interface{})
}

type sortByCommitId struct {
	data   []string
	logger hasPrintf
}

func (s sortByCommitId) Len() int {
	return len(s.data)
}
func (s sortByCommitId) Swap(i, j int) {
	s.data[i], s.data[j] = s.data[j], s.data[i]
}
func (s sortByCommitId) Less(i, j int) bool {
	s1 := s.data[i]
	id1, err1 := ExtractCommitIdFromFilename(s1)
	if err1 != nil {
		s.logger.Printf("sortByCommitId.Less: error parsing config file path: '%s': %v", s1, err1)
	}
	s2 := s.data[j]
	id2, err2 := ExtractCommitIdFromFilename(s2)
	if err2 != nil {
		s.logger.Printf("sortByCommitId.Less: error parsing config file path: '%s': %v", s2, err2)
	}
	return id1 < id2
}

func ExtractCommitIdFromFilename(filename string) (int, error) {
	lastDot := strings.LastIndexByte(filename, '.')
	commitId := filename[lastDot+1:]
	id, err := strconv.Atoi(commitId)
	if err != nil {
		return -1, fmt.Errorf("extractCommitIdFromFilename: error parsing filename [%s]: %v", filename, err)
	}

	return id, nil
}

func FindLastConfig(configPathPrefix string, logger hasPrintf) (string, error) {

	lastIdPath := getLastIdPath(configPathPrefix)
	f, openErr := os.Open(lastIdPath)
	if openErr == nil {
		defer f.Close()
		r := bufio.NewReader(f)
		line, _, readErr := r.ReadLine()
		if readErr == nil {
			id := string(line[:])
			path := getConfigPath(configPathPrefix, id)
			_, statErr := os.Stat(path)
			if statErr == nil {
				logger.Printf("FindLastConfig: found from shortcut: '%s'", path)
				return path, nil
			} else {
				logger.Printf("FindLastConfig: stat failure '%s': %v", lastIdPath, statErr)
			}
		} else {
			logger.Printf("FindLastConfig: read failure '%s': %v", lastIdPath, readErr)
		}
	}
	logger.Printf("FindLastConfig: last id file not found '%s': %v", lastIdPath, openErr)

	// search filesystem directory

	dirname, matches, err := ListConfig(configPathPrefix, logger)
	if err != nil {
		return "", err
	}

	size := len(matches)

	logger.Printf("FindLastConfig: found %d matching files: %v", size, matches)

	if size < 1 {
		return "", fmt.Errorf("FindLastConfig: no config file found for prefix: %s", configPathPrefix)
	}

	maxId := -1
	last := ""
	for _, m := range matches {
		id, idErr := ExtractCommitIdFromFilename(m)
		if idErr != nil {
			return "", fmt.Errorf("FindLastConfig: bad commit id: %s: %v", m, idErr)
		}
		if id >= maxId {
			maxId = id
			last = m
		}
	}

	lastPath := filepath.Join(dirname, last)

	logger.Printf("FindLastConfig: found: %s", lastPath)

	return lastPath, nil
}

func ListConfigSorted(configPathPrefix string, reverse bool, logger hasPrintf) (string, []string, error) {

	dirname, matches, err := ListConfig(configPathPrefix, logger)
	if err != nil {
		return dirname, matches, err
	}

	if reverse {
		sort.Sort(sort.Reverse(sortByCommitId{data: matches, logger: logger}))
	} else {
		sort.Sort(sortByCommitId{data: matches, logger: logger})
	}

	return dirname, matches, nil
}

func ListConfig(configPathPrefix string, logger hasPrintf) (string, []string, error) {

	dirname := filepath.Dir(configPathPrefix)

	dir, err := os.Open(dirname)
	if err != nil {
		return "", nil, fmt.Errorf("ListConfig: error opening dir '%s': %v", dirname, err)
	}

	names, e := dir.Readdirnames(0)
	if e != nil {
		return "", nil, fmt.Errorf("ListConfig: error reading dir '%s': %v", dirname, e)
	}

	dir.Close()

	basename := filepath.Base(configPathPrefix)

	// filter prefix
	matches := names[:0] // slice trick: Filtering without allocating
	for _, x := range names {
		lastByte := rune(x[len(x)-1])
		if unicode.IsDigit(lastByte) && strings.HasPrefix(x, basename) {
			matches = append(matches, x)
		}
	}

	return dirname, matches, nil
}

type HasWrite interface {
	Write(p []byte) (int, error)
}

func getLastIdPath(configPathPrefix string) string {
	return fmt.Sprintf("%slast", configPathPrefix)
}

func getConfigPath(configPathPrefix, id string) string {
	return fmt.Sprintf("%s%s", configPathPrefix, id)
}

func SaveNewConfig(configPathPrefix string, maxFiles int, logger hasPrintf, writeFunc func(HasWrite) error) (string, error) {

	lastConfig, err1 := FindLastConfig(configPathPrefix, logger)
	if err1 != nil {
		logger.Printf("SaveNewConfig: error reading config: [%s]: %v", configPathPrefix, err1)
	}

	id, err2 := ExtractCommitIdFromFilename(lastConfig)
	if err2 != nil {
		logger.Printf("SaveNewConfig: error parsing config path: [%s]: %v", lastConfig, err2)
	}

	newCommitId := id + 1
	newFilepath := getConfigPath(configPathPrefix, strconv.Itoa(newCommitId))

	logger.Printf("SaveNewConfig: newPath=[%s]", newFilepath)

	if _, err := os.Stat(newFilepath); err == nil {
		return "", fmt.Errorf("SaveNewConfig: new file exists: [%s]", newFilepath)
	}

	f, err3 := os.Create(newFilepath)
	if err3 != nil {
		return "", fmt.Errorf("SaveNewConfig: error creating file: [%s]: %v", newFilepath, err3)
	}

	w := bufio.NewWriter(f)

	if err := writeFunc(w); err != nil {
		return "", fmt.Errorf("SaveNewConfig: writeFunc error: [%s]: %v", newFilepath, err)
	}

	if err := w.Flush(); err != nil {
		return "", fmt.Errorf("SaveNewConfig: error flushing file: [%s]: %v", newFilepath, err)
	}

	if err := f.Close(); err != nil {
		return "", fmt.Errorf("SaveNewConfig: error closing file: [%s]: %v", newFilepath, err)
	}

	// write last id into shortcut file
	lastIdPath := getLastIdPath(configPathPrefix)
	if err := ioutil.WriteFile(lastIdPath, []byte(strconv.Itoa(newCommitId)), 0700); err != nil {
		logger.Printf("SaveNewConfig: error writing last id file '%s': %v", lastIdPath, err)
	}

	eraseOldFiles(configPathPrefix, maxFiles, logger)

	return newFilepath, nil
}

func eraseOldFiles(configPathPrefix string, maxFiles int, logger hasPrintf) {

	if maxFiles < 1 {
		return
	}

	dirname, matches, err := ListConfigSorted(configPathPrefix, false, logger)
	if err != nil {
		logger.Printf("eraseOldFiles: %v", err)
		return
	}

	totalFiles := len(matches)

	toDelete := totalFiles - maxFiles
	if toDelete < 1 {
		logger.Printf("eraseOldFiles: nothing to delete existing=%d <= max=%d", totalFiles, maxFiles)
		return
	}

	for i := 0; i < toDelete; i++ {
		path := filepath.Join(dirname, matches[i])
		logger.Printf("eraseOldFiles: delete: [%s]", path)
		if err := os.Remove(path); err != nil {
			logger.Printf("eraseOldFiles: delete: error: [%s]: %v", path, err)
		}
	}
}
<|endoftext|>


<commit_before>package mpb

import (
	"reflect"
	"testing"
)

func TestFillBar(t *testing.T) {
	b := newTestBar(80).SetEmpty('-').SetFill('=').SetTip('>').SetLeftEnd('[').SetRightEnd(']')
	tests := []struct {
		width int
		want  []byte
	}{
		{
			width: 1,
			want:  []byte{'[', ']'},
		},
		{
			width: 2,
			want:  []byte{'[', ']'},
		},
		{
			width: 3,
			want:  []byte{'[', '>', ']'},
		},
		{
			width: 4,
			want:  []byte{'[', '=', '>', ']'},
		},
	}

	for _, test := range tests {
		got := b.fillBar(80, 60, test.width)
		if !reflect.DeepEqual(test.want, got) {
			t.Errorf("Want: %q, Got: %q\n", test.want, got)
		}
	}
}

func newTestBar(width int) *Bar {
	b := &Bar{
		width: width,
	}
	return b
}
<commit_msg>fix test<commit_after>package mpb

import (
	"reflect"
	"testing"
)

func TestFillBar(t *testing.T) {
	b := newTestBar(80).SetEmpty('-').SetFill('=').SetTip('>').SetLeftEnd('[').SetRightEnd(']')
	tests := []struct {
		width int
		want  []byte
	}{
		{
			width: 1,
			want:  []byte{},
		},
		{
			width: 2,
			want:  []byte{'[', ']'},
		},
		{
			width: 3,
			want:  []byte{'[', '>', ']'},
		},
		{
			width: 4,
			want:  []byte{'[', '=', '>', ']'},
		},
	}

	for _, test := range tests {
		got := b.fillBar(80, 60, test.width)
		if !reflect.DeepEqual(test.want, got) {
			t.Errorf("Want: %q, Got: %q\n", test.want, got)
		}
	}
}

func newTestBar(width int) *Bar {
	b := &Bar{
		width: width,
	}
	return b
}
<|endoftext|>


<commit_before>// Converts a string in an arbitrary base to any other arbitrary base.
package baseconv

import (
	"errors"
	"fmt"
	"strings"
)

// Convert num from specified base to a different base.
func Convert(num, fromBase, toBase string) (string, error) {
	if num == "" {
		return "", errors.New("invalid number")
	}

	if len(fromBase) < 2 {
		return "", errors.New("invalid fromBase")
	}

	if len(toBase) < 2 {
		return "", errors.New("invalid toBase")
	}

	fromLen := len(fromBase)
	toLen := len(toBase)
	numLen := len(num)
	result := ""

	number := make([]int, numLen)
	for i := 0; i < numLen; i++ {
		number[i] = strings.IndexByte(fromBase, num[i])
		if number[i] < 0 {
			return "", errors.New(fmt.Sprintf("invalid character '%c' at %d", num[i], i))
		}
	}

	// loop until whole number is converted
	for {
		divide := 0
		newlen := 0

		// perform division manually (which is why this works with big numbers)
		for i := 0; i < numLen; i++ {
			divide = divide*fromLen + number[i]
			if divide >= toLen {
				number[newlen] = int(divide / toLen)
				divide = divide % toLen
				newlen++
			} else if newlen > 0 {
				number[newlen] = 0
				newlen++
			}
		}

		numLen = newlen
		result = string(toBase[divide]) + result // divide is basically num % toLen (i.e. the new character)

		if newlen == 0 {
			break
		}
	}

	return result, nil
}

const (
	DigitsBin = "01"
	DigitsOct = "01234567"
	DigitsDec = "0123456789"
	DigitsHex = "0123456789abcdef"
	Digits36  = "0123456789abcdefghijklmnopqrstuvwxyz"
	Digits62  = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	Digits64  = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
)

// Encode a string into DigitsBin with optional specified base (default: DigitsDec).
func EncodeBin(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, DigitsBin)
}

// Decode a string from DigitsBin with optional specified base (default: DigitsDec).
func DecodeBin(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, DigitsBin, to)
}

// Encode a string into DigitsOct with optional specified base (default: DigitsDec).
func EncodeOct(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, DigitsOct)
}

// Decode a string from DigitsOct with optional specified base (default: DigitsDec).
func DecodeOct(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, DigitsOct, to)
}

// Encode a string into DigitsHex with optional specified base (default: DigitsDec).
func EncodeHex(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, DigitsHex)
}

// Decode a string from DigitsHex with optional specified base (default: DigitsDec).
func DecodeHex(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, DigitsHex, to)
}

// Encode a string into Digits36 with optional specified base (default: DigitsDec).
func Encode36(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, Digits36)
}

// Decode a string from Digits36 with optional specified base (default: DigitsDec).
func Decode36(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, Digits36, to)
}

// Encode a string into Digits62 with optional specified base (default: DigitsDec).
func Encode62(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, Digits62)
}

// Decode a string from Digits62 with optional specified base (default: DigitsDec).
func Decode62(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, Digits62, to)
}

// Encode a string into Digits64 with optional specified base (default: DigitsDec).
func Encode64(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, Digits64)
}

// Decode a string from Digits64 with optional specified base (default: DigitsDec).
func Decode64(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, Digits64, to)
}
<commit_msg>Minor change to use []byte as result storage<commit_after>// Converts a string in an arbitrary base to any other arbitrary base.
package baseconv

import (
	"errors"
	"fmt"
	"strings"
)

// Convert num from specified base to a different base.
func Convert(num, fromBase, toBase string) (string, error) {
	if num == "" {
		return "", errors.New("invalid number")
	}

	if len(fromBase) < 2 {
		return "", errors.New("invalid fromBase")
	}

	if len(toBase) < 2 {
		return "", errors.New("invalid toBase")
	}

	fromLen := len(fromBase)
	toLen := len(toBase)
	numLen := len(num)
	result := make([]byte, 0)

	number := make([]int, numLen)
	for i := 0; i < numLen; i++ {
		number[i] = strings.IndexByte(fromBase, num[i])
		if number[i] < 0 {
			return "", errors.New(fmt.Sprintf("invalid character '%c' at %d", num[i], i))
		}
	}

	// loop until whole number is converted
	for {
		divide := 0
		newlen := 0

		// perform division manually (which is why this works with big numbers)
		for i := 0; i < numLen; i++ {
			divide = divide*fromLen + number[i]
			if divide >= toLen {
				number[newlen] = int(divide / toLen)
				divide = divide % toLen
				newlen++
			} else if newlen > 0 {
				number[newlen] = 0
				newlen++
			}
		}

		numLen = newlen
		result = append([]byte{toBase[divide]}, result...) // divide is basically num % toLen (i.e. the new character)

		if newlen == 0 {
			break
		}
	}

	return string(result), nil
}

const (
	DigitsBin = "01"
	DigitsOct = "01234567"
	DigitsDec = "0123456789"
	DigitsHex = "0123456789abcdef"
	Digits36  = "0123456789abcdefghijklmnopqrstuvwxyz"
	Digits62  = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	Digits64  = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
)

// Encode a string into DigitsBin with optional specified base (default: DigitsDec).
func EncodeBin(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, DigitsBin)
}

// Decode a string from DigitsBin with optional specified base (default: DigitsDec).
func DecodeBin(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, DigitsBin, to)
}

// Encode a string into DigitsOct with optional specified base (default: DigitsDec).
func EncodeOct(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, DigitsOct)
}

// Decode a string from DigitsOct with optional specified base (default: DigitsDec).
func DecodeOct(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, DigitsOct, to)
}

// Encode a string into DigitsHex with optional specified base (default: DigitsDec).
func EncodeHex(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, DigitsHex)
}

// Decode a string from DigitsHex with optional specified base (default: DigitsDec).
func DecodeHex(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, DigitsHex, to)
}

// Encode a string into Digits36 with optional specified base (default: DigitsDec).
func Encode36(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, Digits36)
}

// Decode a string from Digits36 with optional specified base (default: DigitsDec).
func Decode36(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, Digits36, to)
}

// Encode a string into Digits62 with optional specified base (default: DigitsDec).
func Encode62(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, Digits62)
}

// Decode a string from Digits62 with optional specified base (default: DigitsDec).
func Decode62(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, Digits62, to)
}

// Encode a string into Digits64 with optional specified base (default: DigitsDec).
func Encode64(num string, base ...string) (string, error) {
	from := DigitsDec
	if len(base) > 0 {
		from = base[0]
	}

	return Convert(num, from, Digits64)
}

// Decode a string from Digits64 with optional specified base (default: DigitsDec).
func Decode64(num string, base ...string) (string, error) {
	to := DigitsDec
	if len(base) > 0 {
		to = base[0]
	}

	return Convert(num, Digits64, to)
}
<|endoftext|>


<commit_before>// Copyright 2021 The gVisor Authors.
//
// 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.

// +build amd64 arm64

package atomicbitops

import "sync/atomic"

// AlignedAtomicInt64 is an atomic int64 that is guaranteed to be 64-bit
// aligned, even on 32-bit systems. On 64-bit machines, it's just a regular
// int64.
//
// See aligned_unsafe.go in this directory for justification.
//
// +stateify savable
type AlignedAtomicInt64 struct {
	value int64
}

// Load is analagous to atomic.LoadInt64.
func (aa *AlignedAtomicInt64) Load() int64 {
	return atomic.LoadInt64(&aa.value)
}

// Store is analagous to atomic.StoreInt64.
func (aa *AlignedAtomicInt64) Store(v int64) {
	atomic.StoreInt64(&aa.value, v)
}

// Add is analagous to atomic.AddInt64.
func (aa *AlignedAtomicInt64) Add(v int64) int64 {
	return atomic.AddInt64(&aa.value, v)
}

// AlignedAtomicUint64 is an atomic uint64 that is guaranteed to be 64-bit
// aligned, even on 32-bit systems. On 64-bit machines, it's just a regular
// uint64.
//
// See aligned_unsafe.go in this directory for justification.
//
// +stateify savable
type AlignedAtomicUint64 struct {
	value uint64
}

// Load is analagous to atomic.LoadUint64.
func (aa *AlignedAtomicUint64) Load() uint64 {
	return atomic.LoadUint64(&aa.value)
}

// Store is analagous to atomic.StoreUint64.
func (aa *AlignedAtomicUint64) Store(v uint64) {
	atomic.StoreUint64(&aa.value, v)
}

// Add is analagous to atomic.AddUint64.
func (aa *AlignedAtomicUint64) Add(v uint64) uint64 {
	return atomic.AddUint64(&aa.value, v)
}
<commit_msg>Use an exhaustive list of architectures<commit_after>// Copyright 2021 The gVisor Authors.
//
// 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.

// +build !arm,!mips,!386

package atomicbitops

import "sync/atomic"

// AlignedAtomicInt64 is an atomic int64 that is guaranteed to be 64-bit
// aligned, even on 32-bit systems. On most architectures, it's just a regular
// int64.
//
// See aligned_unsafe.go in this directory for justification.
//
// +stateify savable
type AlignedAtomicInt64 struct {
	value int64
}

// Load is analagous to atomic.LoadInt64.
func (aa *AlignedAtomicInt64) Load() int64 {
	return atomic.LoadInt64(&aa.value)
}

// Store is analagous to atomic.StoreInt64.
func (aa *AlignedAtomicInt64) Store(v int64) {
	atomic.StoreInt64(&aa.value, v)
}

// Add is analagous to atomic.AddInt64.
func (aa *AlignedAtomicInt64) Add(v int64) int64 {
	return atomic.AddInt64(&aa.value, v)
}

// AlignedAtomicUint64 is an atomic uint64 that is guaranteed to be 64-bit
// aligned, even on 32-bit systems. On most architectures, it's just a regular
// uint64.
//
// See aligned_unsafe.go in this directory for justification.
//
// +stateify savable
type AlignedAtomicUint64 struct {
	value uint64
}

// Load is analagous to atomic.LoadUint64.
func (aa *AlignedAtomicUint64) Load() uint64 {
	return atomic.LoadUint64(&aa.value)
}

// Store is analagous to atomic.StoreUint64.
func (aa *AlignedAtomicUint64) Store(v uint64) {
	atomic.StoreUint64(&aa.value, v)
}

// Add is analagous to atomic.AddUint64.
func (aa *AlignedAtomicUint64) Add(v uint64) uint64 {
	return atomic.AddUint64(&aa.value, v)
}
<|endoftext|>


<commit_before>package cliutil

import (
	"bufio"
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	grpcCodes "google.golang.org/grpc/codes"
	grpcStatus "google.golang.org/grpc/status"
	empty "google.golang.org/protobuf/types/known/emptypb"
	"gopkg.in/yaml.v2"

	"github.com/datawire/dlib/dexec"
	"github.com/telepresenceio/telepresence/rpc/v2/connector"
	"github.com/telepresenceio/telepresence/v2/pkg/client"
	"github.com/telepresenceio/telepresence/v2/pkg/client/errcat"
	"github.com/telepresenceio/telepresence/v2/pkg/client/userd/auth/authdata"
	"github.com/telepresenceio/telepresence/v2/pkg/filelocation"
)

// EnsureLoggedIn ensures that the user is logged in to Ambassador Cloud.  An error is returned if
// login fails.  The result code will indicate if this is a new login or if it resued an existing
// login.  If the `apikey` argument is empty an interactive login is performed; if it is non-empty
// the key is used instead of performing an interactive login.
func EnsureLoggedIn(ctx context.Context, apikey string) (connector.LoginResult_Code, error) {
	err := GetTelepresencePro(ctx)
	if err != nil {
		return connector.LoginResult_UNSPECIFIED, err
	}
	var code connector.LoginResult_Code
	err = WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		var err error
		code, err = ClientEnsureLoggedIn(ctx, apikey, connectorClient)
		return err
	})
	return code, err
}

// ClientEnsureLoggedIn is like EnsureLoggedIn but uses an already acquired ConnectorClient.
func ClientEnsureLoggedIn(ctx context.Context, apikey string, connectorClient connector.ConnectorClient) (connector.LoginResult_Code, error) {
	resp, err := connectorClient.Login(ctx, &connector.LoginRequest{
		ApiKey: apikey,
	})
	if err != nil {
		if grpcStatus.Code(err) == grpcCodes.PermissionDenied {
			err = errcat.User.New(grpcStatus.Convert(err).Message())
		}
		return connector.LoginResult_UNSPECIFIED, err
	}
	return resp.GetCode(), nil
}

// Logout logs out of Ambassador Cloud.  Returns an error if not logged in.
func Logout(ctx context.Context) error {
	err := WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		_, err := connectorClient.Logout(ctx, &empty.Empty{})
		return err
	})
	if grpcStatus.Code(err) == grpcCodes.NotFound {
		err = errcat.User.New(grpcStatus.Convert(err).Message())
	}
	if err != nil {
		return err
	}
	return nil
}

// EnsureLoggedOut ensures that the user is logged out of Ambassador Cloud.  Returns nil if not
// logged in.
func EnsureLoggedOut(ctx context.Context) error {
	err := WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		_, err := connectorClient.Logout(ctx, &empty.Empty{})
		return err
	})
	if grpcStatus.Code(err) == grpcCodes.NotFound {
		err = nil
	}
	if err != nil {
		return err
	}
	return nil
}

// HasLoggedIn returns true if either the user has an active login session or an expired login
// session, and returns false if either the user has never logged in or has explicitly logged out.
func HasLoggedIn(ctx context.Context) bool {
	_, err := authdata.LoadUserInfoFromUserCache(ctx)
	return err == nil
}

func GetCloudUserInfo(ctx context.Context, autoLogin bool, refresh bool) (*connector.UserInfo, error) {
	var userInfo *connector.UserInfo
	err := WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		var err error
		userInfo, err = connectorClient.GetCloudUserInfo(ctx, &connector.UserInfoRequest{
			AutoLogin: autoLogin,
			Refresh:   refresh,
		})
		return err
	})
	if err != nil {
		return nil, err
	}
	return userInfo, nil
}

func GetCloudAPIKey(ctx context.Context, description string, autoLogin bool) (string, error) {
	var keyData *connector.KeyData
	err := WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		var err error
		keyData, err = connectorClient.GetCloudAPIKey(ctx, &connector.KeyRequest{
			AutoLogin:   autoLogin,
			Description: description,
		})
		return err
	})
	if err != nil {
		return "", err
	}
	return keyData.GetApiKey(), nil
}

// GetCloudLicense communicates with system a to get the jwt version of the
// license, puts it in a kubernetes secret, and then writes that secret to the
// output file for the user to apply to their cluster
func GetCloudLicense(ctx context.Context, outputFile, id string) (string, string, error) {
	var licenseData *connector.LicenseData
	err := WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		var err error
		licenseData, err = connectorClient.GetCloudLicense(ctx, &connector.LicenseRequest{
			Id: id,
		})
		return err
	})
	if err != nil {
		return "", "", err
	}
	return licenseData.GetLicense(), licenseData.GetHostDomain(), nil
}

// GetTelepresencePro prompts the user to optionally install Telepresence Pro
// if it isn't installed. If the user installs it, it also asks the user to
// automatically update their configuration to use the new binary.
func GetTelepresencePro(ctx context.Context) error {
	dir, err := filelocation.AppUserConfigDir(ctx)
	if err != nil {
		return errcat.NoDaemonLogs.Newf("unable to get path to config files: %w", err)
	}

	// If telepresence-pro doesn't exist, then we should ask the user
	// if they want to install it
	telProLocation := filepath.Join(dir, "telepresence-pro")
	if runtime.GOOS == "windows" {
		telProLocation += ".exe"
	}
	if _, err := os.Stat(telProLocation); os.IsNotExist(err) {
		reader := bufio.NewReader(os.Stdin)
		fmt.Printf("Telepresence Pro is recommended when using login features, can Telepresence install it? (y/n)")
		reply, err := reader.ReadString('\n')
		if err != nil {
			return errcat.User.Newf("error reading input: %w", err)
		}

		// If the user doesn't want to install it, then we we'll proceed
		// with launching the daemon normally
		reply = strings.TrimSpace(reply)
		if reply != "y" {
			return nil
		}

		err = installTelepresencePro(ctx, telProLocation)
		if err != nil {
			return err
		}

		// Ask the user if they want to automatically update their config
		// with the telepresence-pro binary.
		// TODO: This will remove any comments that exist in the config file
		// which it's yaml so that's _fine_ but it would be nice if we didn't
		// do that.
		fmt.Printf("Update your Telepresence config to use Telepresence Pro? (y/n)")
		reply, err = reader.ReadString('\n')
		if err != nil {
			return errcat.User.Newf("error reading input: %w", err)
		}
		reply = strings.TrimSpace(reply)
		if reply != "y" {
			return nil
		}
		err = updateConfig(ctx, telProLocation)
		if err != nil {
			return err
		}
	} else {
		// If the binary is present, we check its version to ensure it's compatible
		// with the CLI
		proCmd := dexec.CommandContext(ctx, telProLocation, "pro-version")
		proCmd.DisableLogging = true

		output, err := proCmd.CombinedOutput()
		if err != nil {
			return errcat.NoDaemonLogs.Newf("Unable to get telepresence pro version")
		}

		if !strings.Contains(string(output), client.Version()) {
			reader := bufio.NewReader(os.Stdin)
			fmt.Printf("Telepresence Pro needs to be upgraded to work with CLI version %s, allow Telepresence to upgrade it? (y/n)",
				client.Version())
			reply, err := reader.ReadString('\n')
			if err != nil {
				return errcat.NoDaemonLogs.Newf("error reading input: %w", err)
			}

			// If the user doesn't want to install it, then we we'll proceed
			// with launching the daemon normally
			reply = strings.TrimSpace(reply)
			if reply != "y" {
				return nil
			}
			err = os.Remove(telProLocation)
			if err != nil {
				return errcat.NoDaemonLogs.Newf("error removing Telepresence Pro: %w", err)
			}
			// Since we've already asked the user for permission to upgrade,
			// we can run these functions without asking permission again.
			err = installTelepresencePro(ctx, telProLocation)
			if err != nil {
				return errcat.NoDaemonLogs.Newf("error installing updated Telepresence Pro: %w",
					err)
			}

			// The users configuration is most likely correct if they are upgrading,
			// but we update it just to be extra sure.
			err = updateConfig(ctx, telProLocation)
			if err != nil {
				return errcat.NoDaemonLogs.Newf("error updating config: %w",
					err)
			}
		}
	}
	return nil
}

// installTelepresencePro installs the binary. Users should be asked for
// permission before using this function
func installTelepresencePro(ctx context.Context, telProLocation string) error {
	// We install the correct version of telepresence-pro based on
	// the OSS version that is associated with this client since
	// daemon versions need to match
	clientVersion := strings.Trim(client.Version(), "v")
	systemAHost := client.GetConfig(ctx).Cloud.SystemaHost
	installString := fmt.Sprintf("https://%s/download/tel-pro/%s/%s/%s/latest/%s",
		systemAHost, runtime.GOOS, runtime.GOARCH, clientVersion, filepath.Base(telProLocation))

	resp, err := http.Get(installString)
	if err == nil {
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			err = errors.New(resp.Status)
		}
	}
	if err != nil {
		return errcat.NoDaemonLogs.Newf("unable to download Telepresence Pro: %w", err)
	}

	out, err := os.Create(telProLocation)
	if err != nil {
		return errcat.NoDaemonLogs.Newf("unable to create file %q for Telepresence Pro: %w", telProLocation, err)
	}
	defer out.Close()

	if _, err = io.Copy(out, resp.Body); err != nil {
		return errcat.NoDaemonLogs.Newf("unable to copy Telepresence Pro to %q: %w", telProLocation, err)
	}

	if err = os.Chmod(telProLocation, 0755); err != nil {
		return errcat.NoDaemonLogs.Newf("unable to set permissions of %q to 755: %w", telProLocation, err)
	}
	return nil
}

// updateConfig updates the userDaemonBinary in the config to point to
// telProLocation. Users should be asked for permission before this is done.
func updateConfig(ctx context.Context, telProLocation string) error {
	cfg := client.GetConfig(ctx)
	cfg.Daemons.UserDaemonBinary = telProLocation

	b, err := yaml.Marshal(cfg)
	if err != nil {
		return errcat.NoDaemonLogs.Newf("error marshaling updating config: %w", err)
	}
	cfgFile := client.GetConfigFile(ctx)
	_, err = os.OpenFile(cfgFile, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return errcat.NoDaemonLogs.Newf("error opening config file: %w", err)
	}
	err = os.WriteFile(cfgFile, b, 0644)
	if err != nil {
		return errcat.NoDaemonLogs.Newf("error writing config file: %w", err)
	}
	return nil
}
<commit_msg>Save the old config.yml as config.yml.bak when updating it<commit_after>package cliutil

import (
	"bufio"
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	grpcCodes "google.golang.org/grpc/codes"
	grpcStatus "google.golang.org/grpc/status"
	empty "google.golang.org/protobuf/types/known/emptypb"
	"gopkg.in/yaml.v2"

	"github.com/datawire/dlib/dexec"
	"github.com/telepresenceio/telepresence/rpc/v2/connector"
	"github.com/telepresenceio/telepresence/v2/pkg/client"
	"github.com/telepresenceio/telepresence/v2/pkg/client/errcat"
	"github.com/telepresenceio/telepresence/v2/pkg/client/userd/auth/authdata"
	"github.com/telepresenceio/telepresence/v2/pkg/filelocation"
)

// EnsureLoggedIn ensures that the user is logged in to Ambassador Cloud.  An error is returned if
// login fails.  The result code will indicate if this is a new login or if it resued an existing
// login.  If the `apikey` argument is empty an interactive login is performed; if it is non-empty
// the key is used instead of performing an interactive login.
func EnsureLoggedIn(ctx context.Context, apikey string) (connector.LoginResult_Code, error) {
	err := GetTelepresencePro(ctx)
	if err != nil {
		return connector.LoginResult_UNSPECIFIED, err
	}
	var code connector.LoginResult_Code
	err = WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		var err error
		code, err = ClientEnsureLoggedIn(ctx, apikey, connectorClient)
		return err
	})
	return code, err
}

// ClientEnsureLoggedIn is like EnsureLoggedIn but uses an already acquired ConnectorClient.
func ClientEnsureLoggedIn(ctx context.Context, apikey string, connectorClient connector.ConnectorClient) (connector.LoginResult_Code, error) {
	resp, err := connectorClient.Login(ctx, &connector.LoginRequest{
		ApiKey: apikey,
	})
	if err != nil {
		if grpcStatus.Code(err) == grpcCodes.PermissionDenied {
			err = errcat.User.New(grpcStatus.Convert(err).Message())
		}
		return connector.LoginResult_UNSPECIFIED, err
	}
	return resp.GetCode(), nil
}

// Logout logs out of Ambassador Cloud.  Returns an error if not logged in.
func Logout(ctx context.Context) error {
	err := WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		_, err := connectorClient.Logout(ctx, &empty.Empty{})
		return err
	})
	if grpcStatus.Code(err) == grpcCodes.NotFound {
		err = errcat.User.New(grpcStatus.Convert(err).Message())
	}
	if err != nil {
		return err
	}
	return nil
}

// EnsureLoggedOut ensures that the user is logged out of Ambassador Cloud.  Returns nil if not
// logged in.
func EnsureLoggedOut(ctx context.Context) error {
	err := WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		_, err := connectorClient.Logout(ctx, &empty.Empty{})
		return err
	})
	if grpcStatus.Code(err) == grpcCodes.NotFound {
		err = nil
	}
	if err != nil {
		return err
	}
	return nil
}

// HasLoggedIn returns true if either the user has an active login session or an expired login
// session, and returns false if either the user has never logged in or has explicitly logged out.
func HasLoggedIn(ctx context.Context) bool {
	_, err := authdata.LoadUserInfoFromUserCache(ctx)
	return err == nil
}

func GetCloudUserInfo(ctx context.Context, autoLogin bool, refresh bool) (*connector.UserInfo, error) {
	var userInfo *connector.UserInfo
	err := WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		var err error
		userInfo, err = connectorClient.GetCloudUserInfo(ctx, &connector.UserInfoRequest{
			AutoLogin: autoLogin,
			Refresh:   refresh,
		})
		return err
	})
	if err != nil {
		return nil, err
	}
	return userInfo, nil
}

func GetCloudAPIKey(ctx context.Context, description string, autoLogin bool) (string, error) {
	var keyData *connector.KeyData
	err := WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		var err error
		keyData, err = connectorClient.GetCloudAPIKey(ctx, &connector.KeyRequest{
			AutoLogin:   autoLogin,
			Description: description,
		})
		return err
	})
	if err != nil {
		return "", err
	}
	return keyData.GetApiKey(), nil
}

// GetCloudLicense communicates with system a to get the jwt version of the
// license, puts it in a kubernetes secret, and then writes that secret to the
// output file for the user to apply to their cluster
func GetCloudLicense(ctx context.Context, outputFile, id string) (string, string, error) {
	var licenseData *connector.LicenseData
	err := WithConnector(ctx, func(ctx context.Context, connectorClient connector.ConnectorClient) error {
		var err error
		licenseData, err = connectorClient.GetCloudLicense(ctx, &connector.LicenseRequest{
			Id: id,
		})
		return err
	})
	if err != nil {
		return "", "", err
	}
	return licenseData.GetLicense(), licenseData.GetHostDomain(), nil
}

// GetTelepresencePro prompts the user to optionally install Telepresence Pro
// if it isn't installed. If the user installs it, it also asks the user to
// automatically update their configuration to use the new binary.
func GetTelepresencePro(ctx context.Context) error {
	dir, err := filelocation.AppUserConfigDir(ctx)
	if err != nil {
		return errcat.NoDaemonLogs.Newf("unable to get path to config files: %w", err)
	}

	// If telepresence-pro doesn't exist, then we should ask the user
	// if they want to install it
	telProLocation := filepath.Join(dir, "telepresence-pro")
	if runtime.GOOS == "windows" {
		telProLocation += ".exe"
	}
	if _, err := os.Stat(telProLocation); os.IsNotExist(err) {
		reader := bufio.NewReader(os.Stdin)
		fmt.Printf("Telepresence Pro is recommended when using login features, can Telepresence install it? (y/n)")
		reply, err := reader.ReadString('\n')
		if err != nil {
			return errcat.User.Newf("error reading input: %w", err)
		}

		// If the user doesn't want to install it, then we we'll proceed
		// with launching the daemon normally
		reply = strings.TrimSpace(reply)
		if reply != "y" {
			return nil
		}

		err = installTelepresencePro(ctx, telProLocation)
		if err != nil {
			return err
		}

		// Ask the user if they want to automatically update their config
		// with the telepresence-pro binary.
		// TODO: This will remove any comments that exist in the config file
		// which it's yaml so that's _fine_ but it would be nice if we didn't
		// do that.
		fmt.Printf("Update your Telepresence config to use Telepresence Pro? (y/n)")
		reply, err = reader.ReadString('\n')
		if err != nil {
			return errcat.User.Newf("error reading input: %w", err)
		}
		reply = strings.TrimSpace(reply)
		if reply != "y" {
			return nil
		}
		err = updateConfig(ctx, telProLocation)
		if err != nil {
			return err
		}
	} else {
		// If the binary is present, we check its version to ensure it's compatible
		// with the CLI
		proCmd := dexec.CommandContext(ctx, telProLocation, "pro-version")
		proCmd.DisableLogging = true

		output, err := proCmd.CombinedOutput()
		if err != nil {
			return errcat.NoDaemonLogs.Newf("Unable to get telepresence pro version")
		}

		if !strings.Contains(string(output), client.Version()) {
			reader := bufio.NewReader(os.Stdin)
			fmt.Printf("Telepresence Pro needs to be upgraded to work with CLI version %s, allow Telepresence to upgrade it? (y/n)",
				client.Version())
			reply, err := reader.ReadString('\n')
			if err != nil {
				return errcat.NoDaemonLogs.Newf("error reading input: %w", err)
			}

			// If the user doesn't want to install it, then we we'll proceed
			// with launching the daemon normally
			reply = strings.TrimSpace(reply)
			if reply != "y" {
				return nil
			}
			err = os.Remove(telProLocation)
			if err != nil {
				return errcat.NoDaemonLogs.Newf("error removing Telepresence Pro: %w", err)
			}
			// Since we've already asked the user for permission to upgrade,
			// we can run these functions without asking permission again.
			err = installTelepresencePro(ctx, telProLocation)
			if err != nil {
				return errcat.NoDaemonLogs.Newf("error installing updated Telepresence Pro: %w",
					err)
			}

			// The users configuration is most likely correct if they are upgrading,
			// but we update it just to be extra sure.
			err = updateConfig(ctx, telProLocation)
			if err != nil {
				return errcat.NoDaemonLogs.Newf("error updating config: %w",
					err)
			}
		}
	}
	return nil
}

// installTelepresencePro installs the binary. Users should be asked for
// permission before using this function
func installTelepresencePro(ctx context.Context, telProLocation string) error {
	// We install the correct version of telepresence-pro based on
	// the OSS version that is associated with this client since
	// daemon versions need to match
	clientVersion := strings.Trim(client.Version(), "v")
	systemAHost := client.GetConfig(ctx).Cloud.SystemaHost
	installString := fmt.Sprintf("https://%s/download/tel-pro/%s/%s/%s/latest/%s",
		systemAHost, runtime.GOOS, runtime.GOARCH, clientVersion, filepath.Base(telProLocation))

	resp, err := http.Get(installString)
	if err == nil {
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			err = errors.New(resp.Status)
		}
	}
	if err != nil {
		return errcat.NoDaemonLogs.Newf("unable to download Telepresence Pro: %w", err)
	}

	out, err := os.Create(telProLocation)
	if err != nil {
		return errcat.NoDaemonLogs.Newf("unable to create file %q for Telepresence Pro: %w", telProLocation, err)
	}
	defer out.Close()

	if _, err = io.Copy(out, resp.Body); err != nil {
		return errcat.NoDaemonLogs.Newf("unable to copy Telepresence Pro to %q: %w", telProLocation, err)
	}

	if err = os.Chmod(telProLocation, 0755); err != nil {
		return errcat.NoDaemonLogs.Newf("unable to set permissions of %q to 755: %w", telProLocation, err)
	}
	return nil
}

// updateConfig updates the userDaemonBinary in the config to point to
// telProLocation. Users should be asked for permission before this is done.
func updateConfig(ctx context.Context, telProLocation string) error {
	cfg := client.GetConfig(ctx)
	cfg.Daemons.UserDaemonBinary = telProLocation

	b, err := yaml.Marshal(cfg)
	if err != nil {
		return errcat.NoDaemonLogs.Newf("error marshaling updating config: %w", err)
	}
	cfgFile := client.GetConfigFile(ctx)
	if s, err := os.Stat(cfgFile); err == nil && s.Size() > 0 {
		_ = os.Rename(cfgFile, cfgFile+".bak")
	}

	f, err := os.OpenFile(cfgFile, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return errcat.NoDaemonLogs.Newf("error opening config file: %w", err)
	}
	defer f.Close()
	if _, err = f.Write(b); err != nil {
		return errcat.NoDaemonLogs.Newf("error writing config file: %w", err)
	}
	return nil
}
<|endoftext|>