<commit_before>// Copyright 2017 The go-darwin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package hdiutil

import (
	"bytes"
	"os/exec"
	"strconv"
)

// AttachFlag implements a hdiutil attach command flag interface.
type AttachFlag interface {
	attachFlag() string
}

type rwType int

const (
	// Readonly force the resulting device to be read-only.
	readonly rwType = 1 << iota
	// Readwrite attempt to override the DiskImages framework's decision to attach a particular image read-only.
	// For example, -readwrite can be used to modify the HFS+ filesystem on a HFS+/ISO hybrid CD image.
	readwrite
)

func (rw rwType) attachFlag() string {
	switch rw {
	case readonly:
		return "-readonly"
	case readwrite:
		return "-readwrite"
	default:
		return ""
	}
}

type kernel bool

func (k kernel) attachFlag() string { return boolNoFlag(bool(k), "kernel") }

type notRemovable bool

func (n notRemovable) attachFlag() string { return boolFlag(bool(n), "notremovable") }

type mount string

func (m mount) attachFlag() string { return stringFlag(string(m), "mount") }

type noMount bool

func (n noMount) attachFlag() string { return boolFlag(bool(n), "nomount") }

// MountRoot mount volumes on subdirectories of path instead of under /Volumes. path must exist.
// Full mount point paths must be less than MNAMELEN characters (increased from 90 to 1024 in Mac OS X 10.6).
type MountRoot string

func (m MountRoot) attachFlag() string { return stringFlag(string(m), "mountroot") }

// MountRandom like -mountroot, but mount point directory names are randomized with mkdtemp(3).
type MountRandom string

func (m MountRandom) attachFlag() string { return stringFlag(string(m), "mountrandom") }

// MountPoint assuming only one volume, mount it at path instead of in /Volumes.
// See fstab(5) for ways a system administrator can make particular volumes automatically mount in particular filesystem locations by editing the file /etc/fstab.
type MountPoint string

func (m MountPoint) attachFlag() string { return stringFlag(string(m), "mountpoint") }

type noBrowse bool

func (n noBrowse) attachFlag() string { return boolFlag(bool(n), "nobrowse") }

type owners string

const (
	ownersOn  owners = "on"
	ownersOff owners = "off"
)

func (o owners) attachFlag() string { return stringFlag(string(o), "owners") }

// drivekey specify a key/value pair to be set on the device in the IOKit registry.
type drivekey struct {
	Key   string
	Value string
}

// section attach a subsection of a disk image.
// subspec is any of <offset>, <first-last>, or <start,count> in 0-based sectors.
// Ranges are inclusive.
type section [2]int

func (s section) attachFlag() string {
	var arg string
	for v := range s {
		arg = arg + strconv.Itoa(v)
	}
	return stringFlag(arg, "section")
}

type verify bool

func (v verify) attachFlag() string { return boolNoFlag(bool(v), "verify") }

type ignoreBadChecksums bool

func (i ignoreBadChecksums) attachFlag() string { return boolNoFlag(bool(i), "ignoreBadChecksums") }

type idme bool

func (i idme) attachFlag() string { return boolNoFlag(bool(i), "idme") }

type idmeReveal bool

func (i idmeReveal) attachFlag() string { return boolNoFlag(bool(i), "idmereveal") }

type idmeTrash bool

func (i idmeTrash) attachFlag() string { return boolNoFlag(bool(i), "idmetrash") }

type autoOpen bool

func (a autoOpen) attachFlag() string { return boolNoFlag(bool(a), "autoopen") }

type autoOpenRO bool

func (a autoOpenRO) attachFlag() string { return boolNoFlag(bool(a), "autoopenro") }

type autoOpenRW bool

func (a autoOpenRW) attachFlag() string { return boolNoFlag(bool(a), "autoopenrw") }

type autoFsck bool

func (a autoFsck) attachFlag() string { return boolNoFlag(bool(a), "autofsck") }

const (
	// AttachReadonly force the resulting device to be read-only.
	AttachReadonly rwType = readonly

	// AttachReadWrite attempt to override the DiskImages framework's decision to attach a particular image read-only.
	//
	// For example, -readwrite can be used to modify the HFS+ filesystem on a HFS+/ISO hybrid CD image.
	AttachReadWrite rwType = readwrite

	// AttachKernel attempt to attach this image without a helper process; fail if unsupported.
	//
	// Only UDRW, UDRO, UDZO, ULFO, and UDSP images are supported in-kernel. Encryption and HTTP are supported by the kernel driver.
	AttachKernel kernel = true

	// AttachNoKernel attach with a helper process.  This is (again) the default as of Mac OS X 10.5.
	AttachNoKernel kernel = false

	// AttachNotRemovable prevent this image from being detached. Only root can use this option.
	//
	// A reboot is necessary to cleanly detach an image attached with -notremovable.
	AttachNotRemovable notRemovable = true

	// AttachMountRequired indicate to -mount required.
	AttachMountRequired mount = "required"

	// AttachMountOptional indicate to -mount optional.
	AttachMountOptional mount = "optional"

	// AttachMountSuppressed indicate to -mount suppressed.
	AttachMountSuppressed mount = "suppressed"

	// AttachNoMount identical to -mount suppressed
	AttachNoMount noMount = true

	// AttachNoBrowse render any volumes invisible in applications such as the macOS Finder.
	AttachNoBrowse noBrowse = true

	// AttachOwnersOn owners on any filesystems be honored.
	AttachOwnersOn owners = ownersOn

	// AttachOwnersOff owners on any filesystems be not honored.
	AttachOwnersOff owners = ownersOff

	// AttachVerify do verify the image.
	AttachVerify verify = true

	// AttachNoVerify do not verify the image.
	AttachNoVerify verify = false

	// AttachIgnoreBadChecksums bad checksums should be ignored.
	AttachIgnoreBadChecksums ignoreBadChecksums = true

	// AttachNoIgnoreBadChecksums bad checksums should be not ignored.
	AttachNoIgnoreBadChecksums ignoreBadChecksums = false

	// AttachIdme do perform IDME actions on IDME images.
	AttachIdme idme = true

	// AttachNoIdme do not perform IDME actions on IDME images.
	AttachNoIdme idme = false

	// AttachIdmeReveal do reveal (in the Finder) the results of IDME processing.
	AttachIdmeReveal idmeReveal = true

	// AttachNoIdmeReveal do not reveal (in the Finder) the results of IDME processing.
	AttachNoIdmeReveal idmeReveal = false

	// AttachIdmeTrash do put IDME images in the trash after processing.
	AttachIdmeTrash idmeTrash = true
	// AttachNoIdmeTrash do not put IDME images in the trash after processing.
	AttachNoIdmeTrash idmeTrash = false

	// AttachAutoOpen do not auto-open volumes (in the Finder) after attaching an image.
	AttachAutoOpen autoOpen = true

	// AttachNoAutoOpen do not auto-open volumes (in the Finder) after attaching an image.
	AttachNoAutoOpen autoOpen = false

	// AttachAutoOpenRO do auto-open read-only volumes.
	AttachAutoOpenRO autoOpenRO = true

	// AttachNoAutoOpenRO do not auto-open read-only volumes.
	AttachNoAutoOpenRO autoOpenRO = false

	// AttachAutoOpenRW do auto-open read/write volumes.
	AttachAutoOpenRW autoOpenRW = true

	// AttachNoAutoOpenRW do not auto-open read/write volumes.
	AttachNoAutoOpenRW autoOpenRW = false

	// AttachAutoFsck do force automatic file system checking before mounting a disk image.
	AttachAutoFsck autoFsck = true

	// AttachNoAutoFsck do not force automatic file system checking before mounting a disk image.
	AttachNoAutoFsck autoFsck = false
)

// Attach attach the image file. The returns device node path and error.
func Attach(image string, flags ...AttachFlag) (string, error) {
	cmd := exec.Command(hdiutilPath, "attach", image)

	if len(flags) != 0 {
		args := []string{}
		for _, f := range flags {
			args = append(args, f.attachFlag())
		}
		cmd.Args = append(cmd.Args, args...)
	}

	out, err := cmd.Output()
	if err != nil {
		return "", err
	}

	return string(bytes.TrimSpace(out)), nil
}
<commit_msg>attach: export Section type<commit_after>// Copyright 2017 The go-darwin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package hdiutil

import (
	"bytes"
	"os/exec"
	"strconv"
)

// AttachFlag implements a hdiutil attach command flag interface.
type AttachFlag interface {
	attachFlag() string
}

type rwType int

const (
	// Readonly force the resulting device to be read-only.
	readonly rwType = 1 << iota
	// Readwrite attempt to override the DiskImages framework's decision to attach a particular image read-only.
	// For example, -readwrite can be used to modify the HFS+ filesystem on a HFS+/ISO hybrid CD image.
	readwrite
)

func (rw rwType) attachFlag() string {
	switch rw {
	case readonly:
		return "-readonly"
	case readwrite:
		return "-readwrite"
	default:
		return ""
	}
}

type kernel bool

func (k kernel) attachFlag() string { return boolNoFlag(bool(k), "kernel") }

type notRemovable bool

func (n notRemovable) attachFlag() string { return boolFlag(bool(n), "notremovable") }

type mount string

func (m mount) attachFlag() string { return stringFlag(string(m), "mount") }

type noMount bool

func (n noMount) attachFlag() string { return boolFlag(bool(n), "nomount") }

// MountRoot mount volumes on subdirectories of path instead of under /Volumes. path must exist.
// Full mount point paths must be less than MNAMELEN characters (increased from 90 to 1024 in Mac OS X 10.6).
type MountRoot string

func (m MountRoot) attachFlag() string { return stringFlag(string(m), "mountroot") }

// MountRandom like -mountroot, but mount point directory names are randomized with mkdtemp(3).
type MountRandom string

func (m MountRandom) attachFlag() string { return stringFlag(string(m), "mountrandom") }

// MountPoint assuming only one volume, mount it at path instead of in /Volumes.
// See fstab(5) for ways a system administrator can make particular volumes automatically mount in particular filesystem locations by editing the file /etc/fstab.
type MountPoint string

func (m MountPoint) attachFlag() string { return stringFlag(string(m), "mountpoint") }

type noBrowse bool

func (n noBrowse) attachFlag() string { return boolFlag(bool(n), "nobrowse") }

type owners string

const (
	ownersOn  owners = "on"
	ownersOff owners = "off"
)

func (o owners) attachFlag() string { return stringFlag(string(o), "owners") }

// drivekey specify a key/value pair to be set on the device in the IOKit registry.
type drivekey struct {
	Key   string
	Value string
}

// Section attach a subsection of a disk image.
// subspec is any of <offset>, <first-last>, or <start,count> in 0-based sectors.
// Ranges are inclusive.
type Section [2]int

func (s Section) attachFlag() string {
	var arg string
	for v := range s {
		arg = arg + strconv.Itoa(v)
	}
	return stringFlag(arg, "section")
}

type verify bool

func (v verify) attachFlag() string { return boolNoFlag(bool(v), "verify") }

type ignoreBadChecksums bool

func (i ignoreBadChecksums) attachFlag() string { return boolNoFlag(bool(i), "ignoreBadChecksums") }

type idme bool

func (i idme) attachFlag() string { return boolNoFlag(bool(i), "idme") }

type idmeReveal bool

func (i idmeReveal) attachFlag() string { return boolNoFlag(bool(i), "idmereveal") }

type idmeTrash bool

func (i idmeTrash) attachFlag() string { return boolNoFlag(bool(i), "idmetrash") }

type autoOpen bool

func (a autoOpen) attachFlag() string { return boolNoFlag(bool(a), "autoopen") }

type autoOpenRO bool

func (a autoOpenRO) attachFlag() string { return boolNoFlag(bool(a), "autoopenro") }

type autoOpenRW bool

func (a autoOpenRW) attachFlag() string { return boolNoFlag(bool(a), "autoopenrw") }

type autoFsck bool

func (a autoFsck) attachFlag() string { return boolNoFlag(bool(a), "autofsck") }

