<commit_before>package bugsnag

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"time"

	"github.com/bugsnag/bugsnag-go/errors"
	"github.com/bugsnag/bugsnag-go/sessions"

	// Fixes a bug with SHA-384 intermediate certs on some platforms.
	// - https://github.com/bugsnag/bugsnag-go/issues/9
	_ "crypto/sha512"
)

// VERSION defines the version of this Bugsnag notifier
const VERSION = "1.4.0"

var once sync.Once
var middleware middlewareStack

// Config is the configuration for the default bugsnag notifier.
var Config Configuration
var sessionTrackingConfig sessions.SessionTrackingConfiguration

// DefaultSessionPublishInterval defines how often sessions should be sent to
// Bugsnag.
// Deprecated: Exposed for developer sanity in testing. Modify at own risk.
var DefaultSessionPublishInterval = 60 * time.Second
var defaultNotifier = Notifier{&Config, nil}
var sessionTracker sessions.SessionTracker

// Configure Bugsnag. The only required setting is the APIKey, which can be
// obtained by clicking on "Settings" in your Bugsnag dashboard. This function
// is also responsible for installing the global panic handler, so it should be
// called as early as possible in your initialization process.
func Configure(config Configuration) {
	Config.update(&config)
	once.Do(Config.PanicHandler)
}

// StartSession creates new context from the context.Context instance with
// Bugsnag session data attached. Will start the session tracker if not already
// started
func StartSession(ctx context.Context) context.Context {
	startSessionTracking()
	return sessionTracker.StartSession(ctx)
}

// Notify sends an error.Error to Bugsnag along with the current stack trace.
// Although it's not strictly enforced, it's highly recommended to pass a
// context.Context object that has at one-point been returned from
// bugsnag.StartSession. Doing so ensures your stability score remains accurate,
// and future versions of Bugsnag may extract more useful information from this
// context.
// The remaining rawData is used to send extra information along with the
// error. For example you can pass the current http.Request to Bugsnag to see
// information about it in the dashboard, or set the severity of the
// notification.
func Notify(err error, rawData ...interface{}) error {
	if e := checkForEmptyError(err); e != nil {
		return e
	}
	// Stripping one stackframe to not include this function in the stacktrace
	// for a manual notification.
	skipFrames := 1
	return defaultNotifier.Notify(errors.New(err, skipFrames), rawData...)
}

// AutoNotify logs a panic on a goroutine and then repanics.
// It should only be used in places that have existing panic handlers further
// up the stack.
// Although it's not strictly enforced, it's highly recommended to pass a
// context.Context object that has at one-point been returned from
// bugsnag.StartSession. Doing so ensures your stability score remains accurate,
// and future versions of Bugsnag may extract more useful information from this
// context.
// The rawData is used to send extra information along with any
// panics that are handled this way.
// Usage:
//  go func() {
//      ctx := bugsnag.StartSession(context.Background())
//		defer bugsnag.AutoNotify(ctx)
//      // (possibly crashy code)
//  }()
// See also: bugsnag.Recover()
func AutoNotify(rawData ...interface{}) {
	if err := recover(); err != nil {
		severity := defaultNotifier.getDefaultSeverity(rawData, SeverityError)
		state := HandledState{SeverityReasonHandledPanic, severity, true, ""}
		rawData = append([]interface{}{state}, rawData...)
		// We strip the following stackframes as they don't add much info
		// - runtime/$arch - e.g. runtime/asm_amd64.s#call32
		// - runtime/panic.go#gopanic
		// Panics have their own stacktrace, so no stripping of the current stack
		skipFrames := 2
		defaultNotifier.NotifySync(errors.New(err, skipFrames), true, rawData...)
		sessionTracker.FlushSessions()
		panic(err)
	}
}

// Recover logs a panic on a goroutine and then recovers.
// Although it's not strictly enforced, it's highly recommended to pass a
// context.Context object that has at one-point been returned from
// bugsnag.StartSession. Doing so ensures your stability score remains accurate,
// and future versions of Bugsnag may extract more useful information from this
// context.
// The rawData is used to send extra information along with
// any panics that are handled this way
// Usage:
// go func() {
//     ctx := bugsnag.StartSession(context.Background())
//     defer bugsnag.Recover(ctx)
//     // (possibly crashy code)
// }()
// If you wish that any panics caught by the call to Recover shall affect your
// stability score (it does not by default):
// go func() {
//     ctx := bugsnag.StartSession(context.Background())
//     defer bugsnag.Recover(ctx, bugsnag.HandledState{Unhandled: true})
//     // (possibly crashy code)
// }()
// See also: bugsnag.AutoNotify()
func Recover(rawData ...interface{}) {
	if err := recover(); err != nil {
		severity := defaultNotifier.getDefaultSeverity(rawData, SeverityWarning)
		state := HandledState{SeverityReasonHandledPanic, severity, false, ""}
		rawData = append([]interface{}{state}, rawData...)
		// We strip the following stackframes as they don't add much info
		// - runtime/$arch - e.g. runtime/asm_amd64.s#call32
		// - runtime/panic.go#gopanic
		// Panics have their own stacktrace, so no stripping of the current stack
		skipFrames := 2
		defaultNotifier.Notify(errors.New(err, skipFrames), rawData...)
	}
}

// OnBeforeNotify adds a callback to be run before a notification is sent to
// Bugsnag.  It can be used to modify the event or its MetaData. Changes made
// to the configuration are local to notifying about this event. To prevent the
// event from being sent to Bugsnag return an error, this error will be
// returned from bugsnag.Notify() and the event will not be sent.
func OnBeforeNotify(callback func(event *Event, config *Configuration) error) {
	middleware.OnBeforeNotify(callback)
}

// Handler creates an http Handler that notifies Bugsnag any panics that
// happen. It then repanics so that the default http Server panic handler can
// handle the panic too. The rawData is used to send extra information along
// with any panics that are handled this way.
func Handler(h http.Handler, rawData ...interface{}) http.Handler {
	notifier := New(rawData...)
	if h == nil {
		h = http.DefaultServeMux
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		request := r

		// Record a session if auto notify session is enabled
		ctx := r.Context()
		if Config.IsAutoCaptureSessions() {
			ctx = StartSession(ctx)
		}
		ctx = AttachRequestData(ctx, request)
		request = r.WithContext(ctx)
		defer notifier.AutoNotify(ctx, request)
		h.ServeHTTP(w, request)
	})
}

// HandlerFunc creates an http HandlerFunc that notifies Bugsnag about any
// panics that happen. It then repanics so that the default http Server panic
// handler can handle the panic too. The rawData is used to send extra
// information along with any panics that are handled this way. If you have
// already wrapped your http server using bugsnag.Handler() you don't also need
// to wrap each HandlerFunc.
func HandlerFunc(h http.HandlerFunc, rawData ...interface{}) http.HandlerFunc {
	notifier := New(rawData...)

	return func(w http.ResponseWriter, r *http.Request) {
		request := r
		// Record a session if auto notify session is enabled
		ctx := request.Context()
		if notifier.Config.IsAutoCaptureSessions() {
			ctx = StartSession(ctx)
		}
		ctx = AttachRequestData(ctx, request)
		request = request.WithContext(ctx)
		defer notifier.AutoNotify(ctx)
		h(w, request)
	}
}

// checkForEmptyError checks if the given error (to be reported to Bugsnag) is
// nil. If it is, then log an error messageand return another error wrapping
// this error message.
func checkForEmptyError(err error) error {
	if err != nil {
		return nil
	}
	msg := "attempted to notify Bugsnag without supplying an error. Bugsnag not notified"
	Config.Logger.Printf("ERROR: " + msg)
	return fmt.Errorf(msg)
}

func init() {
	// Set up builtin middlewarez
	OnBeforeNotify(httpRequestMiddleware)

	// Default configuration
	sourceRoot := ""
	if gopath := os.Getenv("GOPATH"); len(gopath) > 0 {
		sourceRoot = filepath.Join(gopath, "src") + "/"
	} else {
		sourceRoot = filepath.Join(runtime.GOROOT(), "src") + "/"
	}
	Config.update(&Configuration{
		APIKey: "",
		Endpoints: Endpoints{
			Notify:   "https://notify.bugsnag.com",
			Sessions: "https://sessions.bugsnag.com",
		},
		Hostname:            "",
		AppType:             "",
		AppVersion:          "",
		AutoCaptureSessions: true,
		ReleaseStage:        "",
		ParamsFilters:       []string{"password", "secret", "authorization", "cookie"},
		SourceRoot:          sourceRoot,
		// * for app-engine
		ProjectPackages:     []string{"main*"},
		NotifyReleaseStages: nil,
		Logger:              log.New(os.Stdout, log.Prefix(), log.Flags()),
		PanicHandler:        defaultPanicHandler,
		Transport:           http.DefaultTransport,

		flushSessionsOnRepanic: true,
	})

	hostname, err := os.Hostname()
	if err == nil {
		Config.Hostname = hostname
	}
}

func startSessionTracking() {
	if sessionTracker == nil {
		sessionTrackingConfig.Update(&sessions.SessionTrackingConfiguration{
			APIKey:          Config.APIKey,
			Endpoint:        Config.Endpoints.Sessions,
			Version:         VERSION,
			PublishInterval: DefaultSessionPublishInterval,
			Transport:       Config.Transport,
			ReleaseStage:    Config.ReleaseStage,
			Hostname:        Config.Hostname,
			AppType:         Config.AppType,
			AppVersion:      Config.AppVersion,
			Logger:          Config.Logger,
		})
		sessionTracker = sessions.NewSessionTracker(&sessionTrackingConfig)
	}
}
<commit_msg>[chore] Update vague bugsnag.Notify godoc<commit_after>package bugsnag

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"time"

	"github.com/bugsnag/bugsnag-go/errors"
	"github.com/bugsnag/bugsnag-go/sessions"

	// Fixes a bug with SHA-384 intermediate certs on some platforms.
	// - https://github.com/bugsnag/bugsnag-go/issues/9
	_ "crypto/sha512"
)

// VERSION defines the version of this Bugsnag notifier
const VERSION = "1.4.0"

var once sync.Once
var middleware middlewareStack

// Config is the configuration for the default bugsnag notifier.
var Config Configuration
var sessionTrackingConfig sessions.SessionTrackingConfiguration

// DefaultSessionPublishInterval defines how often sessions should be sent to
// Bugsnag.
// Deprecated: Exposed for developer sanity in testing. Modify at own risk.
var DefaultSessionPublishInterval = 60 * time.Second
var defaultNotifier = Notifier{&Config, nil}
var sessionTracker sessions.SessionTracker

// Configure Bugsnag. The only required setting is the APIKey, which can be
// obtained by clicking on "Settings" in your Bugsnag dashboard. This function
// is also responsible for installing the global panic handler, so it should be
// called as early as possible in your initialization process.
func Configure(config Configuration) {
	Config.update(&config)
	once.Do(Config.PanicHandler)
}

// StartSession creates new context from the context.Context instance with
// Bugsnag session data attached. Will start the session tracker if not already
// started
func StartSession(ctx context.Context) context.Context {
	startSessionTracking()
	return sessionTracker.StartSession(ctx)
}

// Notify sends an error.Error to Bugsnag along with the current stack trace.
// If at all possible, it is recommended to pass in a context.Context, e.g.
// from a http.Request or bugsnag.StartSession() as Bugsnag will be able to
// extract additional information in some cases. The rawData is used to send
// extra information along with the error. For example you can pass the current
// http.Request to Bugsnag to see information about it in the dashboard, or set
// the severity of the notification. For a detailed list of the information
// that can be extracted, see
// https://docs.bugsnag.com/platforms/go/reporting-handled-errors/
func Notify(err error, rawData ...interface{}) error {
	if e := checkForEmptyError(err); e != nil {
		return e
	}
	// Stripping one stackframe to not include this function in the stacktrace
	// for a manual notification.
	skipFrames := 1
	return defaultNotifier.Notify(errors.New(err, skipFrames), rawData...)
}

