<commit_before>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/acm"
	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/terraform/helper/schema"
)

func dataSourceAwsAcmCertificate() *schema.Resource {
	return &schema.Resource{
		Read: dataSourceAwsAcmCertificateRead,
		Schema: map[string]*schema.Schema{
			"domain": {
				Type:     schema.TypeString,
				Required: true,
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"statuses": {
				Type:     schema.TypeList,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
			},
			"types": {
				Type:     schema.TypeList,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
			},
		},
	}
}

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

	params := &acm.ListCertificatesInput{}
	target := d.Get("domain")
	statuses, ok := d.GetOk("statuses")
	if ok {
		statusStrings := statuses.([]interface{})
		params.CertificateStatuses = expandStringList(statusStrings)
	} else {
		params.CertificateStatuses = []*string{aws.String("ISSUED")}
	}

	var arns []string
	log.Printf("[DEBUG] Reading ACM Certificate: %s", params)
	err := conn.ListCertificatesPages(params, func(page *acm.ListCertificatesOutput, lastPage bool) bool {
		for _, cert := range page.CertificateSummaryList {
			if *cert.DomainName == target {
				arns = append(arns, *cert.CertificateArn)
			}
		}

		return true
	})
	if err != nil {
		return errwrap.Wrapf("Error describing certificates: {{err}}", err)
	}

	// filter based on certificate type (imported or aws-issued)
	types, ok := d.GetOk("types")
	if ok {
		typesStrings := expandStringList(types.([]interface{}))
		var matchedArns []string
		for _, arn := range arns {
			params := &acm.DescribeCertificateInput{}
			params.CertificateArn = &arn

			description, err := conn.DescribeCertificate(params)
			if err != nil {
				return errwrap.Wrapf("Error describing certificates: {{err}}", err)
			}

			for _, certType := range typesStrings {
				if *description.Certificate.Type == *certType {
					matchedArns = append(matchedArns, arn)
					break
				}
			}
		}

		arns = matchedArns
	}

	if len(arns) == 0 {
		return fmt.Errorf("No certificate for domain %q found in this region.", target)
	}
	if len(arns) > 1 {
		return fmt.Errorf("Multiple certificates for domain %q found in this region.", target)
	}

	d.SetId(time.Now().UTC().String())
	d.Set("arn", arns[0])

	return nil
}
<commit_msg>Implement most_recent flag to filter matching certificates by NotBefore field<commit_after>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/acm"
	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/terraform/helper/schema"
)

func dataSourceAwsAcmCertificate() *schema.Resource {
	return &schema.Resource{
		Read: dataSourceAwsAcmCertificateRead,
		Schema: map[string]*schema.Schema{
			"domain": {
				Type:     schema.TypeString,
				Required: true,
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"statuses": {
				Type:     schema.TypeList,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
			},
			"types": {
				Type:     schema.TypeList,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
			},
			"most_recent": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
				Elem:     &schema.Schema{Type: schema.TypeString},
			},
		},
	}
}

type arnData struct {
	arn       string
	notBefore *time.Time
}

func describeCertificate(arn *arnData, conn *acm.ACM) (*acm.DescribeCertificateOutput, error) {
	params := &acm.DescribeCertificateInput{}
	params.CertificateArn = &arn.arn

	description, err := conn.DescribeCertificate(params)
	if err != nil {
		return nil, err
	}

	return description, nil
}

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

	params := &acm.ListCertificatesInput{}
	target := d.Get("domain")
	statuses, ok := d.GetOk("statuses")
	if ok {
		statusStrings := statuses.([]interface{})
		params.CertificateStatuses = expandStringList(statusStrings)
	} else {
		params.CertificateStatuses = []*string{aws.String("ISSUED")}
	}

	var arns []*arnData
	log.Printf("[DEBUG] Reading ACM Certificate: %s", params)
	err := conn.ListCertificatesPages(params, func(page *acm.ListCertificatesOutput, lastPage bool) bool {
		for _, cert := range page.CertificateSummaryList {
			if *cert.DomainName == target {
				arns = append(arns, &arnData{*cert.CertificateArn, nil})
			}
		}

		return true
	})
	if err != nil {
		return errwrap.Wrapf("Error listing certificates: {{err}}", err)
	}

	// filter based on certificate type (imported or aws-issued)
	types, ok := d.GetOk("types")
	if ok {
		typesStrings := expandStringList(types.([]interface{}))
		var matchedArns []*arnData
		for _, arn := range arns {
			description, err := describeCertificate(arn, conn)
			if err != nil {
				return errwrap.Wrapf("Error describing certificates: {{err}}", err)
			}

			for _, certType := range typesStrings {
				if *description.Certificate.Type == *certType {
					matchedArns = append(
						matchedArns,
						&arnData{arn.arn, description.Certificate.NotBefore},
					)
					break
				}
			}
		}

		arns = matchedArns
	}

	if len(arns) == 0 {
		return fmt.Errorf("No certificate for domain %q found in this region.", target)
	}

	// Get most recent sorting by notBefore date. Notice that createdAt field is only valid
	// for ACM issued certificated but not for imported ones so in a mixed scenario only
	// fields extracted from the certificate are valid. I cannot find a scenario where the
	// most recent certificate is not the one with a most recent `NotBefore` field.
	_, ok = d.GetOk("most_recent")
	if ok {
		mr := arns[0]
		for _, arn := range arns[1:] {
			if arn.notBefore == nil {
				description, err := describeCertificate(arn, conn)
				if err != nil {
					return errwrap.Wrapf("Error describing certificates: {{err}}", err)
				}

				arn.notBefore = description.Certificate.NotBefore
			}

			if arn.notBefore.After(*mr.notBefore) {
				mr = arn
			}
		}

		arns = []*arnData{mr}
	}

	if len(arns) > 1 {
		return fmt.Errorf("Multiple certificates for domain %q found in this region.", target)
	}

	d.SetId(time.Now().UTC().String())
	d.Set("arn", arns[0].arn)

	return nil
}
<|endoftext|>


<commit_before>package api

import (
	"io"
	"io/ioutil"
	"log"
)

// Har converts the given HAR archive file into
// a StormForger test case definition
func (c *Client) Har(fileName string, data io.Reader) (string, error) {
	// TODO how to pass options, like --skip-assets here?
	//      defining a struct maybe, but where?
	//      finally: add options here
	extraParams := map[string]string{}

	req, err := fileUploadRequest(c.APIEndpoint+"/har", "POST", extraParams, "har_file", fileName, "application/json", data)

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	err = resp.Body.Close()
	if err != nil {
		return "", err
	}

	return string(body), nil
}
<commit_msg>compress HAR files prior to upload<commit_after>package api

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
)

// Har converts the given HAR archive file into
// a StormForger test case definition
func (c *Client) Har(fileName string, data io.Reader) (string, error) {
	extraParams := map[string]string{}

	input, err := ioutil.ReadAll(data)
	if err != nil {
		log.Fatal(err)
	}

	if !json.Valid(input) {
		return "", fmt.Errorf("given HAR is not valid JSON")
	}

	var buf bytes.Buffer
	harFileGzip := gzip.NewWriter(&buf)
	io.Copy(harFileGzip, bytes.NewReader(input))
	harFileGzip.Close()

	req, err := fileUploadRequest(c.APIEndpoint+"/har", "POST", extraParams, "har_file", fileName, "application/gzip", bytes.NewReader(buf.Bytes()))

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	err = resp.Body.Close()
	if err != nil {
		return "", err
	}

	return string(body), nil
}
<|endoftext|>


<commit_before>package smtp

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"net/textproto"
)

var (
	tlsConf tls.Config
)

const (
	smtpPort = 1587               //TODO: set to 587 later
	hostName = "mail.example.com" //TODO: make configurable
)

func Boot() {
	log.Println("Booting SMTP server")
	listen()
	// tlsConf := tls.Config{}
}

// listen for plain SMTP connections
func listen() {
	ln, err := net.Listen("tcp", fmt.Sprintf(":%v", smtpPort))
	if err != nil {
		// panic("Could not listen on port 587 for SMTP")
		panic(err)
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Println("Could not accept new connection via SMTP")
		}

		go handleIncomingConnection(conn)
	}
}

func handleIncomingConnection(c net.Conn) {
	var (
		err  error
		imsg string
	)
	//TODO: pipeline
	rdr := textproto.NewReader(bufio.NewReader(c))
	defer c.Close()

	// welcome the client
	advMsg := fmt.Sprintf("220 %s SMTP service ready\n", hostName)
	c.Write([]byte(advMsg))

	// read HELO/EHLO
	imsg, err = rdr.ReadLine()
	if err != nil {
		writeError(c)
		return
	}

	// verify HELO/EHLO
	if imsg[:4] != "EHLO" && imsg[:4] != "HELO" {
		writeError(c)
		return
	}
	//TODO: verify host name

	// signalize waiting for content
	welcomeMsg := fmt.Sprintf("250 Hello %s, we are ready over here", "dummy")
	c.Write([]byte(welcomeMsg))

}

func writeError(c net.Conn) {
	c.Write([]byte("500 Error"))
}
<commit_msg>first smtp support (sending mails with thunderbird, without tls and auth works already)<commit_after>package smtp

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"net/textproto"
)

var (
	tlsConf tls.Config
)

const (
	smtpPort        = 1587               //TODO: set to 587 later
	hostName        = "mail.example.com" //TODO: make configurable
	errMsgBadSyntax = "message not understood"
	maxReceivers    = 10
)

func Boot() {
	log.Println("Booting SMTP server")
	listen()
	// tlsConf := tls.Config{}
}

// listen for plain SMTP connections
func listen() {
	ln, err := net.Listen("tcp", fmt.Sprintf(":%v", smtpPort))
	if err != nil {
		// panic("Could not listen on port 587 for SMTP")
		panic(err)
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Println("Could not accept new connection via SMTP")
		}

		go handleIncomingConnection(conn)
	}
}

func handleIncomingConnection(c net.Conn) {
	var (
		err  error
		imsg string // incoming message
	)
	//TODO: pipeline
	rdr := textproto.NewReader(bufio.NewReader(c))
	defer c.Close()

	// welcome the client
	advMsg := fmt.Sprintf("220 %s SMTP service ready\n", hostName)
	c.Write([]byte(advMsg))

	// read HELO/EHLO
	imsg, err = rdr.ReadLine()
	if err != nil {
		writeError(c, errMsgBadSyntax)
		return
	}

	// verify HELO/EHLO
	if len(imsg) < 4 || (imsg[:4] != "EHLO" && imsg[:4] != "HELO") {
		writeError(c, "invalid EHLO")
		return
	}
	//TODO: verify host name

	// signalize waiting for content
	welcomeMsg := fmt.Sprintf("250 Hello %s, we are ready over here\n", "dummy")
	c.Write([]byte(welcomeMsg))

	// now we expect a MAIL FROM:
	imsg, err = rdr.ReadLine()
	if err != nil {
		writeError(c, errMsgBadSyntax)
		return
	}
	if len(imsg) < 10 || imsg[:10] != "MAIL FROM:" {
		writeError(c, "invalid MAIL FROM message")
		return
	}
	fromAddr := imsg[10:] //TODO: forgive whitespaces
	if string(fromAddr[0]) != "<" || string(fromAddr[len(fromAddr)-1]) != ">" {
		writeError(c, "invalid MAIL FROM address")
		return
	}
	okMsg(c)

	// read receivers (RCPT TO) or wait DATA to start
	receivers := []string{}
	for {
		if len(receivers) > maxReceivers {
			writeError(c, "too many receivers")
			return
		}
		imsg, err = rdr.ReadLine()
		if err != nil {
			writeError(c, errMsgBadSyntax)
			return
		}
		// right now, we can receive another RCPT or an DATA statement
		if imsg[:4] == "DATA" {
			break
		}
		if len(imsg) < 8 || imsg[:8] != "RCPT TO:" {
			writeError(c, "invalid RCPT TO message")
			return
		}
		rcptAddr := imsg[8:] //TODO: forgive whitespaces
		if string(rcptAddr[0]) != "<" || string(rcptAddr[len(rcptAddr)-1]) != ">" {
			writeError(c, "invalid RCPT TO address")
			return
		}
		//TODO: mail address validation
		rcptAddr = rcptAddr[1 : len(rcptAddr)-1]
		receivers = append(receivers, rcptAddr)
		okMsg(c)
	}

	c.Write([]byte("354 End data with <CR><LF>.<CR><LF>\n"))

	for {
		//TODO: implement data limit
		imsg, err = rdr.ReadLine()
		if err != nil {
			writeError(c, errMsgBadSyntax)
		}
		//TODO:<CR><LF>.<CR><LF>
		if imsg == "." {
			// schedule mail for delivery
			c.Write([]byte("250 Ok: queued as 1337\n")) //TODO queue id
			log.Println("Received message")
			return
		}
	}
}