const (
	// AttachReadonly force the resulting device to be read-only.
	AttachReadonly rwType = readonly

	// AttachReadWrite attempt to override the DiskImages framework's decision to attach a particular image read-only.
	//
	// For example, -readwrite can be used to modify the HFS+ filesystem on a HFS+/ISO hybrid CD image.
	AttachReadWrite rwType = readwrite

	// AttachKernel attempt to attach this image without a helper process; fail if unsupported.
	//
	// Only UDRW, UDRO, UDZO, ULFO, and UDSP images are supported in-kernel. Encryption and HTTP are supported by the kernel driver.
	AttachKernel kernel = true

	// AttachNoKernel attach with a helper process.  This is (again) the default as of Mac OS X 10.5.
	AttachNoKernel kernel = false

	// AttachNotRemovable prevent this image from being detached. Only root can use this option.
	//
	// A reboot is necessary to cleanly detach an image attached with -notremovable.
	AttachNotRemovable notRemovable = true

	// AttachMountRequired indicate to -mount required.
	AttachMountRequired mount = "required"

	// AttachMountOptional indicate to -mount optional.
	AttachMountOptional mount = "optional"

	// AttachMountSuppressed indicate to -mount suppressed.
	AttachMountSuppressed mount = "suppressed"

	// AttachNoMount identical to -mount suppressed
	AttachNoMount noMount = true

	// AttachNoBrowse render any volumes invisible in applications such as the macOS Finder.
	AttachNoBrowse noBrowse = true

	// AttachOwnersOn owners on any filesystems be honored.
	AttachOwnersOn owners = ownersOn

	// AttachOwnersOff owners on any filesystems be not honored.
	AttachOwnersOff owners = ownersOff

	// AttachVerify do verify the image.
	AttachVerify verify = true

	// AttachNoVerify do not verify the image.
	AttachNoVerify verify = false

	// AttachIgnoreBadChecksums bad checksums should be ignored.
	AttachIgnoreBadChecksums ignoreBadChecksums = true

	// AttachNoIgnoreBadChecksums bad checksums should be not ignored.
	AttachNoIgnoreBadChecksums ignoreBadChecksums = false

	// AttachIdme do perform IDME actions on IDME images.
	AttachIdme idme = true

	// AttachNoIdme do not perform IDME actions on IDME images.
	AttachNoIdme idme = false

	// AttachIdmeReveal do reveal (in the Finder) the results of IDME processing.
	AttachIdmeReveal idmeReveal = true

	// AttachNoIdmeReveal do not reveal (in the Finder) the results of IDME processing.
	AttachNoIdmeReveal idmeReveal = false

	// AttachIdmeTrash do put IDME images in the trash after processing.
	AttachIdmeTrash idmeTrash = true
	// AttachNoIdmeTrash do not put IDME images in the trash after processing.
	AttachNoIdmeTrash idmeTrash = false

	// AttachAutoOpen do not auto-open volumes (in the Finder) after attaching an image.
	AttachAutoOpen autoOpen = true

	// AttachNoAutoOpen do not auto-open volumes (in the Finder) after attaching an image.
	AttachNoAutoOpen autoOpen = false

	// AttachAutoOpenRO do auto-open read-only volumes.
	AttachAutoOpenRO autoOpenRO = true

	// AttachNoAutoOpenRO do not auto-open read-only volumes.
	AttachNoAutoOpenRO autoOpenRO = false

	// AttachAutoOpenRW do auto-open read/write volumes.
	AttachAutoOpenRW autoOpenRW = true

	// AttachNoAutoOpenRW do not auto-open read/write volumes.
	AttachNoAutoOpenRW autoOpenRW = false

	// AttachAutoFsck do force automatic file system checking before mounting a disk image.
	AttachAutoFsck autoFsck = true

	// AttachNoAutoFsck do not force automatic file system checking before mounting a disk image.
	AttachNoAutoFsck autoFsck = false
)

// Attach attach the image file. The returns device node path and error.
func Attach(image string, flags ...AttachFlag) (string, error) {
	cmd := exec.Command(hdiutilPath, "attach", image)

	if len(flags) != 0 {
		args := []string{}
		for _, f := range flags {
			args = append(args, f.attachFlag())
		}
		cmd.Args = append(cmd.Args, args...)
	}

	out, err := cmd.Output()
	if err != nil {
		return "", err
	}

	return string(bytes.TrimSpace(out)), nil
}
<|endoftext|>


<commit_before>// Package augeas provides Go bindings for Augeas, the configuration
// editing tool.
//
// For more information on Augeas itself, check out http://augeas.net/
package augeas // import "honnef.co/go/augeas"

// #cgo pkg-config: libxml-2.0 augeas
// #include <augeas.h>
// #include <stdlib.h>
import "C"
import (
	"unsafe"
)

type Flag uint

const (
	None Flag = 1 << iota

	// Keep the original file with a .augsave extension
	SaveBackup

	// Save changes into a file with extension .augnew, and do not
	// overwrite the original file. Takes precedence over SaveBackup
	SaveNewFile

	// Typecheck lenses; since it can be very expensive it is not done
	// by default
	TypeCheck

	// Do not use the built-in load path for modules
	NoStdinc

	// Make save a no-op process, just record what would have changed
	SaveNoop

	// Do not load the tree automatically
	NoLoad

	NoModlAutoload

	// Track the span in the input of nodes
	EnableSpan

	// Do not close automatically when encountering error during
	// initialization
	NoErrClose
)

type Augeas struct {
	handle *C.augeas
}

type Span struct {
	Filename   string
	LabelStart uint
	LabelEnd   uint
	ValueStart uint
	ValueEnd   uint
	SpanStart  uint
	SpanEnd    uint
}

func New(root, loadPath string, flags Flag) (Augeas, error) {
	cRoot := C.CString(root)
	defer C.free(unsafe.Pointer(cRoot))
	cLoadPath := C.CString(loadPath)
	defer C.free(unsafe.Pointer(cLoadPath))

	handle := C.aug_init(cRoot, cLoadPath, C.uint(flags))
	if flags&NoErrClose > 0 {
		a := Augeas{handle}
		return a, a.error()
	}
	if handle == nil {
		return Augeas{}, Error{CouldNotInitialize, "Could not initialize Augeas tree", "", ""}
	}

	return Augeas{handle}, nil
}

// DefineVariable defines a variable whose value is the result of
// evaluating the expression. If a variable with the name already
// exists, it will be replaced. Context will not be applied to the
// expression.
//
// If the expression is empty, the variable will be removed.
//
// Path variables can be used in path expressions later on by prefixing
// them with '$'.
//
// Returns the number of nodes if the expression evaluates to a
// nodeset.
func (a Augeas) DefineVariable(name, expression string) (int, error) {
	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))

	var cExpression *C.char
	if expression != "" {
		cExpression = C.CString(expression)
		defer C.free(unsafe.Pointer(cExpression))
	}

	ret := C.aug_defvar(a.handle, cName, cExpression)

	if ret == -1 {
		return 0, a.error()
	}

	return int(ret), nil
}

func (a Augeas) RemoveVariable(name string) error {
	_, err := a.DefineVariable(name, "")
	return err
}

// DefineNode defines a variable whose value is the result of
// evaluating the expression, which must not be empty and evaluate to
// a nodeset. If a variable with the name already exists, it will be
// replaced.
//
// If the expression evaluates to an empty nodeset, a node is created.
//
// Returns the number of nodes in the nodeset and whether a node has
// been created or of it already existed.
func (a Augeas) DefineNode(name, expression, value string) (num int, created bool, err error) {
	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))

	cExpression := C.CString(expression)
	defer C.free(unsafe.Pointer(cExpression))

	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	var cCreated C.int
	cNum := C.aug_defnode(a.handle, cName, cExpression, cValue, &cCreated)
	num = int(cNum)
	created = cCreated == 1
	if cNum == -1 {
		err = a.error()
		return
	}

	return
}

// Get looks up the value associated with a path.
//
// Returns an error if there are no or too many matching nodes, or if
// the path is not a legal path expression.
func (a Augeas) Get(path string) (value string, err error) {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	var cValue *C.char
	ret := C.aug_get(a.handle, cPath, &cValue)

	if ret == 1 {
		return C.GoString(cValue), nil
	} else if ret == 0 {
		return "", Error{NoMatch, "No matching node", "", ""}
	} else if ret < 0 {
		return "", a.error()
	}

	panic("Unexpected return value")
}

// GetAll gets all values associated with a path.
func (a Augeas) GetAll(path string) (values []string, err error) {
	paths, err := a.Match(path)
	if err != nil {
		return
	}

	for _, path := range paths {
		value, err := a.Get(path)
		if err != nil {
			return values, err
		}

		values = append(values, value)
	}

	return
}

// Set the value associated with a path. Intermediate entries are
// created if they don't exist.
func (a Augeas) Set(path, value string) error {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	ret := C.aug_set(a.handle, cPath, cValue)

	if ret == -1 {
		return a.error()
	}

	return nil
}

// SetMultiple sets the value of multiple nodes in one operation. Find
// or create a node matching sub by interpreting sub as a path
// expression relative to each node matching base. sub may be empty,
// in which case all the nodes matching base will be modified.
//
// Returns the number of modified nodes.
func (a Augeas) SetMultiple(base, sub, value string) (int, error) {
	cBase := C.CString(base)
	defer C.free(unsafe.Pointer(cBase))

	var cSub *C.char
	if sub != "" {
		cSub := C.CString(sub)
		defer C.free(unsafe.Pointer(cSub))
	}

	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	ret := C.aug_setm(a.handle, cBase, cSub, cValue)

	if ret == -1 {
		return 0, a.error()
	}

	return int(ret), nil
}

// Span gets the span according to input file of the node associated
// with a path. If the node is associated with a file, the filename,
// label and value start and end positions are set.
func (a Augeas) Span(path string) (Span, error) {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	var cFilename *C.char
	var labelStart, labelEnd, valueStart, valueEnd, spanStart, spanEnd C.uint
	var span Span

	ret := C.aug_span(a.handle, cPath, &cFilename,
		&labelStart, &labelEnd,
		&valueStart, &valueEnd,
		&spanStart, &spanEnd)

	if ret == -1 {
		return span, a.error()
	}

	span.LabelStart = uint(labelStart)
	span.LabelEnd = uint(labelEnd)
	span.ValueStart = uint(valueStart)
	span.ValueEnd = uint(valueEnd)
	span.SpanStart = uint(spanStart)
	span.SpanEnd = uint(spanEnd)
	span.Filename = C.GoString(cFilename)
	C.free(unsafe.Pointer(cFilename))

	return span, nil
}

// Insert creates a new sibling for a path by inserting into the
// tree just before the path if before is true or just after it if
// before is false.
//
// The path must match exactly one existing node in the tree, and the
// label must not contain a '/', '*' or end with a bracketed index
// '[N]'.
func (a Augeas) Insert(path, label string, before bool) error {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	cLabel := C.CString(label)
	defer C.free(unsafe.Pointer(cLabel))

	var cBefore C.int
	if before {
		cBefore = 1
	} else {
		cBefore = 0
	}

	ret := C.aug_insert(a.handle, cPath, cLabel, cBefore)
	if ret == -1 {
		return a.error()
	}

	return nil
}

// Remove removes a path and all its children. Returns the number of
// entries removed. All nodes that match the given path, and their
// descendants, are removed.
func (a Augeas) Remove(path string) (num int) {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	return int(C.aug_rm(a.handle, cPath))
}

// Move moves the node src to dst. src must match exactly one node in
// the tree. dst must either match exactly one node in the tree, or
// may not exist yet. If dst exists already, it and all its
// descendants are deleted. If dst does not exist yet, it and all its
// missing ancestors are created.
//
// Note that the node src always becomes the node dst: when you move /a/b
// to /x, the node /a/b is now called /x, no matter whether /x existed
// initially or not.
func (a Augeas) Move(source, destination string) error {
	cSource := C.CString(source)
	defer C.free(unsafe.Pointer(cSource))

	cDestination := C.CString(destination)
	defer C.free(unsafe.Pointer(cDestination))

	ret := C.aug_mv(a.handle, cSource, cDestination)

	if ret == -1 {
		return a.error()
	}

	return nil
}