// AutoNotify logs a panic on a goroutine and then repanics.
// It should only be used in places that have existing panic handlers further
// up the stack.
// Although it's not strictly enforced, it's highly recommended to pass a
// context.Context object that has at one-point been returned from
// bugsnag.StartSession. Doing so ensures your stability score remains accurate,
// and future versions of Bugsnag may extract more useful information from this
// context.
// The rawData is used to send extra information along with any
// panics that are handled this way.
// Usage:
//  go func() {
//      ctx := bugsnag.StartSession(context.Background())
//		defer bugsnag.AutoNotify(ctx)
//      // (possibly crashy code)
//  }()
// See also: bugsnag.Recover()
func AutoNotify(rawData ...interface{}) {
	if err := recover(); err != nil {
		severity := defaultNotifier.getDefaultSeverity(rawData, SeverityError)
		state := HandledState{SeverityReasonHandledPanic, severity, true, ""}
		rawData = append([]interface{}{state}, rawData...)
		// We strip the following stackframes as they don't add much info
		// - runtime/$arch - e.g. runtime/asm_amd64.s#call32
		// - runtime/panic.go#gopanic
		// Panics have their own stacktrace, so no stripping of the current stack
		skipFrames := 2
		defaultNotifier.NotifySync(errors.New(err, skipFrames), true, rawData...)
		sessionTracker.FlushSessions()
		panic(err)
	}
}

// Recover logs a panic on a goroutine and then recovers.
// Although it's not strictly enforced, it's highly recommended to pass a
// context.Context object that has at one-point been returned from
// bugsnag.StartSession. Doing so ensures your stability score remains accurate,
// and future versions of Bugsnag may extract more useful information from this
// context.
// The rawData is used to send extra information along with
// any panics that are handled this way
// Usage:
// go func() {
//     ctx := bugsnag.StartSession(context.Background())
//     defer bugsnag.Recover(ctx)
//     // (possibly crashy code)
// }()
// If you wish that any panics caught by the call to Recover shall affect your
// stability score (it does not by default):
// go func() {
//     ctx := bugsnag.StartSession(context.Background())
//     defer bugsnag.Recover(ctx, bugsnag.HandledState{Unhandled: true})
//     // (possibly crashy code)
// }()
// See also: bugsnag.AutoNotify()
func Recover(rawData ...interface{}) {
	if err := recover(); err != nil {
		severity := defaultNotifier.getDefaultSeverity(rawData, SeverityWarning)
		state := HandledState{SeverityReasonHandledPanic, severity, false, ""}
		rawData = append([]interface{}{state}, rawData...)
		// We strip the following stackframes as they don't add much info
		// - runtime/$arch - e.g. runtime/asm_amd64.s#call32
		// - runtime/panic.go#gopanic
		// Panics have their own stacktrace, so no stripping of the current stack
		skipFrames := 2
		defaultNotifier.Notify(errors.New(err, skipFrames), rawData...)
	}
}

// OnBeforeNotify adds a callback to be run before a notification is sent to
// Bugsnag.  It can be used to modify the event or its MetaData. Changes made
// to the configuration are local to notifying about this event. To prevent the
// event from being sent to Bugsnag return an error, this error will be
// returned from bugsnag.Notify() and the event will not be sent.
func OnBeforeNotify(callback func(event *Event, config *Configuration) error) {
	middleware.OnBeforeNotify(callback)
}

// Handler creates an http Handler that notifies Bugsnag any panics that
// happen. It then repanics so that the default http Server panic handler can
// handle the panic too. The rawData is used to send extra information along
// with any panics that are handled this way.
func Handler(h http.Handler, rawData ...interface{}) http.Handler {
	notifier := New(rawData...)
	if h == nil {
		h = http.DefaultServeMux
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		request := r

		// Record a session if auto notify session is enabled
		ctx := r.Context()
		if Config.IsAutoCaptureSessions() {
			ctx = StartSession(ctx)
		}
		ctx = AttachRequestData(ctx, request)
		request = r.WithContext(ctx)
		defer notifier.AutoNotify(ctx, request)
		h.ServeHTTP(w, request)
	})
}

// HandlerFunc creates an http HandlerFunc that notifies Bugsnag about any
// panics that happen. It then repanics so that the default http Server panic
// handler can handle the panic too. The rawData is used to send extra
// information along with any panics that are handled this way. If you have
// already wrapped your http server using bugsnag.Handler() you don't also need
// to wrap each HandlerFunc.
func HandlerFunc(h http.HandlerFunc, rawData ...interface{}) http.HandlerFunc {
	notifier := New(rawData...)

	return func(w http.ResponseWriter, r *http.Request) {
		request := r
		// Record a session if auto notify session is enabled
		ctx := request.Context()
		if notifier.Config.IsAutoCaptureSessions() {
			ctx = StartSession(ctx)
		}
		ctx = AttachRequestData(ctx, request)
		request = request.WithContext(ctx)
		defer notifier.AutoNotify(ctx)
		h(w, request)
	}
}

// checkForEmptyError checks if the given error (to be reported to Bugsnag) is
// nil. If it is, then log an error messageand return another error wrapping
// this error message.
func checkForEmptyError(err error) error {
	if err != nil {
		return nil
	}
	msg := "attempted to notify Bugsnag without supplying an error. Bugsnag not notified"
	Config.Logger.Printf("ERROR: " + msg)
	return fmt.Errorf(msg)
}

func init() {
	// Set up builtin middlewarez
	OnBeforeNotify(httpRequestMiddleware)

	// Default configuration
	sourceRoot := ""
	if gopath := os.Getenv("GOPATH"); len(gopath) > 0 {
		sourceRoot = filepath.Join(gopath, "src") + "/"
	} else {
		sourceRoot = filepath.Join(runtime.GOROOT(), "src") + "/"
	}
	Config.update(&Configuration{
		APIKey: "",
		Endpoints: Endpoints{
			Notify:   "https://notify.bugsnag.com",
			Sessions: "https://sessions.bugsnag.com",
		},
		Hostname:            "",
		AppType:             "",
		AppVersion:          "",
		AutoCaptureSessions: true,
		ReleaseStage:        "",
		ParamsFilters:       []string{"password", "secret", "authorization", "cookie"},
		SourceRoot:          sourceRoot,
		// * for app-engine
		ProjectPackages:     []string{"main*"},
		NotifyReleaseStages: nil,
		Logger:              log.New(os.Stdout, log.Prefix(), log.Flags()),
		PanicHandler:        defaultPanicHandler,
		Transport:           http.DefaultTransport,

		flushSessionsOnRepanic: true,
	})

	hostname, err := os.Hostname()
	if err == nil {
		Config.Hostname = hostname
	}
}

func startSessionTracking() {
	if sessionTracker == nil {
		sessionTrackingConfig.Update(&sessions.SessionTrackingConfiguration{
			APIKey:          Config.APIKey,
			Endpoint:        Config.Endpoints.Sessions,
			Version:         VERSION,
			PublishInterval: DefaultSessionPublishInterval,
			Transport:       Config.Transport,
			ReleaseStage:    Config.ReleaseStage,
			Hostname:        Config.Hostname,
			AppType:         Config.AppType,
			AppVersion:      Config.AppVersion,
			Logger:          Config.Logger,
		})
		sessionTracker = sessions.NewSessionTracker(&sessionTrackingConfig)
	}
}
<|endoftext|>


<commit_before>package gofinity

import (
	"errors"
	"sync"
	log "github.com/Sirupsen/logrus"
)

// Defines a callback for Bus Probing.
type OnFrameReceived func(*Frame)

// Constants for BusNode.status
const (
	READY    = uint8(0x00)
	RUNNING  = uint8(0x01)
	STOPPING = uint8(0x02)
	INVALID  = uint8(0x03)
)

// BusNode defines frame-based interactions atop a BusTransceiver
type BusNode struct {
	waitGroup   sync.WaitGroup
	transceiver BusTransceiver
	probes      []OnFrameReceived
	status      uint8
}

// Constructs a new BusNode for a BusTransciever
func NewBusNode(transceiver BusTransceiver) (*BusNode) {
	busNode := &BusNode{
		transceiver: transceiver,
		probes:      []OnFrameReceived{},
		waitGroup:   sync.WaitGroup{},
		status:      READY,
	}

	return busNode
}

func (busNode *BusNode) Probe(received OnFrameReceived) {
	busNode.probes = append(busNode.probes, received)
}

// Internal read loop for reading Frames from the transceiver.
func (busNode *BusNode) readLoop() {
	log.Info("Starting BusNode.readLoop()")
	defer log.Info("BusNode.readLoop() finished")
	defer busNode.waitGroup.Done()

	frameBuf := []byte{}
	readBuf := make([]byte, 256)

	// If the transceiver is no longer valid, bail.
	for busNode.status == RUNNING && busNode.transceiver.Valid() {
		// If the transceiver isn't open, reset the framebuffer and (re)open.
		if !busNode.transceiver.IsOpen() {
			frameBuf = []byte{}
			busNode.transceiver.Open()
		}

		// Try to read some bytes.
		n, readErr := busNode.transceiver.Read(readBuf)

		// Append to the frame buffer the bytes we just read.
		frameBuf = append(frameBuf, readBuf[:n]...)

		for {
			// Make sure we have at least a full header.
			if len(frameBuf) < 10 {
				break
			}

			// Byte 5 of valid frames tell us how long the frame is, plus header length.
			frameLength := int(frameBuf[4]) + 10
			if len(frameBuf) < frameLength {
				break;
			}

			frameSlice := frameBuf[:frameLength]

			frame, err := NewFrame(frameSlice)
			if err == nil {
				for _, probe := range busNode.probes {
					probe(frame)
				}

				// This portion, (0 - length), handled.
				// Slice (advance) the buffer.
				frameBuf = frameBuf[:copy(frameBuf, frameBuf[frameLength:])]
			} else {
				// Corrupt Message, or not quite a frame yet.
				// Advance one byte, try again.
				frameBuf = frameBuf[:copy(frameBuf, frameBuf[1:])]
			}
		}

		if readErr != nil {
			log.Warn("Erorr reading : ", readErr)
			busNode.transceiver.Close()
		}
	}

	if !busNode.transceiver.Valid() {
		busNode.status = INVALID
		log.Info("Transceiver no longer valid for reading.")
	}
}

// Starts the BusNode's I/O loops.
func (busNode *BusNode) Start() error {
	if busNode.status != READY {
		return errors.New("BusNode not in 'READY' state.")
	}
	busNode.status = RUNNING

	busNode.waitGroup.Add(1)
	go busNode.readLoop()

	return nil
}

// Stops the BusNode's I/O loops.
// Blocks until all threads running for I/O terminate.
func (busNode *BusNode) Shutdown() error {
	if busNode.status != RUNNING {
		return errors.New("BusNode not 'RUNNING'.")
	}
	busNode.status = STOPPING
	busNode.waitGroup.Wait()
	busNode.status = READY

	return nil
}
<commit_msg>BusProbe now shuts down cleanly after a file replay ends.<commit_after>package gofinity

import (
	"errors"
	"sync"
	log "github.com/Sirupsen/logrus"
)

// Defines a callback for Bus Probing.
type OnFrameReceived func(*Frame)

// Constants for BusNode.status
const (
	READY    = uint8(0x00)
	RUNNING  = uint8(0x01)
	STOPPING = uint8(0x02)
	INVALID  = uint8(0x03)
)

// BusNode defines frame-based interactions atop a BusTransceiver
type BusNode struct {
	waitGroup   sync.WaitGroup
	transceiver BusTransceiver
	probes      []OnFrameReceived
	status      uint8
}

// Constructs a new BusNode for a BusTransciever
func NewBusNode(transceiver BusTransceiver) (*BusNode) {
	busNode := &BusNode{
		transceiver: transceiver,
		probes:      []OnFrameReceived{},
		waitGroup:   sync.WaitGroup{},
		status:      READY,
	}

	return busNode
}

func (busNode *BusNode) Probe(received OnFrameReceived) {
	busNode.probes = append(busNode.probes, received)
}

// Internal read loop for reading Frames from the transceiver.
func (busNode *BusNode) readLoop() {
	log.Info("Starting BusNode.readLoop()")
	defer log.Info("BusNode.readLoop() finished")
	defer busNode.waitGroup.Done()

	frameBuf := []byte{}
	readBuf := make([]byte, 256)

	// If the transceiver is no longer valid, bail.
	for busNode.status == RUNNING && busNode.transceiver.Valid() {
		// If the transceiver isn't open, reset the framebuffer and (re)open.
		if !busNode.transceiver.IsOpen() {
			frameBuf = []byte{}
			busNode.transceiver.Open()
		}

		// Try to read some bytes.
		n, readErr := busNode.transceiver.Read(readBuf)

		// Append to the frame buffer the bytes we just read.
		frameBuf = append(frameBuf, readBuf[:n]...)

		for {
			// Make sure we have at least a full header.
			if len(frameBuf) < 10 {
				break
			}

			// Byte 5 of valid frames tell us how long the frame is, plus header length.
			frameLength := int(frameBuf[4]) + 10
			if len(frameBuf) < frameLength {
				break;
			}

			frameSlice := frameBuf[:frameLength]

			frame, err := NewFrame(frameSlice)
			if err == nil {
				for _, probe := range busNode.probes {
					probe(frame)
				}

				// This portion, (0 - length), handled.
				// Slice (advance) the buffer.
				frameBuf = frameBuf[:copy(frameBuf, frameBuf[frameLength:])]
			} else {
				// Corrupt Message, or not quite a frame yet.
				// Advance one byte, try again.
				frameBuf = frameBuf[:copy(frameBuf, frameBuf[1:])]
			}
		}

		if readErr != nil {
			log.Warn("Erorr reading : ", readErr)
			busNode.transceiver.Close()
		}
	}

	if !busNode.transceiver.Valid() {
		busNode.status = INVALID
		log.Info("Transceiver no longer valid for reading.")
	}
}