func writeError(c net.Conn, msg string) {
	c.Write([]byte(fmt.Sprintf("500 %s\n", msg)))
}

func okMsg(c net.Conn) {
	c.Write([]byte("250 Ok\n"))
}
<|endoftext|>


<commit_before>package app

import (
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"math/big"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"text/template"

	"github.com/braintree/manners"
	"github.com/elazarl/go-bindata-assetfs"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
	"github.com/yudai/hcl"
	"github.com/yudai/umutex"
)

type InitMessage struct {
	Arguments string `json:"Arguments,omitempty"`
	AuthToken string `json:"AuthToken,omitempty"`
}

type App struct {
	command []string
	options *Options

	upgrader *websocket.Upgrader
	server   *manners.GracefulServer

	titleTemplate *template.Template

	onceMutex *umutex.UnblockingMutex
}

type Options struct {
	Address             string                 `hcl:"address"`
	Port                string                 `hcl:"port"`
	PermitWrite         bool                   `hcl:"permit_write"`
	EnableBasicAuth     bool                   `hcl:"enable_basic_auth"`
	Credential          string                 `hcl:"credential"`
	EnableRandomUrl     bool                   `hcl:"enable_random_url"`
	RandomUrlLength     int                    `hcl:"random_url_length"`
	IndexFile           string                 `hcl:"index_file"`
	EnableTLS           bool                   `hcl:"enable_tls"`
	TLSCrtFile          string                 `hcl:"tls_crt_file"`
	TLSKeyFile          string                 `hcl:"tls_key_file"`
	EnableTLSClientAuth bool                   `hcl:"enable_tls_client_auth"`
	TLSCACrtFile        string                 `hcl:"tls_ca_crt_file"`
	TitleFormat         string                 `hcl:"title_format"`
	EnableReconnect     bool                   `hcl:"enable_reconnect"`
	ReconnectTime       int                    `hcl:"reconnect_time"`
	Once                bool                   `hcl:"once"`
	PermitArguments     bool                   `hcl:"permit_arguments"`
	CloseSignal         int                    `hcl:"close_signal"`
	Preferences         HtermPrefernces        `hcl:"preferences"`
	RawPreferences      map[string]interface{} `hcl:"preferences"`
}

var Version = "0.0.11"

var DefaultOptions = Options{
	Address:             "",
	Port:                "8080",
	PermitWrite:         false,
	EnableBasicAuth:     false,
	Credential:          "",
	EnableRandomUrl:     false,
	RandomUrlLength:     8,
	IndexFile:           "",
	EnableTLS:           false,
	TLSCrtFile:          "~/.gotty.crt",
	TLSKeyFile:          "~/.gotty.key",
	EnableTLSClientAuth: false,
	TLSCACrtFile:        "~/.gotty.ca.crt",
	TitleFormat:         "GoTTY - {{ .Command }} ({{ .Hostname }})",
	EnableReconnect:     false,
	ReconnectTime:       10,
	Once:                false,
	CloseSignal:         1, // syscall.SIGHUP
	Preferences:         HtermPrefernces{},
}

func New(command []string, options *Options) (*App, error) {
	titleTemplate, err := template.New("title").Parse(options.TitleFormat)
	if err != nil {
		return nil, errors.New("Title format string syntax error")
	}

	return &App{
		command: command,
		options: options,

		upgrader: &websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
			Subprotocols:    []string{"gotty"},
		},

		titleTemplate: titleTemplate,

		onceMutex: umutex.New(),
	}, nil
}

func ApplyConfigFile(options *Options, filePath string) error {
	filePath = ExpandHomeDir(filePath)
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return err
	}

	fileString := []byte{}
	log.Printf("Loading config file at: %s", filePath)
	fileString, err := ioutil.ReadFile(filePath)
	if err != nil {
		return err
	}

	if err := hcl.Decode(options, string(fileString)); err != nil {
		return err
	}

	return nil
}

func CheckConfig(options *Options) error {
	if options.EnableTLSClientAuth && !options.EnableTLS {
		return errors.New("TLS client authentication is enabled, but TLS is not enabled")
	}
	return nil
}

func (app *App) Run() error {
	if app.options.PermitWrite {
		log.Printf("Permitting clients to write input to the PTY.")
	}

	if app.options.Once {
		log.Printf("Once option is provided, accepting only one client")
	}

	path := ""
	if app.options.EnableRandomUrl {
		path += "/" + generateRandomString(app.options.RandomUrlLength)
	}

	endpoint := net.JoinHostPort(app.options.Address, app.options.Port)

	wsHandler := http.HandlerFunc(app.handleWS)
	customIndexHandler := http.HandlerFunc(app.handleCustomIndex)
	authTokenHandler := http.HandlerFunc(app.handleAuthToken)
	staticHandler := http.FileServer(
		&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "static"},
	)

	var siteMux = http.NewServeMux()

	if app.options.IndexFile != "" {
		log.Printf("Using index file at " + app.options.IndexFile)
		siteMux.Handle(path+"/", customIndexHandler)
	} else {
		siteMux.Handle(path+"/", http.StripPrefix(path+"/", staticHandler))
	}
	siteMux.Handle(path+"/auth_token.js", authTokenHandler)
	siteMux.Handle(path+"/js/", http.StripPrefix(path+"/", staticHandler))
	siteMux.Handle(path+"/favicon.png", http.StripPrefix(path+"/", staticHandler))

	siteHandler := http.Handler(siteMux)

	if app.options.EnableBasicAuth {
		log.Printf("Using Basic Authentication")
		siteHandler = wrapBasicAuth(siteHandler, app.options.Credential)
	}

	siteHandler = wrapHeaders(siteHandler)

	wsMux := http.NewServeMux()
	wsMux.Handle("/", siteHandler)
	wsMux.Handle(path+"/ws", wsHandler)
	siteHandler = (http.Handler(wsMux))

	siteHandler = wrapLogger(siteHandler)

	scheme := "http"
	if app.options.EnableTLS {
		scheme = "https"
	}
	log.Printf(
		"Server is starting with command: %s",
		strings.Join(app.command, " "),
	)
	if app.options.Address != "" {
		log.Printf(
			"URL: %s",
			(&url.URL{Scheme: scheme, Host: endpoint, Path: path + "/"}).String(),
		)
	} else {
		for _, address := range listAddresses() {
			log.Printf(
				"URL: %s",
				(&url.URL{
					Scheme: scheme,
					Host:   net.JoinHostPort(address, app.options.Port),
					Path:   path + "/",
				}).String(),
			)
		}
	}

	server, err := app.makeServer(endpoint, &siteHandler)
	if err != nil {
		return errors.New("Failed to build server: " + err.Error())
	}
	app.server = manners.NewWithServer(
		server,
	)

	if app.options.EnableTLS {
		crtFile := ExpandHomeDir(app.options.TLSCrtFile)
		keyFile := ExpandHomeDir(app.options.TLSKeyFile)
		log.Printf("TLS crt file: " + crtFile)
		log.Printf("TLS key file: " + keyFile)

		err = app.server.ListenAndServeTLS(crtFile, keyFile)
	} else {
		err = app.server.ListenAndServe()
	}
	if err != nil {
		return err
	}

	log.Printf("Exiting...")

	return nil
}

func (app *App) makeServer(addr string, handler *http.Handler) (*http.Server, error) {
	server := &http.Server{
		Addr:    addr,
		Handler: *handler,
	}

	if app.options.EnableTLSClientAuth {
		caFile := ExpandHomeDir(app.options.TLSCACrtFile)
		log.Printf("CA file: " + caFile)
		caCert, err := ioutil.ReadFile(caFile)
		if err != nil {
			return nil, errors.New("Could not open CA crt file " + caFile)
		}
		caCertPool := x509.NewCertPool()
		if !caCertPool.AppendCertsFromPEM(caCert) {
			return nil, errors.New("Could not parse CA crt file data in " + caFile)
		}
		tlsConfig := &tls.Config{
			ClientCAs:  caCertPool,
			ClientAuth: tls.RequireAndVerifyClientCert,
		}
		server.TLSConfig = tlsConfig
	}

	return server, nil
}

func (app *App) handleWS(w http.ResponseWriter, r *http.Request) {
	log.Printf("New client connected: %s", r.RemoteAddr)

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	conn, err := app.upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("Failed to upgrade connection: " + err.Error())
		return
	}

	_, stream, err := conn.ReadMessage()
	if err != nil {
		log.Print("Failed to authenticate websocket connection")
		conn.Close()
		return
	}
	var init InitMessage

	err = json.Unmarshal(stream, &init)
	if err != nil {
		log.Printf("Failed to parse init message %v", err)
		conn.Close()
		return
	}
	if init.AuthToken != app.options.Credential {
		log.Print("Failed to authenticate websocket connection")
		conn.Close()
		return
	}
	argv := app.command[1:]
	if app.options.PermitArguments {
		if init.Arguments == "" {
			init.Arguments = "?"
		}
		query, err := url.Parse(init.Arguments)
		if err != nil {
			log.Print("Failed to parse arguments")
			conn.Close()
			return
		}
		params := query.Query()["arg"]
		if len(params) != 0 {
			argv = append(argv, params...)
		}
	}

	app.server.StartRoutine()

	if app.options.Once {
		if app.onceMutex.TryLock() { // no unlock required, it will die soon
			log.Printf("Last client accepted, closing the listener.")
			app.server.Close()
		} else {
			log.Printf("Server is already closing.")
			conn.Close()
			return
		}
	}

	cmd := exec.Command(app.command[0], argv...)
	ptyIo, err := pty.Start(cmd)
	if err != nil {
		log.Print("Failed to execute command")
		return
	}
	log.Printf("Command is running for client %s with PID %d (args=%q)", r.RemoteAddr, cmd.Process.Pid, strings.Join(argv, " "))

	context := &clientContext{
		app:        app,
		request:    r,
		connection: conn,
		command:    cmd,
		pty:        ptyIo,
		writeMutex: &sync.Mutex{},
	}

	context.goHandleClient()
}

func (app *App) handleCustomIndex(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, ExpandHomeDir(app.options.IndexFile))
}

func (app *App) handleAuthToken(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("var gotty_auth_token = '" + app.options.Credential + "';"))
}

func (app *App) Exit() (firstCall bool) {
	if app.server != nil {
		firstCall = app.server.Close()
		if firstCall {
			log.Printf("Received Exit command, waiting for all clients to close sessions...")
		}
		return firstCall
	}
	return true
}

func wrapLogger(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rw := &responseWrapper{w, 200}
		handler.ServeHTTP(rw, r)
		log.Printf("%s %d %s %s", r.RemoteAddr, rw.status, r.Method, r.URL.Path)
	})
}

func wrapHeaders(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Server", "GoTTY/"+Version)
		handler.ServeHTTP(w, r)
	})
}

func wrapBasicAuth(handler http.Handler, credential string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token := strings.SplitN(r.Header.Get("Authorization"), " ", 2)

		if len(token) != 2 || strings.ToLower(token[0]) != "basic" {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "Bad Request", http.StatusUnauthorized)
			return
		}

		payload, err := base64.StdEncoding.DecodeString(token[1])
		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}

		if credential != string(payload) {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "authorization failed", http.StatusUnauthorized)
			return
		}

		log.Printf("Basic Authentication Succeeded: %s", r.RemoteAddr)
		handler.ServeHTTP(w, r)
	})
}

func generateRandomString(length int) string {
	const base = 36
	size := big.NewInt(base)
	n := make([]byte, length)
	for i, _ := range n {
		c, _ := rand.Int(rand.Reader, size)
		n[i] = strconv.FormatInt(c.Int64(), base)[0]
	}
	return string(n)
}

