<commit_before>/////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2014, Daniel Reiter Horn
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
//    conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of
//    conditions and the following disclaimer in the documentation and/or other materials
//    provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//////////////////////////////////////////////////////////////////////////////////////////////////
package main

import (
    "bufio"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
    "path"
    "strconv"
    "syscall"
)

var PIPE_DIR = "/root"
var STDIN_PATH = "/stdin"
var STDOUT_PATH = "/stdout"
var STDERR_PATH = "/stderr"
var EXITCODE_PATH = "/exitcode"
var COMMAND_PATH = "/command"
var ABORT_PATH = "/abort"
var PERMISSIONS = "0600"
var STARTING_UID = "1000"
var STARTING_GID = "1000"
var MAX_UID = "1004"
var RUNNER = ""
var VERSION = "unknown"

func concatenate_string_arrays(s0, s1 []string) []string {
    retval := make([]string, len(s0)+len(s1))
    copy(retval, s0)
    copy(retval[len(s0):], s1)
    return retval
}

func check_stats(f *os.File, permissions os.FileMode) {
    stat, err := f.Stat()
    if err != nil {
        log.Fatalf("Error checking stats %v", err)
    }
    if (stat.Mode() & 0777) != permissions {
        log.Fatalf("Stats have been altered to %0d", stat.Mode())
    }
}

func accept_commands(command_path string, stdin_path string,
    stdout_path string, stderr_path string, exitcode_path string,
    permissions uint32, command_prefix []string) {
    uid, err := strconv.Atoi(STARTING_UID)
    if err != nil {
        log.Fatalf("Could not convert user id %s: %v", STARTING_UID, err)
    }
    gid, err := strconv.Atoi(STARTING_GID)
    if err != nil {
        log.Fatalf("Could not convert group id %s: %v", STARTING_GID, err)
    }
    max_uid, err := strconv.Atoi(MAX_UID)
    if err != nil {
        log.Fatalf("Could not convert user id %s: %v", MAX_UID, err)
    }
    var command []string
    for {
        command_stream, err := os.Open(command_path)
        if err != nil {
            log.Fatal(err)
        }
        command_buffer := bufio.NewReader(command_stream)
        command_json, err := command_buffer.ReadBytes('\n')
        json_err := json.Unmarshal(command_json, &command)
        if json_err == nil && len(command) > 0 {
            log.Print("Opening stdin: " + stdin_path)
            stdin_stream, err := os.Open(stdin_path)
            if err == nil {
                check_stats(stdin_stream, os.FileMode(permissions))
            }
            if err != nil {
                log.Fatal(err)
            }

            log.Print("Opening stdout: " + stdout_path)
            stdout_stream, err := os.OpenFile(stdout_path, os.O_WRONLY, 0)
            if err == nil {
                check_stats(stdout_stream, os.FileMode(permissions))
            }
            if err != nil {
                stdin_stream.Close()
                log.Fatal(err)
            }
            log.Print("Opening stderr: " + stderr_path)
            stderr_stream, err := os.OpenFile(stderr_path, os.O_WRONLY, 0)
            if err == nil {
                check_stats(stderr_stream, os.FileMode(permissions))
            }
            if err != nil {
                stdin_stream.Close()
                stdout_stream.Close()
                log.Fatal(err)
            }
            log.Print("Opening exitcode: " + exitcode_path)
            exitcode_stream, err := os.OpenFile(exitcode_path, os.O_WRONLY, 0)
            if err == nil {
                check_stats(exitcode_stream, os.FileMode(permissions))
            }
            if err != nil {
                stdin_stream.Close()
                stdout_stream.Close()
                stderr_stream.Close()
                log.Fatal(err)
            }
            log.Print("Starting and waiting " + string(command_json))
            if len(command) > 0 && command[0] == "newuser" {
                uid += 1
                gid += 1
                if uid > max_uid {
                    io.WriteString(stdout_stream, "-1\n")
                    log.Fatalf("uid %d is higher than the maximum allowed %d: restart...", uid, max_uid)
                }
                io.WriteString(stdout_stream, strconv.Itoa(uid)+"\n")
                null_byte := [1]byte{0}
                exitcode_stream.Write(null_byte[:])
            } else {

                concatenated_command := concatenate_string_arrays(command_prefix,
                    command)
                if len(concatenated_command) > 0 {
                    proc := exec.Command(concatenated_command[0])
                    proc.Args = concatenated_command
                    proc.Stdin = stdin_stream
                    proc.Stdout = stdout_stream
                    proc.Stderr = stderr_stream
                    var sys_proc_attr syscall.SysProcAttr
                    var cred syscall.Credential
                    cred.Uid = uint32(uid)
                    cred.Gid = uint32(gid)
                    cred.Groups = make([]uint32, 0)
                    sys_proc_attr.Credential = &cred
                    proc.SysProcAttr = &sys_proc_attr
                    proc.Start()
                    err = proc.Wait()
                    var exit_code [1]byte
                    if err != nil {
                        if proc.ProcessState.Success() {
                            exit_code[0] = 2
                        } else {
                            exit_code[0] = 1
                        }
                    } else {
                        exit_code[0] = 0
                    }
                    log.Printf("Process exited with error code %d", exit_code)
                    exitcode_stream.Write(exit_code[:])
                }
            }
            exitcode_stream.Close()
            stderr_stream.Close()
            stdout_stream.Close()
            stdin_stream.Close()
        }
        command_stream.Close()
        if err != nil {
            break
        }
    }
}

func abort() {
    pid := os.Getpid()
    log.Print(pid) //FIXME:
    _ = os.RemoveAll(STDIN_PATH)
    _ = os.RemoveAll(STDOUT_PATH)
    _ = os.RemoveAll(STDERR_PATH)
    _ = os.RemoveAll(COMMAND_PATH)
    _ = os.RemoveAll(EXITCODE_PATH)
    _ = os.RemoveAll(ABORT_PATH)
    os.Exit(0)
}

func main() {
    if len(os.Args) > 1 && (os.Args[1] == "-version" || os.Args[1] == "--version") {
        fmt.Printf("%s\nCONFIGURED WITH\n", VERSION)
        var configuration_params = []string{
            "pipe directory", PIPE_DIR,
            "command pipe", COMMAND_PATH,
            "stdin pipe", STDIN_PATH,
            "stdout pipe", STDOUT_PATH,
            "stderr pipe", STDERR_PATH,
            "abort pipe", ABORT_PATH,
            "pipe permissions", PERMISSIONS,
            "min uid", STARTING_UID,
            "min gid", STARTING_UID,
            "max uid", MAX_UID,
            "interim binary", RUNNER}
        for i, item := range configuration_params {
            fmt.Printf("%s", item)
            if i%2 == 0 {
                fmt.Printf(": ")
            } else {
                fmt.Printf("\n")
            }
        }
        os.Exit(0)
    }

    perm64, err := strconv.ParseUint(PERMISSIONS, 8, 32)
    permissions := uint32(perm64)
    if err != nil {
        log.Fatalf("Couldn't parse permissions %v", err)
    }
    STDIN_PATH = path.Join(PIPE_DIR, STDIN_PATH)
    STDOUT_PATH = path.Join(PIPE_DIR, STDOUT_PATH)
    STDERR_PATH = path.Join(PIPE_DIR, STDERR_PATH)
    EXITCODE_PATH = path.Join(PIPE_DIR, EXITCODE_PATH)
    ABORT_PATH = path.Join(PIPE_DIR, ABORT_PATH)
    COMMAND_PATH = path.Join(PIPE_DIR, COMMAND_PATH)
    _ = os.RemoveAll(COMMAND_PATH)
    _ = os.RemoveAll(STDIN_PATH)
    _ = os.RemoveAll(STDOUT_PATH)
    _ = os.RemoveAll(STDERR_PATH)
    _ = os.RemoveAll(EXITCODE_PATH)
    _ = os.RemoveAll(ABORT_PATH)
    err = syscall.Mkfifo(ABORT_PATH, permissions)
    if err != nil {
        log.Fatalf("Abort file exists %v", err)
    }
    os.Chmod(ABORT_PATH, os.FileMode(permissions))
    err = syscall.Mkfifo(EXITCODE_PATH, permissions)
    if err != nil {
        log.Fatalf("Exitcode file exists %v", err)
    }
    os.Chmod(EXITCODE_PATH, os.FileMode(permissions))
    err = syscall.Mkfifo(STDERR_PATH, permissions)
    if err != nil {
        log.Fatalf("Stderr file exists %v", err)
    }
    os.Chmod(STDERR_PATH, os.FileMode(permissions))
    err = syscall.Mkfifo(STDOUT_PATH, permissions)
    if err != nil {
        log.Fatalf("Stdout file exists %v", err)
    }
    os.Chmod(STDOUT_PATH, os.FileMode(permissions))
    err = syscall.Mkfifo(STDIN_PATH, permissions)
    if err != nil {
        log.Fatalf("Stdin file exists %v", err)
    }
    os.Chmod(STDIN_PATH, os.FileMode(permissions))
    err = syscall.Mkfifo(COMMAND_PATH, permissions)
    if err != nil {
        log.Fatalf("Command file exists %v", err)
    }
    os.Chmod(COMMAND_PATH, os.FileMode(permissions))
    var subcommand []string
    if len(RUNNER) != 0 {
        subcommand = make([]string, 1)
        subcommand[0] = RUNNER
        subcommand = concatenate_string_arrays(subcommand, os.Args[1:])
    } else {
        subcommand = make([]string, 0)
    }
    io.WriteString(os.Stdout, "ok\n")
    go accept_commands(COMMAND_PATH, STDIN_PATH, STDOUT_PATH, STDERR_PATH, EXITCODE_PATH,
        permissions, subcommand)
    for {
        // multiple people might hold abort open -- we need someone to write a byte
        f, err := os.Open(ABORT_PATH)
        if err == nil {
            var aborted [1]byte
            _, _ = f.Read(aborted[:])
            abort()
            f.Close()
        } else {
            log.Fatalf("Reopening abort %v\n", err)
            abort()
        }
    }
}
<commit_msg>add more flexibility into how args work if no RUNNER is defined<commit_after>/////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2014, Daniel Reiter Horn
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
//    conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of
//    conditions and the following disclaimer in the documentation and/or other materials
//    provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//////////////////////////////////////////////////////////////////////////////////////////////////
package main

import (
    "bufio"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
    "path"
    "strconv"
    "syscall"
)

var PIPE_DIR = "/root"
var STDIN_PATH = "/stdin"
var STDOUT_PATH = "/stdout"
var STDERR_PATH = "/stderr"
var EXITCODE_PATH = "/exitcode"
var COMMAND_PATH = "/command"
var ABORT_PATH = "/abort"
var PERMISSIONS = "0600"
var STARTING_UID = "1000"
var STARTING_GID = "1000"
var MAX_UID = "1004"
var RUNNER = ""
var VERSION = "unknown"

func concatenate_string_arrays(s0, s1 []string) []string {
    retval := make([]string, len(s0)+len(s1))
    copy(retval, s0)
    copy(retval[len(s0):], s1)
    return retval
}

func check_stats(f *os.File, permissions os.FileMode) {
    stat, err := f.Stat()
    if err != nil {
        log.Fatalf("Error checking stats %v", err)
    }
    if (stat.Mode() & 0777) != permissions {
        log.Fatalf("Stats have been altered to %0d", stat.Mode())
    }
}