// Starts the BusNode's I/O loops.
func (busNode *BusNode) Start() error {
	if busNode.status != READY {
		return errors.New("BusNode not in 'READY' state.")
	}
	busNode.status = RUNNING

	busNode.waitGroup.Add(1)
	go busNode.readLoop()

	return nil
}

// Stops the BusNode's I/O loops.
// Blocks until all threads running for I/O terminate.
func (busNode *BusNode) Shutdown() error {
	if busNode.status != RUNNING || busNode.status != INVALID {
		return errors.New("BusNode not 'RUNNING' or 'INVALID'.")
	}
	busNode.status = STOPPING
	busNode.waitGroup.Wait()
	busNode.status = READY

	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 John Beil.
// Use of this source code is governed by the MIT License.
// The MIT license can be found in the LICENSE file.

// BuoyBot 1.5
// Obtains latest observation for NBDC Stations
// Saves observation to database
// Tweets observation
// See README.md for setup information

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/ChimeraCoder/anaconda"
	_ "github.com/lib/pq"
)

// URL format for SF Buoy Observations
const noaaURLFmt = "http://www.ndbc.noaa.gov/data/realtime2/%s.txt"

// Observation struct stores buoy observation data
type Observation struct {
	Date                  time.Time
	WindDirection         string
	WindSpeed             float64
	SignificantWaveHeight float64
	DominantWavePeriod    int
	AveragePeriod         float64
	MeanWaveDirection     string
	AirTemperature        float64
	WaterTemperature      float64
}

// Config struct stores Twitter and Database credentials and buoy ID
type Config struct {
	UserName         string `json:"UserName"`
	ConsumerKey      string `json:"ConsumerKey"`
	ConsumerSecret   string `json:"ConsumerSecret"`
	Token            string `json:"Token"`
	TokenSecret      string `json:"TokenSecret"`
	DatabaseFile     string `json:"DatabaseFile"`
	BuoyId           string `json:"BuoyId"`
}

// Variable for database
var db *sql.DB

func main() {
	fmt.Println("Starting BuoyBot...")

	// Load configuration
	config := Config{}
	loadConfig(&config)

	// Load database
	var err error
	db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Check database connection
	err = db.Ping()
	if err != nil {
		log.Fatal("Error: Could not establish connection with the database.", err)
	}

	// Get latest observation and store in struct
	var observation Observation
	observation = getObservation(config.BuoyId)

	// Save latest observation in database
	saveObservation(observation)

	// Format observation given Observation
	observationOutput := formatObservation(observation)

	// Tweet observation at 0000, 0600, 0800, 1000, 1200, 1400, 1600|| 1800 PST
	t := time.Now()
	if t.Hour() == 0 || t.Hour() == 6 || t.Hour() == 8 || t.Hour() == 10 || t.Hour() == 12 || t.Hour() == 14 || t.Hour() == 16 || t.Hour() == 18 {
		tweetCurrent(config, observationOutput)
	} else {
		fmt.Println("Not at update interval - not tweeting.")
		fmt.Println(observationOutput)
	}

	// Shutdown BuoyBot
	fmt.Println("Exiting BuoyBot...")
}

// Fetches and parses latest NBDC observation and returns data in Observation struct
func getObservation(buoyId string) Observation {
	var noaaURL = fmt.Sprintf(noaaURLFmt, buoyId)
	observationRaw := getDataFromURL(noaaURL)
	observationData := parseData(observationRaw)
	return observationData
}

// Given Observation struct, saves most recent observation in database
func saveObservation(o Observation) {
	_, err := db.Exec("INSERT INTO observations(observationtime, windspeed, winddirection, significantwaveheight, dominantwaveperiod, averageperiod, meanwavedirection, airtemperature, watertemperature) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)", o.Date, o.WindSpeed, o.WindDirection, o.SignificantWaveHeight, o.DominantWavePeriod, o.AveragePeriod, o.MeanWaveDirection, o.AirTemperature, o.WaterTemperature)
	if err != nil {
		log.Fatal("Error saving observation:", err)
	}
}

// Given config and observation, tweets latest update
func tweetCurrent(config Config, o string) {
	fmt.Println("Preparing to tweet observation...")
	var api *anaconda.TwitterApi
	api = anaconda.NewTwitterApi(config.Token, config.TokenSecret)
	anaconda.SetConsumerKey(config.ConsumerKey)
	anaconda.SetConsumerSecret(config.ConsumerSecret)
	tweet, err := api.PostTweet(o, nil)
	if err != nil {
		fmt.Println("update error:", err)
	} else {
		fmt.Println("Tweet posted:")
		fmt.Println(tweet.Text)
	}
}

// Given URL, returns raw data with recent observations from NBDC
func getDataFromURL(url string) (body []byte) {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal("Error fetching data:", err)
	}
	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("ioutil error reading resp.Body:", err)
	}
	// fmt.Println("Status:", resp.Status)
	return
}

// Given path to config.js file, loads credentials
func loadConfig(config *Config) {
	// Load path to config from CONFIGPATH environment variable
	configpath := os.Getenv("CONFIGPATH")
	if configpath != "" {
		file, _ := os.Open(configpath)
		decoder := json.NewDecoder(file)
		err := decoder.Decode(&config)
		if err != nil {
			log.Fatal("Error loading config.json:", err)
		}
	} else {
		log.Fatal("CONFIGPATH environment variable not specified")
	}
}

// Given raw data, parses latest observation and returns Observation struct
func parseData(d []byte) Observation {
	// Each line contains 19 data points
	// Headers are in the first two lines
	// Latest observation data is in the third line
	// Other lines are not needed

	// Extracts relevant data into variable for processing
	var data = string(d[188:281])
	// Convert most recent observation into array of strings
	datafield := strings.Fields(data)

	// Convert wave height from meters to feet
	waveheightmeters, _ := strconv.ParseFloat(datafield[8], 64)
	waveheightfeet := waveheightmeters * 3.28084

	// Convert wave direction from degrees to cardinal
	wavedegrees, _ := strconv.ParseInt(datafield[11], 0, 64)
	wavecardinal := direction(wavedegrees)

	// Convert wind speed from m/s to mph
	windspeedms, _ := strconv.ParseFloat((datafield[6]), 64)
	windspeedmph := windspeedms / 0.44704

	// Convert wind direction from degrees to cardinal
	winddegrees, _ := strconv.ParseInt(datafield[5], 0, 64)
	windcardinal := direction(winddegrees)

	// Convert air temp from C to F
	airtempC, _ := strconv.ParseFloat(datafield[13], 64)
	airtempF := airtempC*9/5 + 32
	airtempF = RoundPlus(airtempF, 1)

	// Convert water temp from C to F
	watertempC, _ := strconv.ParseFloat(datafield[14], 64)
	watertempF := watertempC*9/5 + 32
	watertempF = RoundPlus(watertempF, 1)

	// Process date/time and convert to PST
	rawtime := strings.Join(datafield[0:5], " ")
	t, err := time.Parse("2006 01 02 15 04", rawtime)
	if err != nil {
		log.Fatal("error processing rawtime:", err)
	}
	loc, err := time.LoadLocation("America/Los_Angeles")
	if err != nil {
		log.Fatal("error processing location", err)
	}
	t = t.In(loc)

	// Create Observation struct and populate with parsed data
	var o Observation
	o.Date = t
	o.WindDirection = windcardinal
	o.WindSpeed = windspeedmph
	o.SignificantWaveHeight = waveheightfeet
	o.DominantWavePeriod, err = strconv.Atoi(datafield[9])
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.AveragePeriod, err = strconv.ParseFloat(datafield[10], 64)
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.MeanWaveDirection = wavecardinal
	o.AirTemperature = airtempF
	o.WaterTemperature = watertempF

	return o
}

// Given Observation, returns formatted text for tweet
func formatObservation(o Observation) string {
	output := fmt.Sprint(o.Date.Format(time.RFC822), "\nSwell: ", strconv.FormatFloat(float64(o.SignificantWaveHeight), 'f', 1, 64), "ft at ", o.DominantWavePeriod, " sec from ", o.MeanWaveDirection, "\nWind: ", strconv.FormatFloat(float64(o.WindSpeed), 'f', 0, 64), "mph from ", o.WindDirection, "\n", "\nTemp: Air ", o.AirTemperature, "F / Water: ", o.WaterTemperature, "F")
	return output
}

// Given degrees returns cardinal direction or error message
func direction(deg int64) string {
	switch {
	case deg < 0:
		return "ERROR - DEGREE LESS THAN ZERO"
	case deg <= 11:
		return "N"
	case deg <= 34:
		return "NNE"
	case deg <= 56:
		return "NE"
	case deg <= 79:
		return "ENE"
	case deg <= 101:
		return "E"
	case deg <= 124:
		return "ESE"
	case deg <= 146:
		return "SE"
	case deg <= 169:
		return "SSE"
	case deg <= 191:
		return "S"
	case deg <= 214:
		return "SSW"
	case deg <= 236:
		return "SW"
	case deg <= 259:
		return "WSW"
	case deg <= 281:
		return "W"
	case deg <= 304:
		return "WNW"
	case deg <= 326:
		return "NW"
	case deg <= 349:
		return "NNW"
	case deg <= 360:
		return "N"
	default:
		return "ERROR - DEGREE GREATER THAN 360"
	}
}

// Round input to nearest integer given Float64 and return Float64
func Round(f float64) float64 {
	return math.Floor(f + .5)
}

// RoundPlus truncates a Float64 to a specified number of decimals given Int and Float64, returning Float64
func RoundPlus(f float64, places int) float64 {
	shift := math.Pow(10, float64(places))
	return Round(f*shift) / shift
}
<commit_msg>[issue #1] air temp is always 32F (0C)<commit_after>// Copyright (c) 2016 John Beil.
// Use of this source code is governed by the MIT License.
// The MIT license can be found in the LICENSE file.

// BuoyBot 1.5
// Obtains latest observation for NBDC Stations
// Saves observation to database
// Tweets observation
// See README.md for setup information

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/ChimeraCoder/anaconda"
	_ "github.com/lib/pq"
)

// URL format for SF Buoy Observations
const noaaURLFmt = "http://www.ndbc.noaa.gov/data/realtime2/%s.txt"

// Observation struct stores buoy observation data
type Observation struct {
	Date                  time.Time
	WindDirection         string
	WindSpeed             float64
	SignificantWaveHeight float64
	DominantWavePeriod    int
	AveragePeriod         float64
	MeanWaveDirection     string
	WaterTemperature      float64
}

// Config struct stores Twitter and Database credentials and buoy ID
type Config struct {
	UserName         string `json:"UserName"`
	ConsumerKey      string `json:"ConsumerKey"`
	ConsumerSecret   string `json:"ConsumerSecret"`
	Token            string `json:"Token"`
	TokenSecret      string `json:"TokenSecret"`
	DatabaseFile     string `json:"DatabaseFile"`
	BuoyId           string `json:"BuoyId"`
}

// Variable for database
var db *sql.DB

func main() {
	fmt.Println("Starting BuoyBot...")

	// Load configuration
	config := Config{}
	loadConfig(&config)

	// Load database
	var err error
	db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Check database connection
	err = db.Ping()
	if err != nil {
		log.Fatal("Error: Could not establish connection with the database.", err)
	}

	// Get latest observation and store in struct
	var observation Observation
	observation = getObservation(config.BuoyId)

	// Save latest observation in database
	saveObservation(observation)

	// Format observation given Observation
	observationOutput := formatObservation(observation)

	// Tweet observation at 0000, 0600, 0800, 1000, 1200, 1400, 1600|| 1800 PST
	t := time.Now()
	if t.Hour() == 0 || t.Hour() == 6 || t.Hour() == 8 || t.Hour() == 10 || t.Hour() == 12 || t.Hour() == 14 || t.Hour() == 16 || t.Hour() == 18 {
		tweetCurrent(config, observationOutput)
	} else {
		fmt.Println("Not at update interval - not tweeting.")
		fmt.Println(observationOutput)
	}

	// Shutdown BuoyBot
	fmt.Println("Exiting BuoyBot...")
}