func listAddresses() (addresses []string) {
	ifaces, _ := net.Interfaces()

	addresses = make([]string, 0, len(ifaces))

	for _, iface := range ifaces {
		ifAddrs, _ := iface.Addrs()
		for _, ifAddr := range ifAddrs {
			switch v := ifAddr.(type) {
			case *net.IPNet:
				addresses = append(addresses, v.IP.String())
			case *net.IPAddr:
				addresses = append(addresses, v.IP.String())
			}
		}
	}

	return
}

func ExpandHomeDir(path string) string {
	if path[0:2] == "~/" {
		return os.Getenv("HOME") + path[1:]
	} else {
		return path
	}
}
<commit_msg>Release v0.0.12<commit_after>package app

import (
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"math/big"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"text/template"

	"github.com/braintree/manners"
	"github.com/elazarl/go-bindata-assetfs"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
	"github.com/yudai/hcl"
	"github.com/yudai/umutex"
)

type InitMessage struct {
	Arguments string `json:"Arguments,omitempty"`
	AuthToken string `json:"AuthToken,omitempty"`
}

type App struct {
	command []string
	options *Options

	upgrader *websocket.Upgrader
	server   *manners.GracefulServer

	titleTemplate *template.Template

	onceMutex *umutex.UnblockingMutex
}

type Options struct {
	Address             string                 `hcl:"address"`
	Port                string                 `hcl:"port"`
	PermitWrite         bool                   `hcl:"permit_write"`
	EnableBasicAuth     bool                   `hcl:"enable_basic_auth"`
	Credential          string                 `hcl:"credential"`
	EnableRandomUrl     bool                   `hcl:"enable_random_url"`
	RandomUrlLength     int                    `hcl:"random_url_length"`
	IndexFile           string                 `hcl:"index_file"`
	EnableTLS           bool                   `hcl:"enable_tls"`
	TLSCrtFile          string                 `hcl:"tls_crt_file"`
	TLSKeyFile          string                 `hcl:"tls_key_file"`
	EnableTLSClientAuth bool                   `hcl:"enable_tls_client_auth"`
	TLSCACrtFile        string                 `hcl:"tls_ca_crt_file"`
	TitleFormat         string                 `hcl:"title_format"`
	EnableReconnect     bool                   `hcl:"enable_reconnect"`
	ReconnectTime       int                    `hcl:"reconnect_time"`
	Once                bool                   `hcl:"once"`
	PermitArguments     bool                   `hcl:"permit_arguments"`
	CloseSignal         int                    `hcl:"close_signal"`
	Preferences         HtermPrefernces        `hcl:"preferences"`
	RawPreferences      map[string]interface{} `hcl:"preferences"`
}

var Version = "0.0.12"

var DefaultOptions = Options{
	Address:             "",
	Port:                "8080",
	PermitWrite:         false,
	EnableBasicAuth:     false,
	Credential:          "",
	EnableRandomUrl:     false,
	RandomUrlLength:     8,
	IndexFile:           "",
	EnableTLS:           false,
	TLSCrtFile:          "~/.gotty.crt",
	TLSKeyFile:          "~/.gotty.key",
	EnableTLSClientAuth: false,
	TLSCACrtFile:        "~/.gotty.ca.crt",
	TitleFormat:         "GoTTY - {{ .Command }} ({{ .Hostname }})",
	EnableReconnect:     false,
	ReconnectTime:       10,
	Once:                false,
	CloseSignal:         1, // syscall.SIGHUP
	Preferences:         HtermPrefernces{},
}

func New(command []string, options *Options) (*App, error) {
	titleTemplate, err := template.New("title").Parse(options.TitleFormat)
	if err != nil {
		return nil, errors.New("Title format string syntax error")
	}

	return &App{
		command: command,
		options: options,

		upgrader: &websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
			Subprotocols:    []string{"gotty"},
		},

		titleTemplate: titleTemplate,

		onceMutex: umutex.New(),
	}, nil
}

func ApplyConfigFile(options *Options, filePath string) error {
	filePath = ExpandHomeDir(filePath)
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return err
	}

	fileString := []byte{}
	log.Printf("Loading config file at: %s", filePath)
	fileString, err := ioutil.ReadFile(filePath)
	if err != nil {
		return err
	}

	if err := hcl.Decode(options, string(fileString)); err != nil {
		return err
	}

	return nil
}

func CheckConfig(options *Options) error {
	if options.EnableTLSClientAuth && !options.EnableTLS {
		return errors.New("TLS client authentication is enabled, but TLS is not enabled")
	}
	return nil
}

func (app *App) Run() error {
	if app.options.PermitWrite {
		log.Printf("Permitting clients to write input to the PTY.")
	}

	if app.options.Once {
		log.Printf("Once option is provided, accepting only one client")
	}

	path := ""
	if app.options.EnableRandomUrl {
		path += "/" + generateRandomString(app.options.RandomUrlLength)
	}

	endpoint := net.JoinHostPort(app.options.Address, app.options.Port)

	wsHandler := http.HandlerFunc(app.handleWS)
	customIndexHandler := http.HandlerFunc(app.handleCustomIndex)
	authTokenHandler := http.HandlerFunc(app.handleAuthToken)
	staticHandler := http.FileServer(
		&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "static"},
	)

	var siteMux = http.NewServeMux()

	if app.options.IndexFile != "" {
		log.Printf("Using index file at " + app.options.IndexFile)
		siteMux.Handle(path+"/", customIndexHandler)
	} else {
		siteMux.Handle(path+"/", http.StripPrefix(path+"/", staticHandler))
	}
	siteMux.Handle(path+"/auth_token.js", authTokenHandler)
	siteMux.Handle(path+"/js/", http.StripPrefix(path+"/", staticHandler))
	siteMux.Handle(path+"/favicon.png", http.StripPrefix(path+"/", staticHandler))

	siteHandler := http.Handler(siteMux)

	if app.options.EnableBasicAuth {
		log.Printf("Using Basic Authentication")
		siteHandler = wrapBasicAuth(siteHandler, app.options.Credential)
	}

	siteHandler = wrapHeaders(siteHandler)

	wsMux := http.NewServeMux()
	wsMux.Handle("/", siteHandler)
	wsMux.Handle(path+"/ws", wsHandler)
	siteHandler = (http.Handler(wsMux))

	siteHandler = wrapLogger(siteHandler)

	scheme := "http"
	if app.options.EnableTLS {
		scheme = "https"
	}
	log.Printf(
		"Server is starting with command: %s",
		strings.Join(app.command, " "),
	)
	if app.options.Address != "" {
		log.Printf(
			"URL: %s",
			(&url.URL{Scheme: scheme, Host: endpoint, Path: path + "/"}).String(),
		)
	} else {
		for _, address := range listAddresses() {
			log.Printf(
				"URL: %s",
				(&url.URL{
					Scheme: scheme,
					Host:   net.JoinHostPort(address, app.options.Port),
					Path:   path + "/",
				}).String(),
			)
		}
	}

	server, err := app.makeServer(endpoint, &siteHandler)
	if err != nil {
		return errors.New("Failed to build server: " + err.Error())
	}
	app.server = manners.NewWithServer(
		server,
	)

	if app.options.EnableTLS {
		crtFile := ExpandHomeDir(app.options.TLSCrtFile)
		keyFile := ExpandHomeDir(app.options.TLSKeyFile)
		log.Printf("TLS crt file: " + crtFile)
		log.Printf("TLS key file: " + keyFile)

		err = app.server.ListenAndServeTLS(crtFile, keyFile)
	} else {
		err = app.server.ListenAndServe()
	}
	if err != nil {
		return err
	}

	log.Printf("Exiting...")

	return nil
}

func (app *App) makeServer(addr string, handler *http.Handler) (*http.Server, error) {
	server := &http.Server{
		Addr:    addr,
		Handler: *handler,
	}

	if app.options.EnableTLSClientAuth {
		caFile := ExpandHomeDir(app.options.TLSCACrtFile)
		log.Printf("CA file: " + caFile)
		caCert, err := ioutil.ReadFile(caFile)
		if err != nil {
			return nil, errors.New("Could not open CA crt file " + caFile)
		}
		caCertPool := x509.NewCertPool()
		if !caCertPool.AppendCertsFromPEM(caCert) {
			return nil, errors.New("Could not parse CA crt file data in " + caFile)
		}
		tlsConfig := &tls.Config{
			ClientCAs:  caCertPool,
			ClientAuth: tls.RequireAndVerifyClientCert,
		}
		server.TLSConfig = tlsConfig
	}

	return server, nil
}

func (app *App) handleWS(w http.ResponseWriter, r *http.Request) {
	log.Printf("New client connected: %s", r.RemoteAddr)

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	conn, err := app.upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("Failed to upgrade connection: " + err.Error())
		return
	}

	_, stream, err := conn.ReadMessage()
	if err != nil {
		log.Print("Failed to authenticate websocket connection")
		conn.Close()
		return
	}
	var init InitMessage

	err = json.Unmarshal(stream, &init)
	if err != nil {
		log.Printf("Failed to parse init message %v", err)
		conn.Close()
		return
	}
	if init.AuthToken != app.options.Credential {
		log.Print("Failed to authenticate websocket connection")
		conn.Close()
		return
	}
	argv := app.command[1:]
	if app.options.PermitArguments {
		if init.Arguments == "" {
			init.Arguments = "?"
		}
		query, err := url.Parse(init.Arguments)
		if err != nil {
			log.Print("Failed to parse arguments")
			conn.Close()
			return
		}
		params := query.Query()["arg"]
		if len(params) != 0 {
			argv = append(argv, params...)
		}
	}

	app.server.StartRoutine()

	if app.options.Once {
		if app.onceMutex.TryLock() { // no unlock required, it will die soon
			log.Printf("Last client accepted, closing the listener.")
			app.server.Close()
		} else {
			log.Printf("Server is already closing.")
			conn.Close()
			return
		}
	}

	cmd := exec.Command(app.command[0], argv...)
	ptyIo, err := pty.Start(cmd)
	if err != nil {
		log.Print("Failed to execute command")
		return
	}
	log.Printf("Command is running for client %s with PID %d (args=%q)", r.RemoteAddr, cmd.Process.Pid, strings.Join(argv, " "))

	context := &clientContext{
		app:        app,
		request:    r,
		connection: conn,
		command:    cmd,
		pty:        ptyIo,
		writeMutex: &sync.Mutex{},
	}

	context.goHandleClient()
}

func (app *App) handleCustomIndex(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, ExpandHomeDir(app.options.IndexFile))
}

func (app *App) handleAuthToken(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("var gotty_auth_token = '" + app.options.Credential + "';"))
}

func (app *App) Exit() (firstCall bool) {
	if app.server != nil {
		firstCall = app.server.Close()
		if firstCall {
			log.Printf("Received Exit command, waiting for all clients to close sessions...")
		}
		return firstCall
	}
	return true
}

func wrapLogger(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rw := &responseWrapper{w, 200}
		handler.ServeHTTP(rw, r)
		log.Printf("%s %d %s %s", r.RemoteAddr, rw.status, r.Method, r.URL.Path)
	})
}

func wrapHeaders(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Server", "GoTTY/"+Version)
		handler.ServeHTTP(w, r)
	})
}

func wrapBasicAuth(handler http.Handler, credential string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token := strings.SplitN(r.Header.Get("Authorization"), " ", 2)

		if len(token) != 2 || strings.ToLower(token[0]) != "basic" {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "Bad Request", http.StatusUnauthorized)
			return
		}

		payload, err := base64.StdEncoding.DecodeString(token[1])
		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}

		if credential != string(payload) {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "authorization failed", http.StatusUnauthorized)
			return
		}

		log.Printf("Basic Authentication Succeeded: %s", r.RemoteAddr)
		handler.ServeHTTP(w, r)
	})
}

func generateRandomString(length int) string {
	const base = 36
	size := big.NewInt(base)
	n := make([]byte, length)
	for i, _ := range n {
		c, _ := rand.Int(rand.Reader, size)
		n[i] = strconv.FormatInt(c.Int64(), base)[0]
	}
	return string(n)
}

func listAddresses() (addresses []string) {
	ifaces, _ := net.Interfaces()

	addresses = make([]string, 0, len(ifaces))

	for _, iface := range ifaces {
		ifAddrs, _ := iface.Addrs()
		for _, ifAddr := range ifAddrs {
			switch v := ifAddr.(type) {
			case *net.IPNet:
				addresses = append(addresses, v.IP.String())
			case *net.IPAddr:
				addresses = append(addresses, v.IP.String())
			}
		}
	}

	return
}