// Match returns all paths matching a given path. The returned paths
// are sufficiently qualified to make sure that they match exactly one
// node in the current tree.
//
// Path expressions use a very simple subset of XPath: the path
// consists of a number of segments, separated by '/'; each segment can
// either be a '*', matching any tree node, or a string, optionally
// followed by an index in brackets, matching tree nodes labelled with
// exactly that string. If no index is specified, the expression matches
// all nodes with that label; the index can be a positive number N, which
// matches exactly the Nth node with that label (counting from 1), or the
// special expression 'last()' which matches the last node with the given
// label. All matches are done in fixed positions in the tree, and nothing
// matches more than one path segment.
func (a Augeas) Match(path string) (matches []string, err error) {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	var cMatches **C.char
	cNum := C.aug_match(a.handle, cPath, &cMatches)
	num := int(cNum)

	if num < 0 {
		return nil, a.error()
	}

	q := uintptr(unsafe.Pointer(cMatches))
	for i := 0; i < num; i++ {
		p := (**C.char)(unsafe.Pointer(q))
		matches = append(matches, C.GoString(*p))
		C.free(unsafe.Pointer(*p))
		q += unsafe.Sizeof(q)
	}

	C.free(unsafe.Pointer(cMatches))

	return
}

// Save writes all pending changes to disk.
func (a Augeas) Save() error {
	ret := C.aug_save(a.handle)
	if ret == -1 {
		return a.error()
	}

	return nil
}

// Load loads files into the tree. Which files to load and what lenses
// to use on them is specified under /augeas/load in the tree; each
// entry /augeas/load/NAME specifies a 'transform', by having itself
// exactly one child 'lens' and any number of children labelled 'incl'
// and 'excl'. The value of NAME has no meaning.
//
// The 'lens' grandchild of /augeas/load specifies which lens to use,
// and can either be the fully qualified name of a lens 'Module.lens'
// or '@Module'. The latter form means that the lens from the
// transform marked for autoloading in MODULE should be used.
//
// The 'incl' and 'excl' grandchildren of /augeas/load indicate which
// files to transform. Their value are used as glob patterns. Any file
// that matches at least one 'incl' pattern and no 'excl' pattern is
// transformed. The order of 'incl' and 'excl' entries is irrelevant.
//
// When New is first called, it populates /augeas/load with the
// transforms marked for autoloading in all the modules it finds.
//
// Before loading any files, Load will remove everything underneath
// /augeas/files and /files, regardless of whether any entries have
// been modified or not.
//
// Note that success includes the case where some files could not be
// loaded. Details of such files can be found as '/augeas//error'.
func (a Augeas) Load() error {
	ret := C.aug_load(a.handle)
	if ret == -1 {
		return a.error()
	}

	return nil
}

// Close closes the Augeas instance and frees any storage associated
// with it. After closing, the handle is invalid and can not be
// used for any more operations.
func (a Augeas) Close() {
	C.aug_close(a.handle)
}

// Version returns the Augeas version.
func (a Augeas) Version() string {
	val, _ := a.Get("/augeas/version")
	return val
}
<commit_msg>Use unsafe.Pointer correctly<commit_after>// Package augeas provides Go bindings for Augeas, the configuration
// editing tool.
//
// For more information on Augeas itself, check out http://augeas.net/
package augeas // import "honnef.co/go/augeas"

// #cgo pkg-config: libxml-2.0 augeas
// #include <augeas.h>
// #include <stdlib.h>
import "C"
import (
	"unsafe"
)

type Flag uint

const (
	None Flag = 1 << iota

	// Keep the original file with a .augsave extension
	SaveBackup

	// Save changes into a file with extension .augnew, and do not
	// overwrite the original file. Takes precedence over SaveBackup
	SaveNewFile

	// Typecheck lenses; since it can be very expensive it is not done
	// by default
	TypeCheck

	// Do not use the built-in load path for modules
	NoStdinc

	// Make save a no-op process, just record what would have changed
	SaveNoop

	// Do not load the tree automatically
	NoLoad

	NoModlAutoload

	// Track the span in the input of nodes
	EnableSpan

	// Do not close automatically when encountering error during
	// initialization
	NoErrClose
)

type Augeas struct {
	handle *C.augeas
}

type Span struct {
	Filename   string
	LabelStart uint
	LabelEnd   uint
	ValueStart uint
	ValueEnd   uint
	SpanStart  uint
	SpanEnd    uint
}

func New(root, loadPath string, flags Flag) (Augeas, error) {
	cRoot := C.CString(root)
	defer C.free(unsafe.Pointer(cRoot))
	cLoadPath := C.CString(loadPath)
	defer C.free(unsafe.Pointer(cLoadPath))

	handle := C.aug_init(cRoot, cLoadPath, C.uint(flags))
	if flags&NoErrClose > 0 {
		a := Augeas{handle}
		return a, a.error()
	}
	if handle == nil {
		return Augeas{}, Error{CouldNotInitialize, "Could not initialize Augeas tree", "", ""}
	}

	return Augeas{handle}, nil
}

// DefineVariable defines a variable whose value is the result of
// evaluating the expression. If a variable with the name already
// exists, it will be replaced. Context will not be applied to the
// expression.
//
// If the expression is empty, the variable will be removed.
//
// Path variables can be used in path expressions later on by prefixing
// them with '$'.
//
// Returns the number of nodes if the expression evaluates to a
// nodeset.
func (a Augeas) DefineVariable(name, expression string) (int, error) {
	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))

	var cExpression *C.char
	if expression != "" {
		cExpression = C.CString(expression)
		defer C.free(unsafe.Pointer(cExpression))
	}

	ret := C.aug_defvar(a.handle, cName, cExpression)

	if ret == -1 {
		return 0, a.error()
	}

	return int(ret), nil
}

func (a Augeas) RemoveVariable(name string) error {
	_, err := a.DefineVariable(name, "")
	return err
}

// DefineNode defines a variable whose value is the result of
// evaluating the expression, which must not be empty and evaluate to
// a nodeset. If a variable with the name already exists, it will be
// replaced.
//
// If the expression evaluates to an empty nodeset, a node is created.
//
// Returns the number of nodes in the nodeset and whether a node has
// been created or of it already existed.
func (a Augeas) DefineNode(name, expression, value string) (num int, created bool, err error) {
	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))

	cExpression := C.CString(expression)
	defer C.free(unsafe.Pointer(cExpression))

	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	var cCreated C.int
	cNum := C.aug_defnode(a.handle, cName, cExpression, cValue, &cCreated)
	num = int(cNum)
	created = cCreated == 1
	if cNum == -1 {
		err = a.error()
		return
	}

	return
}

// Get looks up the value associated with a path.
//
// Returns an error if there are no or too many matching nodes, or if
// the path is not a legal path expression.
func (a Augeas) Get(path string) (value string, err error) {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	var cValue *C.char
	ret := C.aug_get(a.handle, cPath, &cValue)

	if ret == 1 {
		return C.GoString(cValue), nil
	} else if ret == 0 {
		return "", Error{NoMatch, "No matching node", "", ""}
	} else if ret < 0 {
		return "", a.error()
	}

	panic("Unexpected return value")
}

// GetAll gets all values associated with a path.
func (a Augeas) GetAll(path string) (values []string, err error) {
	paths, err := a.Match(path)
	if err != nil {
		return
	}

	for _, path := range paths {
		value, err := a.Get(path)
		if err != nil {
			return values, err
		}

		values = append(values, value)
	}

	return
}

// Set the value associated with a path. Intermediate entries are
// created if they don't exist.
func (a Augeas) Set(path, value string) error {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	ret := C.aug_set(a.handle, cPath, cValue)

	if ret == -1 {
		return a.error()
	}

	return nil
}

// SetMultiple sets the value of multiple nodes in one operation. Find
// or create a node matching sub by interpreting sub as a path
// expression relative to each node matching base. sub may be empty,
// in which case all the nodes matching base will be modified.
//
// Returns the number of modified nodes.
func (a Augeas) SetMultiple(base, sub, value string) (int, error) {
	cBase := C.CString(base)
	defer C.free(unsafe.Pointer(cBase))

	var cSub *C.char
	if sub != "" {
		cSub := C.CString(sub)
		defer C.free(unsafe.Pointer(cSub))
	}

	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	ret := C.aug_setm(a.handle, cBase, cSub, cValue)

	if ret == -1 {
		return 0, a.error()
	}

	return int(ret), nil
}

// Span gets the span according to input file of the node associated
// with a path. If the node is associated with a file, the filename,
// label and value start and end positions are set.
func (a Augeas) Span(path string) (Span, error) {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	var cFilename *C.char
	var labelStart, labelEnd, valueStart, valueEnd, spanStart, spanEnd C.uint
	var span Span

	ret := C.aug_span(a.handle, cPath, &cFilename,
		&labelStart, &labelEnd,
		&valueStart, &valueEnd,
		&spanStart, &spanEnd)

	if ret == -1 {
		return span, a.error()
	}

	span.LabelStart = uint(labelStart)
	span.LabelEnd = uint(labelEnd)
	span.ValueStart = uint(valueStart)
	span.ValueEnd = uint(valueEnd)
	span.SpanStart = uint(spanStart)
	span.SpanEnd = uint(spanEnd)
	span.Filename = C.GoString(cFilename)
	C.free(unsafe.Pointer(cFilename))

	return span, nil
}

// Insert creates a new sibling for a path by inserting into the
// tree just before the path if before is true or just after it if
// before is false.
//
// The path must match exactly one existing node in the tree, and the
// label must not contain a '/', '*' or end with a bracketed index
// '[N]'.
func (a Augeas) Insert(path, label string, before bool) error {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	cLabel := C.CString(label)
	defer C.free(unsafe.Pointer(cLabel))

	var cBefore C.int
	if before {
		cBefore = 1
	} else {
		cBefore = 0
	}

	ret := C.aug_insert(a.handle, cPath, cLabel, cBefore)
	if ret == -1 {
		return a.error()
	}

	return nil
}

// Remove removes a path and all its children. Returns the number of
// entries removed. All nodes that match the given path, and their
// descendants, are removed.
func (a Augeas) Remove(path string) (num int) {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	return int(C.aug_rm(a.handle, cPath))
}

// Move moves the node src to dst. src must match exactly one node in
// the tree. dst must either match exactly one node in the tree, or
// may not exist yet. If dst exists already, it and all its
// descendants are deleted. If dst does not exist yet, it and all its
// missing ancestors are created.
//
// Note that the node src always becomes the node dst: when you move /a/b
// to /x, the node /a/b is now called /x, no matter whether /x existed
// initially or not.
func (a Augeas) Move(source, destination string) error {
	cSource := C.CString(source)
	defer C.free(unsafe.Pointer(cSource))

	cDestination := C.CString(destination)
	defer C.free(unsafe.Pointer(cDestination))

	ret := C.aug_mv(a.handle, cSource, cDestination)

	if ret == -1 {
		return a.error()
	}

	return nil
}

// Match returns all paths matching a given path. The returned paths
// are sufficiently qualified to make sure that they match exactly one
// node in the current tree.
//
// Path expressions use a very simple subset of XPath: the path
// consists of a number of segments, separated by '/'; each segment can
// either be a '*', matching any tree node, or a string, optionally
// followed by an index in brackets, matching tree nodes labelled with
// exactly that string. If no index is specified, the expression matches
// all nodes with that label; the index can be a positive number N, which
// matches exactly the Nth node with that label (counting from 1), or the
// special expression 'last()' which matches the last node with the given
// label. All matches are done in fixed positions in the tree, and nothing
// matches more than one path segment.
func (a Augeas) Match(path string) (matches []string, err error) {
	cPath := C.CString(path)
	defer C.free(unsafe.Pointer(cPath))

	var cMatches **C.char
	cNum := C.aug_match(a.handle, cPath, &cMatches)
	num := int(cNum)

	if num < 0 {
		return nil, a.error()
	}

	q := unsafe.Pointer(cMatches)
	for i := 0; i < num; i++ {
		p := (**C.char)(q)
		matches = append(matches, C.GoString(*p))
		C.free(unsafe.Pointer(*p))
		q = unsafe.Pointer(uintptr(q) + unsafe.Sizeof(uintptr(0)))
	}

	C.free(unsafe.Pointer(cMatches))

	return
}