// Fetches and parses latest NBDC observation and returns data in Observation struct
func getObservation(buoyId string) Observation {
	var noaaURL = fmt.Sprintf(noaaURLFmt, buoyId)
	observationRaw := getDataFromURL(noaaURL)
	observationData := parseData(observationRaw)
	return observationData
}

// Given Observation struct, saves most recent observation in database
func saveObservation(o Observation) {
	_, err := db.Exec("INSERT INTO observations(observationtime, windspeed, winddirection, significantwaveheight, dominantwaveperiod, averageperiod, meanwavedirection, watertemperature) VALUES($1, $2, $3, $4, $5, $6, $7, $8)", o.Date, o.WindSpeed, o.WindDirection, o.SignificantWaveHeight, o.DominantWavePeriod, o.AveragePeriod, o.MeanWaveDirection, o.WaterTemperature)
	if err != nil {
		log.Fatal("Error saving observation:", err)
	}
}

// Given config and observation, tweets latest update
func tweetCurrent(config Config, o string) {
	fmt.Println("Preparing to tweet observation...")
	var api *anaconda.TwitterApi
	api = anaconda.NewTwitterApi(config.Token, config.TokenSecret)
	anaconda.SetConsumerKey(config.ConsumerKey)
	anaconda.SetConsumerSecret(config.ConsumerSecret)
	tweet, err := api.PostTweet(o, nil)
	if err != nil {
		fmt.Println("update error:", err)
	} else {
		fmt.Println("Tweet posted:")
		fmt.Println(tweet.Text)
	}
}

// Given URL, returns raw data with recent observations from NBDC
func getDataFromURL(url string) (body []byte) {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal("Error fetching data:", err)
	}
	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("ioutil error reading resp.Body:", err)
	}
	// fmt.Println("Status:", resp.Status)
	return
}

// Given path to config.js file, loads credentials
func loadConfig(config *Config) {
	// Load path to config from CONFIGPATH environment variable
	configpath := os.Getenv("CONFIGPATH")
	if configpath != "" {
		file, _ := os.Open(configpath)
		decoder := json.NewDecoder(file)
		err := decoder.Decode(&config)
		if err != nil {
			log.Fatal("Error loading config.json:", err)
		}
	} else {
		log.Fatal("CONFIGPATH environment variable not specified")
	}
}

// Given raw data, parses latest observation and returns Observation struct
func parseData(d []byte) Observation {
	// Each line contains 19 data points
	// Headers are in the first two lines
	// Latest observation data is in the third line
	// Other lines are not needed

	// Extracts relevant data into variable for processing
	var data = string(d[188:281])
	// Convert most recent observation into array of strings
	datafield := strings.Fields(data)

	// Convert wave height from meters to feet
	waveheightmeters, _ := strconv.ParseFloat(datafield[8], 64)
	waveheightfeet := waveheightmeters * 3.28084

	// Convert wave direction from degrees to cardinal
	wavedegrees, _ := strconv.ParseInt(datafield[11], 0, 64)
	wavecardinal := direction(wavedegrees)

	// Convert wind speed from m/s to mph
	windspeedms, _ := strconv.ParseFloat((datafield[6]), 64)
	windspeedmph := windspeedms / 0.44704

	// Convert wind direction from degrees to cardinal
	winddegrees, _ := strconv.ParseInt(datafield[5], 0, 64)
	windcardinal := direction(winddegrees)

	// Convert water temp from C to F
	watertempC, _ := strconv.ParseFloat(datafield[14], 64)
	watertempF := watertempC*9/5 + 32
	watertempF = RoundPlus(watertempF, 1)

	// Process date/time and convert to PST
	rawtime := strings.Join(datafield[0:5], " ")
	t, err := time.Parse("2006 01 02 15 04", rawtime)
	if err != nil {
		log.Fatal("error processing rawtime:", err)
	}
	loc, err := time.LoadLocation("America/Los_Angeles")
	if err != nil {
		log.Fatal("error processing location", err)
	}
	t = t.In(loc)

	// Create Observation struct and populate with parsed data
	var o Observation
	o.Date = t
	o.WindDirection = windcardinal
	o.WindSpeed = windspeedmph
	o.SignificantWaveHeight = waveheightfeet
	o.DominantWavePeriod, err = strconv.Atoi(datafield[9])
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.AveragePeriod, err = strconv.ParseFloat(datafield[10], 64)
	if err != nil {
		log.Fatal("o.AveragePeriod:", err)
	}
	o.MeanWaveDirection = wavecardinal
	o.WaterTemperature = watertempF

	return o
}

// Given Observation, returns formatted text for tweet
func formatObservation(o Observation) string {
	output := fmt.Sprint(o.Date.Format(time.RFC822), "\nSwell: ", strconv.FormatFloat(float64(o.SignificantWaveHeight), 'f', 1, 64), "ft at ", o.DominantWavePeriod, " sec from ", o.MeanWaveDirection, "\nWind: ", strconv.FormatFloat(float64(o.WindSpeed), 'f', 0, 64), "mph from ", o.WindDirection, "\n", "Water: ", o.WaterTemperature, "F")
	return output
}

// Given degrees returns cardinal direction or error message
func direction(deg int64) string {
	switch {
	case deg < 0:
		return "ERROR - DEGREE LESS THAN ZERO"
	case deg <= 11:
		return "N"
	case deg <= 34:
		return "NNE"
	case deg <= 56:
		return "NE"
	case deg <= 79:
		return "ENE"
	case deg <= 101:
		return "E"
	case deg <= 124:
		return "ESE"
	case deg <= 146:
		return "SE"
	case deg <= 169:
		return "SSE"
	case deg <= 191:
		return "S"
	case deg <= 214:
		return "SSW"
	case deg <= 236:
		return "SW"
	case deg <= 259:
		return "WSW"
	case deg <= 281:
		return "W"
	case deg <= 304:
		return "WNW"
	case deg <= 326:
		return "NW"
	case deg <= 349:
		return "NNW"
	case deg <= 360:
		return "N"
	default:
		return "ERROR - DEGREE GREATER THAN 360"
	}
}

// Round input to nearest integer given Float64 and return Float64
func Round(f float64) float64 {
	return math.Floor(f + .5)
}

// RoundPlus truncates a Float64 to a specified number of decimals given Int and Float64, returning Float64
func RoundPlus(f float64, places int) float64 {
	shift := math.Pow(10, float64(places))
	return Round(f*shift) / shift
}
<|endoftext|>


<commit_before>// Copyright 2015 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package webp

//#include "webp.h"
import "C"
import (
	"errors"
	"io"
	"reflect"
	"unsafe"
)

var (
	_ CBuffer = (*cBuffer)(nil)
)

type CBuffer interface {
	CBufMagic() string
	CanResize() bool
	Resize(size int) error
	CData() []byte
	Own(d []byte) bool
	io.Closer
}

type cBuffer struct {
	dontResize bool
	cptr       unsafe.Pointer
	data       []byte
}

func newCBufferFrom(cptr unsafe.Pointer, size int, dontResize ...bool) CBuffer {
	p := new(cBuffer)
	if cptr != nil && size > 0 {
		p.cptr = cptr
		p.data = (*[1 << 30]byte)(p.cptr)[0:size:size]
	}
	if len(dontResize) > 0 {
		p.dontResize = dontResize[0]
	}
	return p
}

func NewCBuffer(size int, dontResize ...bool) CBuffer {
	p := new(cBuffer)
	if size > 0 {
		p.cptr = C.webpMalloc(C.size_t(size))
		p.data = (*[1 << 30]byte)(p.cptr)[0:size:size]
	}
	if len(dontResize) > 0 {
		p.dontResize = dontResize[0]
	}
	return p
}

func (p *cBuffer) CBufMagic() string {
	return "CBufMagic"
}

func (p *cBuffer) Close() error {
	if p.cptr != nil {
		C.webpFree(p.cptr)
	}
	p.cptr = nil
	p.data = nil
	return nil
}

func (p *cBuffer) CanResize() bool {
	return !p.dontResize
}

func (p *cBuffer) Resize(size int) error {
	if size < 0 {
		return errors.New("webp: cBuffer.Resize, bad size!")
	}
	if p.dontResize {
		return errors.New("webp: cBuffer.Resize, donot resize!")
	}
	p.Close()
	if size > 0 {
		p.cptr = C.webpMalloc(C.size_t(size))
		p.data = (*[1 << 30]byte)(p.cptr)[0:size:size]
	}
	return nil
}

func (p *cBuffer) CData() []byte {
	return p.data
}

func (p *cBuffer) Own(d []byte) bool {
	if cap(d) == 0 || p.cptr == nil {
		return false
	}
	min := uintptr(p.cptr)
	max := uintptr(p.cptr) + uintptr(len(p.data)-1)
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&d))
	if x := hdr.Data; x < min || x > max {
		return false
	}
	if x := hdr.Data + uintptr(hdr.Cap-1); x < min || x > max {
		return false
	}
	return true
}
<commit_msg>CBuffer support auto finalizer<commit_after>// Copyright 2015 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package webp

//#include "webp.h"
import "C"
import (
	"errors"
	"io"
	"reflect"
	"runtime"
	"unsafe"
)

var (
	_ CBuffer = (*cBuffer)(nil)
)

type CBuffer interface {
	CBufMagic() string
	CanResize() bool
	Resize(size int) error
	CData() []byte
	Own(d []byte) bool
	io.Closer
}

type cBuffer struct {
	*innerCBuffer
}

func newCBufferFrom(cptr unsafe.Pointer, size int, dontResize ...bool) CBuffer {
	p := &cBuffer{
		innerCBuffer: newInnerCBufferFrom(cptr, size, dontResize...),
	}
	runtime.SetFinalizer(p.innerCBuffer, (*innerCBuffer).Close)
	return p
}

func NewCBuffer(size int, dontResize ...bool) CBuffer {
	p := &cBuffer{
		innerCBuffer: newInnerCBuffer(size, dontResize...),
	}
	runtime.SetFinalizer(p.innerCBuffer, (*innerCBuffer).Close)
	return p
}

func (p *cBuffer) Close() error {
	var err error
	if p.innerCBuffer != nil {
		err = p.innerCBuffer.Close()
	}
	*p = cBuffer{}
	return err
}

type innerCBuffer struct {
	dontResize bool
	cptr       unsafe.Pointer
	data       []byte
}

func newInnerCBufferFrom(cptr unsafe.Pointer, size int, dontResize ...bool) *innerCBuffer {
	p := new(innerCBuffer)
	if cptr != nil && size > 0 {
		p.cptr = cptr
		p.data = (*[1 << 30]byte)(p.cptr)[0:size:size]
	}
	if len(dontResize) > 0 {
		p.dontResize = dontResize[0]
	}
	return p
}

func newInnerCBuffer(size int, dontResize ...bool) *innerCBuffer {
	p := new(innerCBuffer)
	if size > 0 {
		p.cptr = C.webpMalloc(C.size_t(size))
		p.data = (*[1 << 30]byte)(p.cptr)[0:size:size]
	}
	if len(dontResize) > 0 {
		p.dontResize = dontResize[0]
	}
	return p
}

func (p *innerCBuffer) CBufMagic() string {
	return "CBufMagic"
}

func (p *innerCBuffer) Close() error {
	if p.cptr != nil {
		C.webpFree(p.cptr)
	}
	*p = innerCBuffer{}

	// no need for a finalizer anymore
	runtime.SetFinalizer(p, nil)
	return nil
}

func (p *innerCBuffer) CanResize() bool {
	return !p.dontResize
}

func (p *innerCBuffer) Resize(size int) error {
	if size < 0 {
		return errors.New("webp: cBuffer.Resize, bad size!")
	}
	if p.dontResize {
		return errors.New("webp: cBuffer.Resize, donot resize!")
	}
	if n := len(p.data); n > 0 && n != size {
		C.webpFree(p.cptr)
		p.cptr = nil
		p.data = nil
	}
	p.Close()
	if size > 0 {
		p.cptr = C.webpMalloc(C.size_t(size))
		p.data = (*[1 << 30]byte)(p.cptr)[0:size:size]
	}
	return nil
}

func (p *innerCBuffer) CData() []byte {
	return p.data
}