func ExpandHomeDir(path string) string {
	if path[0:2] == "~/" {
		return os.Getenv("HOME") + path[1:]
	} else {
		return path
	}
}
<|endoftext|>


<commit_before>// Package app implements a NativeActivity glue layer required to
// properly handle the startup process and the native activity events.
//
// Import this package into your Go application to make it Android-compatible.
package app

// #cgo LDFLAGS: -llog
//
// #include <stdlib.h>
// #include <time.h>
import "C"

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"sync"
	"time"
	"unsafe"

	"github.com/xlab/android-go/android"
	"github.com/xlab/android-go/app/internal/callfn"
)

//export callMain
func callMain(mainPC uintptr) {
	// This function as well as the CallFn trampoline is borrowed from the
	// gomobile project.

	// N.B: the main.main must present in the actual app
	// that imports this package.

	for _, name := range []string{"TMPDIR", "PATH", "LD_LIBRARY_PATH"} {
		n := C.CString(name)
		os.Setenv(name, C.GoString(C.getenv(n)))
		C.free(unsafe.Pointer(n))
	}

	// Set timezone.
	//
	// Note that Android zoneinfo is stored in /system/usr/share/zoneinfo,
	// but it is in some kind of packed TZiff file that we do not support
	// yet. As a stopgap, we build a fixed zone using the tm_zone name.
	var curtime C.time_t
	var curtm C.struct_tm
	C.time(&curtime)
	C.localtime_r(&curtime, &curtm)
	tzOffset := int(curtm.tm_gmtoff)
	tz := C.GoString(curtm.tm_zone)
	time.Local = time.FixedZone(tz, tzOffset)

	go callfn.CallFn(mainPC)
}

type NativeActivity interface {
	InitDone()
	NativeWindowRedrawDone()
	InputQueueHandled()
	NativeActivity() *android.NativeActivity
	LifecycleEvents() <-chan LifecycleEvent
	HandleSaveInstanceState(fn SaveStateFunc)
	HandleWindowFocusEvents(out chan<- WindowFocusEvent)
	HandleNativeWindowEvents(out chan<- NativeWindowEvent)
	HandleInputQueueEvents(out chan<- InputQueueEvent)
	HandleContentRectEvents(out chan<- ContentRectEvent)
	HandleActivityEvents(out chan<- ActivityEvent)
	GetAsset(name string) ([]byte, error)
}

var defaultApp = &nativeActivity{
	activityMux:            new(sync.Mutex),
	lifecycleEvents:        make(chan LifecycleEvent),
	nativeWindowRedrawDone: make(chan Signal, 1),
	inputQueueHandled:      make(chan Signal, 1),
	maxDispatchTime:        1 * time.Second,

	initWG: new(sync.WaitGroup),
	mux:    new(sync.RWMutex),
}

func init() {
	defaultApp.initWG.Add(1)
}

func Main(fn func(app NativeActivity)) {
	// runtime.LockOSThread()
	// defer runtime.UnlockOSThread()
	fn(defaultApp)
}

type nativeActivity struct {
	// activity holds the reference to NativeActivity passed to us in the onCreate callback.
	activity    *android.NativeActivity
	activityMux *sync.Mutex

	// lifecycleEvents must be handled in real-time.
	lifecycleEvents chan LifecycleEvent

	// maxDispatchTime sets the maximum time the send operation
	// allowed to wait while channel is blocked.
	maxDispatchTime time.Duration
	// channels below are optional and will be sent to only
	// if handled by an external client.

	windowFocusEvents  chan<- WindowFocusEvent
	nativeWindowEvents chan<- NativeWindowEvent
	inputQueueEvents   chan<- InputQueueEvent
	contentRectEvents  chan<- ContentRectEvent
	activityEvents     chan<- ActivityEvent

	saveInstanceStateFunc  SaveStateFunc
	nativeWindowRedrawDone chan Signal
	inputQueueHandled      chan Signal

	initWG *sync.WaitGroup
	mux    *sync.RWMutex
}

func (a *nativeActivity) InitDone() {
	a.initWG.Done()
}

func (a *nativeActivity) NativeActivity() *android.NativeActivity {
	a.mux.RLock()
	activity := a.activity
	a.mux.RUnlock()
	return activity
}

func (a *nativeActivity) LifecycleEvents() <-chan LifecycleEvent {
	return a.lifecycleEvents
}

type Signal struct{}

// NativeWindowRedrawDone should be invoked as soon as WindowRedrawNeeded event has
// been processed and the redraw has been completed.
//
// Refer to https://developer.android.com/ndk/reference/struct_a_native_activity_callbacks.html
func (a *nativeActivity) NativeWindowRedrawDone() {
	select {
	case a.nativeWindowRedrawDone <- Signal{}:
	default:
	}
}

func (a *nativeActivity) InputQueueHandled() {
	select {
	case a.inputQueueHandled <- Signal{}:
	default:
	}
}

func (a *nativeActivity) HandleWindowFocusEvents(out chan<- WindowFocusEvent) {
	a.mux.Lock()
	a.windowFocusEvents = out
	a.mux.Unlock()
}

func (a *nativeActivity) getWindowFocusEventsOut() chan<- WindowFocusEvent {
	a.mux.RLock()
	out := a.windowFocusEvents
	a.mux.RUnlock()
	return out
}

func (a *nativeActivity) HandleNativeWindowEvents(out chan<- NativeWindowEvent) {
	a.mux.Lock()
	a.nativeWindowEvents = out
	a.mux.Unlock()
}

func (a *nativeActivity) getNativeWindowEventsOut() chan<- NativeWindowEvent {
	a.mux.RLock()
	out := a.nativeWindowEvents
	a.mux.RUnlock()
	return out
}

func (a *nativeActivity) HandleInputQueueEvents(out chan<- InputQueueEvent) {
	a.mux.Lock()
	a.inputQueueEvents = out
	a.mux.Unlock()
}

func (a *nativeActivity) getInputQueueEventsOut() chan<- InputQueueEvent {
	a.mux.RLock()
	out := a.inputQueueEvents
	a.mux.RUnlock()
	return out
}

func (a *nativeActivity) HandleContentRectEvents(out chan<- ContentRectEvent) {
	a.mux.Lock()
	a.contentRectEvents = out
	a.mux.Unlock()
}

func (a *nativeActivity) getContentRectEventsOut() chan<- ContentRectEvent {
	a.mux.RLock()
	out := a.contentRectEvents
	a.mux.RUnlock()
	return out
}

func (a *nativeActivity) HandleActivityEvents(out chan<- ActivityEvent) {
	a.mux.Lock()
	a.activityEvents = out
	a.mux.Unlock()
}

func (a *nativeActivity) getActivityEventsOut() chan<- ActivityEvent {
	a.mux.RLock()
	out := a.activityEvents
	a.mux.RUnlock()
	return out
}

func (a *nativeActivity) HandleSaveInstanceState(fn SaveStateFunc) {
	a.mux.Lock()
	a.saveInstanceStateFunc = fn
	a.mux.Unlock()
}

func (a *nativeActivity) getSaveInstanceStateFunc() SaveStateFunc {
	a.mux.RLock()
	fn := a.saveInstanceStateFunc
	a.mux.RUnlock()
	return fn
}

// GetAsset returns the asset data of the specified asset or an error.
// GetAsset must not be called before the onCreate event was received.
func (a *nativeActivity) GetAsset(name string) ([]byte, error) {
	a.mux.Lock()
	if a.activity == nil {
		a.mux.Unlock()
		err := errors.New("app: GetAsset must be called on initialized native activity")
		return nil, err
	}
	a.activityMux.Lock()
	defer a.activityMux.Unlock()
	if a.activity.AssetManager == nil {
		a.activity.Deref()
	}
	manager := a.activity.AssetManager
	a.mux.Unlock()

	asset := android.AssetManagerOpen(manager, safeString(name), android.AssetModeStreaming)
	if asset == nil {
		return nil, os.ErrNotExist
	}
	defer android.AssetClose(asset)

	buf := new(bytes.Buffer)
	cBuf := allocMemory(1024)
	defer freeMemory(cBuf)
	for {
		n := android.AssetRead(asset, cBuf, 1024)
		if n < 0 {
			err := fmt.Errorf("Read error %d", -n)
			return nil, err
		} else if n == 0 {
			break
		}
		const m = 0x7fffffff
		buf.Write((*[m]byte)(cBuf)[:n])
	}
	return buf.Bytes(), nil
}

var end = "\x00"
var endChar byte = '\x00'

func safeString(s string) string {
	if len(s) == 0 {
		return end
	}
	if s[len(s)-1] != endChar {
		return s + end
	}
	return s
}

// allocMem allocates memory of size n bytes in C.
// The caller is responsible for freeing the this memory via C.free.
func allocMemory(n int) unsafe.Pointer {
	mem, err := C.calloc(C.size_t(n), (C.size_t)(1))
	if err != nil {
		panic("memory alloc error: " + err.Error())
	}
	return mem
}

func freeMemory(mem unsafe.Pointer) {
	C.free(mem)
}
<commit_msg>Revert "Refactor GetAsset()"<commit_after>// Package app implements a NativeActivity glue layer required to
// properly handle the startup process and the native activity events.
//
// Import this package into your Go application to make it Android-compatible.
package app

// #cgo LDFLAGS: -llog
//
// #include <stdlib.h>
// #include <time.h>
import "C"

import (
	"bytes"
	"fmt"
	"os"
	"sync"
	"time"
	"unsafe"

	"github.com/xlab/android-go/android"
	"github.com/xlab/android-go/app/internal/callfn"
)

//export callMain
func callMain(mainPC uintptr) {
	// This function as well as the CallFn trampoline is borrowed from the
	// gomobile project.

	// N.B: the main.main must present in the actual app
	// that imports this package.

	for _, name := range []string{"TMPDIR", "PATH", "LD_LIBRARY_PATH"} {
		n := C.CString(name)
		os.Setenv(name, C.GoString(C.getenv(n)))
		C.free(unsafe.Pointer(n))
	}

	// Set timezone.
	//
	// Note that Android zoneinfo is stored in /system/usr/share/zoneinfo,
	// but it is in some kind of packed TZiff file that we do not support
	// yet. As a stopgap, we build a fixed zone using the tm_zone name.
	var curtime C.time_t
	var curtm C.struct_tm
	C.time(&curtime)
	C.localtime_r(&curtime, &curtm)
	tzOffset := int(curtm.tm_gmtoff)
	tz := C.GoString(curtm.tm_zone)
	time.Local = time.FixedZone(tz, tzOffset)

	go callfn.CallFn(mainPC)
}

type NativeActivity interface {
	InitDone()
	NativeWindowRedrawDone()
	InputQueueHandled()
	NativeActivity() *android.NativeActivity
	LifecycleEvents() <-chan LifecycleEvent
	HandleSaveInstanceState(fn SaveStateFunc)
	HandleWindowFocusEvents(out chan<- WindowFocusEvent)
	HandleNativeWindowEvents(out chan<- NativeWindowEvent)
	HandleInputQueueEvents(out chan<- InputQueueEvent)
	HandleContentRectEvents(out chan<- ContentRectEvent)
	HandleActivityEvents(out chan<- ActivityEvent)
	GetAsset(name string) ([]byte, error)
}

var defaultApp = &nativeActivity{
	lifecycleEvents:        make(chan LifecycleEvent),
	nativeWindowRedrawDone: make(chan Signal, 1),
	inputQueueHandled:      make(chan Signal, 1),
	maxDispatchTime:        1 * time.Second,

	initWG: new(sync.WaitGroup),
	mux:    new(sync.RWMutex),
}

func init() {
	defaultApp.initWG.Add(1)
}

func Main(fn func(app NativeActivity)) {
	// runtime.LockOSThread()
	// defer runtime.UnlockOSThread()
	fn(defaultApp)
}