// Save writes all pending changes to disk.
func (a Augeas) Save() error {
	ret := C.aug_save(a.handle)
	if ret == -1 {
		return a.error()
	}

	return nil
}

// Load loads files into the tree. Which files to load and what lenses
// to use on them is specified under /augeas/load in the tree; each
// entry /augeas/load/NAME specifies a 'transform', by having itself
// exactly one child 'lens' and any number of children labelled 'incl'
// and 'excl'. The value of NAME has no meaning.
//
// The 'lens' grandchild of /augeas/load specifies which lens to use,
// and can either be the fully qualified name of a lens 'Module.lens'
// or '@Module'. The latter form means that the lens from the
// transform marked for autoloading in MODULE should be used.
//
// The 'incl' and 'excl' grandchildren of /augeas/load indicate which
// files to transform. Their value are used as glob patterns. Any file
// that matches at least one 'incl' pattern and no 'excl' pattern is
// transformed. The order of 'incl' and 'excl' entries is irrelevant.
//
// When New is first called, it populates /augeas/load with the
// transforms marked for autoloading in all the modules it finds.
//
// Before loading any files, Load will remove everything underneath
// /augeas/files and /files, regardless of whether any entries have
// been modified or not.
//
// Note that success includes the case where some files could not be
// loaded. Details of such files can be found as '/augeas//error'.
func (a Augeas) Load() error {
	ret := C.aug_load(a.handle)
	if ret == -1 {
		return a.error()
	}

	return nil
}

// Close closes the Augeas instance and frees any storage associated
// with it. After closing, the handle is invalid and can not be
// used for any more operations.
func (a Augeas) Close() {
	C.aug_close(a.handle)
}

// Version returns the Augeas version.
func (a Augeas) Version() string {
	val, _ := a.Get("/augeas/version")
	return val
}
<|endoftext|>


<commit_before>package azurerm

import (
	"fmt"
	"github.com/Azure/azure-sdk-for-go/arm/disk"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
	"log"
	"net/http"
	"strings"
)

func resourceArmManagedDisk() *schema.Resource {
	return &schema.Resource{
		Create: resourceArmManagedDiskCreate,
		Read:   resourceArmManagedDiskRead,
		Update: resourceArmManagedDiskCreate,
		Delete: resourceArmManagedDiskDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"location": locationSchema(),

			"resource_group_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"storage_account_type": {
				Type:     schema.TypeString,
				Required: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(disk.PremiumLRS),
					string(disk.StandardLRS),
				}, true),
			},

			"create_option": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(disk.Import),
					string(disk.Empty),
					string(disk.Copy),
				}, true),
			},

			"source_uri": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"source_resource_id": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"os_type": {
				Type:     schema.TypeString,
				Optional: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(disk.Windows),
					string(disk.Linux),
				}, true),
			},

			"disk_size_gb": {
				Type:         schema.TypeInt,
				Required:     true,
				ValidateFunc: validateDiskSizeGB,
			},

			"tags": tagsSchema(),
		},
	}
}

func validateDiskSizeGB(v interface{}, k string) (ws []string, errors []error) {
	value := v.(int)
	if value < 1 || value > 1023 {
		errors = append(errors, fmt.Errorf(
			"The `disk_size_gb` can only be between 1 and 1023"))
	}
	return
}

func resourceArmManagedDiskCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient)
	diskClient := client.diskClient

	log.Printf("[INFO] preparing arguments for Azure ARM Managed Disk creation.")

	name := d.Get("name").(string)
	location := d.Get("location").(string)
	resGroup := d.Get("resource_group_name").(string)
	tags := d.Get("tags").(map[string]interface{})
	expandedTags := expandTags(tags)

	createDisk := disk.Model{
		Name:     &name,
		Location: &location,
		Tags:     expandedTags,
	}

	storageAccountType := d.Get("storage_account_type").(string)
	osType := d.Get("os_type").(string)

	createDisk.Properties = &disk.Properties{
		AccountType: disk.StorageAccountTypes(storageAccountType),
		OsType:      disk.OperatingSystemTypes(osType),
	}

	if v := d.Get("disk_size_gb"); v != 0 {
		diskSize := int32(v.(int))
		createDisk.Properties.DiskSizeGB = &diskSize
	}
	createOption := d.Get("create_option").(string)

	creationData := &disk.CreationData{
		CreateOption: disk.CreateOption(createOption),
	}

	if strings.EqualFold(createOption, string(disk.Import)) {
		if sourceUri := d.Get("source_uri").(string); sourceUri != "" {
			creationData.SourceURI = &sourceUri
		} else {
			return fmt.Errorf("[ERROR] source_uri must be specified when create_option is `%s`", disk.Import)
		}
	} else if strings.EqualFold(createOption, string(disk.Copy)) {
		if sourceResourceId := d.Get("source_resource_id").(string); sourceResourceId != "" {
			creationData.SourceResourceID = &sourceResourceId
		} else {
			return fmt.Errorf("[ERROR] source_resource_id must be specified when create_option is `%s`", disk.Copy)
		}
	}

	createDisk.CreationData = creationData

	_, diskErr := diskClient.CreateOrUpdate(resGroup, name, createDisk, make(chan struct{}))
	if diskErr != nil {
		return diskErr
	}

	read, err := diskClient.Get(resGroup, name)
	if err != nil {
		return err
	}
	if read.ID == nil {
		return fmt.Errorf("[ERROR] Cannot read Managed Disk %s (resource group %s) ID", name, resGroup)
	}

	d.SetId(*read.ID)

	return resourceArmManagedDiskRead(d, meta)
}

func resourceArmManagedDiskRead(d *schema.ResourceData, meta interface{}) error {
	diskClient := meta.(*ArmClient).diskClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["disks"]

	resp, err := diskClient.Get(resGroup, name)
	if err != nil {
		if resp.StatusCode == http.StatusNotFound {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("[ERROR] Error making Read request on Azure Managed Disk %s (resource group %s): %s", name, resGroup, err)
	}

	d.Set("name", resp.Name)
	d.Set("resource_group_name", resGroup)
	d.Set("location", resp.Location)

	if resp.Properties != nil {
		flattenAzureRmManagedDiskProperties(d, resp.Properties)
	}

	if resp.CreationData != nil {
		flattenAzureRmManagedDiskCreationData(d, resp.CreationData)
	}

	flattenAndSetTags(d, resp.Tags)

	return nil
}

func resourceArmManagedDiskDelete(d *schema.ResourceData, meta interface{}) error {
	diskClient := meta.(*ArmClient).diskClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["disks"]

	if _, err = diskClient.Delete(resGroup, name, make(chan struct{})); err != nil {
		return err
	}

	return nil
}

func flattenAzureRmManagedDiskProperties(d *schema.ResourceData, properties *disk.Properties) {
	d.Set("storage_account_type", string(properties.AccountType))
	if properties.DiskSizeGB != nil {
		d.Set("disk_size_gb", *properties.DiskSizeGB)
	}
	if properties.OsType != "" {
		d.Set("os_type", string(properties.OsType))
	}
}

func flattenAzureRmManagedDiskCreationData(d *schema.ResourceData, creationData *disk.CreationData) {
	d.Set("create_option", string(creationData.CreateOption))
	if creationData.SourceURI != nil {
		d.Set("source_uri", *creationData.SourceURI)
	}
}
<commit_msg>Adding support for 4TB disks<commit_after>package azurerm

import (
	"fmt"
	"log"
	"net/http"
	"strings"

	"github.com/Azure/azure-sdk-for-go/arm/disk"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceArmManagedDisk() *schema.Resource {
	return &schema.Resource{
		Create: resourceArmManagedDiskCreate,
		Read:   resourceArmManagedDiskRead,
		Update: resourceArmManagedDiskCreate,
		Delete: resourceArmManagedDiskDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"location": locationSchema(),

			"resource_group_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"storage_account_type": {
				Type:     schema.TypeString,
				Required: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(disk.PremiumLRS),
					string(disk.StandardLRS),
				}, true),
			},

			"create_option": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(disk.Import),
					string(disk.Empty),
					string(disk.Copy),
				}, true),
			},

			"source_uri": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"source_resource_id": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"os_type": {
				Type:     schema.TypeString,
				Optional: true,
				ValidateFunc: validation.StringInSlice([]string{
					string(disk.Windows),
					string(disk.Linux),
				}, true),
			},

			"disk_size_gb": {
				Type:         schema.TypeInt,
				Required:     true,
				ValidateFunc: validateDiskSizeGB,
			},

			"tags": tagsSchema(),
		},
	}
}

func validateDiskSizeGB(v interface{}, k string) (ws []string, errors []error) {
	value := v.(int)
	if value < 1 || value > 4095 {
		errors = append(errors, fmt.Errorf(
			"The `disk_size_gb` can only be between 1 and 4095"))
	}
	return
}

func resourceArmManagedDiskCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*ArmClient)
	diskClient := client.diskClient

	log.Printf("[INFO] preparing arguments for Azure ARM Managed Disk creation.")

	name := d.Get("name").(string)
	location := d.Get("location").(string)
	resGroup := d.Get("resource_group_name").(string)
	tags := d.Get("tags").(map[string]interface{})
	expandedTags := expandTags(tags)

	createDisk := disk.Model{
		Name:     &name,
		Location: &location,
		Tags:     expandedTags,
	}

	storageAccountType := d.Get("storage_account_type").(string)
	osType := d.Get("os_type").(string)

	createDisk.Properties = &disk.Properties{
		AccountType: disk.StorageAccountTypes(storageAccountType),
		OsType:      disk.OperatingSystemTypes(osType),
	}

	if v := d.Get("disk_size_gb"); v != 0 {
		diskSize := int32(v.(int))
		createDisk.Properties.DiskSizeGB = &diskSize
	}
	createOption := d.Get("create_option").(string)

	creationData := &disk.CreationData{
		CreateOption: disk.CreateOption(createOption),
	}

	if strings.EqualFold(createOption, string(disk.Import)) {
		if sourceUri := d.Get("source_uri").(string); sourceUri != "" {
			creationData.SourceURI = &sourceUri
		} else {
			return fmt.Errorf("[ERROR] source_uri must be specified when create_option is `%s`", disk.Import)
		}
	} else if strings.EqualFold(createOption, string(disk.Copy)) {
		if sourceResourceId := d.Get("source_resource_id").(string); sourceResourceId != "" {
			creationData.SourceResourceID = &sourceResourceId
		} else {
			return fmt.Errorf("[ERROR] source_resource_id must be specified when create_option is `%s`", disk.Copy)
		}
	}

	createDisk.CreationData = creationData

	_, diskErr := diskClient.CreateOrUpdate(resGroup, name, createDisk, make(chan struct{}))
	if diskErr != nil {
		return diskErr
	}

	read, err := diskClient.Get(resGroup, name)
	if err != nil {
		return err
	}
	if read.ID == nil {
		return fmt.Errorf("[ERROR] Cannot read Managed Disk %s (resource group %s) ID", name, resGroup)
	}

	d.SetId(*read.ID)

	return resourceArmManagedDiskRead(d, meta)
}