func accept_commands(command_path string, stdin_path string,
    stdout_path string, stderr_path string, exitcode_path string,
    permissions uint32, command_prefix []string) {
    uid, err := strconv.Atoi(STARTING_UID)
    if err != nil {
        log.Fatalf("Could not convert user id %s: %v", STARTING_UID, err)
    }
    gid, err := strconv.Atoi(STARTING_GID)
    if err != nil {
        log.Fatalf("Could not convert group id %s: %v", STARTING_GID, err)
    }
    max_uid, err := strconv.Atoi(MAX_UID)
    if err != nil {
        log.Fatalf("Could not convert user id %s: %v", MAX_UID, err)
    }
    var command []string
    for {
        command_stream, err := os.Open(command_path)
        if err != nil {
            log.Fatal(err)
        }
        command_buffer := bufio.NewReader(command_stream)
        command_json, err := command_buffer.ReadBytes('\n')
        json_err := json.Unmarshal(command_json, &command)
        if json_err == nil && len(command) > 0 {
            log.Print("Opening stdin: " + stdin_path)
            stdin_stream, err := os.Open(stdin_path)
            if err == nil {
                check_stats(stdin_stream, os.FileMode(permissions))
            }
            if err != nil {
                log.Fatal(err)
            }

            log.Print("Opening stdout: " + stdout_path)
            stdout_stream, err := os.OpenFile(stdout_path, os.O_WRONLY, 0)
            if err == nil {
                check_stats(stdout_stream, os.FileMode(permissions))
            }
            if err != nil {
                stdin_stream.Close()
                log.Fatal(err)
            }
            log.Print("Opening stderr: " + stderr_path)
            stderr_stream, err := os.OpenFile(stderr_path, os.O_WRONLY, 0)
            if err == nil {
                check_stats(stderr_stream, os.FileMode(permissions))
            }
            if err != nil {
                stdin_stream.Close()
                stdout_stream.Close()
                log.Fatal(err)
            }
            log.Print("Opening exitcode: " + exitcode_path)
            exitcode_stream, err := os.OpenFile(exitcode_path, os.O_WRONLY, 0)
            if err == nil {
                check_stats(exitcode_stream, os.FileMode(permissions))
            }
            if err != nil {
                stdin_stream.Close()
                stdout_stream.Close()
                stderr_stream.Close()
                log.Fatal(err)
            }
            log.Print("Starting and waiting " + string(command_json))
            if len(command) > 0 && command[0] == "newuser" {
                uid += 1
                gid += 1
                if uid > max_uid {
                    io.WriteString(stdout_stream, "-1\n")
                    log.Fatalf("uid %d is higher than the maximum allowed %d: restart...", uid, max_uid)
                }
                io.WriteString(stdout_stream, strconv.Itoa(uid)+"\n")
                null_byte := [1]byte{0}
                exitcode_stream.Write(null_byte[:])
            } else {

                concatenated_command := concatenate_string_arrays(command_prefix,
                    command)
                if len(concatenated_command) > 0 {
                    proc := exec.Command(concatenated_command[0])
                    proc.Args = concatenated_command
                    proc.Stdin = stdin_stream
                    proc.Stdout = stdout_stream
                    proc.Stderr = stderr_stream
                    var sys_proc_attr syscall.SysProcAttr
                    var cred syscall.Credential
                    cred.Uid = uint32(uid)
                    cred.Gid = uint32(gid)
                    cred.Groups = make([]uint32, 0)
                    sys_proc_attr.Credential = &cred
                    proc.SysProcAttr = &sys_proc_attr
                    proc.Start()
                    err = proc.Wait()
                    var exit_code [1]byte
                    if err != nil {
                        if proc.ProcessState.Success() {
                            exit_code[0] = 2
                        } else {
                            exit_code[0] = 1
                        }
                    } else {
                        exit_code[0] = 0
                    }
                    log.Printf("Process exited with error code %d", exit_code)
                    exitcode_stream.Write(exit_code[:])
                }
            }
            exitcode_stream.Close()
            stderr_stream.Close()
            stdout_stream.Close()
            stdin_stream.Close()
        }
        command_stream.Close()
        if err != nil {
            break
        }
    }
}

func abort() {
    pid := os.Getpid()
    log.Print(pid) //FIXME:
    _ = os.RemoveAll(STDIN_PATH)
    _ = os.RemoveAll(STDOUT_PATH)
    _ = os.RemoveAll(STDERR_PATH)
    _ = os.RemoveAll(COMMAND_PATH)
    _ = os.RemoveAll(EXITCODE_PATH)
    _ = os.RemoveAll(ABORT_PATH)
    os.Exit(0)
}

func main() {
    if len(os.Args) > 1 && (os.Args[1] == "-version" || os.Args[1] == "--version") {
        fmt.Printf("%s\nCONFIGURED WITH\n", VERSION)
        var configuration_params = []string{
            "pipe directory", PIPE_DIR,
            "command pipe", COMMAND_PATH,
            "stdin pipe", STDIN_PATH,
            "stdout pipe", STDOUT_PATH,
            "stderr pipe", STDERR_PATH,
            "abort pipe", ABORT_PATH,
            "pipe permissions", PERMISSIONS,
            "min uid", STARTING_UID,
            "min gid", STARTING_UID,
            "max uid", MAX_UID,
            "interim binary", RUNNER}
        for i, item := range configuration_params {
            fmt.Printf("%s", item)
            if i%2 == 0 {
                fmt.Printf(": ")
            } else {
                fmt.Printf("\n")
            }
        }
        os.Exit(0)
    }

    perm64, err := strconv.ParseUint(PERMISSIONS, 8, 32)
    permissions := uint32(perm64)
    if err != nil {
        log.Fatalf("Couldn't parse permissions %v", err)
    }
    STDIN_PATH = path.Join(PIPE_DIR, STDIN_PATH)
    STDOUT_PATH = path.Join(PIPE_DIR, STDOUT_PATH)
    STDERR_PATH = path.Join(PIPE_DIR, STDERR_PATH)
    EXITCODE_PATH = path.Join(PIPE_DIR, EXITCODE_PATH)
    ABORT_PATH = path.Join(PIPE_DIR, ABORT_PATH)
    COMMAND_PATH = path.Join(PIPE_DIR, COMMAND_PATH)
    _ = os.RemoveAll(COMMAND_PATH)
    _ = os.RemoveAll(STDIN_PATH)
    _ = os.RemoveAll(STDOUT_PATH)
    _ = os.RemoveAll(STDERR_PATH)
    _ = os.RemoveAll(EXITCODE_PATH)
    _ = os.RemoveAll(ABORT_PATH)
    err = syscall.Mkfifo(ABORT_PATH, permissions)
    if err != nil {
        log.Fatalf("Abort file exists %v", err)
    }
    os.Chmod(ABORT_PATH, os.FileMode(permissions))
    err = syscall.Mkfifo(EXITCODE_PATH, permissions)
    if err != nil {
        log.Fatalf("Exitcode file exists %v", err)
    }
    os.Chmod(EXITCODE_PATH, os.FileMode(permissions))
    err = syscall.Mkfifo(STDERR_PATH, permissions)
    if err != nil {
        log.Fatalf("Stderr file exists %v", err)
    }
    os.Chmod(STDERR_PATH, os.FileMode(permissions))
    err = syscall.Mkfifo(STDOUT_PATH, permissions)
    if err != nil {
        log.Fatalf("Stdout file exists %v", err)
    }
    os.Chmod(STDOUT_PATH, os.FileMode(permissions))
    err = syscall.Mkfifo(STDIN_PATH, permissions)
    if err != nil {
        log.Fatalf("Stdin file exists %v", err)
    }
    os.Chmod(STDIN_PATH, os.FileMode(permissions))
    err = syscall.Mkfifo(COMMAND_PATH, permissions)
    if err != nil {
        log.Fatalf("Command file exists %v", err)
    }
    os.Chmod(COMMAND_PATH, os.FileMode(permissions))
    var subcommand []string
    if len(RUNNER) != 0 {
        subcommand = make([]string, 1)
        subcommand[0] = RUNNER
        subcommand = concatenate_string_arrays(subcommand, os.Args[1:])
    } else {
        subcommand = os.Args[1:]
    }
    io.WriteString(os.Stdout, "ok\n")
    go accept_commands(COMMAND_PATH, STDIN_PATH, STDOUT_PATH, STDERR_PATH, EXITCODE_PATH,
        permissions, subcommand)
    for {
        // multiple people might hold abort open -- we need someone to write a byte
        f, err := os.Open(ABORT_PATH)
        if err == nil {
            var aborted [1]byte
            _, _ = f.Read(aborted[:])
            abort()
            f.Close()
        } else {
            log.Fatalf("Reopening abort %v\n", err)
            abort()
        }
    }
}
<|endoftext|>


<commit_before>/*
Package update allows a program to "self-update", replacing its executable file
with new bytes.

Package update provides the facility to create user experiences like auto-updating
or user-approved updates which manifest as user prompts in commercial applications
with copy similar to "Restart to being using the new version of X".

Updating your program to a new version is as easy as:

	err := update.FromUrl("http://release.example.com/2.0/myprogram")
	if err != nil {
		fmt.Printf("Update failed: %v", err)
	}

The most low-level API is FromStream() which updates the current executable
with the bytes read from an io.Reader.

Additional APIs are provided for common update strategies which include
updating from a file with FromFile() and updating from the internet with
FromUrl().

Using the more advaced Download.UpdateFromUrl() API gives you the ability
to resume an interrupted download to enable large updates to complete even
over intermittent or slow connections. This API also enables more fine-grained
control over how the update is downloaded from the internet as well as access to
download progress,
*/
package update

import (
	"compress/gzip"
	"fmt"
	execpath "github.com/inconshreveable/go-execpath"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
)

type MeteredReader struct {
	rd        io.ReadCloser
	totalSize int64
	progress  chan int
	totalRead int64
	ticks     int64
}

func (m *MeteredReader) Close() error {
	return m.rd.Close()
}

func (m *MeteredReader) Read(b []byte) (n int, err error) {
	chunkSize := (m.totalSize / 100) + 1
	lenB := int64(len(b))

	var nChunk int
	for start := int64(0); start < lenB; start += int64(nChunk) {
		end := start + chunkSize
		if end > lenB {
			end = lenB
		}

		nChunk, err = m.rd.Read(b[start:end])

		n += nChunk
		m.totalRead += int64(nChunk)

		if m.totalRead > (m.ticks * chunkSize) {
			m.ticks += 1
			// try to send on channel, but don't block if it's full
			select {
			case m.progress <- int(m.ticks + 1):
			default:
			}

			// give the progress channel consumer a chance to run
			runtime.Gosched()
		}

		if err != nil {
			return
		}
	}

	return
}

// We wrap the round tripper when making requests
// because we need to add headers to the requests we make
// even when they are requests made after a redirect
type RoundTripper struct {
	RoundTripFn func(*http.Request) (*http.Response, error)
}

func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	return rt.RoundTripFn(r)
}

// Type Download encapsulates the necessary parameters and state
// needed to download an update from the internet. Create an instance
// with the NewDownload() factory function.
//
// You may only use a Download once,
type Download struct {
	// net/http.Client to use when downloading the update.
	// If nil, a default http.Client is used
	HttpClient *http.Client

	// Path on the file system to dowload the update to
	// If empty, a temporary file is used.
	// After the download begins, this path will be set
	// so that the client can use it to resume aborted
	// downloads
	Path string

	// Progress returns the percentage of the download
	// completed as an integer between 0 and 100
	Progress chan (int)

	// HTTP Method to use in the download request. Default is "GET"
	Method string

	// Set to true when the server confirms a new version is available
	// even if the updating process encounters an error later on
	Available bool
}