type nativeActivity struct {
	// activity holds the reference to NativeActivity passed to us in the onCreate callback.
	activity *android.NativeActivity

	// lifecycleEvents must be handled in real-time.
	lifecycleEvents chan LifecycleEvent

	// maxDispatchTime sets the maximum time the send operation
	// allowed to wait while channel is blocked.
	maxDispatchTime time.Duration
	// channels below are optional and will be sent to only
	// if handled by an external client.

	windowFocusEvents  chan<- WindowFocusEvent
	nativeWindowEvents chan<- NativeWindowEvent
	inputQueueEvents   chan<- InputQueueEvent
	contentRectEvents  chan<- ContentRectEvent
	activityEvents     chan<- ActivityEvent

	saveInstanceStateFunc  SaveStateFunc
	nativeWindowRedrawDone chan Signal
	inputQueueHandled      chan Signal

	initWG *sync.WaitGroup
	mux    *sync.RWMutex
}

func (a *nativeActivity) InitDone() {
	a.initWG.Done()
}

func (a *nativeActivity) NativeActivity() *android.NativeActivity {
	a.mux.RLock()
	activity := a.activity
	a.mux.RUnlock()
	return activity
}

func (a *nativeActivity) LifecycleEvents() <-chan LifecycleEvent {
	return a.lifecycleEvents
}

type Signal struct{}

// NativeWindowRedrawDone should be invoked as soon as WindowRedrawNeeded event has
// been processed and the redraw has been completed.
//
// Refer to https://developer.android.com/ndk/reference/struct_a_native_activity_callbacks.html
func (a *nativeActivity) NativeWindowRedrawDone() {
	select {
	case a.nativeWindowRedrawDone <- Signal{}:
	default:
	}
}

func (a *nativeActivity) InputQueueHandled() {
	select {
	case a.inputQueueHandled <- Signal{}:
	default:
	}
}

func (a *nativeActivity) HandleWindowFocusEvents(out chan<- WindowFocusEvent) {
	a.mux.Lock()
	a.windowFocusEvents = out
	a.mux.Unlock()
}

func (a *nativeActivity) getWindowFocusEventsOut() chan<- WindowFocusEvent {
	a.mux.RLock()
	out := a.windowFocusEvents
	a.mux.RUnlock()
	return out
}

func (a *nativeActivity) HandleNativeWindowEvents(out chan<- NativeWindowEvent) {
	a.mux.Lock()
	a.nativeWindowEvents = out
	a.mux.Unlock()
}

func (a *nativeActivity) getNativeWindowEventsOut() chan<- NativeWindowEvent {
	a.mux.RLock()
	out := a.nativeWindowEvents
	a.mux.RUnlock()
	return out
}

func (a *nativeActivity) HandleInputQueueEvents(out chan<- InputQueueEvent) {
	a.mux.Lock()
	a.inputQueueEvents = out
	a.mux.Unlock()
}

func (a *nativeActivity) getInputQueueEventsOut() chan<- InputQueueEvent {
	a.mux.RLock()
	out := a.inputQueueEvents
	a.mux.RUnlock()
	return out
}

func (a *nativeActivity) HandleContentRectEvents(out chan<- ContentRectEvent) {
	a.mux.Lock()
	a.contentRectEvents = out
	a.mux.Unlock()
}

func (a *nativeActivity) getContentRectEventsOut() chan<- ContentRectEvent {
	a.mux.RLock()
	out := a.contentRectEvents
	a.mux.RUnlock()
	return out
}

func (a *nativeActivity) HandleActivityEvents(out chan<- ActivityEvent) {
	a.mux.Lock()
	a.activityEvents = out
	a.mux.Unlock()
}

func (a *nativeActivity) getActivityEventsOut() chan<- ActivityEvent {
	a.mux.RLock()
	out := a.activityEvents
	a.mux.RUnlock()
	return out
}

func (a *nativeActivity) HandleSaveInstanceState(fn SaveStateFunc) {
	a.mux.Lock()
	a.saveInstanceStateFunc = fn
	a.mux.Unlock()
}

func (a *nativeActivity) getSaveInstanceStateFunc() SaveStateFunc {
	a.mux.RLock()
	fn := a.saveInstanceStateFunc
	a.mux.RUnlock()
	return fn
}

// GetAsset returns the asset data of the specified asset or an error. GetAsset must not be called before the onCreate event was received.
func (a *nativeActivity) GetAsset(name string) (byt []byte, err error) {
	a.mux.RLock()
	var activity = a.activity
	activity.Deref()
	var asset = android.AssetManagerOpen(activity.AssetManager, name+"\x00", android.AssetModeStreaming)
	if asset == nil {
		err = os.ErrNotExist
	} else {
		var buf bytes.Buffer
		for {
			var read [1024]byte
			var n = int(android.AssetRead(asset, unsafe.Pointer(&read[0]), uint32(len(read))))
			if n < 0 {
				err = fmt.Errorf("Read error %d", -n)
				break
			} else if n == 0 {
				byt = buf.Bytes()
				break
			}
			buf.Write(read[:n])
		}
		android.AssetClose(asset)
	}
	a.mux.RUnlock()
	return
}
<|endoftext|>


<commit_before>package proxmox

import (
	"encoding/json"
	"fmt"
	"io"
	"strconv"
	"strings"
)

// LXC options for the Proxmox API
type configLxc struct {
	Ostemplate         string      `json:"ostemplate"`
	Arch               string      `json:"arch"`
	BWLimit            int         `json:"bwlimit,omitempty"`
	CMode              string      `json:"cmode"`
	Console            bool        `json:"console"`
	Cores              int         `json:"cores,omitempty"`
	CPULimit           int         `json:"cpulimit"`
	CPUUnits           int         `json:"cpuunits"`
	Description        string      `json:"description,omitempty"`
	Features           QemuDevice  `json:"features,omitempty"`
	Force              bool        `json:"force,omitempty"`
	Hookscript         string      `json:"hookscript,omitempty"`
	Hostname           string      `json:"hostname,omitempty"`
	IgnoreUnpackErrors bool        `json:"ignore-unpack-errors,omitempty"`
	Lock               string      `json:"lock,omitempty"`
	Memory             int         `json:"memory"`
	Mountpoints        QemuDevices `json:"mountpoints,omitempty"`
	Nameserver         string      `json:"nameserver,omitempty"`
	Networks           QemuDevices `json:"networks,omitempty"`
	OnBoot             bool        `json:"onboot"`
	OsType             string      `json:"ostype,omitempty"`
	Password           string      `json:"password,omitempty"`
	Pool               string      `json:"pool,omitempty"`
	Protection         bool        `json:"protection"`
	Restore            bool        `json:"restore,omitempty"`
	RootFs             QemuDevice  `json:"rootfs,omitempty"`
	SearchDomain       string      `json:"searchdomain,omitempty"`
	SSHPublicKeys      string      `json:"ssh-public-keys,omitempty"`
	Start              bool        `json:"start"`
	Startup            string      `json:"startup,omitempty"`
	Storage            string      `json:"storage"`
	Swap               int         `json:"swap"`
	Template           bool        `json:"template,omitempty"`
	Tty                int         `json:"tty"`
	Unique             bool        `json:"unique,omitempty"`
	Unprivileged       bool        `json:"unprivileged"`
	Tags               string      `json:"tags"`
	Unused             []string    `json:"unused,omitempty"`
}

func NewConfigLxc() configLxc {
	return configLxc{
		Arch:         "amd64",
		CMode:        "tty",
		Console:      true,
		CPULimit:     0,
		CPUUnits:     1024,
		Memory:       512,
		OnBoot:       false,
		Protection:   false,
		Start:        false,
		Storage:      "local",
		Swap:         512,
		Template:     false,
		Tty:          2,
		Unprivileged: false,
	}
}

func NewConfigLxcFromJson(io io.Reader) (config configLxc, err error) {
	config = NewConfigLxc()
	err = json.NewDecoder(io).Decode(config)
	return config, err
}

func NewConfigLxcFromApi(vmr *VmRef, client *Client) (config *configLxc, err error) {
	// prepare json map to receive the information from the api
	var lxcConfig map[string]interface{}
	lxcConfig, err = client.GetVmConfig(vmr)
	if err != nil {
		return nil, err
	}

	// prepare a new lxc config to store and return\
	// the information from api
	newConfig := NewConfigLxc()
	config = &newConfig

	arch := ""
	if _, isSet := lxcConfig["arch"]; isSet {
		arch = lxcConfig["arch"].(string)
	}
	cmode := ""
	if _, isSet := lxcConfig["cmode"]; isSet {
		cmode = lxcConfig["cmode"].(string)
	}
	console := true
	if _, isSet := lxcConfig["console"]; isSet {
		console = Itob(int(lxcConfig["console"].(float64)))
	}
	cores := 0
	if _, isSet := lxcConfig["cores"]; isSet {
		cores = int(lxcConfig["cores"].(float64))
	}
	cpulimit := 0
	if _, isSet := lxcConfig["cpulimit"]; isSet {
		cpulimit, _ = strconv.Atoi(lxcConfig["cpulimit"].(string))
	}
	cpuunits := 1024
	if _, isSet := lxcConfig["cpuunits"]; isSet {
		cpuunits = int(lxcConfig["cpuunits"].(float64))
	}
	description := ""
	if _, isSet := lxcConfig["description"]; isSet {
		description = lxcConfig["description"].(string)
	}

	// add features, if any
	if features, isSet := lxcConfig["features"]; isSet {
		featureList := strings.Split(features.(string), ",")

		// create new device map to store features
		featureMap := QemuDevice{}
		// add all features to device map
		featureMap.readDeviceConfig(featureList)
		// prepare empty feature map
		if config.Features == nil {
			config.Features = QemuDevice{}
		}
		// and device config to networks
		if len(featureMap) > 0 {
			config.Features = featureMap
		}
	}

	hookscript := ""
	if _, isSet := lxcConfig["hookscript"]; isSet {
		hookscript = lxcConfig["hookscript"].(string)
	}
	hostname := ""
	if _, isSet := lxcConfig["hostname"]; isSet {
		hostname = lxcConfig["hostname"].(string)
	}
	lock := ""
	if _, isSet := lxcConfig["lock"]; isSet {
		lock = lxcConfig["lock"].(string)
	}
	memory := 512
	if _, isSet := lxcConfig["memory"]; isSet {
		memory = int(lxcConfig["memory"].(float64))
	}

	// add rootfs
	rootfs := QemuDevice{}
	if rootfsStr, isSet := lxcConfig["rootfs"]; isSet {
		rootfs = ParsePMConf(rootfsStr.(string), "volume")
	}

	// add mountpoints
	mpNames := []string{}

	for k, _ := range lxcConfig {
		if mpName := rxMpName.FindStringSubmatch(k); len(mpName) > 0 {
			mpNames = append(mpNames, mpName[0])
		}
	}

	for _, mpName := range mpNames {
		mpConfStr := lxcConfig[mpName].(string)
		mpConfMap := ParseLxcDisk(mpConfStr)

		// add mp id
		id := rxDeviceID.FindStringSubmatch(mpName)
		mpID, _ := strconv.Atoi(id[0])
		mpConfMap["slot"] = mpID

		// prepare empty mountpoint map
		if config.Mountpoints == nil {
			config.Mountpoints = QemuDevices{}
		}
		// and device config to mountpoints
		if len(mpConfMap) > 0 {
			config.Mountpoints[mpID] = mpConfMap
		}
	}

	nameserver := ""
	if _, isSet := lxcConfig["nameserver"]; isSet {
		nameserver = lxcConfig["nameserver"].(string)
	}

	// add networks
	nicNames := []string{}

	for k, _ := range lxcConfig {
		if nicName := rxNicName.FindStringSubmatch(k); len(nicName) > 0 {
			nicNames = append(nicNames, nicName[0])
		}
	}

	for _, nicName := range nicNames {
		nicConfStr := lxcConfig[nicName]
		nicConfList := strings.Split(nicConfStr.(string), ",")

		id := rxDeviceID.FindStringSubmatch(nicName)
		nicID, _ := strconv.Atoi(id[0])
		// add nic id
		nicConfMap := QemuDevice{
			"id": nicID,
		}
		// add rest of device config
		nicConfMap.readDeviceConfig(nicConfList)

		if nicConfMap["firewall"] == 1 {
			nicConfMap["firewall"] = true
		} else if nicConfMap["firewall"] == 0 {
			nicConfMap["firewall"] = false
		}

		// prepare empty network map
		if config.Networks == nil {
			config.Networks = QemuDevices{}
		}
		// and device config to networks
		if len(nicConfMap) > 0 {
			config.Networks[nicID] = nicConfMap
		}
	}

	onboot := false
	if _, isSet := lxcConfig["onboot"]; isSet {
		onboot = Itob(int(lxcConfig["onboot"].(float64)))
	}
	ostype := ""
	if _, isSet := lxcConfig["ostype"]; isSet {
		ostype = lxcConfig["ostype"].(string)
	}
	protection := false
	if _, isSet := lxcConfig["protection"]; isSet {
		protection = Itob(int(lxcConfig["protection"].(float64)))
	}
	searchdomain := ""
	if _, isSet := lxcConfig["searchdomain"]; isSet {
		searchdomain = lxcConfig["searchdomain"].(string)
	}
	startup := ""
	if _, isSet := lxcConfig["startup"]; isSet {
		startup = lxcConfig["startup"].(string)
	}
	swap := 512
	if _, isSet := lxcConfig["swap"]; isSet {
		swap = int(lxcConfig["swap"].(float64))
	}
	template := false
	if _, isSet := lxcConfig["template"]; isSet {
		template = Itob(int(lxcConfig["template"].(float64)))
	}
	tty := 2
	if _, isSet := lxcConfig["tty"]; isSet {
		tty = int(lxcConfig["tty"].(float64))
	}
	unprivileged := false
	if _, isset := lxcConfig["unprivileged"]; isset {
		unprivileged = Itob(int(lxcConfig["unprivileged"].(float64)))
	}
	tags := ""
	if _, isSet := lxcConfig["tags"]; isSet {
		tags = lxcConfig["tags"].(string)
	}
	var unused []string
	if _, isset := lxcConfig["unused"]; isset {
		unused = lxcConfig["unused"].([]string)
	}

	config.Arch = arch
	config.CMode = cmode
	config.Console = console
	config.Cores = cores
	config.CPULimit = cpulimit
	config.CPUUnits = cpuunits
	config.Description = description
	config.OnBoot = onboot
	config.Hookscript = hookscript
	config.Hostname = hostname
	config.Lock = lock
	config.Memory = memory
	config.Nameserver = nameserver
	config.OnBoot = onboot
	config.OsType = ostype
	config.Protection = protection
	config.RootFs = rootfs
	config.SearchDomain = searchdomain
	config.Startup = startup
	config.Swap = swap
	config.Template = template
	config.Tty = tty
	config.Unprivileged = unprivileged
	config.Unused = unused
	config.Tags = tags

	return
}