func (p *innerCBuffer) Own(d []byte) bool {
	if cap(d) == 0 || p.cptr == nil {
		return false
	}
	min := uintptr(p.cptr)
	max := uintptr(p.cptr) + uintptr(len(p.data)-1)
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&d))
	if x := hdr.Data; x < min || x > max {
		return false
	}
	if x := hdr.Data + uintptr(hdr.Cap-1); x < min || x > max {
		return false
	}
	return true
}
<|endoftext|>


<commit_before>package drivers

import (
	"fmt"
	"io"
	"os"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/migration"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/lxd/rsync"
	"github.com/lxc/lxd/lxd/storage/quota"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/instancewriter"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/units"
)

// CreateVolume creates an empty volume and can optionally fill it by executing the supplied
// filler function.
func (d *dir) CreateVolume(vol Volume, filler *VolumeFiller, op *operations.Operation) error {
	volPath := vol.MountPath()

	revert := revert.New()
	defer revert.Fail()

	// Create the volume itself.
	err := vol.EnsureMountPath()
	if err != nil {
		return err
	}
	revert.Add(func() { os.RemoveAll(volPath) })

	// Create sparse loopback file if volume is block.
	rootBlockPath := ""
	if vol.contentType == ContentTypeBlock {
		// We expect the filler to copy the VM image into this path.
		rootBlockPath, err = d.GetVolumeDiskPath(vol)
		if err != nil {
			return err
		}
	} else {
		// Filesystem quotas only used with non-block volume types.
		revertFunc, err := d.setupInitialQuota(vol)
		if err != nil {
			return err
		}

		if revertFunc != nil {
			revert.Add(revertFunc)
		}
	}

	// Run the volume filler function if supplied.
	if filler != nil && filler.Fill != nil {
		d.logger.Debug("Running filler function", log.Ctx{"path": volPath})
		err = filler.Fill(volPath, rootBlockPath)
		if err != nil {
			return err
		}
	}

	// If we are creating a block volume, resize it to the requested size or the default.
	// We expect the filler function to have converted the qcow2 image to raw into the rootBlockPath.
	if vol.contentType == ContentTypeBlock {
		// Convert to bytes.
		sizeBytes, err := units.ParseByteSizeString(d.volumeSize(vol))
		if err != nil {
			return err
		}

		err = ensureVolumeBlockFile(rootBlockPath, sizeBytes)
		if err != nil {
			return err
		}

		// Move the GPT alt header to end of disk if needed and if filler specified.
		if vol.IsVMBlock() && filler != nil && filler.Fill != nil {
			err = d.moveGPTAltHeader(rootBlockPath)
			if err != nil {
				return err
			}
		}
	}

	revert.Success()
	return nil
}

// CreateVolumeFromBackup restores a backup tarball onto the storage device.
func (d *dir) CreateVolumeFromBackup(vol Volume, snapshots []string, srcData io.ReadSeeker, optimizedStorage bool, op *operations.Operation) (func(vol Volume) error, func(), error) {
	// Run the generic backup unpacker
	postHook, revertHook, err := genericVFSBackupUnpack(d.withoutGetVolID(), vol, snapshots, srcData, op)
	if err != nil {
		return nil, nil, err
	}

	// Define a post hook function that can be run once the backup config has been restored.
	// This will setup the quota using the restored config.
	postHookWrapper := func(vol Volume) error {
		if postHook != nil {
			err := postHook(vol)
			if err != nil {
				return err
			}
		}

		_, err := d.setupInitialQuota(vol)
		if err != nil {
			return err
		}

		return nil
	}

	return postHookWrapper, revertHook, nil
}

// CreateVolumeFromCopy provides same-pool volume copying functionality.
func (d *dir) CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots bool, op *operations.Operation) error {
	var err error
	var srcSnapshots []Volume

	if copySnapshots && !srcVol.IsSnapshot() {
		// Get the list of snapshots from the source.
		srcSnapshots, err = srcVol.Snapshots(op)
		if err != nil {
			return err
		}
	}

	// Run the generic copy.
	return genericVFSCopyVolume(d, d.setupInitialQuota, vol, srcVol, srcSnapshots, false, op)
}

// CreateVolumeFromMigration creates a volume being sent via a migration.
func (d *dir) CreateVolumeFromMigration(vol Volume, conn io.ReadWriteCloser, volTargetArgs migration.VolumeTargetArgs, preFiller *VolumeFiller, op *operations.Operation) error {
	return genericVFSCreateVolumeFromMigration(d, d.setupInitialQuota, vol, conn, volTargetArgs, preFiller, op)
}

// RefreshVolume provides same-pool volume and specific snapshots syncing functionality.
func (d *dir) RefreshVolume(vol Volume, srcVol Volume, srcSnapshots []Volume, op *operations.Operation) error {
	return genericVFSCopyVolume(d, d.setupInitialQuota, vol, srcVol, srcSnapshots, true, op)
}

// DeleteVolume deletes a volume of the storage device. If any snapshots of the volume remain then
// this function will return an error.
func (d *dir) DeleteVolume(vol Volume, op *operations.Operation) error {
	snapshots, err := d.VolumeSnapshots(vol, op)
	if err != nil {
		return err
	}

	if len(snapshots) > 0 {
		return fmt.Errorf("Cannot remove a volume that has snapshots")
	}

	volPath := vol.MountPath()

	// If the volume doesn't exist, then nothing more to do.
	if !shared.PathExists(volPath) {
		return nil
	}

	// Get the volume ID for the volume, which is used to remove project quota.
	volID, err := d.getVolID(vol.volType, vol.name)
	if err != nil {
		return err
	}

	// Remove the project quota.
	err = d.deleteQuota(volPath, volID)
	if err != nil {
		return err
	}

	// Remove the volume from the storage device.
	err = forceRemoveAll(volPath)
	if err != nil && !os.IsNotExist(err) {
		return errors.Wrapf(err, "Failed to remove '%s'", volPath)
	}

	// Although the volume snapshot directory should already be removed, lets remove it here
	// to just in case the top-level directory is left.
	err = deleteParentSnapshotDirIfEmpty(d.name, vol.volType, vol.name)
	if err != nil {
		return err
	}

	return nil
}

// HasVolume indicates whether a specific volume exists on the storage pool.
func (d *dir) HasVolume(vol Volume) bool {
	return genericVFSHasVolume(vol)
}

// ValidateVolume validates the supplied volume config. Optionally removes invalid keys from the volume's config.
func (d *dir) ValidateVolume(vol Volume, removeUnknownKeys bool) error {
	return d.validateVolume(vol, nil, removeUnknownKeys)
}

// UpdateVolume applies config changes to the volume.
func (d *dir) UpdateVolume(vol Volume, changedConfig map[string]string) error {
	if vol.contentType != ContentTypeFS {
		return ErrNotSupported
	}

	if _, changed := changedConfig["size"]; changed {
		err := d.SetVolumeQuota(vol, changedConfig["size"], nil)
		if err != nil {
			return err
		}
	}

	return nil
}

// GetVolumeUsage returns the disk space used by the volume.
func (d *dir) GetVolumeUsage(vol Volume) (int64, error) {
	// Snapshot usage not supported for Dir.
	if vol.IsSnapshot() {
		return -1, ErrNotSupported
	}

	volPath := vol.MountPath()
	ok, err := quota.Supported(volPath)
	if err != nil || !ok {
		return -1, ErrNotSupported
	}

	// Get the volume ID for the volume to access quota.
	volID, err := d.getVolID(vol.volType, vol.name)
	if err != nil {
		return -1, err
	}

	projectID := d.quotaProjectID(volID)

	// Get project quota used.
	size, err := quota.GetProjectUsage(volPath, projectID)
	if err != nil {
		return -1, err
	}

	return size, nil
}

// SetVolumeQuota sets the quota on the volume.
func (d *dir) SetVolumeQuota(vol Volume, size string, op *operations.Operation) error {
	// For VM block files, resize the file if needed.
	if vol.contentType == ContentTypeBlock {
		rootBlockPath, err := d.GetVolumeDiskPath(vol)
		if err != nil {
			return err
		}

		// If size not specified in volume config, then use pool's default block size.
		if size == "" || size == "0" {
			size = defaultBlockSize
		}

		resized, err := genericVFSResizeBlockFile(rootBlockPath, size)
		if err != nil {
			return err
		}

		// Move the GPT alt header to end of disk if needed and resize has taken place (not needed in
		// unsafe resize mode as it is  expected the caller will do all necessary post resize actions
		// themselves).
		if vol.IsVMBlock() && resized && !vol.allowUnsafeResize {
			err = d.moveGPTAltHeader(rootBlockPath)
			if err != nil {
				return err
			}
		}

		return nil
	}

	// For non-VM block volumes, set filesystem quota.
	volID, err := d.getVolID(vol.volType, vol.name)
	if err != nil {
		return err
	}

	return d.setQuota(vol.MountPath(), volID, size)
}

// GetVolumeDiskPath returns the location of a disk volume.
func (d *dir) GetVolumeDiskPath(vol Volume) (string, error) {
	return genericVFSGetVolumeDiskPath(vol)
}

// MountVolume simulates mounting a volume. As dir driver doesn't have volumes to mount it returns
// false indicating that there is no need to issue an unmount.
func (d *dir) MountVolume(vol Volume, op *operations.Operation) (bool, error) {
	err := vol.EnsureMountPath()
	if err != nil {
		return false, err
	}

	return false, nil
}

// UnmountVolume simulates unmounting a volume. As dir driver doesn't have volumes to unmount it
// returns false indicating the volume was already unmounted.
func (d *dir) UnmountVolume(vol Volume, op *operations.Operation) (bool, error) {
	return false, nil
}

// RenameVolume renames a volume and its snapshots.
func (d *dir) RenameVolume(vol Volume, newVolName string, op *operations.Operation) error {
	return genericVFSRenameVolume(d, vol, newVolName, op)
}

// MigrateVolume sends a volume for migration.
func (d *dir) MigrateVolume(vol Volume, conn io.ReadWriteCloser, volSrcArgs *migration.VolumeSourceArgs, op *operations.Operation) error {
	return genericVFSMigrateVolume(d, d.state, vol, conn, volSrcArgs, op)
}

// BackupVolume copies a volume (and optionally its snapshots) to a specified target path.
// This driver does not support optimized backups.
func (d *dir) BackupVolume(vol Volume, tarWriter *instancewriter.InstanceTarWriter, optimized bool, snapshots bool, op *operations.Operation) error {
	return genericVFSBackupVolume(d, vol, tarWriter, snapshots, op)
}

// CreateVolumeSnapshot creates a snapshot of a volume.
func (d *dir) CreateVolumeSnapshot(snapVol Volume, op *operations.Operation) error {
	parentName, _, _ := shared.InstanceGetParentAndSnapshotName(snapVol.name)
	srcPath := GetVolumeMountPath(d.name, snapVol.volType, parentName)
	snapPath := snapVol.MountPath()

	// Create the parent directory.
	err := createParentSnapshotDirIfMissing(d.name, snapVol.volType, parentName)
	if err != nil {
		return err
	}

	// Create snapshot directory.
	err = snapVol.EnsureMountPath()
	if err != nil {
		return err
	}

	revertPath := true
	defer func() {
		if revertPath {
			os.RemoveAll(snapPath)
		}
	}()

	bwlimit := d.config["rsync.bwlimit"]

	// Copy volume into snapshot directory.
	_, err = rsync.LocalCopy(srcPath, snapPath, bwlimit, true)
	if err != nil {
		return err
	}

	revertPath = false
	return nil
}

// DeleteVolumeSnapshot removes a snapshot from the storage device. The volName and snapshotName
// must be bare names and should not be in the format "volume/snapshot".
func (d *dir) DeleteVolumeSnapshot(snapVol Volume, op *operations.Operation) error {
	snapPath := snapVol.MountPath()

	// Remove the snapshot from the storage device.
	err := forceRemoveAll(snapPath)
	if err != nil && !os.IsNotExist(err) {
		return errors.Wrapf(err, "Failed to remove '%s'", snapPath)
	}

	parentName, _, _ := shared.InstanceGetParentAndSnapshotName(snapVol.name)

	// Remove the parent snapshot directory if this is the last snapshot being removed.
	err = deleteParentSnapshotDirIfEmpty(d.name, snapVol.volType, parentName)
	if err != nil {
		return err
	}

	return nil
}