// NewDownload initializes a new Download object
func NewDownload() *Download {
	return &Download{
		HttpClient: new(http.Client),
		Progress:   make(chan int),
		Method:     "GET",
	}
}

// UpdateFromUrl downloads the given url from the internet to a file on disk
// and then calls FromStream() to update the current program's executable file
// with the contents of that file.
//
// If the update is successful, the downloaded file will be erased from disk.
// Otherwise, it will remain in d.Path to allow the download to resume later
// or be skipped entirely.
//
// Only HTTP/1.1 servers that implement the Range header are supported.
//
// UpdateFromUrl() uses HTTP status codes to determine what action to take.
//
// - The HTTP server should return 200 or 206 for the update to be downloaded.
//
// - The HTTP server should return 204 if no update is available at this time.
//
// - If the HTTP server returns a 3XX redirect, it will be followed
// according to d.HttpClient's redirect policy.
//
// - Any other HTTP status code will cause UpdateFromUrl to return an error.
func (d *Download) UpdateFromUrl(url string) (err error) {
	var offset int64 = 0
	var fp *os.File

	// Close the progress channel whenever this function completes
	defer close(d.Progress)

	// open a file where we will stream the downloaded update to
	// we do this first because if the caller specified a non-empty dlpath
	// we need to determine how large it is in order to resume the download
	if d.Path == "" {
		// no dlpath specified, use a random tempfile
		fp, err = ioutil.TempFile("", "update")
		if err != nil {
			return
		}
		defer fp.Close()

		// remember the path
		d.Path = fp.Name()
	} else {
		fp, err = os.OpenFile(d.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600)
		if err != nil {
			return
		}
		defer fp.Close()

		// determine the file size so we can resume the download, if possible
		var fi os.FileInfo
		fi, err = fp.Stat()
		if err != nil {
			return
		}

		offset = fi.Size()
	}

	// create the download request
	req, err := http.NewRequest(d.Method, url, nil)
	if err != nil {
		return
	}

	// we have to add headers like this so they get used across redirects
	trans := d.HttpClient.Transport
	if trans == nil {
		trans = http.DefaultTransport
	}

	d.HttpClient.Transport = &RoundTripper{
		RoundTripFn: func(r *http.Request) (*http.Response, error) {
			// add header for download continuation
			if offset > 0 {
				r.Header.Add("Range", fmt.Sprintf("%d-", offset))
			}

			// ask for gzipped content so that net/http won't unzip it for us
			// and destroy the content length header we need for progress calculations
			r.Header.Add("Accept-Encoding", "gzip")

			return trans.RoundTrip(r)
		},
	}

	// start downloading the file
	resp, err := d.HttpClient.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	// ok
	case 200, 206:
		d.Available = true

	// no update available
	case 204:
		return

	// server error
	default:
		err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
		return
	}

	// Determine how much we have to download
	// net/http sets this to -1 when it is unknown
	clength := resp.ContentLength

	// Read the content from the response body
	rd := resp.Body

	// meter the rate at which we download content for
	// progress reporting if we know how much to expect
	if clength > 0 {
		rd = &MeteredReader{rd: rd, totalSize: clength, progress: d.Progress}
	}

	// Decompress the content if necessary
	if resp.Header.Get("Content-Encoding") == "gzip" {
		rd, err = gzip.NewReader(rd)
		if err != nil {
			return
		}
	}

	// Download the update
	_, err = io.Copy(fp, rd)
	if err != nil {
		return
	}

	// Seek to the beginning of the file before we pass fp to FromStream()
	_, err = fp.Seek(0, os.SEEK_SET)
	if err != nil {
		return
	}

	// Perform the update
	err = FromStream(fp)
	if err != nil {
		return
	}

	// remove the downloaded binary after it's been installed
	os.Remove(d.Path)

	return
}

// FromUrl downloads the contents of the given url and uses them to update
// the current program's executable file. It is a convenience function which is equivalent to
//
// 	NewDownload().UpdateFromUrl(url)
//
// See Download.UpdateFromUrl for more details.
func FromUrl(url string) error {
	return NewDownload().UpdateFromUrl(url)
}

// FromFile reads the contents of the given file and uses them
// to update the current program's executable file by calling FromStream().
func FromFile(filepath string) (err error) {
	// open the new binary
	fp, err := os.Open(filepath)
	if err != nil {
		return
	}
	defer fp.Close()

	// do the update
	err = FromStream(fp)
	return
}

// FromStream reads the contents of the supplied io.Reader newBinary
// and uses them to update the current program's executable file.
//
// FromStream performs the following actions to ensure a cross-platform safe
// update:
//
// - Creates a new file, /path/to/.program-name.new with mode 0755 and copies
// the contents of newBinary into the file
//
// - Renames the current program's executable file from /path/to/program-name
// to /path/to/.program-name.old
//
// - Renames /path/to/.program-name.new to /path/to/program-name
//
// - If the rename is successful, it erases /path/to/.program.old. If this operation
// fails, no error is reported.
//
// - If the rename is unsuccessful, it attempts to rename /path/to/.program-name.old
// back to /path/to/program-name. If this operation fails, the error is not reported
// in order to not mask the error that caused the rename recovery attempt.
func FromStream(newBinary io.Reader) (err error) {
	// get the path to the executable
	thisExecPath, err := execpath.Get()
	if err != nil {
		return
	}

	// get the directory the executable exists in
	execDir := filepath.Dir(thisExecPath)
	execName := filepath.Base(thisExecPath)

	// Copy the contents of of newbinary to a the new executable file
	newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
	fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
	if err != nil {
		return
	}
	defer fp.Close()
	_, err = io.Copy(fp, newBinary)

	// if we don't call fp.Close(), windows won't let us move the new executable
	// because the file will still be "in use"
	fp.Close()

	// move the existing executable to a new file in the same directory
	oldExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.old", execName))
	err = os.Rename(thisExecPath, oldExecPath)
	if err != nil {
		return
	}

	// move the new exectuable in to become the new program
	err = os.Rename(newExecPath, thisExecPath)

	if err != nil {
		// copy unsuccessful
		_ = os.Rename(oldExecPath, thisExecPath)
	} else {
		// copy successful, remove the old binary
		_ = os.Remove(oldExecPath)
	}

	return
}
<commit_msg>improved go-update's API by separating out a function for downloading as well as downloading and updating. added a function which simply checks if an update is available but does not try to apply it. added a pre-update sanity check which verifies permissions are correct<commit_after>/*
Package update allows a program to "self-update", replacing its executable file
with new bytes.

Package update provides the facility to create user experiences like auto-updating
or user-approved updates which manifest as user prompts in commercial applications
with copy similar to "Restart to being using the new version of X".

Updating your program to a new version is as easy as:

	err := update.FromUrl("http://release.example.com/2.0/myprogram")
	if err != nil {
		fmt.Printf("Update failed: %v", err)
	}

The most low-level API is FromStream() which updates the current executable
with the bytes read from an io.Reader.

Additional APIs are provided for common update strategies which include
updating from a file with FromFile() and updating from the internet with
FromUrl().

Using the more advaced Download.UpdateFromUrl() API gives you the ability
to resume an interrupted download to enable large updates to complete even
over intermittent or slow connections. This API also enables more fine-grained
control over how the update is downloaded from the internet as well as access to
download progress,
*/
package update

import (
	"compress/gzip"
	"fmt"
	execpath "github.com/inconshreveable/go-execpath"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
)

type MeteredReader struct {
	rd        io.ReadCloser
	totalSize int64
	progress  chan int
	totalRead int64
	ticks     int64
}

func (m *MeteredReader) Close() error {
	return m.rd.Close()
}

func (m *MeteredReader) Read(b []byte) (n int, err error) {
	chunkSize := (m.totalSize / 100) + 1
	lenB := int64(len(b))

	var nChunk int
	for start := int64(0); start < lenB; start += int64(nChunk) {
		end := start + chunkSize
		if end > lenB {
			end = lenB
		}

		nChunk, err = m.rd.Read(b[start:end])

		n += nChunk
		m.totalRead += int64(nChunk)

		if m.totalRead > (m.ticks * chunkSize) {
			m.ticks += 1
			// try to send on channel, but don't block if it's full
			select {
			case m.progress <- int(m.ticks + 1):
			default:
			}

			// give the progress channel consumer a chance to run
			runtime.Gosched()
		}

		if err != nil {
			return
		}
	}

	return
}

// We wrap the round tripper when making requests
// because we need to add headers to the requests we make
// even when they are requests made after a redirect
type RoundTripper struct {
	RoundTripFn func(*http.Request) (*http.Response, error)
}

func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	return rt.RoundTripFn(r)
}

// Type Download encapsulates the necessary parameters and state
// needed to download an update from the internet. Create an instance
// with the NewDownload() factory function.
//
// You may only use a Download once,
type Download struct {
	// net/http.Client to use when downloading the update.
	// If nil, a default http.Client is used
	HttpClient *http.Client

	// Path on the file system to dowload the update to
	// If empty, a temporary file is used.
	// After the download begins, this path will be set
	// so that the client can use it to resume aborted
	// downloads
	Path string

	// Progress returns the percentage of the download
	// completed as an integer between 0 and 100
	Progress chan (int)

	// HTTP Method to use in the download request. Default is "GET"
	Method string

	// HTTP URL to issue the download request to
	Url string

	// Set to true when the server confirms a new version is available
	// even if the updating process encounters an error later on
	Available bool
}

// NewDownload initializes a new Download object
func NewDownload(url string) *Download {
	return &Download{
		HttpClient: new(http.Client),
		Progress:   make(chan int),
		Method:     "GET",
		Url: url,
	}
}

func (d *Download) sharedHttp(offset int64) (resp *http.Response, err error) {
	// create the download request
	req, err := http.NewRequest(d.Method, d.Url, nil)
	if err != nil {
		return
	}

	// we have to add headers like this so they get used across redirects
	trans := d.HttpClient.Transport
	if trans == nil {
		trans = http.DefaultTransport
	}

	d.HttpClient.Transport = &RoundTripper{
		RoundTripFn: func(r *http.Request) (*http.Response, error) {
			// add header for download continuation
			if offset > 0 {
				r.Header.Add("Range", fmt.Sprintf("%d-", offset))
			}

			// ask for gzipped content so that net/http won't unzip it for us
			// and destroy the content length header we need for progress calculations
			r.Header.Add("Accept-Encoding", "gzip")

			return trans.RoundTrip(r)
		},
	}

	// issue the download request
	return d.HttpClient.Do(req)
}

func (d *Download) Check() (available bool, err error) {
	resp, err := d.sharedHttp(0)
	if err != nil {
		return
	}
	resp.Body.Close()

	switch resp.StatusCode {
	// ok
	case 200, 206:
		available = true

	// no update available
	case 204:
		available = false

	// server error
	default:
		err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
		return
	}

	return
}