// create LXC container using the Proxmox API
func (config configLxc) CreateLxc(vmr *VmRef, client *Client) (err error) {
	vmr.SetVmType("lxc")
	paramMap := config.mapToAPIParams()

	// amend vmid
	paramMap["vmid"] = vmr.vmId

	exitStatus, err := client.CreateLxcContainer(vmr.node, paramMap)
	if err != nil {
		params, _ := json.Marshal(&paramMap)
		return fmt.Errorf("Error creating LXC container: %v, error status: %s (params: %v)", err, exitStatus, string(params))
	}
	return
}

func (config configLxc) UpdateConfig(vmr *VmRef, client *Client) (err error) {
	paramMap := config.mapToAPIParams()

	// delete parameters wich are not supported in updated operations
	delete(paramMap, "pool")
	delete(paramMap, "storage")
	delete(paramMap, "password")
	delete(paramMap, "ostemplate")
	delete(paramMap, "start")

	// even though it is listed as a PUT option in the API documentation
	// we remove it here because "it should not be modified manually";
	// also, error "500 unable to modify read-only option: 'unprivileged'"
	delete(paramMap, "unprivileged")

	_, err = client.SetLxcConfig(vmr, paramMap)
	return err
}

func ParseLxcDisk(diskStr string) QemuDevice {
	disk := ParsePMConf(diskStr, "volume")

	// add features, if any
	if mountoptions, isSet := disk["mountoptions"]; isSet {
		moList := strings.Split(mountoptions.(string), ";")
		moMap := map[string]bool{}
		for _, mo := range moList {
			moMap[mo] = true
		}
		disk["mountoptions"] = moMap
	}

	storageName, fileName := ParseSubConf(disk["volume"].(string), ":")
	disk["storage"] = storageName
	disk["file"] = fileName

	return disk
}

func (config configLxc) mapToAPIParams() map[string]interface{} {
	// convert config to map
	params, _ := json.Marshal(&config)
	var paramMap map[string]interface{}
	json.Unmarshal(params, &paramMap)

	// build list of features
	// add features as parameter list to lxc parameters
	// this overwrites the orginal formatting with a
	// comma separated list of "key=value" pairs
	paramMap["features"] = formatDeviceParam(config.Features)

	// format rootfs params as expected
	if rootfs := config.RootFs; rootfs != nil {
		paramMap["rootfs"] = FormatDiskParam(rootfs)
	}

	// build list of mountpoints
	// this does the same as for the feature list
	// except that there can be multiple of these mountpoint sets
	// and each mountpoint set comes with a new id
	for _, mpConfMap := range config.Mountpoints {
		// add mp to lxc parameters
		mpID := mpConfMap["slot"]
		mpName := fmt.Sprintf("mp%v", mpID)
		paramMap[mpName] = FormatDiskParam(mpConfMap)
	}

	// build list of network parameters
	for nicID, nicConfMap := range config.Networks {
		// add nic to lxc parameters
		nicName := fmt.Sprintf("net%v", nicID)
		paramMap[nicName] = formatDeviceParam(nicConfMap)
	}

	// build list of unused volumes for sake of completeness,
	// even if it is not recommended to change these volumes manually
	for volID, vol := range config.Unused {
		// add volume to lxc parameters
		volName := fmt.Sprintf("unused%v", volID)
		paramMap[volName] = vol
	}

	// now that we concatenated the key value parameter
	// list for the networks, mountpoints and unused volumes,
	// remove the original keys, since the Proxmox API does
	// not know how to handle this key
	delete(paramMap, "networks")
	delete(paramMap, "mountpoints")
	delete(paramMap, "unused")

	return paramMap
}
<commit_msg>Exported ConfigLxc type<commit_after>package proxmox

import (
	"encoding/json"
	"fmt"
	"io"
	"strconv"
	"strings"
)

// LXC options for the Proxmox API
type ConfigLxc struct {
	Ostemplate         string      `json:"ostemplate"`
	Arch               string      `json:"arch"`
	BWLimit            int         `json:"bwlimit,omitempty"`
	CMode              string      `json:"cmode"`
	Console            bool        `json:"console"`
	Cores              int         `json:"cores,omitempty"`
	CPULimit           int         `json:"cpulimit"`
	CPUUnits           int         `json:"cpuunits"`
	Description        string      `json:"description,omitempty"`
	Features           QemuDevice  `json:"features,omitempty"`
	Force              bool        `json:"force,omitempty"`
	Hookscript         string      `json:"hookscript,omitempty"`
	Hostname           string      `json:"hostname,omitempty"`
	IgnoreUnpackErrors bool        `json:"ignore-unpack-errors,omitempty"`
	Lock               string      `json:"lock,omitempty"`
	Memory             int         `json:"memory"`
	Mountpoints        QemuDevices `json:"mountpoints,omitempty"`
	Nameserver         string      `json:"nameserver,omitempty"`
	Networks           QemuDevices `json:"networks,omitempty"`
	OnBoot             bool        `json:"onboot"`
	OsType             string      `json:"ostype,omitempty"`
	Password           string      `json:"password,omitempty"`
	Pool               string      `json:"pool,omitempty"`
	Protection         bool        `json:"protection"`
	Restore            bool        `json:"restore,omitempty"`
	RootFs             QemuDevice  `json:"rootfs,omitempty"`
	SearchDomain       string      `json:"searchdomain,omitempty"`
	SSHPublicKeys      string      `json:"ssh-public-keys,omitempty"`
	Start              bool        `json:"start"`
	Startup            string      `json:"startup,omitempty"`
	Storage            string      `json:"storage"`
	Swap               int         `json:"swap"`
	Template           bool        `json:"template,omitempty"`
	Tty                int         `json:"tty"`
	Unique             bool        `json:"unique,omitempty"`
	Unprivileged       bool        `json:"unprivileged"`
	Tags               string      `json:"tags"`
	Unused             []string    `json:"unused,omitempty"`
}

func NewConfigLxc() ConfigLxc {
	return ConfigLxc{
		Arch:         "amd64",
		CMode:        "tty",
		Console:      true,
		CPULimit:     0,
		CPUUnits:     1024,
		Memory:       512,
		OnBoot:       false,
		Protection:   false,
		Start:        false,
		Storage:      "local",
		Swap:         512,
		Template:     false,
		Tty:          2,
		Unprivileged: false,
	}
}

func NewConfigLxcFromJson(io io.Reader) (config ConfigLxc, err error) {
	config = NewConfigLxc()
	err = json.NewDecoder(io).Decode(config)
	return config, err
}

func NewConfigLxcFromApi(vmr *VmRef, client *Client) (config *ConfigLxc, err error) {
	// prepare json map to receive the information from the api
	var lxcConfig map[string]interface{}
	lxcConfig, err = client.GetVmConfig(vmr)
	if err != nil {
		return nil, err
	}

	// prepare a new lxc config to store and return\
	// the information from api
	newConfig := NewConfigLxc()
	config = &newConfig

	arch := ""
	if _, isSet := lxcConfig["arch"]; isSet {
		arch = lxcConfig["arch"].(string)
	}
	cmode := ""
	if _, isSet := lxcConfig["cmode"]; isSet {
		cmode = lxcConfig["cmode"].(string)
	}
	console := true
	if _, isSet := lxcConfig["console"]; isSet {
		console = Itob(int(lxcConfig["console"].(float64)))
	}
	cores := 0
	if _, isSet := lxcConfig["cores"]; isSet {
		cores = int(lxcConfig["cores"].(float64))
	}
	cpulimit := 0
	if _, isSet := lxcConfig["cpulimit"]; isSet {
		cpulimit, _ = strconv.Atoi(lxcConfig["cpulimit"].(string))
	}
	cpuunits := 1024
	if _, isSet := lxcConfig["cpuunits"]; isSet {
		cpuunits = int(lxcConfig["cpuunits"].(float64))
	}
	description := ""
	if _, isSet := lxcConfig["description"]; isSet {
		description = lxcConfig["description"].(string)
	}

	// add features, if any
	if features, isSet := lxcConfig["features"]; isSet {
		featureList := strings.Split(features.(string), ",")

		// create new device map to store features
		featureMap := QemuDevice{}
		// add all features to device map
		featureMap.readDeviceConfig(featureList)
		// prepare empty feature map
		if config.Features == nil {
			config.Features = QemuDevice{}
		}
		// and device config to networks
		if len(featureMap) > 0 {
			config.Features = featureMap
		}
	}

	hookscript := ""
	if _, isSet := lxcConfig["hookscript"]; isSet {
		hookscript = lxcConfig["hookscript"].(string)
	}
	hostname := ""
	if _, isSet := lxcConfig["hostname"]; isSet {
		hostname = lxcConfig["hostname"].(string)
	}
	lock := ""
	if _, isSet := lxcConfig["lock"]; isSet {
		lock = lxcConfig["lock"].(string)
	}
	memory := 512
	if _, isSet := lxcConfig["memory"]; isSet {
		memory = int(lxcConfig["memory"].(float64))
	}

	// add rootfs
	rootfs := QemuDevice{}
	if rootfsStr, isSet := lxcConfig["rootfs"]; isSet {
		rootfs = ParsePMConf(rootfsStr.(string), "volume")
	}

	// add mountpoints
	mpNames := []string{}

	for k, _ := range lxcConfig {
		if mpName := rxMpName.FindStringSubmatch(k); len(mpName) > 0 {
			mpNames = append(mpNames, mpName[0])
		}
	}

	for _, mpName := range mpNames {
		mpConfStr := lxcConfig[mpName].(string)
		mpConfMap := ParseLxcDisk(mpConfStr)

		// add mp id
		id := rxDeviceID.FindStringSubmatch(mpName)
		mpID, _ := strconv.Atoi(id[0])
		mpConfMap["slot"] = mpID

		// prepare empty mountpoint map
		if config.Mountpoints == nil {
			config.Mountpoints = QemuDevices{}
		}
		// and device config to mountpoints
		if len(mpConfMap) > 0 {
			config.Mountpoints[mpID] = mpConfMap
		}
	}

	nameserver := ""
	if _, isSet := lxcConfig["nameserver"]; isSet {
		nameserver = lxcConfig["nameserver"].(string)
	}

	// add networks
	nicNames := []string{}

	for k, _ := range lxcConfig {
		if nicName := rxNicName.FindStringSubmatch(k); len(nicName) > 0 {
			nicNames = append(nicNames, nicName[0])
		}
	}

	for _, nicName := range nicNames {
		nicConfStr := lxcConfig[nicName]
		nicConfList := strings.Split(nicConfStr.(string), ",")

		id := rxDeviceID.FindStringSubmatch(nicName)
		nicID, _ := strconv.Atoi(id[0])
		// add nic id
		nicConfMap := QemuDevice{
			"id": nicID,
		}
		// add rest of device config
		nicConfMap.readDeviceConfig(nicConfList)

		if nicConfMap["firewall"] == 1 {
			nicConfMap["firewall"] = true
		} else if nicConfMap["firewall"] == 0 {
			nicConfMap["firewall"] = false
		}

		// prepare empty network map
		if config.Networks == nil {
			config.Networks = QemuDevices{}
		}
		// and device config to networks
		if len(nicConfMap) > 0 {
			config.Networks[nicID] = nicConfMap
		}
	}

	onboot := false
	if _, isSet := lxcConfig["onboot"]; isSet {
		onboot = Itob(int(lxcConfig["onboot"].(float64)))
	}
	ostype := ""
	if _, isSet := lxcConfig["ostype"]; isSet {
		ostype = lxcConfig["ostype"].(string)
	}
	protection := false
	if _, isSet := lxcConfig["protection"]; isSet {
		protection = Itob(int(lxcConfig["protection"].(float64)))
	}
	searchdomain := ""
	if _, isSet := lxcConfig["searchdomain"]; isSet {
		searchdomain = lxcConfig["searchdomain"].(string)
	}
	startup := ""
	if _, isSet := lxcConfig["startup"]; isSet {
		startup = lxcConfig["startup"].(string)
	}
	swap := 512
	if _, isSet := lxcConfig["swap"]; isSet {
		swap = int(lxcConfig["swap"].(float64))
	}
	template := false
	if _, isSet := lxcConfig["template"]; isSet {
		template = Itob(int(lxcConfig["template"].(float64)))
	}
	tty := 2
	if _, isSet := lxcConfig["tty"]; isSet {
		tty = int(lxcConfig["tty"].(float64))
	}
	unprivileged := false
	if _, isset := lxcConfig["unprivileged"]; isset {
		unprivileged = Itob(int(lxcConfig["unprivileged"].(float64)))
	}
	tags := ""
	if _, isSet := lxcConfig["tags"]; isSet {
		tags = lxcConfig["tags"].(string)
	}
	var unused []string
	if _, isset := lxcConfig["unused"]; isset {
		unused = lxcConfig["unused"].([]string)
	}

	config.Arch = arch
	config.CMode = cmode
	config.Console = console
	config.Cores = cores
	config.CPULimit = cpulimit
	config.CPUUnits = cpuunits
	config.Description = description
	config.OnBoot = onboot
	config.Hookscript = hookscript
	config.Hostname = hostname
	config.Lock = lock
	config.Memory = memory
	config.Nameserver = nameserver
	config.OnBoot = onboot
	config.OsType = ostype
	config.Protection = protection
	config.RootFs = rootfs
	config.SearchDomain = searchdomain
	config.Startup = startup
	config.Swap = swap
	config.Template = template
	config.Tty = tty
	config.Unprivileged = unprivileged
	config.Unused = unused
	config.Tags = tags

	return
}