func resourceArmManagedDiskRead(d *schema.ResourceData, meta interface{}) error {
	diskClient := meta.(*ArmClient).diskClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["disks"]

	resp, err := diskClient.Get(resGroup, name)
	if err != nil {
		if resp.StatusCode == http.StatusNotFound {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("[ERROR] Error making Read request on Azure Managed Disk %s (resource group %s): %s", name, resGroup, err)
	}

	d.Set("name", resp.Name)
	d.Set("resource_group_name", resGroup)
	d.Set("location", resp.Location)

	if resp.Properties != nil {
		flattenAzureRmManagedDiskProperties(d, resp.Properties)
	}

	if resp.CreationData != nil {
		flattenAzureRmManagedDiskCreationData(d, resp.CreationData)
	}

	flattenAndSetTags(d, resp.Tags)

	return nil
}

func resourceArmManagedDiskDelete(d *schema.ResourceData, meta interface{}) error {
	diskClient := meta.(*ArmClient).diskClient

	id, err := parseAzureResourceID(d.Id())
	if err != nil {
		return err
	}
	resGroup := id.ResourceGroup
	name := id.Path["disks"]

	if _, err = diskClient.Delete(resGroup, name, make(chan struct{})); err != nil {
		return err
	}

	return nil
}

func flattenAzureRmManagedDiskProperties(d *schema.ResourceData, properties *disk.Properties) {
	d.Set("storage_account_type", string(properties.AccountType))
	if properties.DiskSizeGB != nil {
		d.Set("disk_size_gb", *properties.DiskSizeGB)
	}
	if properties.OsType != "" {
		d.Set("os_type", string(properties.OsType))
	}
}

func flattenAzureRmManagedDiskCreationData(d *schema.ResourceData, creationData *disk.CreationData) {
	d.Set("create_option", string(creationData.CreateOption))
	if creationData.SourceURI != nil {
		d.Set("source_uri", *creationData.SourceURI)
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"sort"
	"strings"
	"time"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/route53"
)

func resourceAwsRoute53Zone() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsRoute53ZoneCreate,
		Read:   resourceAwsRoute53ZoneRead,
		Update: resourceAwsRoute53ZoneUpdate,
		Delete: resourceAwsRoute53ZoneDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"comment": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "Managed by Terraform",
			},

			"vpc_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"vpc_region": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},

			"zone_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"delegation_set_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"name_servers": &schema.Schema{
				Type:     schema.TypeList,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Computed: true,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceAwsRoute53ZoneCreate(d *schema.ResourceData, meta interface{}) error {
	r53 := meta.(*AWSClient).r53conn

	req := &route53.CreateHostedZoneInput{
		Name:             aws.String(d.Get("name").(string)),
		HostedZoneConfig: &route53.HostedZoneConfig{Comment: aws.String(d.Get("comment").(string))},
		CallerReference:  aws.String(time.Now().Format(time.RFC3339Nano)),
	}
	if v := d.Get("vpc_id"); v != "" {
		req.VPC = &route53.VPC{
			VPCId:     aws.String(v.(string)),
			VPCRegion: aws.String(meta.(*AWSClient).region),
		}
		if w := d.Get("vpc_region"); w != "" {
			req.VPC.VPCRegion = aws.String(w.(string))
		}
		d.Set("vpc_region", req.VPC.VPCRegion)
	}

	if v, ok := d.GetOk("delegation_set_id"); ok {
		req.DelegationSetId = aws.String(v.(string))
	}

	log.Printf("[DEBUG] Creating Route53 hosted zone: %s", *req.Name)
	var err error
	resp, err := r53.CreateHostedZone(req)
	if err != nil {
		return err
	}

	// Store the zone_id
	zone := cleanZoneID(*resp.HostedZone.Id)
	d.Set("zone_id", zone)
	d.SetId(zone)

	// Wait until we are done initializing
	wait := resource.StateChangeConf{
		Delay:      30 * time.Second,
		Pending:    []string{"PENDING"},
		Target:     []string{"INSYNC"},
		Timeout:    10 * time.Minute,
		MinTimeout: 2 * time.Second,
		Refresh: func() (result interface{}, state string, err error) {
			changeRequest := &route53.GetChangeInput{
				Id: aws.String(cleanChangeID(*resp.ChangeInfo.Id)),
			}
			return resourceAwsGoRoute53Wait(r53, changeRequest)
		},
	}
	_, err = wait.WaitForState()
	if err != nil {
		return err
	}
	return resourceAwsRoute53ZoneUpdate(d, meta)
}

func resourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error {
	r53 := meta.(*AWSClient).r53conn
	zone, err := r53.GetHostedZone(&route53.GetHostedZoneInput{Id: aws.String(d.Id())})
	if err != nil {
		// Handle a deleted zone
		if r53err, ok := err.(awserr.Error); ok && r53err.Code() == "NoSuchHostedZone" {
			d.SetId("")
			return nil
		}
		return err
	}

	// In the import case this will be empty
	if _, ok := d.GetOk("zone_id"); !ok {
		d.Set("zone_id", d.Id())
	}
	if _, ok := d.GetOk("name"); !ok {
		d.Set("name", zone.HostedZone.Name)
	}

	if !*zone.HostedZone.Config.PrivateZone {
		ns := make([]string, len(zone.DelegationSet.NameServers))
		for i := range zone.DelegationSet.NameServers {
			ns[i] = *zone.DelegationSet.NameServers[i]
		}
		sort.Strings(ns)
		if err := d.Set("name_servers", ns); err != nil {
			return fmt.Errorf("[DEBUG] Error setting name servers for: %s, error: %#v", d.Id(), err)
		}
	} else {
		ns, err := getNameServers(d.Id(), d.Get("name").(string), r53)
		if err != nil {
			return err
		}
		if err := d.Set("name_servers", ns); err != nil {
			return fmt.Errorf("[DEBUG] Error setting name servers for: %s, error: %#v", d.Id(), err)
		}

		// In the import case we just associate it with the first VPC
		if _, ok := d.GetOk("vpc_id"); !ok {
			if len(zone.VPCs) > 1 {
				return fmt.Errorf(
					"Can't import a route53_zone with more than one VPC attachment")
			}

			if len(zone.VPCs) > 0 {
				d.Set("vpc_id", zone.VPCs[0].VPCId)
				d.Set("vpc_region", zone.VPCs[0].VPCRegion)
			}
		}

		var associatedVPC *route53.VPC
		for _, vpc := range zone.VPCs {
			if *vpc.VPCId == d.Get("vpc_id") {
				associatedVPC = vpc
				break
			}
		}
		if associatedVPC == nil {
			return fmt.Errorf("[DEBUG] VPC: %v is not associated with Zone: %v", d.Get("vpc_id"), d.Id())
		}
	}

	if zone.DelegationSet != nil && zone.DelegationSet.Id != nil {
		d.Set("delegation_set_id", cleanDelegationSetId(*zone.DelegationSet.Id))
	}

	if zone.HostedZone != nil && zone.HostedZone.Config != nil && zone.HostedZone.Config.Comment != nil {
		d.Set("comment", zone.HostedZone.Config.Comment)
	}

	// get tags
	req := &route53.ListTagsForResourceInput{
		ResourceId:   aws.String(d.Id()),
		ResourceType: aws.String("hostedzone"),
	}

	resp, err := r53.ListTagsForResource(req)
	if err != nil {
		return err
	}

	var tags []*route53.Tag
	if resp.ResourceTagSet != nil {
		tags = resp.ResourceTagSet.Tags
	}

	if err := d.Set("tags", tagsToMapR53(tags)); err != nil {
		return err
	}

	return nil
}

func resourceAwsRoute53ZoneUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).r53conn

	d.Partial(true)

	if d.HasChange("comment") {
		zoneInput := route53.UpdateHostedZoneCommentInput{
			Id:      aws.String(d.Id()),
			Comment: aws.String(d.Get("comment").(string)),
		}

		_, err := conn.UpdateHostedZoneComment(&zoneInput)
		if err != nil {
			return err
		} else {
			d.SetPartial("comment")
		}
	}

	if err := setTagsR53(conn, d, "hostedzone"); err != nil {
		return err
	} else {
		d.SetPartial("tags")
	}

	d.Partial(false)

	return resourceAwsRoute53ZoneRead(d, meta)
}

func resourceAwsRoute53ZoneDelete(d *schema.ResourceData, meta interface{}) error {
	r53 := meta.(*AWSClient).r53conn

	log.Printf("[DEBUG] Deleting Route53 hosted zone: %s (ID: %s)",
		d.Get("name").(string), d.Id())
	_, err := r53.DeleteHostedZone(&route53.DeleteHostedZoneInput{Id: aws.String(d.Id())})
	if err != nil {
		if r53err, ok := err.(awserr.Error); ok && r53err.Code() == "NoSuchHostedZone" {
			log.Printf("[DEBUG] No matching Route 53 Zone found for: %s, removing from state file", d.Id())
			d.SetId("")
			return nil
		}
		return err
	}

	return nil
}

func resourceAwsGoRoute53Wait(r53 *route53.Route53, ref *route53.GetChangeInput) (result interface{}, state string, err error) {

	status, err := r53.GetChange(ref)
	if err != nil {
		return nil, "UNKNOWN", err
	}
	return true, *status.ChangeInfo.Status, nil
}

// cleanChangeID is used to remove the leading /change/
func cleanChangeID(ID string) string {
	return cleanPrefix(ID, "/change/")
}

// cleanZoneID is used to remove the leading /hostedzone/
func cleanZoneID(ID string) string {
	return cleanPrefix(ID, "/hostedzone/")
}

// cleanPrefix removes a string prefix from an ID
func cleanPrefix(ID, prefix string) string {
	if strings.HasPrefix(ID, prefix) {
		ID = strings.TrimPrefix(ID, prefix)
	}
	return ID
}