// MountVolumeSnapshot sets up a read-only mount on top of the snapshot to avoid accidental modifications.
func (d *dir) MountVolumeSnapshot(snapVol Volume, op *operations.Operation) (bool, error) {
	err := snapVol.EnsureMountPath()
	if err != nil {
		return false, err
	}

	snapPath := snapVol.MountPath()
	return mountReadOnly(snapPath, snapPath)
}

// UnmountVolumeSnapshot removes the read-only mount placed on top of a snapshot.
func (d *dir) UnmountVolumeSnapshot(snapVol Volume, op *operations.Operation) (bool, error) {
	snapPath := snapVol.MountPath()
	return forceUnmount(snapPath)
}

// VolumeSnapshots returns a list of snapshots for the volume.
func (d *dir) VolumeSnapshots(vol Volume, op *operations.Operation) ([]string, error) {
	return genericVFSVolumeSnapshots(d, vol, op)
}

// RestoreVolume restores a volume from a snapshot.
func (d *dir) RestoreVolume(vol Volume, snapshotName string, op *operations.Operation) error {
	srcPath := GetVolumeMountPath(d.name, vol.volType, GetSnapshotVolumeName(vol.name, snapshotName))
	if !shared.PathExists(srcPath) {
		return fmt.Errorf("Snapshot not found")
	}

	volPath := vol.MountPath()

	// Restore using rsync.
	bwlimit := d.config["rsync.bwlimit"]
	_, err := rsync.LocalCopy(srcPath, volPath, bwlimit, true)
	if err != nil {
		return errors.Wrap(err, "Failed to rsync volume")
	}

	return nil
}

// RenameVolumeSnapshot renames a volume snapshot.
func (d *dir) RenameVolumeSnapshot(snapVol Volume, newSnapshotName string, op *operations.Operation) error {
	return genericVFSRenameVolumeSnapshot(d, snapVol, newSnapshotName, op)
}
<commit_msg>lxd/storage/drivers/driver/dir/volumes: Updates SetVolumeQuota to be byte oriented internally<commit_after>package drivers

import (
	"fmt"
	"io"
	"os"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/lxd/migration"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/lxd/rsync"
	"github.com/lxc/lxd/lxd/storage/quota"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/instancewriter"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/units"
)

// CreateVolume creates an empty volume and can optionally fill it by executing the supplied
// filler function.
func (d *dir) CreateVolume(vol Volume, filler *VolumeFiller, op *operations.Operation) error {
	volPath := vol.MountPath()

	revert := revert.New()
	defer revert.Fail()

	// Create the volume itself.
	err := vol.EnsureMountPath()
	if err != nil {
		return err
	}
	revert.Add(func() { os.RemoveAll(volPath) })

	// Create sparse loopback file if volume is block.
	rootBlockPath := ""
	if vol.contentType == ContentTypeBlock {
		// We expect the filler to copy the VM image into this path.
		rootBlockPath, err = d.GetVolumeDiskPath(vol)
		if err != nil {
			return err
		}
	} else {
		// Filesystem quotas only used with non-block volume types.
		revertFunc, err := d.setupInitialQuota(vol)
		if err != nil {
			return err
		}

		if revertFunc != nil {
			revert.Add(revertFunc)
		}
	}

	// Run the volume filler function if supplied.
	if filler != nil && filler.Fill != nil {
		d.logger.Debug("Running filler function", log.Ctx{"path": volPath})
		err = filler.Fill(volPath, rootBlockPath)
		if err != nil {
			return err
		}
	}

	// If we are creating a block volume, resize it to the requested size or the default.
	// We expect the filler function to have converted the qcow2 image to raw into the rootBlockPath.
	if vol.contentType == ContentTypeBlock {
		// Convert to bytes.
		sizeBytes, err := units.ParseByteSizeString(d.volumeSize(vol))
		if err != nil {
			return err
		}

		err = ensureVolumeBlockFile(rootBlockPath, sizeBytes)
		if err != nil {
			return err
		}

		// Move the GPT alt header to end of disk if needed and if filler specified.
		if vol.IsVMBlock() && filler != nil && filler.Fill != nil {
			err = d.moveGPTAltHeader(rootBlockPath)
			if err != nil {
				return err
			}
		}
	}

	revert.Success()
	return nil
}

// CreateVolumeFromBackup restores a backup tarball onto the storage device.
func (d *dir) CreateVolumeFromBackup(vol Volume, snapshots []string, srcData io.ReadSeeker, optimizedStorage bool, op *operations.Operation) (func(vol Volume) error, func(), error) {
	// Run the generic backup unpacker
	postHook, revertHook, err := genericVFSBackupUnpack(d.withoutGetVolID(), vol, snapshots, srcData, op)
	if err != nil {
		return nil, nil, err
	}

	// Define a post hook function that can be run once the backup config has been restored.
	// This will setup the quota using the restored config.
	postHookWrapper := func(vol Volume) error {
		if postHook != nil {
			err := postHook(vol)
			if err != nil {
				return err
			}
		}

		_, err := d.setupInitialQuota(vol)
		if err != nil {
			return err
		}

		return nil
	}

	return postHookWrapper, revertHook, nil
}

// CreateVolumeFromCopy provides same-pool volume copying functionality.
func (d *dir) CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots bool, op *operations.Operation) error {
	var err error
	var srcSnapshots []Volume

	if copySnapshots && !srcVol.IsSnapshot() {
		// Get the list of snapshots from the source.
		srcSnapshots, err = srcVol.Snapshots(op)
		if err != nil {
			return err
		}
	}

	// Run the generic copy.
	return genericVFSCopyVolume(d, d.setupInitialQuota, vol, srcVol, srcSnapshots, false, op)
}

// CreateVolumeFromMigration creates a volume being sent via a migration.
func (d *dir) CreateVolumeFromMigration(vol Volume, conn io.ReadWriteCloser, volTargetArgs migration.VolumeTargetArgs, preFiller *VolumeFiller, op *operations.Operation) error {
	return genericVFSCreateVolumeFromMigration(d, d.setupInitialQuota, vol, conn, volTargetArgs, preFiller, op)
}

// RefreshVolume provides same-pool volume and specific snapshots syncing functionality.
func (d *dir) RefreshVolume(vol Volume, srcVol Volume, srcSnapshots []Volume, op *operations.Operation) error {
	return genericVFSCopyVolume(d, d.setupInitialQuota, vol, srcVol, srcSnapshots, true, op)
}

// DeleteVolume deletes a volume of the storage device. If any snapshots of the volume remain then
// this function will return an error.
func (d *dir) DeleteVolume(vol Volume, op *operations.Operation) error {
	snapshots, err := d.VolumeSnapshots(vol, op)
	if err != nil {
		return err
	}

	if len(snapshots) > 0 {
		return fmt.Errorf("Cannot remove a volume that has snapshots")
	}

	volPath := vol.MountPath()

	// If the volume doesn't exist, then nothing more to do.
	if !shared.PathExists(volPath) {
		return nil
	}

	// Get the volume ID for the volume, which is used to remove project quota.
	volID, err := d.getVolID(vol.volType, vol.name)
	if err != nil {
		return err
	}

	// Remove the project quota.
	err = d.deleteQuota(volPath, volID)
	if err != nil {
		return err
	}

	// Remove the volume from the storage device.
	err = forceRemoveAll(volPath)
	if err != nil && !os.IsNotExist(err) {
		return errors.Wrapf(err, "Failed to remove '%s'", volPath)
	}

	// Although the volume snapshot directory should already be removed, lets remove it here
	// to just in case the top-level directory is left.
	err = deleteParentSnapshotDirIfEmpty(d.name, vol.volType, vol.name)
	if err != nil {
		return err
	}

	return nil
}

// HasVolume indicates whether a specific volume exists on the storage pool.
func (d *dir) HasVolume(vol Volume) bool {
	return genericVFSHasVolume(vol)
}

// ValidateVolume validates the supplied volume config. Optionally removes invalid keys from the volume's config.
func (d *dir) ValidateVolume(vol Volume, removeUnknownKeys bool) error {
	return d.validateVolume(vol, nil, removeUnknownKeys)
}

// UpdateVolume applies config changes to the volume.
func (d *dir) UpdateVolume(vol Volume, changedConfig map[string]string) error {
	if vol.contentType != ContentTypeFS {
		return ErrNotSupported
	}

	if _, changed := changedConfig["size"]; changed {
		err := d.SetVolumeQuota(vol, changedConfig["size"], nil)
		if err != nil {
			return err
		}
	}

	return nil
}

// GetVolumeUsage returns the disk space used by the volume.
func (d *dir) GetVolumeUsage(vol Volume) (int64, error) {
	// Snapshot usage not supported for Dir.
	if vol.IsSnapshot() {
		return -1, ErrNotSupported
	}

	volPath := vol.MountPath()
	ok, err := quota.Supported(volPath)
	if err != nil || !ok {
		return -1, ErrNotSupported
	}

	// Get the volume ID for the volume to access quota.
	volID, err := d.getVolID(vol.volType, vol.name)
	if err != nil {
		return -1, err
	}

	projectID := d.quotaProjectID(volID)

	// Get project quota used.
	size, err := quota.GetProjectUsage(volPath, projectID)
	if err != nil {
		return -1, err
	}

	return size, nil
}

// SetVolumeQuota sets the quota on the volume.
// Does nothing if supplied with an empty/zero size for block volumes, and for filesystem volumes removes quota.
func (d *dir) SetVolumeQuota(vol Volume, size string, op *operations.Operation) error {
	// Convert to bytes.
	sizeBytes, err := units.ParseByteSizeString(size)
	if err != nil {
		return err
	}

	// For VM block files, resize the file if needed.
	if vol.contentType == ContentTypeBlock {
		// Do nothing if size isn't specified.
		if sizeBytes <= 0 {
			return nil
		}

		rootBlockPath, err := d.GetVolumeDiskPath(vol)
		if err != nil {
			return err
		}

		resized, err := genericVFSResizeBlockFile(rootBlockPath, sizeBytes)
		if err != nil {
			return err
		}

		// Move the GPT alt header to end of disk if needed and resize has taken place (not needed in
		// unsafe resize mode as it is  expected the caller will do all necessary post resize actions
		// themselves).
		if vol.IsVMBlock() && resized && !vol.allowUnsafeResize {
			err = d.moveGPTAltHeader(rootBlockPath)
			if err != nil {
				return err
			}
		}

		return nil
	}

	// For non-VM block volumes, set filesystem quota.
	volID, err := d.getVolID(vol.volType, vol.name)
	if err != nil {
		return err
	}

	return d.setQuota(vol.MountPath(), volID, size)
}

// GetVolumeDiskPath returns the location of a disk volume.
func (d *dir) GetVolumeDiskPath(vol Volume) (string, error) {
	return genericVFSGetVolumeDiskPath(vol)
}

// MountVolume simulates mounting a volume. As dir driver doesn't have volumes to mount it returns
// false indicating that there is no need to issue an unmount.
func (d *dir) MountVolume(vol Volume, op *operations.Operation) (bool, error) {
	err := vol.EnsureMountPath()
	if err != nil {
		return false, err
	}

	return false, nil
}

// UnmountVolume simulates unmounting a volume. As dir driver doesn't have volumes to unmount it
// returns false indicating the volume was already unmounted.
func (d *dir) UnmountVolume(vol Volume, op *operations.Operation) (bool, error) {
	return false, nil
}

// RenameVolume renames a volume and its snapshots.
func (d *dir) RenameVolume(vol Volume, newVolName string, op *operations.Operation) error {
	return genericVFSRenameVolume(d, vol, newVolName, op)
}

// MigrateVolume sends a volume for migration.
func (d *dir) MigrateVolume(vol Volume, conn io.ReadWriteCloser, volSrcArgs *migration.VolumeSourceArgs, op *operations.Operation) error {
	return genericVFSMigrateVolume(d, d.state, vol, conn, volSrcArgs, op)
}

// BackupVolume copies a volume (and optionally its snapshots) to a specified target path.
// This driver does not support optimized backups.
func (d *dir) BackupVolume(vol Volume, tarWriter *instancewriter.InstanceTarWriter, optimized bool, snapshots bool, op *operations.Operation) error {
	return genericVFSBackupVolume(d, vol, tarWriter, snapshots, op)
}