// create LXC container using the Proxmox API
func (config ConfigLxc) CreateLxc(vmr *VmRef, client *Client) (err error) {
	vmr.SetVmType("lxc")
	paramMap := config.mapToAPIParams()

	// amend vmid
	paramMap["vmid"] = vmr.vmId

	exitStatus, err := client.CreateLxcContainer(vmr.node, paramMap)
	if err != nil {
		params, _ := json.Marshal(&paramMap)
		return fmt.Errorf("Error creating LXC container: %v, error status: %s (params: %v)", err, exitStatus, string(params))
	}
	return
}

func (config ConfigLxc) UpdateConfig(vmr *VmRef, client *Client) (err error) {
	paramMap := config.mapToAPIParams()

	// delete parameters wich are not supported in updated operations
	delete(paramMap, "pool")
	delete(paramMap, "storage")
	delete(paramMap, "password")
	delete(paramMap, "ostemplate")
	delete(paramMap, "start")

	// even though it is listed as a PUT option in the API documentation
	// we remove it here because "it should not be modified manually";
	// also, error "500 unable to modify read-only option: 'unprivileged'"
	delete(paramMap, "unprivileged")

	_, err = client.SetLxcConfig(vmr, paramMap)
	return err
}

func ParseLxcDisk(diskStr string) QemuDevice {
	disk := ParsePMConf(diskStr, "volume")

	// add features, if any
	if mountoptions, isSet := disk["mountoptions"]; isSet {
		moList := strings.Split(mountoptions.(string), ";")
		moMap := map[string]bool{}
		for _, mo := range moList {
			moMap[mo] = true
		}
		disk["mountoptions"] = moMap
	}

	storageName, fileName := ParseSubConf(disk["volume"].(string), ":")
	disk["storage"] = storageName
	disk["file"] = fileName

	return disk
}

func (config ConfigLxc) mapToAPIParams() map[string]interface{} {
	// convert config to map
	params, _ := json.Marshal(&config)
	var paramMap map[string]interface{}
	json.Unmarshal(params, &paramMap)

	// build list of features
	// add features as parameter list to lxc parameters
	// this overwrites the orginal formatting with a
	// comma separated list of "key=value" pairs
	paramMap["features"] = formatDeviceParam(config.Features)

	// format rootfs params as expected
	if rootfs := config.RootFs; rootfs != nil {
		paramMap["rootfs"] = FormatDiskParam(rootfs)
	}

	// build list of mountpoints
	// this does the same as for the feature list
	// except that there can be multiple of these mountpoint sets
	// and each mountpoint set comes with a new id
	for _, mpConfMap := range config.Mountpoints {
		// add mp to lxc parameters
		mpID := mpConfMap["slot"]
		mpName := fmt.Sprintf("mp%v", mpID)
		paramMap[mpName] = FormatDiskParam(mpConfMap)
	}

	// build list of network parameters
	for nicID, nicConfMap := range config.Networks {
		// add nic to lxc parameters
		nicName := fmt.Sprintf("net%v", nicID)
		paramMap[nicName] = formatDeviceParam(nicConfMap)
	}

	// build list of unused volumes for sake of completeness,
	// even if it is not recommended to change these volumes manually
	for volID, vol := range config.Unused {
		// add volume to lxc parameters
		volName := fmt.Sprintf("unused%v", volID)
		paramMap[volName] = vol
	}

	// now that we concatenated the key value parameter
	// list for the networks, mountpoints and unused volumes,
	// remove the original keys, since the Proxmox API does
	// not know how to handle this key
	delete(paramMap, "networks")
	delete(paramMap, "mountpoints")
	delete(paramMap, "unused")

	return paramMap
}
<|endoftext|>


<commit_before>package filesys

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

	"github.com/seaweedfs/fuse"
	"github.com/seaweedfs/fuse/fs"

	"github.com/chrislusf/seaweedfs/weed/filer2"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/chrislusf/seaweedfs/weed/util"
)

const blockSize = 512

var _ = fs.Node(&File{})
var _ = fs.NodeOpener(&File{})
var _ = fs.NodeFsyncer(&File{})
var _ = fs.NodeSetattrer(&File{})
var _ = fs.NodeGetxattrer(&File{})
var _ = fs.NodeSetxattrer(&File{})
var _ = fs.NodeRemovexattrer(&File{})
var _ = fs.NodeListxattrer(&File{})
var _ = fs.NodeForgetter(&File{})

type File struct {
	Name           string
	dir            *Dir
	wfs            *WFS
	entry          *filer_pb.Entry
	entryViewCache []filer2.VisibleInterval
	isOpen         int
	reader         io.ReaderAt
	dirtyMetadata  bool
}

func (file *File) fullpath() util.FullPath {
	return util.NewFullPath(file.dir.FullPath(), file.Name)
}

func (file *File) Attr(ctx context.Context, attr *fuse.Attr) error {

	glog.V(5).Infof("file Attr %s, open:%v, existing attr: %+v", file.fullpath(), file.isOpen, attr)

	if file.isOpen <= 0 {
		if err := file.maybeLoadEntry(ctx); err != nil {
			return err
		}
	}

	attr.Inode = file.fullpath().AsInode()
	attr.Valid = time.Second
	attr.Mode = os.FileMode(file.entry.Attributes.FileMode)
	attr.Size = filer2.FileSize(file.entry)
	if file.isOpen > 0 {
		attr.Size = file.entry.Attributes.FileSize
		glog.V(4).Infof("file Attr %s, open:%v, size: %d", file.fullpath(), file.isOpen, attr.Size)
	}
	attr.Crtime = time.Unix(file.entry.Attributes.Crtime, 0)
	attr.Mtime = time.Unix(file.entry.Attributes.Mtime, 0)
	attr.Gid = file.entry.Attributes.Gid
	attr.Uid = file.entry.Attributes.Uid
	attr.Blocks = attr.Size/blockSize + 1
	attr.BlockSize = uint32(file.wfs.option.ChunkSizeLimit)

	return nil

}

func (file *File) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {

	glog.V(4).Infof("file Getxattr %s", file.fullpath())

	if err := file.maybeLoadEntry(ctx); err != nil {
		return err
	}

	return getxattr(file.entry, req, resp)
}