func getNameServers(zoneId string, zoneName string, r53 *route53.Route53) ([]string, error) {
	resp, err := r53.ListResourceRecordSets(&route53.ListResourceRecordSetsInput{
		HostedZoneId:    aws.String(zoneId),
		StartRecordName: aws.String(zoneName),
		StartRecordType: aws.String("NS"),
	})
	if err != nil {
		return nil, err
	}
	if len(resp.ResourceRecordSets) == 0 {
		return nil, nil
	}
	ns := make([]string, len(resp.ResourceRecordSets[0].ResourceRecords))
	for i := range resp.ResourceRecordSets[0].ResourceRecords {
		ns[i] = *resp.ResourceRecordSets[0].ResourceRecords[i].Value
	}
	sort.Strings(ns)
	return ns, nil
}
<commit_msg>aws/route53_zone: Make delegation_set_id conflict w/ vpc_id (#7213)<commit_after>package aws

import (
	"fmt"
	"log"
	"sort"
	"strings"
	"time"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/route53"
)

func resourceAwsRoute53Zone() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsRoute53ZoneCreate,
		Read:   resourceAwsRoute53ZoneRead,
		Update: resourceAwsRoute53ZoneUpdate,
		Delete: resourceAwsRoute53ZoneDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"comment": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "Managed by Terraform",
			},

			"vpc_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"delegation_set_id"},
			},

			"vpc_region": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},

			"zone_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"delegation_set_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"vpc_id"},
			},

			"name_servers": &schema.Schema{
				Type:     schema.TypeList,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Computed: true,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceAwsRoute53ZoneCreate(d *schema.ResourceData, meta interface{}) error {
	r53 := meta.(*AWSClient).r53conn

	req := &route53.CreateHostedZoneInput{
		Name:             aws.String(d.Get("name").(string)),
		HostedZoneConfig: &route53.HostedZoneConfig{Comment: aws.String(d.Get("comment").(string))},
		CallerReference:  aws.String(time.Now().Format(time.RFC3339Nano)),
	}
	if v := d.Get("vpc_id"); v != "" {
		req.VPC = &route53.VPC{
			VPCId:     aws.String(v.(string)),
			VPCRegion: aws.String(meta.(*AWSClient).region),
		}
		if w := d.Get("vpc_region"); w != "" {
			req.VPC.VPCRegion = aws.String(w.(string))
		}
		d.Set("vpc_region", req.VPC.VPCRegion)
	}

	if v, ok := d.GetOk("delegation_set_id"); ok {
		req.DelegationSetId = aws.String(v.(string))
	}

	log.Printf("[DEBUG] Creating Route53 hosted zone: %s", *req.Name)
	var err error
	resp, err := r53.CreateHostedZone(req)
	if err != nil {
		return err
	}

	// Store the zone_id
	zone := cleanZoneID(*resp.HostedZone.Id)
	d.Set("zone_id", zone)
	d.SetId(zone)

	// Wait until we are done initializing
	wait := resource.StateChangeConf{
		Delay:      30 * time.Second,
		Pending:    []string{"PENDING"},
		Target:     []string{"INSYNC"},
		Timeout:    10 * time.Minute,
		MinTimeout: 2 * time.Second,
		Refresh: func() (result interface{}, state string, err error) {
			changeRequest := &route53.GetChangeInput{
				Id: aws.String(cleanChangeID(*resp.ChangeInfo.Id)),
			}
			return resourceAwsGoRoute53Wait(r53, changeRequest)
		},
	}
	_, err = wait.WaitForState()
	if err != nil {
		return err
	}
	return resourceAwsRoute53ZoneUpdate(d, meta)
}

func resourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error {
	r53 := meta.(*AWSClient).r53conn
	zone, err := r53.GetHostedZone(&route53.GetHostedZoneInput{Id: aws.String(d.Id())})
	if err != nil {
		// Handle a deleted zone
		if r53err, ok := err.(awserr.Error); ok && r53err.Code() == "NoSuchHostedZone" {
			d.SetId("")
			return nil
		}
		return err
	}

	// In the import case this will be empty
	if _, ok := d.GetOk("zone_id"); !ok {
		d.Set("zone_id", d.Id())
	}
	if _, ok := d.GetOk("name"); !ok {
		d.Set("name", zone.HostedZone.Name)
	}

	if !*zone.HostedZone.Config.PrivateZone {
		ns := make([]string, len(zone.DelegationSet.NameServers))
		for i := range zone.DelegationSet.NameServers {
			ns[i] = *zone.DelegationSet.NameServers[i]
		}
		sort.Strings(ns)
		if err := d.Set("name_servers", ns); err != nil {
			return fmt.Errorf("[DEBUG] Error setting name servers for: %s, error: %#v", d.Id(), err)
		}
	} else {
		ns, err := getNameServers(d.Id(), d.Get("name").(string), r53)
		if err != nil {
			return err
		}
		if err := d.Set("name_servers", ns); err != nil {
			return fmt.Errorf("[DEBUG] Error setting name servers for: %s, error: %#v", d.Id(), err)
		}

		// In the import case we just associate it with the first VPC
		if _, ok := d.GetOk("vpc_id"); !ok {
			if len(zone.VPCs) > 1 {
				return fmt.Errorf(
					"Can't import a route53_zone with more than one VPC attachment")
			}

			if len(zone.VPCs) > 0 {
				d.Set("vpc_id", zone.VPCs[0].VPCId)
				d.Set("vpc_region", zone.VPCs[0].VPCRegion)
			}
		}

		var associatedVPC *route53.VPC
		for _, vpc := range zone.VPCs {
			if *vpc.VPCId == d.Get("vpc_id") {
				associatedVPC = vpc
				break
			}
		}
		if associatedVPC == nil {
			return fmt.Errorf("[DEBUG] VPC: %v is not associated with Zone: %v", d.Get("vpc_id"), d.Id())
		}
	}

	if zone.DelegationSet != nil && zone.DelegationSet.Id != nil {
		d.Set("delegation_set_id", cleanDelegationSetId(*zone.DelegationSet.Id))
	}

	if zone.HostedZone != nil && zone.HostedZone.Config != nil && zone.HostedZone.Config.Comment != nil {
		d.Set("comment", zone.HostedZone.Config.Comment)
	}

	// get tags
	req := &route53.ListTagsForResourceInput{
		ResourceId:   aws.String(d.Id()),
		ResourceType: aws.String("hostedzone"),
	}

	resp, err := r53.ListTagsForResource(req)
	if err != nil {
		return err
	}

	var tags []*route53.Tag
	if resp.ResourceTagSet != nil {
		tags = resp.ResourceTagSet.Tags
	}

	if err := d.Set("tags", tagsToMapR53(tags)); err != nil {
		return err
	}

	return nil
}

func resourceAwsRoute53ZoneUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).r53conn

	d.Partial(true)

	if d.HasChange("comment") {
		zoneInput := route53.UpdateHostedZoneCommentInput{
			Id:      aws.String(d.Id()),
			Comment: aws.String(d.Get("comment").(string)),
		}

		_, err := conn.UpdateHostedZoneComment(&zoneInput)
		if err != nil {
			return err
		} else {
			d.SetPartial("comment")
		}
	}

	if err := setTagsR53(conn, d, "hostedzone"); err != nil {
		return err
	} else {
		d.SetPartial("tags")
	}

	d.Partial(false)

	return resourceAwsRoute53ZoneRead(d, meta)
}

func resourceAwsRoute53ZoneDelete(d *schema.ResourceData, meta interface{}) error {
	r53 := meta.(*AWSClient).r53conn

	log.Printf("[DEBUG] Deleting Route53 hosted zone: %s (ID: %s)",
		d.Get("name").(string), d.Id())
	_, err := r53.DeleteHostedZone(&route53.DeleteHostedZoneInput{Id: aws.String(d.Id())})
	if err != nil {
		if r53err, ok := err.(awserr.Error); ok && r53err.Code() == "NoSuchHostedZone" {
			log.Printf("[DEBUG] No matching Route 53 Zone found for: %s, removing from state file", d.Id())
			d.SetId("")
			return nil
		}
		return err
	}

	return nil
}

func resourceAwsGoRoute53Wait(r53 *route53.Route53, ref *route53.GetChangeInput) (result interface{}, state string, err error) {

	status, err := r53.GetChange(ref)
	if err != nil {
		return nil, "UNKNOWN", err
	}
	return true, *status.ChangeInfo.Status, nil
}

// cleanChangeID is used to remove the leading /change/
func cleanChangeID(ID string) string {
	return cleanPrefix(ID, "/change/")
}

// cleanZoneID is used to remove the leading /hostedzone/
func cleanZoneID(ID string) string {
	return cleanPrefix(ID, "/hostedzone/")
}

// cleanPrefix removes a string prefix from an ID
func cleanPrefix(ID, prefix string) string {
	if strings.HasPrefix(ID, prefix) {
		ID = strings.TrimPrefix(ID, prefix)
	}
	return ID
}

func getNameServers(zoneId string, zoneName string, r53 *route53.Route53) ([]string, error) {
	resp, err := r53.ListResourceRecordSets(&route53.ListResourceRecordSetsInput{
		HostedZoneId:    aws.String(zoneId),
		StartRecordName: aws.String(zoneName),
		StartRecordType: aws.String("NS"),
	})
	if err != nil {
		return nil, err
	}
	if len(resp.ResourceRecordSets) == 0 {
		return nil, nil
	}
	ns := make([]string, len(resp.ResourceRecordSets[0].ResourceRecords))
	for i := range resp.ResourceRecordSets[0].ResourceRecords {
		ns[i] = *resp.ResourceRecordSets[0].ResourceRecords[i].Value
	}
	sort.Strings(ns)
	return ns, nil
}
<|endoftext|>


<commit_before>package data

import (
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/url"

	"github.com/cozy/cozy-stack/pkg/accounts"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	perms "github.com/cozy/cozy-stack/pkg/permissions"
	"github.com/cozy/cozy-stack/web/jsonapi"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/cozy/cozy-stack/web/permissions"
	"github.com/labstack/echo"
)

// TODO: make specific routes for managing accounts. The overriding of the
// /data/io.cozy.accounts/* routes is here mainly for retro-compatible reasons,
// but specific routes would improve the API.

func accountDoctype(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		doctype := consts.Accounts
		c.Set("doctype", doctype)
		docidraw := c.Param("docid")
		docid, err := url.QueryUnescape(docidraw)
		if err != nil {
			return jsonapi.NewError(http.StatusBadRequest, "Invalid docid '%s'", docid)
		}
		c.Set("docid", docid)
		return next(c)
	}
}

func getAccount(c echo.Context) error {
	instance := middlewares.GetInstance(c)
	doctype := consts.Accounts
	docid := c.Get("docid").(string)
	if docid == "" {
		return dbStatus(c)
	}

	var out couchdb.JSONDoc
	var err error
	rev := c.QueryParam("rev")
	if rev != "" {
		err = couchdb.GetDocRev(instance, doctype, docid, rev, &out)
	} else {
		err = couchdb.GetDoc(instance, doctype, docid, &out)
	}
	if err != nil {
		return fixErrorNoDatabaseIsWrongDoctype(err)
	}
	out.Type = doctype

	if err = permissions.Allow(c, permissions.GET, &out); err != nil {
		return err
	}

	if encryptAccount(out) {
		if err = couchdb.UpdateDoc(instance, out); err != nil {
			return err
		}
	}

	perm, err := permissions.GetPermission(c)
	if err != nil {
		return err
	}
	if perm.Type == perms.TypeKonnector {
		decryptAccount(out)
	}

	return c.JSON(http.StatusOK, out.ToMapWithType())
}

func updateAccount(c echo.Context) error {
	instance := middlewares.GetInstance(c)

	var doc couchdb.JSONDoc
	if err := json.NewDecoder(c.Request().Body).Decode(&doc); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	doc.Type = consts.Accounts

	if (doc.ID() == "") != (doc.Rev() == "") {
		return jsonapi.NewError(http.StatusBadRequest,
			"You must either provide an _id and _rev in document (update) or neither (create with fixed id).")
	}

	if doc.ID() != "" && doc.ID() != c.Get("docid").(string) {
		return jsonapi.NewError(http.StatusBadRequest, "document _id doesnt match url")
	}

	if doc.ID() == "" {
		doc.SetID(c.Get("docid").(string))
		return createNamedDoc(c, doc)
	}

	errWhole := permissions.AllowWholeType(c, permissions.PUT, doc.DocType())
	if errWhole != nil {
		// we cant apply to whole type, let's fetch old doc and see if it applies there
		var old couchdb.JSONDoc
		errFetch := couchdb.GetDoc(instance, doc.DocType(), doc.ID(), &old)
		if errFetch != nil {
			return errFetch
		}
		old.Type = doc.DocType()
		// check if permissions set allows manipulating old doc
		errOld := permissions.Allow(c, permissions.PUT, &old)
		if errOld != nil {
			return errOld
		}

		// also check if permissions set allows manipulating new doc
		errNew := permissions.Allow(c, permissions.PUT, &doc)
		if errNew != nil {
			return errNew
		}
	}

	encryptAccount(doc)

	errUpdate := couchdb.UpdateDoc(instance, doc)
	if errUpdate != nil {
		return fixErrorNoDatabaseIsWrongDoctype(errUpdate)
	}

	return c.JSON(http.StatusOK, echo.Map{
		"ok":   true,
		"id":   doc.ID(),
		"rev":  doc.Rev(),
		"type": doc.DocType(),
		"data": doc.ToMapWithType(),
	})
}

func encryptAccount(doc couchdb.JSONDoc) bool {
	return encryptMap(doc.M)
}

func decryptAccount(doc couchdb.JSONDoc) bool {
	return decryptMap(doc.M)
}

func encryptMap(m map[string]interface{}) bool {
	encrypted := false
	var passwordEncrypted []byte
	var err error
	for k, v := range m {
		var ok bool
		var password string
		if k == "password" {
			if password, ok = v.(string); ok && len(password) > 0 {
				login, _ := m["login"].(string)
				passwordEncrypted, err = accounts.EncryptCredentials(login, password)
				encrypted = err == nil
			}
		} else if mm, ok := v.(map[string]interface{}); ok && encryptMap(mm) {
			encrypted = true
		}
	}
	if len(passwordEncrypted) > 0 {
		delete(m, "password")
		m["credentials_encrypted"] = base64.StdEncoding.EncodeToString(passwordEncrypted)
	}
	return encrypted
}

func decryptMap(m map[string]interface{}) bool {
	decrypted := false
	var login, password string
	for k, v := range m {
		if k == "credentials_encrypted" {
			encodedEncryptedCreds, ok := v.(string)
			if ok {
				encryptedCreds, err := base64.StdEncoding.DecodeString(encodedEncryptedCreds)
				if err == nil {
					login, password, err = accounts.DecryptCredentials(encryptedCreds)
					decrypted = err == nil
				}
			}
		} else if mm, ok := v.(map[string]interface{}); ok && decryptMap(mm) {
			decrypted = true
		}
	}
	if len(login) > 0 && len(password) > 0 {
		delete(m, "credentials_encrypted")
		m["login"] = login
		m["password"] = password
	}
	return decrypted
}