// Get() downloads the given url from the internet to a file on disk
// and then calls FromStream() to update the current program's executable file
// with the contents of that file.
//
// If the update is successful, the downloaded file will be erased from disk.
// Otherwise, it will remain in d.Path to allow the download to resume later
// or be skipped entirely.
//
// Only HTTP/1.1 servers that implement the Range header support resuming a
// partially completed download.
//
// UpdateFromUrl() uses HTTP status codes to determine what action to take.
//
// - The HTTP server should return 200 or 206 for the update to be downloaded.
//
// - The HTTP server should return 204 if no update is available at this time.
//
// - If the HTTP server returns a 3XX redirect, it will be followed
// according to d.HttpClient's redirect policy.
//
// - Any other HTTP status code will cause UpdateFromUrl to return an error.
func (d *Download) Get() (err error) {
	var offset int64 = 0
	var fp *os.File

	// Close the progress channel whenever this function completes
	defer close(d.Progress)

	// open a file where we will stream the downloaded update to
	// we do this first because if the caller specified a non-empty dlpath
	// we need to determine how large it is in order to resume the download
	if d.Path == "" {
		// no dlpath specified, use a random tempfile
		fp, err = ioutil.TempFile("", "update")
		if err != nil {
			return
		}
		defer fp.Close()

		// remember the path
		d.Path = fp.Name()
	} else {
		fp, err = os.OpenFile(d.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600)
		if err != nil {
			return
		}
		defer fp.Close()

		// determine the file size so we can resume the download, if possible
		var fi os.FileInfo
		fi, err = fp.Stat()
		if err != nil {
			return
		}

		offset = fi.Size()
	}

	// start downloading the file
	resp, err := d.sharedHttp(offset)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	// ok
	case 200, 206:
		d.Available = true

	// no update available
	case 204:
		return

	// server error
	default:
		err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
		return
	}

	// Determine how much we have to download
	// net/http sets this to -1 when it is unknown
	clength := resp.ContentLength

	// Read the content from the response body
	rd := resp.Body

	// meter the rate at which we download content for
	// progress reporting if we know how much to expect
	if clength > 0 {
		rd = &MeteredReader{rd: rd, totalSize: clength, progress: d.Progress}
	}

	// Decompress the content if necessary
	if resp.Header.Get("Content-Encoding") == "gzip" {
		rd, err = gzip.NewReader(rd)
		if err != nil {
			return
		}
	}

	// Download the update
	_, err = io.Copy(fp, rd)
	if err != nil {
		return
	}

	return
}

func (d *Download) GetAndUpdate() (err error, errRecover error) {
	// check before we download if this will work
	if err = SanityCheck(); err != nil {
		return
	}

	// download the update
	if err = d.Get(); err != nil {
		return
	}

	// apply the update
	if err, errRecover = FromFile(d.Path); err != nil || errRecover != nil {
		return
	}

	// remove the temporary file
	os.Remove(d.Path)
	return
}

// FromUrl downloads the contents of the given url and uses them to update
// the current program's executable file. It is a convenience function which is equivalent to
//
// 	NewDownload(url).GetAndUpdate()
//
// See Download.Get() for more details.
func FromUrl(url string) (err error, errRecover error) {
	return NewDownload(url).GetAndUpdate()
}

// FromFile reads the contents of the given file and uses them
// to update the current program's executable file by calling FromStream().
func FromFile(filepath string) (err error, errRecover error) {
	// open the new binary
	fp, err := os.Open(filepath)
	if err != nil {
		return
	}
	defer fp.Close()

	// do the update
	return FromStream(fp)
}

// FromStream reads the contents of the supplied io.Reader newBinary
// and uses them to update the current program's executable file.
//
// FromStream performs the following actions to ensure a cross-platform safe
// update:
//
// - Creates a new file, /path/to/.program-name.new with mode 0755 and copies
// the contents of newBinary into the file
//
// - Renames the current program's executable file from /path/to/program-name
// to /path/to/.program-name.old
//
// - Renames /path/to/.program-name.new to /path/to/program-name
//
// - If the rename is successful, it erases /path/to/.program.old. If this operation
// fails, no error is reported.
//
// - If the rename is unsuccessful, it attempts to rename /path/to/.program-name.old
// back to /path/to/program-name. If this operation fails, the error is not reported
// in order to not mask the error that caused the rename recovery attempt.
func FromStream(newBinary io.Reader) (err error, errRecover error) {
	// get the path to the executable
	thisExecPath, err := execpath.Get()
	if err != nil {
		return
	}

	// get the directory the executable exists in
	execDir := filepath.Dir(thisExecPath)
	execName := filepath.Base(thisExecPath)

	// Copy the contents of of newbinary to a the new executable file
	newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
	fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
	if err != nil {
		return
	}
	defer fp.Close()
	_, err = io.Copy(fp, newBinary)

	// if we don't call fp.Close(), windows won't let us move the new executable
	// because the file will still be "in use"
	fp.Close()

	// move the existing executable to a new file in the same directory
	oldExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.old", execName))
	err = os.Rename(thisExecPath, oldExecPath)
	if err != nil {
		return
	}

	// move the new exectuable in to become the new program
	err = os.Rename(newExecPath, thisExecPath)

	if err != nil {
		// copy unsuccessful
		errRecover = os.Rename(oldExecPath, thisExecPath)
	} else {
		// copy successful, remove the old binary
		_ = os.Remove(oldExecPath)
	}

	return
}

// SanityCheck() attempts to determine whether an in-place executable update could
// succeed by performing preliminary checks (to establish valid permissions, etc).
// This helps avoid downloading updates when we know the update can't be successfully
// applied later.
func SanityCheck() (err error) {
	// get the path to the executable
	thisExecPath, err := execpath.Get()
	if err != nil {
		return
	}

	// get the directory the executable exists in
	execDir := filepath.Dir(thisExecPath)
	execName := filepath.Base(thisExecPath)

	// attempt to open a file in the executable's directory
	newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
	fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
	if err != nil {
		return
	}
	defer fp.Close()

	os.Remove(newExecPath)
	return
}
<|endoftext|>


<commit_before>package handler

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	log "github.com/cihub/seelog"

	"github.com/mondough/phosphor/store"
	"github.com/mondough/phosphor/util"
)

// DefaultStore is a reference to our persistence layer which we can query
var DefaultStore store.Store

// Index
// @todo return version information etc
func Index(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, fmt.Sprintf(`{
		"name": "phosphor",
		"version": "%s"
	}`, util.VERSION))
}

// TraceLookup retrieves a trace from the persistence layer
func TraceLookup(w http.ResponseWriter, r *http.Request) {
	traceId := r.URL.Query().Get("traceId")
	if traceId == "" {
		errorResponse(w, http.StatusBadRequest, errors.New("traceId param not provided"))
		return
	}

	log.Debugf("Trace lookup - TraceId: %s", traceId)
	t, err := DefaultStore.ReadTrace(traceId)
	if err != nil {
		log.Errorf("Trace lookup failed: %s", err)
		errorResponse(w, http.StatusInternalServerError, fmt.Errorf("could not load trace: %s", err))
		return
	}

	// If we don't find the trace return 404
	if t == nil {
		log.Debugf("Trace not found: %s", traceId)
		errorResponse(w, http.StatusNotFound, errors.New("traceId not found"))
		return
	}

	// Return trace
	response(
		w,
		map[string]interface{}{
			"trace": prettyFormatTrace(t),
		},
	)
}

// response sends the response back to the client, marshaling to JSON
func response(w http.ResponseWriter, resp interface{}) {
	writeResponse(w, http.StatusOK, resp)
}

// errorResponse marshals an error to JSON and returns this to the client
func errorResponse(w http.ResponseWriter, code int, err error) {
	resp := map[string]interface{}{
		"error": err.Error(),
	}

	writeResponse(w, code, resp)
}

// response marshals a response to json and returns to the client
func writeResponse(w http.ResponseWriter, code int, resp interface{}) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")

	b, err := json.Marshal(resp)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, `{"error":"failed to marshal json"}`)
		return
	}

	w.WriteHeader(code)
	fmt.Fprintln(w, string(b))
}
<commit_msg>Support CORS on /trace endpoint<commit_after>package handler

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	log "github.com/cihub/seelog"

	"github.com/mondough/phosphor/store"
	"github.com/mondough/phosphor/util"
)

// DefaultStore is a reference to our persistence layer which we can query
var DefaultStore store.Store

// Index
// @todo return version information etc
func Index(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, fmt.Sprintf(`{
		"name": "phosphor",
		"version": "%s"
	}`, util.VERSION))
}

// TraceLookup retrieves a trace from the persistence layer
func TraceLookup(w http.ResponseWriter, r *http.Request) {
	traceId := r.URL.Query().Get("traceId")
	if traceId == "" {
		errorResponse(r, w, http.StatusBadRequest, errors.New("traceId param not provided"))
		return
	}

	log.Debugf("Trace lookup - TraceId: %s", traceId)
	t, err := DefaultStore.ReadTrace(traceId)
	if err != nil {
		log.Errorf("Trace lookup failed: %s", err)
		errorResponse(r, w, http.StatusInternalServerError, fmt.Errorf("could not load trace: %s", err))
		return
	}

	// If we don't find the trace return 404
	if t == nil {
		log.Debugf("Trace not found: %s", traceId)
		errorResponse(r, w, http.StatusNotFound, errors.New("traceId not found"))
		return
	}

	// Return trace
	response(
		r,
		w,
		map[string]interface{}{
			"trace": prettyFormatTrace(t),
		},
	)
}

// response sends the response back to the client, marshaling to JSON
func response(r *http.Request, w http.ResponseWriter, resp interface{}) {
	writeResponse(r, w, http.StatusOK, resp)
}

// errorResponse marshals an error to JSON and returns this to the client
func errorResponse(r *http.Request, w http.ResponseWriter, code int, err error) {
	resp := map[string]interface{}{
		"error": err.Error(),
	}

	writeResponse(r, w, code, resp)
}

// response marshals a response to json and returns to the client
func writeResponse(r *http.Request, w http.ResponseWriter, code int, resp interface{}) {

	// Deal with CORS
	if origin := r.Header.Get("Origin"); origin != "" {
		w.Header().Set("Access-Control-Allow-Origin", origin)
		w.Header().Set("Access-Control-Allow-Methods", "DELETE, GET, HEAD, OPTIONS, POST, PUT")
		w.Header().Set("Access-Control-Allow-Credentials", "true")
		// Allow any headers
		if wantedHeaders := r.Header.Get("Access-Control-Request-Headers"); wantedHeaders != "" {
			w.Header().Set("Access-Control-Allow-Headers", wantedHeaders)
		}
	}

	w.Header().Set("Content-Type", "text/plain; charset=utf-8")

	b, err := json.Marshal(resp)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, `{"error":"failed to marshal json"}`)
		return
	}

	w.WriteHeader(code)
	fmt.Fprintln(w, string(b))
}
<|endoftext|>


<commit_before>package handlers

import (
	"fmt"
	"net/http"
	"os"
	"path"
	"strings"
	"time"
)

type (
	staticHandler struct {
		basePath  string
		urlPrefix string
	}
)

func NewStaticHandler(basePath string, urlPrefix string) http.Handler {
	checkFunc := func(r *http.Request) bool {
		exts := []string{".html", ".css", ".js", ".map", ".yml", ".xml", ".json", ".txt", ".md", ".csv", ".svg"}
		for _, ext := range exts {
			if path.Ext(r.URL.Path) == ext {
				return true
			}
		}
		return false
	}

	return NewGzipHandler(checkFunc, &staticHandler{
		basePath:  basePath,
		urlPrefix: urlPrefix,
	})
}