// CreateVolumeSnapshot creates a snapshot of a volume.
func (d *dir) CreateVolumeSnapshot(snapVol Volume, op *operations.Operation) error {
	parentName, _, _ := shared.InstanceGetParentAndSnapshotName(snapVol.name)
	srcPath := GetVolumeMountPath(d.name, snapVol.volType, parentName)
	snapPath := snapVol.MountPath()

	// Create the parent directory.
	err := createParentSnapshotDirIfMissing(d.name, snapVol.volType, parentName)
	if err != nil {
		return err
	}

	// Create snapshot directory.
	err = snapVol.EnsureMountPath()
	if err != nil {
		return err
	}

	revertPath := true
	defer func() {
		if revertPath {
			os.RemoveAll(snapPath)
		}
	}()

	bwlimit := d.config["rsync.bwlimit"]

	// Copy volume into snapshot directory.
	_, err = rsync.LocalCopy(srcPath, snapPath, bwlimit, true)
	if err != nil {
		return err
	}

	revertPath = false
	return nil
}

// DeleteVolumeSnapshot removes a snapshot from the storage device. The volName and snapshotName
// must be bare names and should not be in the format "volume/snapshot".
func (d *dir) DeleteVolumeSnapshot(snapVol Volume, op *operations.Operation) error {
	snapPath := snapVol.MountPath()

	// Remove the snapshot from the storage device.
	err := forceRemoveAll(snapPath)
	if err != nil && !os.IsNotExist(err) {
		return errors.Wrapf(err, "Failed to remove '%s'", snapPath)
	}

	parentName, _, _ := shared.InstanceGetParentAndSnapshotName(snapVol.name)

	// Remove the parent snapshot directory if this is the last snapshot being removed.
	err = deleteParentSnapshotDirIfEmpty(d.name, snapVol.volType, parentName)
	if err != nil {
		return err
	}

	return nil
}

// MountVolumeSnapshot sets up a read-only mount on top of the snapshot to avoid accidental modifications.
func (d *dir) MountVolumeSnapshot(snapVol Volume, op *operations.Operation) (bool, error) {
	err := snapVol.EnsureMountPath()
	if err != nil {
		return false, err
	}

	snapPath := snapVol.MountPath()
	return mountReadOnly(snapPath, snapPath)
}

// UnmountVolumeSnapshot removes the read-only mount placed on top of a snapshot.
func (d *dir) UnmountVolumeSnapshot(snapVol Volume, op *operations.Operation) (bool, error) {
	snapPath := snapVol.MountPath()
	return forceUnmount(snapPath)
}

// VolumeSnapshots returns a list of snapshots for the volume.
func (d *dir) VolumeSnapshots(vol Volume, op *operations.Operation) ([]string, error) {
	return genericVFSVolumeSnapshots(d, vol, op)
}

// RestoreVolume restores a volume from a snapshot.
func (d *dir) RestoreVolume(vol Volume, snapshotName string, op *operations.Operation) error {
	srcPath := GetVolumeMountPath(d.name, vol.volType, GetSnapshotVolumeName(vol.name, snapshotName))
	if !shared.PathExists(srcPath) {
		return fmt.Errorf("Snapshot not found")
	}

	volPath := vol.MountPath()

	// Restore using rsync.
	bwlimit := d.config["rsync.bwlimit"]
	_, err := rsync.LocalCopy(srcPath, volPath, bwlimit, true)
	if err != nil {
		return errors.Wrap(err, "Failed to rsync volume")
	}

	return nil
}

// RenameVolumeSnapshot renames a volume snapshot.
func (d *dir) RenameVolumeSnapshot(snapVol Volume, newSnapshotName string, op *operations.Operation) error {
	return genericVFSRenameVolumeSnapshot(d, snapVol, newSnapshotName, op)
}
<|endoftext|>


<commit_before>package progressui

import (
	"context"
	"fmt"
	"io"
	"os"
	"sort"
	"strings"
	"time"

	digest "github.com/opencontainers/go-digest"
	"github.com/tonistiigi/units"
)

const antiFlicker = 5 * time.Second
const maxDelay = 10 * time.Second
const minTimeDelta = 5 * time.Second
const minProgressDelta = 0.05 // %

type lastStatus struct {
	Current   int64
	Timestamp time.Time
}

type textMux struct {
	w        io.Writer
	current  digest.Digest
	last     map[string]lastStatus
	notFirst bool
}

func (p *textMux) printVtx(t *trace, dgst digest.Digest) {
	if p.last == nil {
		p.last = make(map[string]lastStatus)
	}

	v, ok := t.byDigest[dgst]
	if !ok {
		return
	}

	if dgst != p.current {
		if p.current != "" {
			old := t.byDigest[p.current]
			if old.logsPartial {
				fmt.Fprintln(p.w, "")
			}
			old.logsOffset = 0
			old.count = 0
			fmt.Fprintf(p.w, "#%d ...\n", old.index)
		}

		if p.notFirst {
			fmt.Fprintln(p.w, "")
		} else {
			p.notFirst = true
		}

		if os.Getenv("PROGRESS_NO_TRUNC") == "1" {
			fmt.Fprintf(p.w, "#%d %s\n", v.index, v.Name)
			fmt.Fprintf(p.w, "#%d %s\n", v.index, v.Digest)
		} else {
			fmt.Fprintf(p.w, "#%d %s\n", v.index, limitString(v.Name, 72))
		}

	}

	if len(v.events) != 0 {
		v.logsOffset = 0
	}
	for _, ev := range v.events {
		fmt.Fprintf(p.w, "#%d %s\n", v.index, ev)
	}
	v.events = v.events[:0]

	for _, s := range v.statuses {
		if _, ok := v.statusUpdates[s.ID]; ok {
			doPrint := true

			if last, ok := p.last[s.ID]; ok && s.Completed == nil {
				var progressDelta float64
				if s.Total > 0 {
					progressDelta = float64(s.Current-last.Current) / float64(s.Total)
				}
				timeDelta := s.Timestamp.Sub(last.Timestamp)
				if progressDelta < minProgressDelta && timeDelta < minTimeDelta {
					doPrint = false
				}
			}

			if !doPrint {
				continue
			}

			p.last[s.ID] = lastStatus{
				Timestamp: s.Timestamp,
				Current:   s.Current,
			}

			var bytes string
			if s.Total != 0 {
				bytes = fmt.Sprintf(" %.2f / %.2f", units.Bytes(s.Current), units.Bytes(s.Total))
			} else if s.Current != 0 {
				bytes = fmt.Sprintf(" %.2f", units.Bytes(s.Current))
			}
			var tm string
			endTime := s.Timestamp
			if s.Completed != nil {
				endTime = *s.Completed
			}
			if s.Started != nil {
				diff := endTime.Sub(*s.Started).Seconds()
				if diff > 0.01 {
					tm = fmt.Sprintf(" %.1fs", diff)
				}
			}
			if s.Completed != nil {
				tm += " done"
			}
			fmt.Fprintf(p.w, "#%d %s%s%s\n", v.index, s.ID, bytes, tm)
		}
	}
	v.statusUpdates = map[string]struct{}{}

	for i, l := range v.logs {
		if i == 0 {
			l = l[v.logsOffset:]
		}
		fmt.Fprintf(p.w, "%s", []byte(l))
		if i != len(v.logs)-1 || !v.logsPartial {
			fmt.Fprintln(p.w, "")
		}
	}

	if len(v.logs) > 0 {
		if v.logsPartial {
			v.logs = v.logs[len(v.logs)-1:]
			v.logsOffset = len(v.logs[0])
		} else {
			v.logs = nil
			v.logsOffset = 0
		}
	}

	p.current = dgst
	if v.Completed != nil {
		p.current = ""
		v.count = 0

		if v.Error != "" {
			if v.logsPartial {
				fmt.Fprintln(p.w, "")
			}
			if strings.HasSuffix(v.Error, context.Canceled.Error()) {
				fmt.Fprintf(p.w, "#%d CANCELED\n", v.index)
			} else {
				fmt.Fprintf(p.w, "#%d ERROR: %s\n", v.index, v.Error)
			}
		} else if v.Cached {
			fmt.Fprintf(p.w, "#%d CACHED\n", v.index)
		} else {
			tm := ""
			if v.Started != nil {
				tm = fmt.Sprintf(" %.1fs", v.Completed.Sub(*v.Started).Seconds())
			}
			fmt.Fprintf(p.w, "#%d DONE%s\n", v.index, tm)
		}

	}

	delete(t.updates, dgst)
}

func sortCompleted(t *trace, m map[digest.Digest]struct{}) []digest.Digest {
	out := make([]digest.Digest, 0, len(m))
	for k := range m {
		out = append(out, k)
	}
	sort.Slice(out, func(i, j int) bool {
		return t.byDigest[out[i]].Completed.Before(*t.byDigest[out[j]].Completed)
	})
	return out
}

func (p *textMux) print(t *trace) {
	completed := map[digest.Digest]struct{}{}
	rest := map[digest.Digest]struct{}{}

	for dgst := range t.updates {
		v, ok := t.byDigest[dgst]
		if !ok {
			continue
		}
		if v.Vertex.Completed != nil {
			completed[dgst] = struct{}{}
		} else {
			rest[dgst] = struct{}{}
		}
	}

	current := p.current

	// items that have completed need to be printed first
	if _, ok := completed[current]; ok {
		p.printVtx(t, current)
	}

	for _, dgst := range sortCompleted(t, completed) {
		if dgst != current {
			p.printVtx(t, dgst)
		}
	}

	if len(rest) == 0 {
		if current != "" {
			if v := t.byDigest[current]; v.Started != nil && v.Completed == nil {
				return
			}
		}
		// make any open vertex active
		for dgst, v := range t.byDigest {
			if v.Started != nil && v.Completed == nil {
				p.printVtx(t, dgst)
				return
			}
		}
		return
	}

	// now print the active one
	if _, ok := rest[current]; ok {
		p.printVtx(t, current)
	}

	stats := map[digest.Digest]*vtxStat{}
	now := time.Now()
	sum := 0.0
	var max digest.Digest
	if current != "" {
		rest[current] = struct{}{}
	}
	for dgst := range rest {
		v, ok := t.byDigest[dgst]
		if !ok {
			continue
		}
		tm := now.Sub(*v.lastBlockTime)
		speed := float64(v.count) / tm.Seconds()
		overLimit := tm > maxDelay && dgst != current
		stats[dgst] = &vtxStat{blockTime: tm, speed: speed, overLimit: overLimit}
		sum += speed
		if overLimit || max == "" || stats[max].speed < speed {
			max = dgst
		}
	}
	for dgst := range stats {
		stats[dgst].share = stats[dgst].speed / sum
	}

	if _, ok := completed[current]; ok || current == "" {
		p.printVtx(t, max)
		return
	}

	// show items that were hidden
	for dgst := range rest {
		if stats[dgst].overLimit {
			p.printVtx(t, dgst)
			return
		}
	}

	// fair split between vertexes
	if 1.0/(1.0-stats[current].share)*antiFlicker.Seconds() < stats[current].blockTime.Seconds() {
		p.printVtx(t, max)
		return
	}
}

type vtxStat struct {
	blockTime time.Duration
	speed     float64
	share     float64
	overLimit bool
}

func limitString(s string, l int) string {
	if len(s) > l {
		return s[:l] + "..."
	}
	return s
}
<commit_msg>progress: disable truncating by default when using --progress=plain<commit_after>package progressui

import (
	"context"
	"fmt"
	"io"
	"os"
	"sort"
	"strings"
	"time"

	digest "github.com/opencontainers/go-digest"
	"github.com/tonistiigi/units"
)

const antiFlicker = 5 * time.Second
const maxDelay = 10 * time.Second
const minTimeDelta = 5 * time.Second
const minProgressDelta = 0.05 // %

type lastStatus struct {
	Current   int64
	Timestamp time.Time
}

type textMux struct {
	w        io.Writer
	current  digest.Digest
	last     map[string]lastStatus
	notFirst bool
}