func (file *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {

	glog.V(5).Infof("file %v open %+v", file.fullpath(), req)

	file.isOpen++

	handle := file.wfs.AcquireHandle(file, req.Uid, req.Gid)

	resp.Handle = fuse.HandleID(handle.handle)

	glog.V(5).Infof("%v file open handle id = %d", file.fullpath(), handle.handle)

	return handle, nil

}

func (file *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error {

	glog.V(5).Infof("%v file setattr %+v, old:%+v", file.fullpath(), req, file.entry.Attributes)

	if err := file.maybeLoadEntry(ctx); err != nil {
		return err
	}

	if req.Valid.Size() {

		glog.V(4).Infof("%v file setattr set size=%v chunks=%d", file.fullpath(), req.Size, len(file.entry.Chunks))
		if req.Size < filer2.TotalSize(file.entry.Chunks) {
			// fmt.Printf("truncate %v \n", fullPath)
			var chunks []*filer_pb.FileChunk
			var truncatedChunks []*filer_pb.FileChunk
			for _, chunk := range file.entry.Chunks {
				int64Size := int64(chunk.Size)
				if chunk.Offset+int64Size > int64(req.Size) {
					// this chunk is truncated
					int64Size = int64(req.Size) - chunk.Offset
					if int64Size > 0 {
						chunks = append(chunks, chunk)
						glog.V(4).Infof("truncated chunk %+v from %d to %d\n", chunk.GetFileIdString(), chunk.Size, int64Size)
						chunk.Size = uint64(int64Size)
					} else {
						glog.V(4).Infof("truncated whole chunk %+v\n", chunk.GetFileIdString())
						truncatedChunks = append(truncatedChunks, chunk)
					}
				}
			}
			file.wfs.deleteFileChunks(truncatedChunks)
			file.entry.Chunks = chunks
			file.entryViewCache = nil
			file.reader = nil
			file.dirtyMetadata = true
		}
		file.entry.Attributes.FileSize = req.Size
	}
	if req.Valid.Mode() {
		file.entry.Attributes.FileMode = uint32(req.Mode)
		file.dirtyMetadata = true
	}

	if req.Valid.Uid() {
		file.entry.Attributes.Uid = req.Uid
		file.dirtyMetadata = true
	}

	if req.Valid.Gid() {
		file.entry.Attributes.Gid = req.Gid
		file.dirtyMetadata = true
	}

	if req.Valid.Crtime() {
		file.entry.Attributes.Crtime = req.Crtime.Unix()
		file.dirtyMetadata = true
	}

	if req.Valid.Mtime() {
		file.entry.Attributes.Mtime = req.Mtime.Unix()
		file.dirtyMetadata = true
	}

	if req.Valid.Handle() {
		// fmt.Printf("file handle => %d\n", req.Handle)
	}

	if file.isOpen > 0 {
		return nil
	}

	return file.saveEntry()

}

func (file *File) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {

	glog.V(4).Infof("file Setxattr %s: %s", file.fullpath(), req.Name)

	if err := file.maybeLoadEntry(ctx); err != nil {
		return err
	}

	if err := setxattr(file.entry, req); err != nil {
		return err
	}

	return file.saveEntry()

}

func (file *File) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {

	glog.V(4).Infof("file Removexattr %s: %s", file.fullpath(), req.Name)

	if err := file.maybeLoadEntry(ctx); err != nil {
		return err
	}

	if err := removexattr(file.entry, req); err != nil {
		return err
	}

	return file.saveEntry()

}

func (file *File) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {

	glog.V(4).Infof("file Listxattr %s", file.fullpath())

	if err := file.maybeLoadEntry(ctx); err != nil {
		return err
	}

	if err := listxattr(file.entry, req, resp); err != nil {
		return err
	}

	return nil

}

func (file *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
	// fsync works at OS level
	// write the file chunks to the filerGrpcAddress
	glog.V(4).Infof("%s/%s fsync file %+v", file.dir.FullPath(), file.Name, req)

	return nil
}

func (file *File) Forget() {
	t := util.NewFullPath(file.dir.FullPath(), file.Name)
	glog.V(5).Infof("Forget file %s", t)
	file.wfs.fsNodeCache.DeleteFsNode(t)
}

func (file *File) maybeLoadEntry(ctx context.Context) error {
	if file.entry == nil || file.isOpen <= 0 {
		entry, err := file.wfs.maybeLoadEntry(file.dir.FullPath(), file.Name)
		if err != nil {
			glog.V(3).Infof("maybeLoadEntry file %s/%s: %v", file.dir.FullPath(), file.Name, err)
			return err
		}
		if entry != nil {
			file.setEntry(entry)
		}
	}
	return nil
}

func (file *File) addChunks(chunks []*filer_pb.FileChunk) {

	sort.Slice(chunks, func(i, j int) bool {
		return chunks[i].Mtime < chunks[j].Mtime
	})

	var newVisibles []filer2.VisibleInterval
	for _, chunk := range chunks {
		newVisibles = filer2.MergeIntoVisibles(file.entryViewCache, newVisibles, chunk)
		t := file.entryViewCache[:0]
		file.entryViewCache = newVisibles
		newVisibles = t
	}

	file.reader = nil

	glog.V(4).Infof("%s existing %d chunks adds %d more", file.fullpath(), len(file.entry.Chunks), len(chunks))

	file.entry.Chunks = append(file.entry.Chunks, chunks...)
}

func (file *File) setEntry(entry *filer_pb.Entry) {
	file.entry = entry
	file.entryViewCache, _ = filer2.NonOverlappingVisibleIntervals(filer2.LookupFn(file.wfs), file.entry.Chunks)
	file.reader = nil
}

func (file *File) saveEntry() error {
	return file.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {

		request := &filer_pb.UpdateEntryRequest{
			Directory: file.dir.FullPath(),
			Entry:     file.entry,
		}

		glog.V(1).Infof("save file entry: %v", request)
		_, err := client.UpdateEntry(context.Background(), request)
		if err != nil {
			glog.V(0).Infof("UpdateEntry file %s/%s: %v", file.dir.FullPath(), file.Name, err)
			return fuse.EIO
		}

		file.wfs.metaCache.UpdateEntry(context.Background(), filer2.FromPbEntry(request.Directory, request.Entry))

		return nil
	})
}
<commit_msg>update metadata only if changed<commit_after>package filesys

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

	"github.com/seaweedfs/fuse"
	"github.com/seaweedfs/fuse/fs"

	"github.com/chrislusf/seaweedfs/weed/filer2"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/chrislusf/seaweedfs/weed/util"
)

const blockSize = 512

var _ = fs.Node(&File{})
var _ = fs.NodeOpener(&File{})
var _ = fs.NodeFsyncer(&File{})
var _ = fs.NodeSetattrer(&File{})
var _ = fs.NodeGetxattrer(&File{})
var _ = fs.NodeSetxattrer(&File{})
var _ = fs.NodeRemovexattrer(&File{})
var _ = fs.NodeListxattrer(&File{})
var _ = fs.NodeForgetter(&File{})

type File struct {
	Name           string
	dir            *Dir
	wfs            *WFS
	entry          *filer_pb.Entry
	entryViewCache []filer2.VisibleInterval
	isOpen         int
	reader         io.ReaderAt
	dirtyMetadata  bool
}

func (file *File) fullpath() util.FullPath {
	return util.NewFullPath(file.dir.FullPath(), file.Name)
}

func (file *File) Attr(ctx context.Context, attr *fuse.Attr) error {

	glog.V(5).Infof("file Attr %s, open:%v, existing attr: %+v", file.fullpath(), file.isOpen, attr)

	if file.isOpen <= 0 {
		if err := file.maybeLoadEntry(ctx); err != nil {
			return err
		}
	}

	attr.Inode = file.fullpath().AsInode()
	attr.Valid = time.Second
	attr.Mode = os.FileMode(file.entry.Attributes.FileMode)
	attr.Size = filer2.FileSize(file.entry)
	if file.isOpen > 0 {
		attr.Size = file.entry.Attributes.FileSize
		glog.V(4).Infof("file Attr %s, open:%v, size: %d", file.fullpath(), file.isOpen, attr.Size)
	}
	attr.Crtime = time.Unix(file.entry.Attributes.Crtime, 0)
	attr.Mtime = time.Unix(file.entry.Attributes.Mtime, 0)
	attr.Gid = file.entry.Attributes.Gid
	attr.Uid = file.entry.Attributes.Uid
	attr.Blocks = attr.Size/blockSize + 1
	attr.BlockSize = uint32(file.wfs.option.ChunkSizeLimit)

	return nil

}

func (file *File) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {

	glog.V(4).Infof("file Getxattr %s", file.fullpath())

	if err := file.maybeLoadEntry(ctx); err != nil {
		return err
	}

	return getxattr(file.entry, req, resp)
}

func (file *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {

	glog.V(5).Infof("file %v open %+v", file.fullpath(), req)

	file.isOpen++

	handle := file.wfs.AcquireHandle(file, req.Uid, req.Gid)

	resp.Handle = fuse.HandleID(handle.handle)

	glog.V(5).Infof("%v file open handle id = %d", file.fullpath(), handle.handle)

	return handle, nil

}

func (file *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error {

	glog.V(5).Infof("%v file setattr %+v, old:%+v", file.fullpath(), req, file.entry.Attributes)

	if err := file.maybeLoadEntry(ctx); err != nil {
		return err
	}

	if req.Valid.Size() {

		glog.V(4).Infof("%v file setattr set size=%v chunks=%d", file.fullpath(), req.Size, len(file.entry.Chunks))
		if req.Size < filer2.TotalSize(file.entry.Chunks) {
			// fmt.Printf("truncate %v \n", fullPath)
			var chunks []*filer_pb.FileChunk
			var truncatedChunks []*filer_pb.FileChunk
			for _, chunk := range file.entry.Chunks {
				int64Size := int64(chunk.Size)
				if chunk.Offset+int64Size > int64(req.Size) {
					// this chunk is truncated
					int64Size = int64(req.Size) - chunk.Offset
					if int64Size > 0 {
						chunks = append(chunks, chunk)
						glog.V(4).Infof("truncated chunk %+v from %d to %d\n", chunk.GetFileIdString(), chunk.Size, int64Size)
						chunk.Size = uint64(int64Size)
					} else {
						glog.V(4).Infof("truncated whole chunk %+v\n", chunk.GetFileIdString())
						truncatedChunks = append(truncatedChunks, chunk)
					}
				}
			}
			file.wfs.deleteFileChunks(truncatedChunks)
			file.entry.Chunks = chunks
			file.entryViewCache = nil
			file.reader = nil
			file.dirtyMetadata = true
		}
		file.entry.Attributes.FileSize = req.Size
	}
	if req.Valid.Mode() {
		file.entry.Attributes.FileMode = uint32(req.Mode)
		file.dirtyMetadata = true
	}

	if req.Valid.Uid() {
		file.entry.Attributes.Uid = req.Uid
		file.dirtyMetadata = true
	}

	if req.Valid.Gid() {
		file.entry.Attributes.Gid = req.Gid
		file.dirtyMetadata = true
	}

	if req.Valid.Crtime() {
		file.entry.Attributes.Crtime = req.Crtime.Unix()
		file.dirtyMetadata = true
	}

	if req.Valid.Mtime() {
		file.entry.Attributes.Mtime = req.Mtime.Unix()
		file.dirtyMetadata = true
	}

	if req.Valid.Handle() {
		// fmt.Printf("file handle => %d\n", req.Handle)
	}

	if file.isOpen > 0 {
		return nil
	}

	if !file.dirtyMetadata {
		return nil
	}

	return file.saveEntry()

}

func (file *File) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {

	glog.V(4).Infof("file Setxattr %s: %s", file.fullpath(), req.Name)

	if err := file.maybeLoadEntry(ctx); err != nil {
		return err
	}

	if err := setxattr(file.entry, req); err != nil {
		return err
	}

	return file.saveEntry()

}

func (file *File) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {

	glog.V(4).Infof("file Removexattr %s: %s", file.fullpath(), req.Name)

	if err := file.maybeLoadEntry(ctx); err != nil {
		return err
	}

	if err := removexattr(file.entry, req); err != nil {
		return err
	}

	return file.saveEntry()

}

func (file *File) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {

	glog.V(4).Infof("file Listxattr %s", file.fullpath())

	if err := file.maybeLoadEntry(ctx); err != nil {
		return err
	}

	if err := listxattr(file.entry, req, resp); err != nil {
		return err
	}

	return nil

}

func (file *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
	// fsync works at OS level
	// write the file chunks to the filerGrpcAddress
	glog.V(4).Infof("%s/%s fsync file %+v", file.dir.FullPath(), file.Name, req)

	return nil
}

func (file *File) Forget() {
	t := util.NewFullPath(file.dir.FullPath(), file.Name)
	glog.V(5).Infof("Forget file %s", t)
	file.wfs.fsNodeCache.DeleteFsNode(t)
}

func (file *File) maybeLoadEntry(ctx context.Context) error {
	if file.entry == nil || file.isOpen <= 0 {
		entry, err := file.wfs.maybeLoadEntry(file.dir.FullPath(), file.Name)
		if err != nil {
			glog.V(3).Infof("maybeLoadEntry file %s/%s: %v", file.dir.FullPath(), file.Name, err)
			return err
		}
		if entry != nil {
			file.setEntry(entry)
		}
	}
	return nil
}

func (file *File) addChunks(chunks []*filer_pb.FileChunk) {

	sort.Slice(chunks, func(i, j int) bool {
		return chunks[i].Mtime < chunks[j].Mtime
	})

	var newVisibles []filer2.VisibleInterval
	for _, chunk := range chunks {
		newVisibles = filer2.MergeIntoVisibles(file.entryViewCache, newVisibles, chunk)
		t := file.entryViewCache[:0]
		file.entryViewCache = newVisibles
		newVisibles = t
	}

	file.reader = nil

	glog.V(4).Infof("%s existing %d chunks adds %d more", file.fullpath(), len(file.entry.Chunks), len(chunks))

	file.entry.Chunks = append(file.entry.Chunks, chunks...)
}

func (file *File) setEntry(entry *filer_pb.Entry) {
	file.entry = entry
	file.entryViewCache, _ = filer2.NonOverlappingVisibleIntervals(filer2.LookupFn(file.wfs), file.entry.Chunks)
	file.reader = nil
}

func (file *File) saveEntry() error {
	return file.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {

		request := &filer_pb.UpdateEntryRequest{
			Directory: file.dir.FullPath(),
			Entry:     file.entry,
		}

		glog.V(1).Infof("save file entry: %v", request)
		_, err := client.UpdateEntry(context.Background(), request)
		if err != nil {
			glog.V(0).Infof("UpdateEntry file %s/%s: %v", file.dir.FullPath(), file.Name, err)
			return fuse.EIO
		}

		file.wfs.metaCache.UpdateEntry(context.Background(), filer2.FromPbEntry(request.Directory, request.Entry))

		return nil
	})
}
<|endoftext|>