func (h *staticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	p := path.Join(h.basePath, r.URL.Path[len(h.urlPrefix):])

	if isHiddenPath(p) {
		http.NotFound(w, r)
		return
	} else if fi, err := os.Stat(p); err != nil || fi.IsDir() {
		http.NotFound(w, r)
		return
	}

	w.Header().Set("X-Frame-Options", "SAMEORIGIN")
	if isFromMSIE(r) {
		w.Header().Set("X-UA-Compatible", "IE=edge")
	}

	if strings.Contains(p, "/vendor/") || strings.Contains(p, "/assets/") {
		year := time.Hour * 24 * 365
		w.Header().Set("Expires", time.Now().Add(year).Format(http.TimeFormat))
		w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", year/time.Second))
	} else {
		w.Header().Set("Cache-Control", "no-cache")
	}

	http.ServeFile(w, r, p)
}

func isHiddenPath(p string) bool {
	ix := strings.Index(p, "/.")

	return ix != -1 && len(p) > ix+2 && p[ix+2] != '.'
}
<commit_msg>handlers/static: work around non-working Go SVG detection<commit_after>package handlers

import (
	"fmt"
	"net/http"
	"os"
	"path"
	"strings"
	"time"
)

type (
	staticHandler struct {
		basePath  string
		urlPrefix string
	}
)

const (
	_SVG_SIG          = "<SVG"
	_SVG_DETECT_BLOCK = 512
)

func NewStaticHandler(basePath string, urlPrefix string) http.Handler {
	checkFunc := func(r *http.Request) bool {
		exts := []string{".html", ".css", ".js", ".map", ".yml", ".xml", ".json", ".txt", ".md", ".csv", ".svg"}
		for _, ext := range exts {
			if path.Ext(r.URL.Path) == ext {
				return true
			}
		}
		return false
	}

	return NewGzipHandler(checkFunc, &staticHandler{
		basePath:  basePath,
		urlPrefix: urlPrefix,
	})
}

func (h *staticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	p := path.Join(h.basePath, r.URL.Path[len(h.urlPrefix):])

	if isHiddenPath(p) {
		http.NotFound(w, r)
		return
	} else if fi, err := os.Stat(p); err != nil || fi.IsDir() {
		http.NotFound(w, r)
		return
	}

	w.Header().Set("X-Frame-Options", "SAMEORIGIN")
	if isFromMSIE(r) {
		w.Header().Set("X-UA-Compatible", "IE=edge")
	}

	if strings.Contains(p, "/vendor/") || strings.Contains(p, "/assets/") {
		year := time.Hour * 24 * 365
		w.Header().Set("Expires", time.Now().Add(year).Format(http.TimeFormat))
		w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", year/time.Second))
	} else {
		w.Header().Set("Cache-Control", "no-cache")
	}

	// ServeFile() sends 'text/xml; charset=utf-8' for SVG by default
	if path.Ext(p) == "" && detectSVG(p) {
		w.Header().Set("Content-Type", "image/svg+xml")
	}

	http.ServeFile(w, r, p)
}

func detectSVG(p string) bool {
	f, err := os.Open(p)
	if err != nil {
		return false
	}
	defer f.Close()

	buf := make([]byte, _SVG_DETECT_BLOCK)
	f.Read(buf)

	return strings.Contains(strings.ToUpper(string(buf)), _SVG_SIG)
}

func isHiddenPath(p string) bool {
	ix := strings.Index(p, "/.")

	return ix != -1 && len(p) > ix+2 && p[ix+2] != '.'
}
<|endoftext|>