func (p *textMux) printVtx(t *trace, dgst digest.Digest) {
	if p.last == nil {
		p.last = make(map[string]lastStatus)
	}

	v, ok := t.byDigest[dgst]
	if !ok {
		return
	}

	if dgst != p.current {
		if p.current != "" {
			old := t.byDigest[p.current]
			if old.logsPartial {
				fmt.Fprintln(p.w, "")
			}
			old.logsOffset = 0
			old.count = 0
			fmt.Fprintf(p.w, "#%d ...\n", old.index)
		}

		if p.notFirst {
			fmt.Fprintln(p.w, "")
		} else {
			p.notFirst = true
		}

		if os.Getenv("PROGRESS_NO_TRUNC") == "0" {
			fmt.Fprintf(p.w, "#%d %s\n", v.index, limitString(v.Name, 72))
		} else {
			fmt.Fprintf(p.w, "#%d %s\n", v.index, v.Name)
			fmt.Fprintf(p.w, "#%d %s\n", v.index, v.Digest)
		}

	}

	if len(v.events) != 0 {
		v.logsOffset = 0
	}
	for _, ev := range v.events {
		fmt.Fprintf(p.w, "#%d %s\n", v.index, ev)
	}
	v.events = v.events[:0]

	for _, s := range v.statuses {
		if _, ok := v.statusUpdates[s.ID]; ok {
			doPrint := true

			if last, ok := p.last[s.ID]; ok && s.Completed == nil {
				var progressDelta float64
				if s.Total > 0 {
					progressDelta = float64(s.Current-last.Current) / float64(s.Total)
				}
				timeDelta := s.Timestamp.Sub(last.Timestamp)
				if progressDelta < minProgressDelta && timeDelta < minTimeDelta {
					doPrint = false
				}
			}

			if !doPrint {
				continue
			}

			p.last[s.ID] = lastStatus{
				Timestamp: s.Timestamp,
				Current:   s.Current,
			}

			var bytes string
			if s.Total != 0 {
				bytes = fmt.Sprintf(" %.2f / %.2f", units.Bytes(s.Current), units.Bytes(s.Total))
			} else if s.Current != 0 {
				bytes = fmt.Sprintf(" %.2f", units.Bytes(s.Current))
			}
			var tm string
			endTime := s.Timestamp
			if s.Completed != nil {
				endTime = *s.Completed
			}
			if s.Started != nil {
				diff := endTime.Sub(*s.Started).Seconds()
				if diff > 0.01 {
					tm = fmt.Sprintf(" %.1fs", diff)
				}
			}
			if s.Completed != nil {
				tm += " done"
			}
			fmt.Fprintf(p.w, "#%d %s%s%s\n", v.index, s.ID, bytes, tm)
		}
	}
	v.statusUpdates = map[string]struct{}{}

	for i, l := range v.logs {
		if i == 0 {
			l = l[v.logsOffset:]
		}
		fmt.Fprintf(p.w, "%s", []byte(l))
		if i != len(v.logs)-1 || !v.logsPartial {
			fmt.Fprintln(p.w, "")
		}
	}

	if len(v.logs) > 0 {
		if v.logsPartial {
			v.logs = v.logs[len(v.logs)-1:]
			v.logsOffset = len(v.logs[0])
		} else {
			v.logs = nil
			v.logsOffset = 0
		}
	}

	p.current = dgst
	if v.Completed != nil {
		p.current = ""
		v.count = 0

		if v.Error != "" {
			if v.logsPartial {
				fmt.Fprintln(p.w, "")
			}
			if strings.HasSuffix(v.Error, context.Canceled.Error()) {
				fmt.Fprintf(p.w, "#%d CANCELED\n", v.index)
			} else {
				fmt.Fprintf(p.w, "#%d ERROR: %s\n", v.index, v.Error)
			}
		} else if v.Cached {
			fmt.Fprintf(p.w, "#%d CACHED\n", v.index)
		} else {
			tm := ""
			if v.Started != nil {
				tm = fmt.Sprintf(" %.1fs", v.Completed.Sub(*v.Started).Seconds())
			}
			fmt.Fprintf(p.w, "#%d DONE%s\n", v.index, tm)
		}

	}

	delete(t.updates, dgst)
}

func sortCompleted(t *trace, m map[digest.Digest]struct{}) []digest.Digest {
	out := make([]digest.Digest, 0, len(m))
	for k := range m {
		out = append(out, k)
	}
	sort.Slice(out, func(i, j int) bool {
		return t.byDigest[out[i]].Completed.Before(*t.byDigest[out[j]].Completed)
	})
	return out
}

func (p *textMux) print(t *trace) {
	completed := map[digest.Digest]struct{}{}
	rest := map[digest.Digest]struct{}{}

	for dgst := range t.updates {
		v, ok := t.byDigest[dgst]
		if !ok {
			continue
		}
		if v.Vertex.Completed != nil {
			completed[dgst] = struct{}{}
		} else {
			rest[dgst] = struct{}{}
		}
	}

	current := p.current

	// items that have completed need to be printed first
	if _, ok := completed[current]; ok {
		p.printVtx(t, current)
	}

	for _, dgst := range sortCompleted(t, completed) {
		if dgst != current {
			p.printVtx(t, dgst)
		}
	}

	if len(rest) == 0 {
		if current != "" {
			if v := t.byDigest[current]; v.Started != nil && v.Completed == nil {
				return
			}
		}
		// make any open vertex active
		for dgst, v := range t.byDigest {
			if v.Started != nil && v.Completed == nil {
				p.printVtx(t, dgst)
				return
			}
		}
		return
	}

	// now print the active one
	if _, ok := rest[current]; ok {
		p.printVtx(t, current)
	}

	stats := map[digest.Digest]*vtxStat{}
	now := time.Now()
	sum := 0.0
	var max digest.Digest
	if current != "" {
		rest[current] = struct{}{}
	}
	for dgst := range rest {
		v, ok := t.byDigest[dgst]
		if !ok {
			continue
		}
		tm := now.Sub(*v.lastBlockTime)
		speed := float64(v.count) / tm.Seconds()
		overLimit := tm > maxDelay && dgst != current
		stats[dgst] = &vtxStat{blockTime: tm, speed: speed, overLimit: overLimit}
		sum += speed
		if overLimit || max == "" || stats[max].speed < speed {
			max = dgst
		}
	}
	for dgst := range stats {
		stats[dgst].share = stats[dgst].speed / sum
	}

	if _, ok := completed[current]; ok || current == "" {
		p.printVtx(t, max)
		return
	}

	// show items that were hidden
	for dgst := range rest {
		if stats[dgst].overLimit {
			p.printVtx(t, dgst)
			return
		}
	}

	// fair split between vertexes
	if 1.0/(1.0-stats[current].share)*antiFlicker.Seconds() < stats[current].blockTime.Seconds() {
		p.printVtx(t, max)
		return
	}
}

type vtxStat struct {
	blockTime time.Duration
	speed     float64
	share     float64
	overLimit bool
}

func limitString(s string, l int) string {
	if len(s) > l {
		return s[:l] + "..."
	}
	return s
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"github.com/mndrix/tap-go"
	rspecs "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/opencontainers/runtime-tools/specerror"
	"github.com/opencontainers/runtime-tools/validation/util"
	uuid "github.com/satori/go.uuid"
)

func saveConfig(path string, v interface{}) error {
	data, err := json.Marshal(v)
	if err != nil {
		return err
	}

	return ioutil.WriteFile(path, data, 0644)
}

func main() {
	t := tap.New()
	t.Header(0)
	bundleDir, err := util.PrepareBundle()
	if err != nil {
		util.Fatal(err)
	}
	defer os.RemoveAll(bundleDir)
	configFile := filepath.Join(bundleDir, "config.json")

	type extendedSpec struct {
		rspecs.Spec
		Unknown string `json:"unknown,omitempty"`
	}

	containerID := uuid.NewV4().String()
	basicConfig, err := util.GetDefaultGenerator()
	if err != nil {
		util.Fatal(err)
	}
	basicConfig.SetProcessArgs([]string{"true"})
	annotationConfig := basicConfig
	annotationConfig.AddAnnotation(fmt.Sprintf("org.%s", containerID), "")
	invalidConfig := basicConfig
	invalidConfig.SetVersion("invalid")

	cases := []struct {
		eSpec       extendedSpec
		action      util.LifecycleAction
		errExpected bool
		err         error
	}{
		{extendedSpec{Spec: *annotationConfig.Spec()}, util.LifecycleActionCreate | util.LifecycleActionStart | util.LifecycleActionDelete, true, specerror.NewError(specerror.AnnotationsKeyIgnoreUnknown, fmt.Errorf("implementations that are reading/processing this configuration file MUST NOT generate an error if they encounter an unknown annotation key"), rspecs.Version)},
		{extendedSpec{Spec: *basicConfig.Spec(), Unknown: "unknown"}, util.LifecycleActionCreate | util.LifecycleActionStart | util.LifecycleActionDelete, true, specerror.NewError(specerror.ExtensibilityIgnoreUnknownProp, fmt.Errorf("runtimes that are reading or processing this configuration file MUST NOT generate an error if they encounter an unknown property"), rspecs.Version)},
		{extendedSpec{Spec: *invalidConfig.Spec()}, util.LifecycleActionCreate | util.LifecycleActionStart | util.LifecycleActionDelete, false, specerror.NewError(specerror.ValidValues, fmt.Errorf("runtimes that are reading or processing this configuration file MUST generate an error when invalid or unsupported values are encountered"), rspecs.Version)},
	}

	for _, c := range cases {
		config := util.LifecycleConfig{
			BundleDir: bundleDir,
			Actions:   c.action,
			PreCreate: func(r *util.Runtime) error {
				r.SetID(containerID)
				return saveConfig(configFile, c.eSpec)
			},
			PreDelete: func(r *util.Runtime) error {
				util.WaitingForStatus(*r, util.LifecycleStatusCreated|util.LifecycleStatusStopped, time.Second*10, time.Second*1)
				return nil
			},
		}
		err = util.RuntimeLifecycleValidate(config)
		util.SpecErrorOK(t, (err == nil) == c.errExpected, c.err, err)
	}

	t.AutoPlan()
}
<commit_msg>misc: use different objects for the different tests<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"github.com/mndrix/tap-go"
	rspecs "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/opencontainers/runtime-tools/specerror"
	"github.com/opencontainers/runtime-tools/validation/util"
	uuid "github.com/satori/go.uuid"
)

func saveConfig(path string, v interface{}) error {
	data, err := json.Marshal(v)
	if err != nil {
		return err
	}

	return ioutil.WriteFile(path, data, 0644)
}

func main() {
	t := tap.New()
	t.Header(0)
	bundleDir, err := util.PrepareBundle()
	if err != nil {
		util.Fatal(err)
	}
	defer os.RemoveAll(bundleDir)
	configFile := filepath.Join(bundleDir, "config.json")

	type extendedSpec struct {
		rspecs.Spec
		Unknown string `json:"unknown,omitempty"`
	}

	containerID := uuid.NewV4().String()
	basicConfig, err := util.GetDefaultGenerator()
	if err != nil {
		util.Fatal(err)
	}
	basicConfig.SetProcessArgs([]string{"true"})
	annotationConfig, err := util.GetDefaultGenerator()
	if err != nil {
		util.Fatal(err)
	}
	annotationConfig.AddAnnotation(fmt.Sprintf("org.%s", containerID), "")
	invalidConfig, err := util.GetDefaultGenerator()
	if err != nil {
		util.Fatal(err)
	}
	invalidConfig.SetVersion("invalid")

	cases := []struct {
		eSpec       extendedSpec
		action      util.LifecycleAction
		errExpected bool
		err         error
	}{
		{extendedSpec{Spec: *annotationConfig.Spec()}, util.LifecycleActionCreate | util.LifecycleActionStart | util.LifecycleActionDelete, true, specerror.NewError(specerror.AnnotationsKeyIgnoreUnknown, fmt.Errorf("implementations that are reading/processing this configuration file MUST NOT generate an error if they encounter an unknown annotation key"), rspecs.Version)},
		{extendedSpec{Spec: *basicConfig.Spec(), Unknown: "unknown"}, util.LifecycleActionCreate | util.LifecycleActionStart | util.LifecycleActionDelete, true, specerror.NewError(specerror.ExtensibilityIgnoreUnknownProp, fmt.Errorf("runtimes that are reading or processing this configuration file MUST NOT generate an error if they encounter an unknown property"), rspecs.Version)},
		{extendedSpec{Spec: *invalidConfig.Spec()}, util.LifecycleActionCreate | util.LifecycleActionStart | util.LifecycleActionDelete, false, specerror.NewError(specerror.ValidValues, fmt.Errorf("runtimes that are reading or processing this configuration file MUST generate an error when invalid or unsupported values are encountered"), rspecs.Version)},
	}

	for _, c := range cases {
		config := util.LifecycleConfig{
			BundleDir: bundleDir,
			Actions:   c.action,
			PreCreate: func(r *util.Runtime) error {
				r.SetID(containerID)
				return saveConfig(configFile, c.eSpec)
			},
			PreDelete: func(r *util.Runtime) error {
				util.WaitingForStatus(*r, util.LifecycleStatusCreated|util.LifecycleStatusStopped, time.Second*10, time.Second*1)
				return nil
			},
		}
		err = util.RuntimeLifecycleValidate(config)
		util.SpecErrorOK(t, (err == nil) == c.errExpected, c.err, err)
	}

	t.AutoPlan()
}
<|endoftext|>