func createAccount(c echo.Context) error {
	doctype := consts.Accounts
	instance := middlewares.GetInstance(c)

	doc := couchdb.JSONDoc{Type: doctype}
	if err := json.NewDecoder(c.Request().Body).Decode(&doc.M); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	if err := permissions.Allow(c, permissions.POST, &doc); err != nil {
		return err
	}

	encryptAccount(doc)

	if err := couchdb.CreateDoc(instance, doc); err != nil {
		return err
	}

	return c.JSON(http.StatusCreated, echo.Map{
		"ok":   true,
		"id":   doc.ID(),
		"rev":  doc.Rev(),
		"type": doc.DocType(),
		"data": doc.ToMapWithType(),
	})
}
<commit_msg>Decrypto accounts on PUT request<commit_after>package data

import (
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/url"

	"github.com/cozy/cozy-stack/pkg/accounts"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	perms "github.com/cozy/cozy-stack/pkg/permissions"
	"github.com/cozy/cozy-stack/web/jsonapi"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/cozy/cozy-stack/web/permissions"
	"github.com/labstack/echo"
)

// TODO: make specific routes for managing accounts. The overriding of the
// /data/io.cozy.accounts/* routes is here mainly for retro-compatible reasons,
// but specific routes would improve the API.

func accountDoctype(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		doctype := consts.Accounts
		c.Set("doctype", doctype)
		docidraw := c.Param("docid")
		docid, err := url.QueryUnescape(docidraw)
		if err != nil {
			return jsonapi.NewError(http.StatusBadRequest, "Invalid docid '%s'", docid)
		}
		c.Set("docid", docid)
		return next(c)
	}
}

func getAccount(c echo.Context) error {
	instance := middlewares.GetInstance(c)
	doctype := consts.Accounts
	docid := c.Get("docid").(string)
	if docid == "" {
		return dbStatus(c)
	}

	var out couchdb.JSONDoc
	var err error
	rev := c.QueryParam("rev")
	if rev != "" {
		err = couchdb.GetDocRev(instance, doctype, docid, rev, &out)
	} else {
		err = couchdb.GetDoc(instance, doctype, docid, &out)
	}
	if err != nil {
		return fixErrorNoDatabaseIsWrongDoctype(err)
	}
	out.Type = doctype

	if err = permissions.Allow(c, permissions.GET, &out); err != nil {
		return err
	}

	if encryptAccount(out) {
		if err = couchdb.UpdateDoc(instance, out); err != nil {
			return err
		}
	}

	perm, err := permissions.GetPermission(c)
	if err != nil {
		return err
	}
	if perm.Type == perms.TypeKonnector {
		decryptAccount(out)
	}

	return c.JSON(http.StatusOK, out.ToMapWithType())
}

func updateAccount(c echo.Context) error {
	instance := middlewares.GetInstance(c)

	var doc couchdb.JSONDoc
	if err := json.NewDecoder(c.Request().Body).Decode(&doc); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	doc.Type = consts.Accounts

	if (doc.ID() == "") != (doc.Rev() == "") {
		return jsonapi.NewError(http.StatusBadRequest,
			"You must either provide an _id and _rev in document (update) or neither (create with fixed id).")
	}

	if doc.ID() != "" && doc.ID() != c.Get("docid").(string) {
		return jsonapi.NewError(http.StatusBadRequest, "document _id doesnt match url")
	}

	if doc.ID() == "" {
		doc.SetID(c.Get("docid").(string))
		return createNamedDoc(c, doc)
	}

	errWhole := permissions.AllowWholeType(c, permissions.PUT, doc.DocType())
	if errWhole != nil {
		// we cant apply to whole type, let's fetch old doc and see if it applies there
		var old couchdb.JSONDoc
		errFetch := couchdb.GetDoc(instance, doc.DocType(), doc.ID(), &old)
		if errFetch != nil {
			return errFetch
		}
		old.Type = doc.DocType()
		// check if permissions set allows manipulating old doc
		errOld := permissions.Allow(c, permissions.PUT, &old)
		if errOld != nil {
			return errOld
		}

		// also check if permissions set allows manipulating new doc
		errNew := permissions.Allow(c, permissions.PUT, &doc)
		if errNew != nil {
			return errNew
		}
	}

	encryptAccount(doc)

	errUpdate := couchdb.UpdateDoc(instance, doc)
	if errUpdate != nil {
		return fixErrorNoDatabaseIsWrongDoctype(errUpdate)
	}

	perm, err := permissions.GetPermission(c)
	if err != nil {
		return err
	}
	if perm.Type == perms.TypeKonnector {
		decryptAccount(doc)
	}

	return c.JSON(http.StatusOK, echo.Map{
		"ok":   true,
		"id":   doc.ID(),
		"rev":  doc.Rev(),
		"type": doc.DocType(),
		"data": doc.ToMapWithType(),
	})
}

func encryptAccount(doc couchdb.JSONDoc) bool {
	return encryptMap(doc.M)
}

func decryptAccount(doc couchdb.JSONDoc) bool {
	return decryptMap(doc.M)
}

func encryptMap(m map[string]interface{}) bool {
	encrypted := false
	var passwordEncrypted []byte
	var err error
	for k, v := range m {
		var ok bool
		var password string
		if k == "password" {
			if password, ok = v.(string); ok && len(password) > 0 {
				login, _ := m["login"].(string)
				passwordEncrypted, err = accounts.EncryptCredentials(login, password)
				encrypted = err == nil
			}
		} else if mm, ok := v.(map[string]interface{}); ok && encryptMap(mm) {
			encrypted = true
		}
	}
	if len(passwordEncrypted) > 0 {
		delete(m, "password")
		m["credentials_encrypted"] = base64.StdEncoding.EncodeToString(passwordEncrypted)
	}
	return encrypted
}

func decryptMap(m map[string]interface{}) bool {
	decrypted := false
	var login, password string
	for k, v := range m {
		if k == "credentials_encrypted" {
			encodedEncryptedCreds, ok := v.(string)
			if ok {
				encryptedCreds, err := base64.StdEncoding.DecodeString(encodedEncryptedCreds)
				if err == nil {
					login, password, err = accounts.DecryptCredentials(encryptedCreds)
					decrypted = err == nil
				}
			}
		} else if mm, ok := v.(map[string]interface{}); ok && decryptMap(mm) {
			decrypted = true
		}
	}
	if len(login) > 0 && len(password) > 0 {
		delete(m, "credentials_encrypted")
		m["login"] = login
		m["password"] = password
	}
	return decrypted
}

func createAccount(c echo.Context) error {
	doctype := consts.Accounts
	instance := middlewares.GetInstance(c)

	doc := couchdb.JSONDoc{Type: doctype}
	if err := json.NewDecoder(c.Request().Body).Decode(&doc.M); err != nil {
		return jsonapi.NewError(http.StatusBadRequest, err)
	}

	if err := permissions.Allow(c, permissions.POST, &doc); err != nil {
		return err
	}

	encryptAccount(doc)

	if err := couchdb.CreateDoc(instance, doc); err != nil {
		return err
	}

	return c.JSON(http.StatusCreated, echo.Map{
		"ok":   true,
		"id":   doc.ID(),
		"rev":  doc.Rev(),
		"type": doc.DocType(),
		"data": doc.ToMapWithType(),
	})
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"

	"github.com/hiromaily/golibs/config"
	"github.com/hiromaily/golibs/db/mysql"
	"github.com/hiromaily/golibs/db/redis"
	"github.com/hiromaily/golibs/web/server/handlers"
	"github.com/hiromaily/golibs/web/server/middlewares"
	"github.com/hiromaily/golibs/web/server/validations"
)

type Server struct {
	srvMux *http.ServeMux
}

func NewServer(conf *config.Config, db *mysql.MS, rd *redis.RD) (*Server, error) {
	s := &Server{
		srvMux: http.NewServeMux(),
	}

	redisRegistry := NewRedisRegistry(conf, rd)
	dbRegistry := NewDBRegistory(conf, db)

	//redis
	redisHandler := NewHandler(redisRegistry)
	//db
	dbHandler := NewHandler(dbRegistry)

	//health check
	hcHandler := handlers.NewHealthChecker(true)

	s.srvMux.Handle("/redis", middlewares.RecoverMiddleware(validations.Redis(redisHandler)))
	s.srvMux.Handle("/db", middlewares.RecoverMiddleware(validations.DB(dbHandler)))
	s.srvMux.Handle("/hc", hcHandler)

	return s, nil
}

func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	s.srvMux.ServeHTTP(w, req)
}
<commit_msg>add comment<commit_after>package server

import (
	"net/http"

	"github.com/hiromaily/golibs/config"
	"github.com/hiromaily/golibs/db/mysql"
	"github.com/hiromaily/golibs/db/redis"
	"github.com/hiromaily/golibs/web/server/handlers"
	"github.com/hiromaily/golibs/web/server/middlewares"
	"github.com/hiromaily/golibs/web/server/validations"
)

type Server struct {
	srvMux *http.ServeMux
}

func NewServer(conf *config.Config, db *mysql.MS, rd *redis.RD) (*Server, error) {
	s := &Server{
		srvMux: http.NewServeMux(),
	}

	redisRegistry := NewRedisRegistry(conf, rd)
	dbRegistry := NewDBRegistory(conf, db)

	//redis
	redisHandler := NewHandler(redisRegistry)
	//db
	dbHandler := NewHandler(dbRegistry)

	//health check
	hcHandler := handlers.NewHealthChecker(true)

	//http.Handler
	s.srvMux.Handle("/redis", middlewares.RecoverMiddleware(validations.Redis(redisHandler)))
	s.srvMux.Handle("/db", middlewares.RecoverMiddleware(validations.DB(dbHandler)))
	s.srvMux.Handle("/hc", hcHandler)

	return s, nil
}

func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	s.srvMux.ServeHTTP(w, req)
}
<|endoftext|>


<commit_before>package signals

import (
	"encoding/gob"
	"errors"
	"io"
	"net/http"
	"net/url"
	"encoding/base64"
	"os"
	"regexp"
	"strconv"
	"strings"
)

func init() {
	gob.Register(&Wave{})
}

const bufferSize = 16

// an offset PCM Signal, that reads from a source, as required, its data.
// if queried for a property value from an x that is more than 32 samples lower than a previous query, will return zero.
type Wave struct {
	Offset
	URL    string
	reader io.Reader
}