<commit_before>/*
   Copyright 2012 the go.wde authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package win

import (
	"fmt"
	"github.com/AllenDang/w32"
	"github.com/skelterjohn/go.wde"
	"image"
	"unsafe"
)

type EventData struct {
	lastX, lastY int
	button       wde.Button
	noX          int
	trackMouse   bool
}

func (this *EventData) InitEventData() {
	this.noX = 1<<31 - 1
	this.noX++
	this.lastX = this.noX
}

func buttonForDetail(button uint32) wde.Button {
	switch button {
	case w32.WM_LBUTTONDOWN, w32.WM_LBUTTONUP:
		return wde.LeftButton
	case w32.WM_RBUTTONDOWN, w32.WM_RBUTTONUP:
		return wde.RightButton
	case w32.WM_MBUTTONDOWN, w32.WM_MBUTTONUP:
		return wde.MiddleButton
	}
	return 0
}

func WndProc(hwnd w32.HWND, msg uint32, wparam, lparam uintptr) uintptr {
	wnd := GetMsgHandler(hwnd)
	if wnd == nil {
		return uintptr(w32.DefWindowProc(hwnd, msg, wparam, lparam))
	}

	var rc uintptr
	switch msg {
	case w32.WM_LBUTTONDOWN, w32.WM_RBUTTONDOWN, w32.WM_MBUTTONDOWN:
		wnd.button = wnd.button | buttonForDetail(msg)
		var bpe wde.MouseDownEvent
		bpe.Which = buttonForDetail(msg)
		bpe.Where.X = int(lparam) & 0xFFFF
		bpe.Where.Y = int(lparam>>16) & 0xFFFF
		wnd.lastX = bpe.Where.X
		wnd.lastY = bpe.Where.Y
		wnd.events <- bpe

	case w32.WM_LBUTTONUP, w32.WM_RBUTTONUP, w32.WM_MBUTTONUP:
		wnd.button = wnd.button & ^buttonForDetail(msg)
		var bpe wde.MouseUpEvent
		bpe.Which = buttonForDetail(msg)
		bpe.Where.X = int(lparam) & 0xFFFF
		bpe.Where.Y = int(lparam>>16) & 0xFFFF
		wnd.lastX = bpe.Where.X
		wnd.lastY = bpe.Where.Y
		wnd.events <- bpe

	case w32.WM_MOUSEMOVE:
		var mme wde.MouseMovedEvent
		mme.Where.X = int(lparam) & 0xFFFF
		mme.Where.Y = int(lparam>>16) & 0xFFFF
		if wnd.lastX != wnd.noX {
			mme.From.X = int(wnd.lastX)
			mme.From.Y = int(wnd.lastY)
		} else {
			mme.From.X = mme.Where.X
			mme.From.Y = mme.Where.Y
		}
		wnd.lastX = mme.Where.X
		wnd.lastY = mme.Where.Y

		if !wnd.trackMouse {
			var tme w32.TRACKMOUSEEVENT
			tme.CbSize = uint32(unsafe.Sizeof(tme))
			tme.DwFlags = w32.TME_LEAVE
			tme.HwndTrack = hwnd
			tme.DwHoverTime = w32.HOVER_DEFAULT
			w32.TrackMouseEvent(&tme)
			wnd.trackMouse = true
			wnd.events <- wde.MouseEnteredEvent(mme)
		} else {
			if wnd.button == 0 {
				wnd.events <- mme
			} else {
				var mde wde.MouseDraggedEvent
				mde.MouseMovedEvent = mme
				mde.Which = wnd.button
				wnd.events <- mde
			}
		}

	case w32.WM_MOUSELEAVE:
		wnd.trackMouse = false

		var wee wde.MouseExitedEvent
		// TODO: get real position
		wee.Where.Y = wnd.lastX
		wee.Where.X = wnd.lastY
		wnd.events <- wee

	case w32.WM_KEYDOWN:
		// TODO: letter
		key, exists := codeKeys[wparam]
		if !exists {
			key = fmt.Sprintf("%d", wparam)
		}
		ke := wde.KeyEvent{key}

		wnd.events <- wde.KeyDownEvent(ke)
		kpe := wde.KeyTypedEvent{
			KeyEvent: ke,
		}
		wnd.events <- kpe

	case w32.WM_KEYUP:
		// TODO: letter
		key, exists := codeKeys[wparam]
		if !exists {
			key = fmt.Sprintf("%d", wparam)
		}
		wnd.events <- wde.KeyUpEvent{key}

	case w32.WM_SIZE:
		width := int(lparam) & 0xFFFF
		height := int(lparam>>16) & 0xFFFF
		wnd.buffer = NewDIB(image.Rect(0, 0, width, height))
		wnd.events <- wde.ResizeEvent{width, height}
		rc = w32.DefWindowProc(hwnd, msg, wparam, lparam)

	case w32.WM_PAINT:
		rc = w32.DefWindowProc(hwnd, msg, wparam, lparam)

	case w32.WM_CLOSE:
		UnRegMsgHandler(hwnd)
		w32.DestroyWindow(hwnd)
		wnd.events <- wde.CloseEvent{}

	case w32.WM_DESTROY:
		w32.PostQuitMessage(0)

	default:
		rc = w32.DefWindowProc(hwnd, msg, wparam, lparam)
	}

	return rc
}
<commit_msg>win: Report mousewheel buttons<commit_after>/*
   Copyright 2012 the go.wde authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package win

import (
	"fmt"
	"github.com/AllenDang/w32"
	"github.com/skelterjohn/go.wde"
	"log"
	"image"
	"unsafe"
)

type EventData struct {
	lastX, lastY int
	button       wde.Button
	noX          int
	trackMouse   bool
}

func (this *EventData) InitEventData() {
	this.noX = 1<<31 - 1
	this.noX++
	this.lastX = this.noX
}

func buttonForDetail(button uint32) wde.Button {
	switch button {
	case w32.WM_LBUTTONDOWN, w32.WM_LBUTTONUP:
		return wde.LeftButton
	case w32.WM_RBUTTONDOWN, w32.WM_RBUTTONUP:
		return wde.RightButton
	case w32.WM_MBUTTONDOWN, w32.WM_MBUTTONUP:
		return wde.MiddleButton
	}
	return 0
}

func WndProc(hwnd w32.HWND, msg uint32, wparam, lparam uintptr) uintptr {
	wnd := GetMsgHandler(hwnd)
	if wnd == nil {
		return uintptr(w32.DefWindowProc(hwnd, msg, wparam, lparam))
	}

	var rc uintptr
	switch msg {
	case w32.WM_LBUTTONDOWN, w32.WM_RBUTTONDOWN, w32.WM_MBUTTONDOWN:
		wnd.button = wnd.button | buttonForDetail(msg)
		var bpe wde.MouseDownEvent
		bpe.Which = buttonForDetail(msg)
		bpe.Where.X = int(lparam) & 0xFFFF
		bpe.Where.Y = int(lparam>>16) & 0xFFFF
		wnd.lastX = bpe.Where.X
		wnd.lastY = bpe.Where.Y
		wnd.events <- bpe

	case w32.WM_LBUTTONUP, w32.WM_RBUTTONUP, w32.WM_MBUTTONUP:
		wnd.button = wnd.button & ^buttonForDetail(msg)
		var bpe wde.MouseUpEvent
		bpe.Which = buttonForDetail(msg)
		bpe.Where.X = int(lparam) & 0xFFFF
		bpe.Where.Y = int(lparam>>16) & 0xFFFF
		wnd.lastX = bpe.Where.X
		wnd.lastY = bpe.Where.Y
		wnd.events <- bpe

	case w32.WM_MOUSEWHEEL:
		var mde wde.MouseDownEvent
		var mue wde.MouseUpEvent
		mde.Where.X = int(lparam) & 0xFFFF
		mde.Where.Y = int(lparam>>16) & 0xFFFF
		mue.Where.X = int(lparam) & 0xFFFF
		mue.Where.Y = int(lparam>>16) & 0xFFFF
		delta := int16((wparam>>16) & 0xFFFF)
		log.Println(delta)
		if delta > 0 {
			mde.Which = wde.WheelUpButton
			mue.Which = wde.WheelUpButton
		} else {
			mde.Which = wde.WheelDownButton
			mue.Which = wde.WheelDownButton
		}
		wnd.lastX = mde.Where.X
		wnd.lastX = mde.Where.Y
		wnd.events <- mde
		wnd.events <- mue

	case w32.WM_MOUSEMOVE:
		var mme wde.MouseMovedEvent
		mme.Where.X = int(lparam) & 0xFFFF
		mme.Where.Y = int(lparam>>16) & 0xFFFF
		if wnd.lastX != wnd.noX {
			mme.From.X = int(wnd.lastX)
			mme.From.Y = int(wnd.lastY)
		} else {
			mme.From.X = mme.Where.X
			mme.From.Y = mme.Where.Y
		}
		wnd.lastX = mme.Where.X
		wnd.lastY = mme.Where.Y

		if !wnd.trackMouse {
			var tme w32.TRACKMOUSEEVENT
			tme.CbSize = uint32(unsafe.Sizeof(tme))
			tme.DwFlags = w32.TME_LEAVE
			tme.HwndTrack = hwnd
			tme.DwHoverTime = w32.HOVER_DEFAULT
			w32.TrackMouseEvent(&tme)
			wnd.trackMouse = true
			wnd.events <- wde.MouseEnteredEvent(mme)
		} else {
			if wnd.button == 0 {
				wnd.events <- mme
			} else {
				var mde wde.MouseDraggedEvent
				mde.MouseMovedEvent = mme
				mde.Which = wnd.button
				wnd.events <- mde
			}
		}

	case w32.WM_MOUSELEAVE:
		wnd.trackMouse = false

		var wee wde.MouseExitedEvent
		// TODO: get real position
		wee.Where.Y = wnd.lastX
		wee.Where.X = wnd.lastY
		wnd.events <- wee

	case w32.WM_KEYDOWN:
		// TODO: letter
		key, exists := codeKeys[wparam]
		if !exists {
			key = fmt.Sprintf("%d", wparam)
		}
		ke := wde.KeyEvent{key}

		wnd.events <- wde.KeyDownEvent(ke)
		kpe := wde.KeyTypedEvent{
			KeyEvent: ke,
		}
		wnd.events <- kpe

	case w32.WM_KEYUP:
		// TODO: letter
		key, exists := codeKeys[wparam]
		if !exists {
			key = fmt.Sprintf("%d", wparam)
		}
		wnd.events <- wde.KeyUpEvent{key}

	case w32.WM_SIZE:
		width := int(lparam) & 0xFFFF
		height := int(lparam>>16) & 0xFFFF
		wnd.buffer = NewDIB(image.Rect(0, 0, width, height))
		wnd.events <- wde.ResizeEvent{width, height}
		rc = w32.DefWindowProc(hwnd, msg, wparam, lparam)

	case w32.WM_PAINT:
		rc = w32.DefWindowProc(hwnd, msg, wparam, lparam)

	case w32.WM_CLOSE:
		UnRegMsgHandler(hwnd)
		w32.DestroyWindow(hwnd)
		wnd.events <- wde.CloseEvent{}

	case w32.WM_DESTROY:
		w32.PostQuitMessage(0)

	default:
		rc = w32.DefWindowProc(hwnd, msg, wparam, lparam)
	}

	return rc
}
<|endoftext|>


<commit_before>package hclwrite

import (
	"github.com/hashicorp/hcl2/hcl/hclsyntax"
)

// placeholder token used when we don't have a token but we don't want
// to pass a real "nil" and complicate things with nil pointer checks
var nilToken = &Token{
	Type:         hclsyntax.TokenNil,
	Bytes:        []byte{},
	SpacesBefore: 0,
}

// format rewrites tokens within the given sequence, in-place, to adjust the
// whitespace around their content to achieve canonical formatting.
func format(tokens Tokens) {
	// Formatting is a multi-pass process. More details on the passes below,
	// but this is the overview:
	// - adjust the leading space on each line to create appropriate
	//   indentation
	// - adjust spaces between tokens in a single cell using a set of rules
	// - adjust the leading space in the "assign" and "comment" cells on each
	//   line to vertically align with neighboring lines.
	// All of these steps operate in-place on the given tokens, so a caller
	// may collect a flat sequence of all of the tokens underlying an AST
	// and pass it here and we will then indirectly modify the AST itself.
	// Formatting must change only whitespace. Specifically, that means
	// changing the SpacesBefore attribute on a token while leaving the
	// other token attributes unchanged.

	lines := linesForFormat(tokens)
	formatIndent(lines)
	formatSpaces(lines)
	formatCells(lines)
}

func formatIndent(lines []formatLine) {
	// Our methodology for indents is to take the input one line at a time
	// and count the bracketing delimiters on each line. If a line has a net
	// increase in open brackets, we increase the indent level by one and
	// remember how many new openers we had. If the line has a net _decrease_,
	// we'll compare it to the most recent number of openers and decrease the
	// dedent level by one each time we pass an indent level remembered
	// earlier.
	// The "indent stack" used here allows for us to recognize degenerate
	// input where brackets are not symmetrical within lines and avoid
	// pushing things too far left or right, creating confusion.

	// We'll start our indent stack at a reasonable capacity to minimize the
	// chance of us needing to grow it; 10 here means 10 levels of indent,
	// which should be more than enough for reasonable HCL uses.
	indents := make([]int, 0, 10)

	for i := range lines {
		// TODO: need to track when we're inside a multi-line template and
		// suspend indentation processing.

		line := &lines[i]
		if len(line.lead) == 0 {
			continue
		}
		if line.lead[0].Type == hclsyntax.TokenNewline {
			// Never place spaces before a newline
			line.lead[0].SpacesBefore = 0
			continue
		}

		netBrackets := 0
		for _, token := range line.lead {
			netBrackets += tokenBracketChange(token)
		}
		for _, token := range line.assign {
			netBrackets += tokenBracketChange(token)
		}

		switch {
		case netBrackets > 0:
			line.lead[0].SpacesBefore = 2 * len(indents)
			indents = append(indents, netBrackets)
		case netBrackets < 0:
			closed := -netBrackets
			for closed > 0 && len(indents) > 0 {
				switch {

				case closed > indents[len(indents)-1]:
					closed -= indents[len(indents)-1]
					indents = indents[:len(indents)-1]

				case closed < indents[len(indents)-1]:
					indents[len(indents)-1] -= closed
					closed = 0

				default:
					indents = indents[:len(indents)-1]
					closed = 0
				}
			}
			line.lead[0].SpacesBefore = 2 * len(indents)
		default:
			line.lead[0].SpacesBefore = 2 * len(indents)
		}
	}
}

func formatSpaces(lines []formatLine) {
	for _, line := range lines {
		for i, token := range line.lead {
			var before, after *Token
			if i > 0 {
				before = line.lead[i-1]
			} else {
				before = nilToken
			}
			if i < (len(line.lead) - 1) {
				after = line.lead[i+1]
			} else {
				after = nilToken
			}
			if spaceAfterToken(token, before, after) {
				after.SpacesBefore = 1
			} else {
				after.SpacesBefore = 0
			}
		}
		for i, token := range line.assign {
			if i == 0 {
				// first token in "assign" always has one space before to
				// separate the equals sign from what it's assigning.
				token.SpacesBefore = 1
			}

			var before, after *Token
			if i > 0 {
				before = line.assign[i-1]
			} else {
				before = nilToken
			}
			if i < (len(line.assign) - 1) {
				after = line.assign[i+1]
			} else {
				after = nilToken
			}
			if spaceAfterToken(token, before, after) {
				after.SpacesBefore = 1
			} else {
				after.SpacesBefore = 0
			}
		}

	}
}

func formatCells(lines []formatLine) {

	chainStart := -1
	maxColumns := 0

	// We'll deal with the "assign" cell first, since moving that will
	// also impact the "comment" cell.
	closeAssignChain := func(i int) {
		for _, chainLine := range lines[chainStart:i] {
			columns := chainLine.lead.Columns()
			spaces := (maxColumns - columns) + 1
			chainLine.assign[0].SpacesBefore = spaces
		}
		chainStart = -1
		maxColumns = 0
	}
	for i, line := range lines {
		if line.assign == nil {
			if chainStart != -1 {
				closeAssignChain(i)
			}
		} else {
			if chainStart == -1 {
				chainStart = i
			}
			columns := line.lead.Columns()
			if columns > maxColumns {
				maxColumns = columns
			}
		}
	}
	if chainStart != -1 {
		closeAssignChain(len(lines))
	}

	// Now we'll deal with the comments
	closeCommentChain := func(i int) {
		for _, chainLine := range lines[chainStart:i] {
			columns := chainLine.lead.Columns() + chainLine.assign.Columns()
			spaces := (maxColumns - columns) + 1
			chainLine.comment[0].SpacesBefore = spaces
		}
		chainStart = -1
		maxColumns = 0
	}
	for i, line := range lines {
		if line.comment == nil {
			if chainStart != -1 {
				closeCommentChain(i)
			}
		} else {
			if chainStart == -1 {
				chainStart = i
			}
			columns := line.lead.Columns() + line.assign.Columns()
			if columns > maxColumns {
				maxColumns = columns
			}
		}
	}
	if chainStart != -1 {
		closeCommentChain(len(lines))
	}

}

// spaceAfterToken decides whether a particular subject token should have a
// space after it when surrounded by the given before and after tokens.
// "before" can be TokenNil, if the subject token is at the start of a sequence.
func spaceAfterToken(subject, before, after *Token) bool {
	switch {

	case after.Type == hclsyntax.TokenNewline || after.Type == hclsyntax.TokenNil:
		// Never add spaces before a newline
		return false

	case subject.Type == hclsyntax.TokenIdent && after.Type == hclsyntax.TokenOParen:
		// Don't split a function name from open paren in a call
		return false

	case subject.Type == hclsyntax.TokenDot || after.Type == hclsyntax.TokenDot:
		// Don't use spaces around attribute access dots
		return false

	case after.Type == hclsyntax.TokenComma:
		// No space right before a comma in an argument list
		return false

	case subject.Type == hclsyntax.TokenQuotedLit || subject.Type == hclsyntax.TokenStringLit || subject.Type == hclsyntax.TokenOQuote || subject.Type == hclsyntax.TokenOHeredoc || after.Type == hclsyntax.TokenQuotedLit || after.Type == hclsyntax.TokenStringLit || after.Type == hclsyntax.TokenCQuote || after.Type == hclsyntax.TokenCHeredoc:
		// No extra spaces within templates
		return false

	case after.Type == hclsyntax.TokenOBrack && (subject.Type == hclsyntax.TokenIdent || subject.Type == hclsyntax.TokenNumberLit || tokenBracketChange(subject) < 0):
		return false

	case subject.Type == hclsyntax.TokenMinus:
		// Since a minus can either be subtraction or negation, and the latter
		// should _not_ have a space after it, we need to use some heuristics
		// to decide which case this is.
		// We guess that we have a negation if the token before doesn't look
		// like it could be the end of an expression.

		switch before.Type {

		case hclsyntax.TokenNil:
			// Minus at the start of input must be a negation
			return false

		case hclsyntax.TokenOParen, hclsyntax.TokenOBrace, hclsyntax.TokenOBrack, hclsyntax.TokenEqual, hclsyntax.TokenColon, hclsyntax.TokenComma, hclsyntax.TokenQuestion:
			// Minus immediately after an opening bracket or separator must be a negation.
			return false

		case hclsyntax.TokenPlus, hclsyntax.TokenStar, hclsyntax.TokenSlash, hclsyntax.TokenPercent, hclsyntax.TokenMinus:
			// Minus immediately after another arithmetic operator must be negation.
			return false

		case hclsyntax.TokenEqualOp, hclsyntax.TokenNotEqual, hclsyntax.TokenGreaterThan, hclsyntax.TokenGreaterThanEq, hclsyntax.TokenLessThan, hclsyntax.TokenLessThanEq:
			// Minus immediately after another comparison operator must be negation.
			return false

		case hclsyntax.TokenAnd, hclsyntax.TokenOr, hclsyntax.TokenBang:
			// Minus immediately after logical operator doesn't make sense but probably intended as negation.
			return false

		default:
			return true
		}

	case tokenBracketChange(subject) > 0:
		// No spaces after open brackets
		return false

	case tokenBracketChange(after) < 0:
		// No spaces before close brackets
		return false

	default:
		// Most tokens are space-separated
		return true

	}
}

func linesForFormat(tokens Tokens) []formatLine {
	if len(tokens) == 0 {
		// should never happen, since we should always have EOF, but let's
		// not crash anyway.
		return make([]formatLine, 0)
	}

	// first we'll count our lines, so we can allocate the array for them in
	// a single block. (We want to minimize memory pressure in this codepath,
	// so it can be run somewhat-frequently by editor integrations.)
	lineCount := 1 // if there are zero newlines then there is one line
	for _, tok := range tokens {
		if tokenIsNewline(tok) {
			lineCount++
		}
	}

	// To start, we'll just put everything in the "lead" cell on each line,
	// and then do another pass over the lines afterwards to adjust.
	lines := make([]formatLine, lineCount)
	li := 0
	lineStart := 0
	for i, tok := range tokens {
		if tok.Type == hclsyntax.TokenEOF {
			// The EOF token doesn't belong to any line, and terminates the
			// token sequence.
			lines[li].lead = tokens[lineStart:i]
			break
		}

		if tokenIsNewline(tok) {
			lines[li].lead = tokens[lineStart : i+1]
			lineStart = i + 1
			li++
		}
	}

	// Now we'll pick off any trailing comments and attribute assignments
	// to shuffle off into the "comment" and "assign" cells.
	for i := range lines {
		line := &lines[i]
		if len(line.lead) == 0 {
			// if the line is empty then there's nothing for us to do
			// (this should happen only for the final line, because all other
			// lines would have a newline token of some kind)
			continue
		}

		if len(line.lead) > 1 && line.lead[len(line.lead)-1].Type == hclsyntax.TokenComment {
			line.comment = line.lead[len(line.lead)-1:]
			line.lead = line.lead[:len(line.lead)-1]
		}

		for i, tok := range line.lead {
			if i > 0 && tok.Type == hclsyntax.TokenEqual {
				// We only move the tokens into "assign" if the RHS seems to
				// be a whole expression, which we determine by counting
				// brackets. If there's a net positive number of brackets
				// then that suggests we're introducing a multi-line expression.
				netBrackets := 0
				for _, token := range line.lead[i:] {
					netBrackets += tokenBracketChange(token)
				}

				if netBrackets == 0 {
					line.assign = line.lead[i:]
					line.lead = line.lead[:i]
				}
				break
			}
		}
	}

	return lines
}

func tokenIsNewline(tok *Token) bool {
	if tok.Type == hclsyntax.TokenNewline {
		return true
	} else if tok.Type == hclsyntax.TokenComment {
		// Single line tokens (# and //) consume their terminating newline,
		// so we need to treat them as newline tokens as well.
		if len(tok.Bytes) > 0 && tok.Bytes[len(tok.Bytes)-1] == '\n' {
			return true
		}
	}
	return false
}

func tokenBracketChange(tok *Token) int {
	switch tok.Type {
	case hclsyntax.TokenOBrace, hclsyntax.TokenOBrack, hclsyntax.TokenOParen, hclsyntax.TokenTemplateControl, hclsyntax.TokenTemplateInterp:
		return 1
	case hclsyntax.TokenCBrace, hclsyntax.TokenCBrack, hclsyntax.TokenCParen, hclsyntax.TokenTemplateSeqEnd:
		return -1
	default:
		return 0
	}
}

// formatLine represents a single line of source code for formatting purposes,
// splitting its tokens into up to three "cells":
//
// lead: always present, representing everything up to one of the others
// assign: if line contains an attribute assignment, represents the tokens
//    starting at (and including) the equals symbol
// comment: if line contains any non-comment tokens and ends with a
//    single-line comment token, represents the comment.
//
// When formatting, the leading spaces of the first tokens in each of these
// cells is adjusted to align vertically their occurences on consecutive
// rows.
type formatLine struct {
	lead    Tokens
	assign  Tokens
	comment Tokens
}
<commit_msg>hclwrite: Allow format to be called on fragment of tokens<commit_after>package hclwrite

import (
	"github.com/hashicorp/hcl2/hcl/hclsyntax"
)

// placeholder token used when we don't have a token but we don't want
// to pass a real "nil" and complicate things with nil pointer checks
var nilToken = &Token{
	Type:         hclsyntax.TokenNil,
	Bytes:        []byte{},
	SpacesBefore: 0,
}

// format rewrites tokens within the given sequence, in-place, to adjust the
// whitespace around their content to achieve canonical formatting.
func format(tokens Tokens) {
	// Formatting is a multi-pass process. More details on the passes below,
	// but this is the overview:
	// - adjust the leading space on each line to create appropriate
	//   indentation
	// - adjust spaces between tokens in a single cell using a set of rules
	// - adjust the leading space in the "assign" and "comment" cells on each
	//   line to vertically align with neighboring lines.
	// All of these steps operate in-place on the given tokens, so a caller
	// may collect a flat sequence of all of the tokens underlying an AST
	// and pass it here and we will then indirectly modify the AST itself.
	// Formatting must change only whitespace. Specifically, that means
	// changing the SpacesBefore attribute on a token while leaving the
	// other token attributes unchanged.

	lines := linesForFormat(tokens)
	formatIndent(lines)
	formatSpaces(lines)
	formatCells(lines)
}

func formatIndent(lines []formatLine) {
	// Our methodology for indents is to take the input one line at a time
	// and count the bracketing delimiters on each line. If a line has a net
	// increase in open brackets, we increase the indent level by one and
	// remember how many new openers we had. If the line has a net _decrease_,
	// we'll compare it to the most recent number of openers and decrease the
	// dedent level by one each time we pass an indent level remembered
	// earlier.
	// The "indent stack" used here allows for us to recognize degenerate
	// input where brackets are not symmetrical within lines and avoid
	// pushing things too far left or right, creating confusion.

	// We'll start our indent stack at a reasonable capacity to minimize the
	// chance of us needing to grow it; 10 here means 10 levels of indent,
	// which should be more than enough for reasonable HCL uses.
	indents := make([]int, 0, 10)

	for i := range lines {
		// TODO: need to track when we're inside a multi-line template and
		// suspend indentation processing.

		line := &lines[i]
		if len(line.lead) == 0 {
			continue
		}
		if line.lead[0].Type == hclsyntax.TokenNewline {
			// Never place spaces before a newline
			line.lead[0].SpacesBefore = 0
			continue
		}

		netBrackets := 0
		for _, token := range line.lead {
			netBrackets += tokenBracketChange(token)
		}
		for _, token := range line.assign {
			netBrackets += tokenBracketChange(token)
		}

		switch {
		case netBrackets > 0:
			line.lead[0].SpacesBefore = 2 * len(indents)
			indents = append(indents, netBrackets)
		case netBrackets < 0:
			closed := -netBrackets
			for closed > 0 && len(indents) > 0 {
				switch {

				case closed > indents[len(indents)-1]:
					closed -= indents[len(indents)-1]
					indents = indents[:len(indents)-1]

				case closed < indents[len(indents)-1]:
					indents[len(indents)-1] -= closed
					closed = 0

				default:
					indents = indents[:len(indents)-1]
					closed = 0
				}
			}
			line.lead[0].SpacesBefore = 2 * len(indents)
		default:
			line.lead[0].SpacesBefore = 2 * len(indents)
		}
	}
}

func formatSpaces(lines []formatLine) {
	for _, line := range lines {
		for i, token := range line.lead {
			var before, after *Token
			if i > 0 {
				before = line.lead[i-1]
			} else {
				before = nilToken
			}
			if i < (len(line.lead) - 1) {
				after = line.lead[i+1]
			} else {
				after = nilToken
			}
			if spaceAfterToken(token, before, after) {
				after.SpacesBefore = 1
			} else {
				after.SpacesBefore = 0
			}
		}
		for i, token := range line.assign {
			if i == 0 {
				// first token in "assign" always has one space before to
				// separate the equals sign from what it's assigning.
				token.SpacesBefore = 1
			}

			var before, after *Token
			if i > 0 {
				before = line.assign[i-1]
			} else {
				before = nilToken
			}
			if i < (len(line.assign) - 1) {
				after = line.assign[i+1]
			} else {
				after = nilToken
			}
			if spaceAfterToken(token, before, after) {
				after.SpacesBefore = 1
			} else {
				after.SpacesBefore = 0
			}
		}

	}
}

func formatCells(lines []formatLine) {

	chainStart := -1
	maxColumns := 0

	// We'll deal with the "assign" cell first, since moving that will
	// also impact the "comment" cell.
	closeAssignChain := func(i int) {
		for _, chainLine := range lines[chainStart:i] {
			columns := chainLine.lead.Columns()
			spaces := (maxColumns - columns) + 1
			chainLine.assign[0].SpacesBefore = spaces
		}
		chainStart = -1
		maxColumns = 0
	}
	for i, line := range lines {
		if line.assign == nil {
			if chainStart != -1 {
				closeAssignChain(i)
			}
		} else {
			if chainStart == -1 {
				chainStart = i
			}
			columns := line.lead.Columns()
			if columns > maxColumns {
				maxColumns = columns
			}
		}
	}
	if chainStart != -1 {
		closeAssignChain(len(lines))
	}

	// Now we'll deal with the comments
	closeCommentChain := func(i int) {
		for _, chainLine := range lines[chainStart:i] {
			columns := chainLine.lead.Columns() + chainLine.assign.Columns()
			spaces := (maxColumns - columns) + 1
			chainLine.comment[0].SpacesBefore = spaces
		}
		chainStart = -1
		maxColumns = 0
	}
	for i, line := range lines {
		if line.comment == nil {
			if chainStart != -1 {
				closeCommentChain(i)
			}
		} else {
			if chainStart == -1 {
				chainStart = i
			}
			columns := line.lead.Columns() + line.assign.Columns()
			if columns > maxColumns {
				maxColumns = columns
			}
		}
	}
	if chainStart != -1 {
		closeCommentChain(len(lines))
	}

}

// spaceAfterToken decides whether a particular subject token should have a
// space after it when surrounded by the given before and after tokens.
// "before" can be TokenNil, if the subject token is at the start of a sequence.
func spaceAfterToken(subject, before, after *Token) bool {
	switch {

	case after.Type == hclsyntax.TokenNewline || after.Type == hclsyntax.TokenNil:
		// Never add spaces before a newline
		return false

	case subject.Type == hclsyntax.TokenIdent && after.Type == hclsyntax.TokenOParen:
		// Don't split a function name from open paren in a call
		return false

	case subject.Type == hclsyntax.TokenDot || after.Type == hclsyntax.TokenDot:
		// Don't use spaces around attribute access dots
		return false

	case after.Type == hclsyntax.TokenComma:
		// No space right before a comma in an argument list
		return false

	case subject.Type == hclsyntax.TokenQuotedLit || subject.Type == hclsyntax.TokenStringLit || subject.Type == hclsyntax.TokenOQuote || subject.Type == hclsyntax.TokenOHeredoc || after.Type == hclsyntax.TokenQuotedLit || after.Type == hclsyntax.TokenStringLit || after.Type == hclsyntax.TokenCQuote || after.Type == hclsyntax.TokenCHeredoc:
		// No extra spaces within templates
		return false

	case after.Type == hclsyntax.TokenOBrack && (subject.Type == hclsyntax.TokenIdent || subject.Type == hclsyntax.TokenNumberLit || tokenBracketChange(subject) < 0):
		return false

	case subject.Type == hclsyntax.TokenMinus:
		// Since a minus can either be subtraction or negation, and the latter
		// should _not_ have a space after it, we need to use some heuristics
		// to decide which case this is.
		// We guess that we have a negation if the token before doesn't look
		// like it could be the end of an expression.

		switch before.Type {

		case hclsyntax.TokenNil:
			// Minus at the start of input must be a negation
			return false

		case hclsyntax.TokenOParen, hclsyntax.TokenOBrace, hclsyntax.TokenOBrack, hclsyntax.TokenEqual, hclsyntax.TokenColon, hclsyntax.TokenComma, hclsyntax.TokenQuestion:
			// Minus immediately after an opening bracket or separator must be a negation.
			return false

		case hclsyntax.TokenPlus, hclsyntax.TokenStar, hclsyntax.TokenSlash, hclsyntax.TokenPercent, hclsyntax.TokenMinus:
			// Minus immediately after another arithmetic operator must be negation.
			return false

		case hclsyntax.TokenEqualOp, hclsyntax.TokenNotEqual, hclsyntax.TokenGreaterThan, hclsyntax.TokenGreaterThanEq, hclsyntax.TokenLessThan, hclsyntax.TokenLessThanEq:
			// Minus immediately after another comparison operator must be negation.
			return false

		case hclsyntax.TokenAnd, hclsyntax.TokenOr, hclsyntax.TokenBang:
			// Minus immediately after logical operator doesn't make sense but probably intended as negation.
			return false

		default:
			return true
		}

	case tokenBracketChange(subject) > 0:
		// No spaces after open brackets
		return false

	case tokenBracketChange(after) < 0:
		// No spaces before close brackets
		return false

	default:
		// Most tokens are space-separated
		return true

	}
}

func linesForFormat(tokens Tokens) []formatLine {
	if len(tokens) == 0 {
		return make([]formatLine, 0)
	}

	// first we'll count our lines, so we can allocate the array for them in
	// a single block. (We want to minimize memory pressure in this codepath,
	// so it can be run somewhat-frequently by editor integrations.)
	lineCount := 1 // if there are zero newlines then there is one line
	for _, tok := range tokens {
		if tokenIsNewline(tok) {
			lineCount++
		}
	}

	// To start, we'll just put everything in the "lead" cell on each line,
	// and then do another pass over the lines afterwards to adjust.
	lines := make([]formatLine, lineCount)
	li := 0
	lineStart := 0
	for i, tok := range tokens {
		if tok.Type == hclsyntax.TokenEOF {
			// The EOF token doesn't belong to any line, and terminates the
			// token sequence.
			lines[li].lead = tokens[lineStart:i]
			break
		}

		if tokenIsNewline(tok) {
			lines[li].lead = tokens[lineStart : i+1]
			lineStart = i + 1
			li++
		}
	}

	// If a set of tokens doesn't end in TokenEOF (e.g. because it's a
	// fragment of tokens from the middle of a file) then we might fall
	// out here with a line still pending.
	if lineStart < len(tokens) {
		lines[li].lead = tokens[lineStart:]
		if lines[li].lead[len(lines[li].lead)-1].Type == hclsyntax.TokenEOF {
			lines[li].lead = lines[li].lead[:len(lines[li].lead)-1]
		}
	}

	// Now we'll pick off any trailing comments and attribute assignments
	// to shuffle off into the "comment" and "assign" cells.
	for i := range lines {
		line := &lines[i]
		if len(line.lead) == 0 {
			// if the line is empty then there's nothing for us to do
			// (this should happen only for the final line, because all other
			// lines would have a newline token of some kind)
			continue
		}

		if len(line.lead) > 1 && line.lead[len(line.lead)-1].Type == hclsyntax.TokenComment {
			line.comment = line.lead[len(line.lead)-1:]
			line.lead = line.lead[:len(line.lead)-1]
		}

		for i, tok := range line.lead {
			if i > 0 && tok.Type == hclsyntax.TokenEqual {
				// We only move the tokens into "assign" if the RHS seems to
				// be a whole expression, which we determine by counting
				// brackets. If there's a net positive number of brackets
				// then that suggests we're introducing a multi-line expression.
				netBrackets := 0
				for _, token := range line.lead[i:] {
					netBrackets += tokenBracketChange(token)
				}

				if netBrackets == 0 {
					line.assign = line.lead[i:]
					line.lead = line.lead[:i]
				}
				break
			}
		}
	}

	return lines
}

func tokenIsNewline(tok *Token) bool {
	if tok.Type == hclsyntax.TokenNewline {
		return true
	} else if tok.Type == hclsyntax.TokenComment {
		// Single line tokens (# and //) consume their terminating newline,
		// so we need to treat them as newline tokens as well.
		if len(tok.Bytes) > 0 && tok.Bytes[len(tok.Bytes)-1] == '\n' {
			return true
		}
	}
	return false
}

func tokenBracketChange(tok *Token) int {
	switch tok.Type {
	case hclsyntax.TokenOBrace, hclsyntax.TokenOBrack, hclsyntax.TokenOParen, hclsyntax.TokenTemplateControl, hclsyntax.TokenTemplateInterp:
		return 1
	case hclsyntax.TokenCBrace, hclsyntax.TokenCBrack, hclsyntax.TokenCParen, hclsyntax.TokenTemplateSeqEnd:
		return -1
	default:
		return 0
	}
}

// formatLine represents a single line of source code for formatting purposes,
// splitting its tokens into up to three "cells":
//
// lead: always present, representing everything up to one of the others
// assign: if line contains an attribute assignment, represents the tokens
//    starting at (and including) the equals symbol
// comment: if line contains any non-comment tokens and ends with a
//    single-line comment token, represents the comment.
//
// When formatting, the leading spaces of the first tokens in each of these
// cells is adjusted to align vertically their occurences on consecutive
// rows.
type formatLine struct {
	lead    Tokens
	assign  Tokens
	comment Tokens
}
<|endoftext|>


<commit_before>// Copyright 2015 CloudAwan LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package healthcheck

import (
//"github.com/cloudawan/cloudone/control/glusterfs"
)

func GetAllStatus() (map[string]interface{}, error) {
	jsonMap := make(map[string]interface{})
	// Kubernetes
	kubernetesNodeControl, err := CreateKubernetesNodeControl()
	if err != nil {
		log.Error(err)
		return jsonMap, err
	}
	jsonMap["kubernetes"], err = kubernetesNodeControl.GetStatus()
	if err != nil {
		log.Error(err)
		return jsonMap, err
	}
	ipSlice, err := kubernetesNodeControl.GetHostWithinFlannelNetwork()
	if err != nil {
		log.Error(err)
		return jsonMap, err
	}
	for _, ip := range ipSlice {
		if jsonMap["kubernetes"].(map[string]interface{})[ip] == nil {
			jsonMap["kubernetes"].(map[string]interface{})[ip] = make(map[string]interface{})
			jsonMap["kubernetes"].(map[string]interface{})[ip].(map[string]interface{})["active"] = false
		} else {
			jsonMap["kubernetes"].(map[string]interface{})[ip].(map[string]interface{})["active"] = true
		}
	}
	// Glusterfs
	/*
		glusterfsVolumeControl, err := glusterfs.CreateGlusterfsVolumeControl()
		if err != nil {
			log.Error(err)
			return jsonMap, err
		}
		hostStatusMap := glusterfsVolumeControl.GetHostStatus()
		jsonMap["glusterfs"] = make(map[string]interface{})
		for key, value := range hostStatusMap {
			jsonMap["glusterfs"].(map[string]interface{})[key] = make(map[string]interface{})
			jsonMap["glusterfs"].(map[string]interface{})[key].(map[string]interface{})["active"] = true
			jsonMap["glusterfs"].(map[string]interface{})[key].(map[string]interface{})["service"] = make(map[string]interface{})
			jsonMap["glusterfs"].(map[string]interface{})[key].(map[string]interface{})["service"].(map[string]interface{})["glusterfs"] = value
		}
	*/
	// CloudOne
	cloudoneControl, err := CreateCloudoneControl()
	if err != nil {
		log.Error(err)
		return jsonMap, err
	}
	jsonMap["cloudone"] = cloudoneControl.GetStatus()

	return jsonMap, nil
}
<commit_msg>Label the disabled but running node inactive<commit_after>// Copyright 2015 CloudAwan LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package healthcheck