func (s *Wave) property(p x) y {
	if s.reader == nil {
		wav, err := NewWave(s.URL)
		failOn(err)
		s.Offset = wav.Offset
		s.reader = wav.reader
	}
	for p > s.MaxX() {
		// append available data onto the PCM slice.
		// also possibly shift off some data, shortening the PCM slice, retaining at least two buffer lengths.
		// partial samples are read but not accessed by property.
		switch st := s.Offset.LimitedSignal.(type) {
		case PCM8bit:
			sd := PCM8bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize+n]
			if len(sd.Data) > bufferSize*3 {
				sd.Data = sd.Data[bufferSize:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		case PCM16bit:
			sd := PCM16bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize*2)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize*2:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize*2+n]
			if len(sd.Data) > bufferSize*2*3 {
				sd.Data = sd.Data[bufferSize*2:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		case PCM24bit:
			sd := PCM24bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize*3)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize*3:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize*3+n]
			if len(sd.Data) > bufferSize*3*3 {
				sd.Data = sd.Data[bufferSize*3:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		case PCM32bit:
			sd := PCM32bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize*4)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize*4:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize*4+n]
			if len(sd.Data) > bufferSize*4*3 {
				sd.Data = sd.Data[bufferSize*4:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		case PCM48bit:
			sd := PCM48bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize*6)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize*6:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize*6+n]
			if len(sd.Data) > bufferSize*6*3 {
				sd.Data = sd.Data[bufferSize*6:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		case PCM64bit:
			sd := PCM64bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize*8)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize*8:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize*8+n]
			if len(sd.Data) > bufferSize*8*3 {
				sd.Data = sd.Data[bufferSize*8:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		}
	}
	return s.Offset.property(p)
}

//func updateShifted(s Shifted, r io.Reader, b *[]byte, blockSize int) (err error){
//	b=append(b,make([]byte,bufferSize*blockSize)...)
//	n, err := r.Read(b[len(b)-bufferSize*blockSize:])
//	failOn(err)
//	b=b[:len(b)-bufferSize*blockSize+n]
//	if len(b)>bufferSize*blockSize*3{
//		b=b[bufferSize*blockSize:]
//		s.Offset+=bufferSize*s.samplePeriod
//	}
//}

func NewWave(URL string) (*Wave, error) {
	r, channels, bytes, rate, err := PCMReader(URL)
	if err != nil {
		return nil, err
	}
	if channels != 1 {
		return nil, errors.New(URL + ":Needs to be mono.")
	}
	b := make([]byte, bufferSize*bytes)
	n, err := r.Read(b)
	failOn(err)
	b = b[:n]
	switch bytes {
	case 1:
		return &Wave{Offset{NewPCM8bit(rate, b), 0}, URL, r}, nil
	case 2:
		return &Wave{Offset{NewPCM16bit(rate, b), 0}, URL, r}, nil
	case 3:
		return &Wave{Offset{NewPCM24bit(rate, b), 0}, URL, r}, nil
	case 4:
		return &Wave{Offset{NewPCM32bit(rate, b), 0}, URL, r}, nil
	case 6:
		return &Wave{Offset{NewPCM48bit(rate, b), 0}, URL, r}, nil
	case 8:
		return &Wave{Offset{NewPCM64bit(rate, b), 0}, URL, r}, nil
	}
	return nil, ErrWaveParse{"Source bit rate not supported."}
}

var contentTypeParse = regexp.MustCompile(`^audio/l(\d+);rate=(\d+)$`)

// returns a reader to a resource, along with its Channel count, Precision (bytes) and Samples per second.
func PCMReader(resourceLocation string) (io.Reader, uint16, uint16, uint32, error) {
	//	resp, err := http.Get(resourceLocation)
	url, err := url.Parse(resourceLocation)
	if err != nil {
		return nil, 0, 0, 0, err
	}
	switch url.Scheme {
	case "file":
		file, err := os.Open(url.Path)
		if err != nil {
			return nil, 0, 0, 0, err
		}
		_, format, err := readWaveHeader(file)
		if err != nil {
			return nil, 0, 0, 0, err
		}
		return file, format.Channels, format.SampleBytes, format.SampleRate, nil
	case "data":
		mimeAndRest := strings.SplitN(url.Opaque, ";", 2)
		encodingAndData := strings.SplitN(mimeAndRest[1], ",", 2)
		var r io.Reader
		if encodingAndData[0]=="base64" {
			r= base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodingAndData[1]))
		}else{
			r= strings.NewReader(encodingAndData[1])
		}
		if mimeAndRest[0] == "sound/wav" || mimeAndRest[0] == "audio/x-wav" {
			_, format, err := readWaveHeader(r)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			return r, format.Channels, format.SampleBytes, format.SampleRate, nil
		}
		pcmFormat := contentTypeParse.FindStringSubmatch(mimeAndRest[0])
		if pcmFormat != nil {
			bits, err := strconv.ParseUint(pcmFormat[1], 10, 19)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			rate, err := strconv.ParseUint(pcmFormat[2], 10, 32)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			return r, 1, uint16(bits / 8), uint32(rate), nil
		}
	default: // whatever supported and placed in Body, currently basically "http" or "https"
		resp, err := http.DefaultClient.Do(&http.Request{Method: "GET", URL: url})

		if err != nil {
			return nil, 0, 0, 0, err
		}
		if resp.Header["Content-Type"][0] == "sound/wav" || resp.Header["Content-Type"][0] == "audio/x-wav" {
			_, format, err := readWaveHeader(resp.Body)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			return resp.Body, format.Channels, format.SampleBytes, format.SampleRate, nil
		}
		pcmFormat := contentTypeParse.FindStringSubmatch(resp.Header["Content-Type"][0])
		if pcmFormat != nil {
			bits, err := strconv.ParseUint(pcmFormat[1], 10, 19)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			rate, err := strconv.ParseUint(pcmFormat[2], 10, 32)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			return resp.Body, 1, uint16(bits / 8), uint32(rate), nil
		}
	}
	return nil, 0, 0, 0, errors.New("Source:" + resourceLocation + " unsupported." )
}

func failOn(e error) {
	if e != nil {
		panic(e)
	}
}

<commit_msg>dont expose pcmReader<commit_after>package signals

import (
	"encoding/gob"
	"errors"
	"io"
	"net/http"
	"net/url"
	"encoding/base64"
	"os"
	"regexp"
	"strconv"
	"strings"
)

func init() {
	gob.Register(&Wave{})
}

const bufferSize = 16

// an offset PCM Signal, that reads from a source, as required, its data.
// if queried for a property value from an x that is more than 32 samples lower than a previous query, will return zero.
type Wave struct {
	Offset
	URL    string
	reader io.Reader
}

func (s *Wave) property(p x) y {
	if s.reader == nil {
		wav, err := NewWave(s.URL)
		failOn(err)
		s.Offset = wav.Offset
		s.reader = wav.reader
	}
	for p > s.MaxX() {
		// append available data onto the PCM slice.
		// also possibly shift off some data, shortening the PCM slice, retaining at least two buffer lengths.
		// partial samples are read but not accessed by property.
		switch st := s.Offset.LimitedSignal.(type) {
		case PCM8bit:
			sd := PCM8bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize+n]
			if len(sd.Data) > bufferSize*3 {
				sd.Data = sd.Data[bufferSize:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		case PCM16bit:
			sd := PCM16bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize*2)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize*2:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize*2+n]
			if len(sd.Data) > bufferSize*2*3 {
				sd.Data = sd.Data[bufferSize*2:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		case PCM24bit:
			sd := PCM24bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize*3)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize*3:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize*3+n]
			if len(sd.Data) > bufferSize*3*3 {
				sd.Data = sd.Data[bufferSize*3:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		case PCM32bit:
			sd := PCM32bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize*4)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize*4:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize*4+n]
			if len(sd.Data) > bufferSize*4*3 {
				sd.Data = sd.Data[bufferSize*4:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		case PCM48bit:
			sd := PCM48bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize*6)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize*6:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize*6+n]
			if len(sd.Data) > bufferSize*6*3 {
				sd.Data = sd.Data[bufferSize*6:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		case PCM64bit:
			sd := PCM64bit{st.PCM}
			sd.Data = append(sd.Data, make([]byte, bufferSize*8)...)
			n, err := s.reader.Read(sd.Data[len(sd.Data)-bufferSize*8:])
			failOn(err)
			sd.Data = sd.Data[:len(sd.Data)-bufferSize*8+n]
			if len(sd.Data) > bufferSize*8*3 {
				sd.Data = sd.Data[bufferSize*8:]
				s.Offset = Offset{sd, s.Offset.Offset + bufferSize*st.samplePeriod}
			} else {
				s.Offset = Offset{sd, s.Offset.Offset}
			}
		}
	}
	return s.Offset.property(p)
}

//func updateShifted(s Shifted, r io.Reader, b *[]byte, blockSize int) (err error){
//	b=append(b,make([]byte,bufferSize*blockSize)...)
//	n, err := r.Read(b[len(b)-bufferSize*blockSize:])
//	failOn(err)
//	b=b[:len(b)-bufferSize*blockSize+n]
//	if len(b)>bufferSize*blockSize*3{
//		b=b[bufferSize*blockSize:]
//		s.Offset+=bufferSize*s.samplePeriod
//	}
//}

func NewWave(URL string) (*Wave, error) {
	r, channels, bytes, rate, err := pcmReader(URL)
	if err != nil {
		return nil, err
	}
	if channels != 1 {
		return nil, errors.New(URL + ":Needs to be mono.")
	}
	b := make([]byte, bufferSize*bytes)
	n, err := r.Read(b)
	failOn(err)
	b = b[:n]
	switch bytes {
	case 1:
		return &Wave{Offset{NewPCM8bit(rate, b), 0}, URL, r}, nil
	case 2:
		return &Wave{Offset{NewPCM16bit(rate, b), 0}, URL, r}, nil
	case 3:
		return &Wave{Offset{NewPCM24bit(rate, b), 0}, URL, r}, nil
	case 4:
		return &Wave{Offset{NewPCM32bit(rate, b), 0}, URL, r}, nil
	case 6:
		return &Wave{Offset{NewPCM48bit(rate, b), 0}, URL, r}, nil
	case 8:
		return &Wave{Offset{NewPCM64bit(rate, b), 0}, URL, r}, nil
	}
	return nil, ErrWaveParse{"Source bit rate not supported."}
}

var contentTypeParse = regexp.MustCompile(`^audio/l(\d+);rate=(\d+)$`)

// returns a reader to a resource, along with its Channel count, Precision (bytes) and Samples per second.
func pcmReader(resourceLocation string) (io.Reader, uint16, uint16, uint32, error) {
	//	resp, err := http.Get(resourceLocation)
	url, err := url.Parse(resourceLocation)
	if err != nil {
		return nil, 0, 0, 0, err
	}
	switch url.Scheme {
	case "file":
		file, err := os.Open(url.Path)
		if err != nil {
			return nil, 0, 0, 0, err
		}
		_, format, err := readWaveHeader(file)
		if err != nil {
			return nil, 0, 0, 0, err
		}
		return file, format.Channels, format.SampleBytes, format.SampleRate, nil
	case "data":
		mimeAndRest := strings.SplitN(url.Opaque, ";", 2)
		encodingAndData := strings.SplitN(mimeAndRest[1], ",", 2)
		var r io.Reader
		if encodingAndData[0]=="base64" {
			r= base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodingAndData[1]))
		}else{
			r= strings.NewReader(encodingAndData[1])
		}
		if mimeAndRest[0] == "sound/wav" || mimeAndRest[0] == "audio/x-wav" {
			_, format, err := readWaveHeader(r)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			return r, format.Channels, format.SampleBytes, format.SampleRate, nil
		}
		pcmFormat := contentTypeParse.FindStringSubmatch(mimeAndRest[0])
		if pcmFormat != nil {
			bits, err := strconv.ParseUint(pcmFormat[1], 10, 19)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			rate, err := strconv.ParseUint(pcmFormat[2], 10, 32)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			return r, 1, uint16(bits / 8), uint32(rate), nil
		}
	default: // whatever supported and placed in Body, currently basically "http" or "https"
		resp, err := http.DefaultClient.Do(&http.Request{Method: "GET", URL: url})

		if err != nil {
			return nil, 0, 0, 0, err
		}
		if resp.Header["Content-Type"][0] == "sound/wav" || resp.Header["Content-Type"][0] == "audio/x-wav" {
			_, format, err := readWaveHeader(resp.Body)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			return resp.Body, format.Channels, format.SampleBytes, format.SampleRate, nil
		}
		pcmFormat := contentTypeParse.FindStringSubmatch(resp.Header["Content-Type"][0])
		if pcmFormat != nil {
			bits, err := strconv.ParseUint(pcmFormat[1], 10, 19)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			rate, err := strconv.ParseUint(pcmFormat[2], 10, 32)
			if err != nil {
				return nil, 0, 0, 0, err
			}
			return resp.Body, 1, uint16(bits / 8), uint32(rate), nil
		}
	}
	return nil, 0, 0, 0, errors.New("Source:" + resourceLocation + " unsupported." )
}

func failOn(e error) {
	if e != nil {
		panic(e)
	}
}

/*  Hal3 Thu Jul 28 00:03:39 BST 2016 go version go1.5.1 linux/amd64
FAIL	_/home/simon/Dropbox/github/working/signals [build failed]
Thu Jul 28 00:03:40 BST 2016 */

<|endoftext|>