import (
//"github.com/cloudawan/cloudone/control/glusterfs"
)

func GetAllStatus() (map[string]interface{}, error) {
	jsonMap := make(map[string]interface{})
	// Kubernetes
	kubernetesNodeControl, err := CreateKubernetesNodeControl()
	if err != nil {
		log.Error(err)
		return jsonMap, err
	}
	jsonMap["kubernetes"], err = kubernetesNodeControl.GetStatus()
	if err != nil {
		log.Error(err)
		return jsonMap, err
	}
	ipSlice, err := kubernetesNodeControl.GetHostWithinFlannelNetwork()
	if err != nil {
		log.Error(err)
		return jsonMap, err
	}
	for key, _ := range jsonMap["kubernetes"].(map[string]interface{}) {
		jsonMap["kubernetes"].(map[string]interface{})[key].(map[string]interface{})["active"] = false
	}
	for _, ip := range ipSlice {
		if jsonMap["kubernetes"].(map[string]interface{})[ip] == nil {
			jsonMap["kubernetes"].(map[string]interface{})[ip] = make(map[string]interface{})
			jsonMap["kubernetes"].(map[string]interface{})[ip].(map[string]interface{})["active"] = false
		} else {
			jsonMap["kubernetes"].(map[string]interface{})[ip].(map[string]interface{})["active"] = true
		}
	}
	// Glusterfs
	/*
		glusterfsVolumeControl, err := glusterfs.CreateGlusterfsVolumeControl()
		if err != nil {
			log.Error(err)
			return jsonMap, err
		}
		hostStatusMap := glusterfsVolumeControl.GetHostStatus()
		jsonMap["glusterfs"] = make(map[string]interface{})
		for key, value := range hostStatusMap {
			jsonMap["glusterfs"].(map[string]interface{})[key] = make(map[string]interface{})
			jsonMap["glusterfs"].(map[string]interface{})[key].(map[string]interface{})["active"] = true
			jsonMap["glusterfs"].(map[string]interface{})[key].(map[string]interface{})["service"] = make(map[string]interface{})
			jsonMap["glusterfs"].(map[string]interface{})[key].(map[string]interface{})["service"].(map[string]interface{})["glusterfs"] = value
		}
	*/
	// CloudOne
	cloudoneControl, err := CreateCloudoneControl()
	if err != nil {
		log.Error(err)
		return jsonMap, err
	}
	jsonMap["cloudone"] = cloudoneControl.GetStatus()

	return jsonMap, nil
}
<|endoftext|>