<commit_before>//
// util.go -- various utilities
//

package srnd

import (
  "github.com/majestrate/srndv2/src/nacl"
  "crypto/sha1"
  "crypto/sha512"
  "encoding/base64"
  "encoding/hex"
  "fmt"
  "io"
  "log"
  "net"
  "os"
  "path/filepath"
  "strconv"
  "strings"
  "time"
)

func DelFile(fname string) {
  if CheckFile(fname) {
    os.Remove(fname)
  }
}

func CheckFile(fname string) bool {
  if _, err := os.Stat(fname) ; os.IsNotExist(err) {
    return false
  }
  return true
}

func IsDir(dirname string) bool {
  stat, err := os.Stat(dirname)
  if err != nil {
    log.Fatal(err)
  }
  return stat.IsDir()
}

// ensure a directory exists
func EnsureDir(dirname string) {
  stat, err := os.Stat(dirname)
  if os.IsNotExist(err) {
    os.Mkdir(dirname, 0755)
  } else if ! stat.IsDir() {
    os.Remove(dirname)
    os.Mkdir(dirname, 0755)
  }
}

// TODO make this work better
func ValidMessageID(id string) bool {
  id_len := len(id)

  if id_len < 5 {
    return false
  }

  at_idx := strings.Index(id, "@")
  if at_idx < 3 {
    return false
  }

  for idx, c := range id {
    if idx == 0 {
      if c == '<' {
        continue
      }
    } else if idx == id_len - 1 {
      if c == '>' {
        continue
      }
    } else {
      if idx == at_idx {
        continue
      }
      if c >= 'a' && c <= 'z' {
        continue
      }
      if c >= 'A' && c <= 'Z' {
        continue
      }
      if c >= '0' && c <= '9' {
        continue
      }
      if c == '.' {
        continue
      }
    }
    log.Printf("bad message ID: len=%d %s , invalid char at %d: %c", id_len, id, idx, c)
    return false
  }
  return true
}

// message id hash
func HashMessageID(msgid string) string {
  return fmt.Sprintf("%x", sha1.Sum([]byte(msgid)))
}
// short message id hash
func ShortHashMessageID(msgid string) string {
  return strings.ToLower(HashMessageID(msgid)[:18])
}

// will this message id produce quads?
func MessageIDWillDoQuads(msgid string) bool {
  h := HashMessageID(msgid)
  return h[9] == h[8] && h[8] == h[7] && h[7] == h[6]
}

// will this message id produce trips?
func MessageIDWillDoTrips(msgid string) bool {
  h := HashMessageID(msgid)
  return h[9] == h[8] && h[8] == h[7]
}

// will this message id produce dubs?
func MessageIDWillDoDubs(msgid string) bool {
  h := HashMessageID(msgid)
  return h[9] == h[8]
}

// shorter message id hash
func ShorterHashMessageID(msgid string) string {
  return strings.ToLower(HashMessageID(msgid)[:10])
}

type lineWriter struct {
  io.Writer
  wr io.Writer
  delim []byte
}

func NewLineWriter(wr io.Writer, delim string) io.Writer {
  return lineWriter{wr, wr, []byte(delim)}
}

func (self lineWriter) Write(data []byte) (n int, err error) {
  n, err = self.wr.Write(data)
  self.wr.Write(self.delim)
  return n, err
}

func OpenFileWriter(fname string) (io.WriteCloser, error) {
  return os.Create(fname)
}

// make a random string
func randStr(length int) string {
  return hex.EncodeToString(nacl.RandBytes(length))[length:]
}

// time for right now as int64
func timeNow() int64 {
  return time.Now().Unix()
}

// sanitize data for nntp
func nntpSanitize(data string) string {
  parts := strings.Split(data, "\n.\n")
  return parts[0]
}

type int64Sorter []int64

func (self int64Sorter) Len() int {
  return len(self)
}

func (self int64Sorter) Less(i, j int) bool {
  return self[i] < self[j]
}

func (self int64Sorter) Swap(i, j int) {
  tmp := self[j]
  self[j] = self[i]
  self[i] = tmp
}

// obtain the "real" ip address
func getRealIP(name string) string {
  if len(name) > 0 {
    ip , err := net.ResolveIPAddr("ip", name)
    if err == nil {
      if ip.IP.IsGlobalUnicast() {
      return ip.IP.String()
      }
    }
  }
  return ""
}

// check that we have permission to access this
// fatal on fail
func checkPerms(fname string) {
  fstat, err := os.Stat(fname)
  if err != nil {
    log.Fatalf("Cannot access %s, %s", fname, err)
  }
  // check if we can access this dir
  if fstat.IsDir() {
    tmpfname := filepath.Join(fname, ".test")
    f, err := os.Create(tmpfname)
    if err != nil {
      log.Fatalf("No Write access in %s, %s", fname, err)
    }
    err = f.Close()
    if err != nil {
      log.Fatalf("failed to close test file %s !? %s", tmpfname, err)
    }
    err = os.Remove(tmpfname)
    if err != nil {
      log.Fatalf("failed to remove test file %s, %s", tmpfname, err)
    }
  } else {
    // this isn't a dir, treat it like a regular file
    f, err := os.Open(fname)
    if err != nil {
      log.Fatalf("cannot read file %s, %s", fname, err)
    }
    f.Close()
  }
}

// number of bytes to use in otp
func encAddrBytes() int {
  return 64
}

// length of an encrypted clearnet address
func encAddrLen() int {
  return 88
}

// length of an i2p dest hash
func i2pDestHashLen() int {
  return 44
}

// given an address
// generate a new encryption key for it
// return the encryption key and the encrypted address
func newAddrEnc(addr string) (string, string) {
  key_bytes := nacl.RandBytes(encAddrBytes())
  key := base64.StdEncoding.EncodeToString(key_bytes)
  return key, encAddr(addr, key)
}

// xor address with a one time pad
// if the address isn't long enough it's padded with spaces
func encAddr(addr, key string) string {
  key_bytes, err := base64.StdEncoding.DecodeString(key)

  if err != nil {
    log.Println("encAddr() key base64 decode", err)
    return ""
  }

  if len(addr) > len(key_bytes) {
    log.Println("encAddr() len(addr) > len(key_bytes)")
    return ""
  }

  // pad with spaces
  for len(addr) < len(key_bytes) {
    addr += " "
  }

  addr_bytes := []byte(addr)
  res_bytes := make([]byte, len(addr_bytes))
  for idx, b := range key_bytes {
    res_bytes[idx] = addr_bytes[idx] ^ b
  }

  return base64.StdEncoding.EncodeToString(res_bytes)
}

func checkError(err error) {
  if err != nil {
    log.Fatal(err)
  }
}

// decrypt an address
// strips any whitespaces
func decAddr(encaddr, key string) string {
  encaddr_bytes, err := base64.StdEncoding.DecodeString(encaddr)
  if err != nil {
    log.Println("decAddr() encaddr base64 decode", err)
    return ""
  }
  if len(encaddr_bytes) != len(key) {
    log.Println("decAddr() len(encaddr_bytes) != len(key)")
    return ""
  }
  key_bytes, err := base64.StdEncoding.DecodeString(key)
  if err != nil {
    log.Println("decAddr() key base64 decode", err)
  }
  res_bytes := make([]byte, len(key))
  for idx, b := range key_bytes {
    res_bytes[idx] = encaddr_bytes[idx] ^ b
  }
  res := string(res_bytes)
  return strings.Trim(res, " ")
}

func newsgroupValidFormat(newsgroup string) bool {
  // too long newsgroup
  if len(newsgroup) > 128 {
    return false
  }
  for _, ch := range newsgroup {
    if ch >= 'a' && ch <= 'z' {
      continue
    }
    if ch >= '0' && ch <= '9' {
      continue
    }
    if ch >= 'A' && ch <= 'Z' {
      continue
    }
    if ch == '.' {
      continue
    }
    return false
  }
  return true
}

// generate a new signing keypair
// public, secret
func newSignKeypair() (string, string) {
  kp := nacl.GenSignKeypair()
  defer kp.Free()
  pk := kp.Public()
  sk := kp.Seed()
  return hex.EncodeToString(pk), hex.EncodeToString(sk)
}

// make a utf-8 tripcode
func makeTripcode(pk string) string {
  data, err := hex.DecodeString(pk)
  if err == nil {
    tripcode := ""
    //  here is the python code this is based off of
    //  i do something slightly different but this is the base
    //
    //  for x in range(0, length / 2):
    //    pub_short += '&#%i;' % (9600 + int(full_pubkey_hex[x*2:x*2+2], 16))
    //  length -= length / 2
    //  for x in range(0, length):
    //    pub_short += '&#%i;' % (9600 + int(full_pubkey_hex[-(length*2):][x*2:x*2+2], 16))
    //
    for _, c := range data {
      ch := 9600
      ch += int(c)
      tripcode += fmt.Sprintf("&#%04d;", ch)
    }
    return tripcode
  }
  return "[invalid]"
}

// generate a new message id with base name
func genMessageID(name string) string {
  return fmt.Sprintf("<%s%d@%s>", randStr(5), timeNow(), name)
}

// time now as a string timestamp
func timeNowStr() string {
  return time.Unix(timeNow(), 0).UTC().Format(time.RFC1123Z)
}

// get from a map an int given a key or fall back to a default value
func mapGetInt(m map[string]string, key string, fallback int) int {
  val, ok := m[key]
  if ok {
    i, err := strconv.ParseInt(val, 10, 32)
    if err == nil {
      return int(i)
    }
  }
  return fallback
}

func isSage(str string) bool {
  str = strings.ToLower(str)
  return str == "sage" || strings.HasPrefix(str, "sage ")
}

func unhex(str string) []byte {
  buff, _ := hex.DecodeString(str)
  return buff
}

func hexify(data []byte) string {
  return hex.EncodeToString(data)
}

// extract pubkey from secret key
// return as base32
func getSignPubkey(sk []byte) string {
  return hexify(nacl.GetSignPubkey(sk))
}

// sign data with secret key the fucky srnd way
// return signature as base32
func cryptoSign(data, sk []byte) string {
  // hash
  hash := sha512.Sum512(data)
  log.Printf("hash=%s len=%s", hexify(hash[:]), len(data))
  // sign
  sig := nacl.CryptoSignFucky(hash[:], sk)
  return hexify(sig)
}

// given a tripcode after the #
// make a seed byteslice
func parseTripcodeSecret(str string) []byte {
  // try decoding hex
  raw := unhex(str)
  keylen := nacl.CryptoSignSeedLen()
  if raw == nil || len(raw) != keylen {
    // treat this as a "regular" chan tripcode
    // decode as bytes then pad the rest with 0s if it doesn't fit
    raw = make([]byte, keylen)
    str_bytes := []byte(str)
    if len(str_bytes) > keylen {
      copy(raw, str_bytes[:keylen])
    } else {
      copy(raw, str_bytes)
    }
  }
  return raw
}
<commit_msg>change dubs location<commit_after>//
// util.go -- various utilities
//

package srnd

import (
  "github.com/majestrate/srndv2/src/nacl"
  "crypto/sha1"
  "crypto/sha512"
  "encoding/base64"
  "encoding/hex"
  "fmt"
  "io"
  "log"
  "net"
  "os"
  "path/filepath"
  "strconv"
  "strings"
  "time"
)

func DelFile(fname string) {
  if CheckFile(fname) {
    os.Remove(fname)
  }
}

func CheckFile(fname string) bool {
  if _, err := os.Stat(fname) ; os.IsNotExist(err) {
    return false
  }
  return true
}

func IsDir(dirname string) bool {
  stat, err := os.Stat(dirname)
  if err != nil {
    log.Fatal(err)
  }
  return stat.IsDir()
}

// ensure a directory exists
func EnsureDir(dirname string) {
  stat, err := os.Stat(dirname)
  if os.IsNotExist(err) {
    os.Mkdir(dirname, 0755)
  } else if ! stat.IsDir() {
    os.Remove(dirname)
    os.Mkdir(dirname, 0755)
  }
}

// TODO make this work better
func ValidMessageID(id string) bool {
  id_len := len(id)

  if id_len < 5 {
    return false
  }

  at_idx := strings.Index(id, "@")
  if at_idx < 3 {
    return false
  }

  for idx, c := range id {
    if idx == 0 {
      if c == '<' {
        continue
      }
    } else if idx == id_len - 1 {
      if c == '>' {
        continue
      }
    } else {
      if idx == at_idx {
        continue
      }
      if c >= 'a' && c <= 'z' {
        continue
      }
      if c >= 'A' && c <= 'Z' {
        continue
      }
      if c >= '0' && c <= '9' {
        continue
      }
      if c == '.' {
        continue
      }
    }
    log.Printf("bad message ID: len=%d %s , invalid char at %d: %c", id_len, id, idx, c)
    return false
  }
  return true
}

// message id hash
func HashMessageID(msgid string) string {
  return fmt.Sprintf("%x", sha1.Sum([]byte(msgid)))
}
// short message id hash
func ShortHashMessageID(msgid string) string {
  return strings.ToLower(HashMessageID(msgid)[:18])
}

// will this message id produce quads?
func MessageIDWillDoQuads(msgid string) bool {
  h := HashMessageID(msgid)
  return h[0] == h[1] && h[1] == h[2] && h[2] == h[3]
}

// will this message id produce trips?
func MessageIDWillDoTrips(msgid string) bool {
  h := HashMessageID(msgid)
  return h[0] == h[1] && h[1] == h[2]
}

// will this message id produce dubs?
func MessageIDWillDoDubs(msgid string) bool {
  h := HashMessageID(msgid)
  return h[0] == h[1]
}

// shorter message id hash
func ShorterHashMessageID(msgid string) string {
  return strings.ToLower(HashMessageID(msgid)[:10])
}

type lineWriter struct {
  io.Writer
  wr io.Writer
  delim []byte
}

func NewLineWriter(wr io.Writer, delim string) io.Writer {
  return lineWriter{wr, wr, []byte(delim)}
}

func (self lineWriter) Write(data []byte) (n int, err error) {
  n, err = self.wr.Write(data)
  self.wr.Write(self.delim)
  return n, err
}

func OpenFileWriter(fname string) (io.WriteCloser, error) {
  return os.Create(fname)
}

// make a random string
func randStr(length int) string {
  return hex.EncodeToString(nacl.RandBytes(length))[length:]
}

// time for right now as int64
func timeNow() int64 {
  return time.Now().Unix()
}

// sanitize data for nntp
func nntpSanitize(data string) string {
  parts := strings.Split(data, "\n.\n")
  return parts[0]
}

type int64Sorter []int64

func (self int64Sorter) Len() int {
  return len(self)
}

func (self int64Sorter) Less(i, j int) bool {
  return self[i] < self[j]
}

func (self int64Sorter) Swap(i, j int) {
  tmp := self[j]
  self[j] = self[i]
  self[i] = tmp
}

// obtain the "real" ip address
func getRealIP(name string) string {
  if len(name) > 0 {
    ip , err := net.ResolveIPAddr("ip", name)
    if err == nil {
      if ip.IP.IsGlobalUnicast() {
      return ip.IP.String()
      }
    }
  }
  return ""
}

// check that we have permission to access this
// fatal on fail
func checkPerms(fname string) {
  fstat, err := os.Stat(fname)
  if err != nil {
    log.Fatalf("Cannot access %s, %s", fname, err)
  }
  // check if we can access this dir
  if fstat.IsDir() {
    tmpfname := filepath.Join(fname, ".test")
    f, err := os.Create(tmpfname)
    if err != nil {
      log.Fatalf("No Write access in %s, %s", fname, err)
    }
    err = f.Close()
    if err != nil {
      log.Fatalf("failed to close test file %s !? %s", tmpfname, err)
    }
    err = os.Remove(tmpfname)
    if err != nil {
      log.Fatalf("failed to remove test file %s, %s", tmpfname, err)
    }
  } else {
    // this isn't a dir, treat it like a regular file
    f, err := os.Open(fname)
    if err != nil {
      log.Fatalf("cannot read file %s, %s", fname, err)
    }
    f.Close()
  }
}

// number of bytes to use in otp
func encAddrBytes() int {
  return 64
}

// length of an encrypted clearnet address
func encAddrLen() int {
  return 88
}

// length of an i2p dest hash
func i2pDestHashLen() int {
  return 44
}

// given an address
// generate a new encryption key for it
// return the encryption key and the encrypted address
func newAddrEnc(addr string) (string, string) {
  key_bytes := nacl.RandBytes(encAddrBytes())
  key := base64.StdEncoding.EncodeToString(key_bytes)
  return key, encAddr(addr, key)
}

// xor address with a one time pad
// if the address isn't long enough it's padded with spaces
func encAddr(addr, key string) string {
  key_bytes, err := base64.StdEncoding.DecodeString(key)

  if err != nil {
    log.Println("encAddr() key base64 decode", err)
    return ""
  }

  if len(addr) > len(key_bytes) {
    log.Println("encAddr() len(addr) > len(key_bytes)")
    return ""
  }

  // pad with spaces
  for len(addr) < len(key_bytes) {
    addr += " "
  }

  addr_bytes := []byte(addr)
  res_bytes := make([]byte, len(addr_bytes))
  for idx, b := range key_bytes {
    res_bytes[idx] = addr_bytes[idx] ^ b
  }

  return base64.StdEncoding.EncodeToString(res_bytes)
}

func checkError(err error) {
  if err != nil {
    log.Fatal(err)
  }
}

// decrypt an address
// strips any whitespaces
func decAddr(encaddr, key string) string {
  encaddr_bytes, err := base64.StdEncoding.DecodeString(encaddr)
  if err != nil {
    log.Println("decAddr() encaddr base64 decode", err)
    return ""
  }
  if len(encaddr_bytes) != len(key) {
    log.Println("decAddr() len(encaddr_bytes) != len(key)")
    return ""
  }
  key_bytes, err := base64.StdEncoding.DecodeString(key)
  if err != nil {
    log.Println("decAddr() key base64 decode", err)
  }
  res_bytes := make([]byte, len(key))
  for idx, b := range key_bytes {
    res_bytes[idx] = encaddr_bytes[idx] ^ b
  }
  res := string(res_bytes)
  return strings.Trim(res, " ")
}

func newsgroupValidFormat(newsgroup string) bool {
  // too long newsgroup
  if len(newsgroup) > 128 {
    return false
  }
  for _, ch := range newsgroup {
    if ch >= 'a' && ch <= 'z' {
      continue
    }
    if ch >= '0' && ch <= '9' {
      continue
    }
    if ch >= 'A' && ch <= 'Z' {
      continue
    }
    if ch == '.' {
      continue
    }
    return false
  }
  return true
}

// generate a new signing keypair
// public, secret
func newSignKeypair() (string, string) {
  kp := nacl.GenSignKeypair()
  defer kp.Free()
  pk := kp.Public()
  sk := kp.Seed()
  return hex.EncodeToString(pk), hex.EncodeToString(sk)
}

// make a utf-8 tripcode
func makeTripcode(pk string) string {
  data, err := hex.DecodeString(pk)
  if err == nil {
    tripcode := ""
    //  here is the python code this is based off of
    //  i do something slightly different but this is the base
    //
    //  for x in range(0, length / 2):
    //    pub_short += '&#%i;' % (9600 + int(full_pubkey_hex[x*2:x*2+2], 16))
    //  length -= length / 2
    //  for x in range(0, length):
    //    pub_short += '&#%i;' % (9600 + int(full_pubkey_hex[-(length*2):][x*2:x*2+2], 16))
    //
    for _, c := range data {
      ch := 9600
      ch += int(c)
      tripcode += fmt.Sprintf("&#%04d;", ch)
    }
    return tripcode
  }
  return "[invalid]"
}

// generate a new message id with base name
func genMessageID(name string) string {
  return fmt.Sprintf("<%s%d@%s>", randStr(5), timeNow(), name)
}

// time now as a string timestamp
func timeNowStr() string {
  return time.Unix(timeNow(), 0).UTC().Format(time.RFC1123Z)
}

// get from a map an int given a key or fall back to a default value
func mapGetInt(m map[string]string, key string, fallback int) int {
  val, ok := m[key]
  if ok {
    i, err := strconv.ParseInt(val, 10, 32)
    if err == nil {
      return int(i)
    }
  }
  return fallback
}

func isSage(str string) bool {
  str = strings.ToLower(str)
  return str == "sage" || strings.HasPrefix(str, "sage ")
}

func unhex(str string) []byte {
  buff, _ := hex.DecodeString(str)
  return buff
}

func hexify(data []byte) string {
  return hex.EncodeToString(data)
}

// extract pubkey from secret key
// return as base32
func getSignPubkey(sk []byte) string {
  return hexify(nacl.GetSignPubkey(sk))
}

// sign data with secret key the fucky srnd way
// return signature as base32
func cryptoSign(data, sk []byte) string {
  // hash
  hash := sha512.Sum512(data)
  log.Printf("hash=%s len=%s", hexify(hash[:]), len(data))
  // sign
  sig := nacl.CryptoSignFucky(hash[:], sk)
  return hexify(sig)
}

// given a tripcode after the #
// make a seed byteslice
func parseTripcodeSecret(str string) []byte {
  // try decoding hex
  raw := unhex(str)
  keylen := nacl.CryptoSignSeedLen()
  if raw == nil || len(raw) != keylen {
    // treat this as a "regular" chan tripcode
    // decode as bytes then pad the rest with 0s if it doesn't fit
    raw = make([]byte, keylen)
    str_bytes := []byte(str)
    if len(str_bytes) > keylen {
      copy(raw, str_bytes[:keylen])
    } else {
      copy(raw, str_bytes)
    }
  }
  return raw
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/deevatech/manager/models/tests"
	"github.com/deevatech/manager/runner"
	. "github.com/deevatech/manager/types"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"strconv"
)

func init() {
	log.Println("Deeva Manager!")
}

func main() {
	router := gin.Default()
	router.POST("/run", handleRunRequest)
	router.GET("/tests/:id", handleTestLookupRequest)
	router.POST("/tests/:id/submit", handleTestSubmitRequest)

	port := os.Getenv("DEEVA_MANAGER_PORT")
	if len(port) == 0 {
		port = "8080"
	}

	log.Printf("Starting in %s mode on port %s\n", gin.Mode(), port)
	host := fmt.Sprintf(":%s", port)
	router.Run(host)
}

func handleRunRequest(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "*")

	var run RunParams
	if errParams := c.BindJSON(&run); errParams == nil {
		if result, errRun := runner.Run(run); errRun == nil {
			c.JSON(http.StatusOK, result)
		} else {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": errRun,
			})
		}
	} else {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": errParams,
		})
	}
}

func handleTestLookupRequest(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "*")

	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": err,
		})
		return
	}

	test := tests.FindById(id)
	c.JSON(http.StatusOK, test)
}

func handleTestSubmitRequest(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "*")

	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": err,
		})
		return
	}

	// lookup test
	test := tests.FindById(id)

	var submit TestSubmitParams
	if errParams := c.BindJSON(&submit); errParams == nil {
		run := RunParams{
			Language: test.Language,
			Source:   submit.Code,
			Spec:     test.Spec,
		}
		if result, errRun := runner.Run(run); errRun == nil {
			c.JSON(http.StatusOK, result)
		} else {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": errRun,
			})
		}
	} else {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": errParams,
		})
	}
}
<commit_msg>Add some logging<commit_after>package main

import (
	"fmt"
	"github.com/deevatech/manager/models/tests"
	"github.com/deevatech/manager/runner"
	. "github.com/deevatech/manager/types"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"strconv"
)

func init() {
	log.Println("Deeva Manager!")
}

func main() {
	router := gin.Default()
	router.POST("/run", handleRunRequest)
	router.GET("/tests/:id", handleTestLookupRequest)
	router.POST("/tests/:id/submit", handleTestSubmitRequest)

	port := os.Getenv("DEEVA_MANAGER_PORT")
	if len(port) == 0 {
		port = "8080"
	}

	log.Printf("Starting in %s mode on port %s\n", gin.Mode(), port)
	host := fmt.Sprintf(":%s", port)
	router.Run(host)
}

func handleRunRequest(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "*")

	var run RunParams
	if errParams := c.BindJSON(&run); errParams == nil {
		if result, errRun := runner.Run(run); errRun == nil {
			c.JSON(http.StatusOK, result)
		} else {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": errRun,
			})
		}
	} else {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": errParams,
		})
	}
}

func handleTestLookupRequest(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "*")

	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": err,
		})
		return
	}

	test := tests.FindById(id)
	c.JSON(http.StatusOK, test)
}

func handleTestSubmitRequest(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "*")

	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": err,
		})
		return
	}

	// lookup test
	test := tests.FindById(id)

	var submit TestSubmitParams
	if errParams := c.BindJSON(&submit); errParams == nil {
		log.Printf("TestSubmitParams: %#v", submit)
		run := RunParams{
			Language: test.Language,
			Source:   submit.Code,
			Spec:     test.Spec,
		}
		log.Printf("RunParams: %#v", run)
		if result, errRun := runner.Run(run); errRun == nil {
			c.JSON(http.StatusOK, result)
		} else {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": errRun,
			})
		}
	} else {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": errParams,
		})
	}
}
<|endoftext|>


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

package sync

import (
	"internal/race"
	"runtime"
	"sync/atomic"
	"unsafe"
)

// A Pool is a set of temporary objects that may be individually saved and
// retrieved.
//
// Any item stored in the Pool may be removed automatically at any time without
// notification. If the Pool holds the only reference when this happens, the
// item might be deallocated.
//
// A Pool is safe for use by multiple goroutines simultaneously.
//
// Pool's purpose is to cache allocated but unused items for later reuse,
// relieving pressure on the garbage collector. That is, it makes it easy to
// build efficient, thread-safe free lists. However, it is not suitable for all
// free lists.
//
// An appropriate use of a Pool is to manage a group of temporary items
// silently shared among and potentially reused by concurrent independent
// clients of a package. Pool provides a way to amortize allocation overhead
// across many clients.
//
// An example of good use of a Pool is in the fmt package, which maintains a
// dynamically-sized store of temporary output buffers. The store scales under
// load (when many goroutines are actively printing) and shrinks when
// quiescent.
//
// On the other hand, a free list maintained as part of a short-lived object is
// not a suitable use for a Pool, since the overhead does not amortize well in
// that scenario. It is more efficient to have such objects implement their own
// free list.
//
// A Pool must not be copied after first use.
type Pool struct {
	noCopy noCopy

	local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
	localSize uintptr        // size of the local array

	// New optionally specifies a function to generate
	// a value when Get would otherwise return nil.
	// It may not be changed concurrently with calls to Get.
	New func() interface{}
}

// Local per-P Pool appendix.
type poolLocal struct {
	private interface{}   // Can be used only by the respective P.
	shared  []interface{} // Can be used by any P.
	Mutex                 // Protects shared.
	pad     [128]byte     // Prevents false sharing.
}

// from runtime
func fastrand() uint32

var poolRaceHash [128]uint64

// poolRaceAddr returns an address to use as the synchronization point
// for race detector logic. We don't use the actual pointer stored in x
// directly, for fear of conflicting with other synchronization on that address.
// Instead, we hash the pointer to get an index into poolRaceHash.
// See discussion on golang.org/cl/31589.
func poolRaceAddr(x interface{}) unsafe.Pointer {
	ptr := uintptr((*[2]unsafe.Pointer)(unsafe.Pointer(&x))[1])
	h := uint32((uint64(uint32(ptr)) * 0x85ebca6b) >> 16)
	return unsafe.Pointer(&poolRaceHash[h%uint32(len(poolRaceHash))])
}

// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
	if x == nil {
		return
	}
	if race.Enabled {
		if fastrand()%4 == 0 {
			// Randomly drop x on floor.
			return
		}
		race.ReleaseMerge(poolRaceAddr(x))
		race.Disable()
	}
	l := p.pin()
	if l.private == nil {
		l.private = x
		x = nil
	}
	runtime_procUnpin()
	if x != nil {
		l.Lock()
		l.shared = append(l.shared, x)
		l.Unlock()
	}
	if race.Enabled {
		race.Enable()
	}
}

// Get selects an arbitrary item from the Pool, removes it from the
// Pool, and returns it to the caller.
// Get may choose to ignore the pool and treat it as empty.
// Callers should not assume any relation between values passed to Put and
// the values returned by Get.
//
// If Get would otherwise return nil and p.New is non-nil, Get returns
// the result of calling p.New.
func (p *Pool) Get() interface{} {
	if race.Enabled {
		race.Disable()
	}
	l := p.pin()
	x := l.private
	l.private = nil
	runtime_procUnpin()
	if x == nil {
		l.Lock()
		last := len(l.shared) - 1
		if last >= 0 {
			x = l.shared[last]
			l.shared = l.shared[:last]
		}
		l.Unlock()
		if x == nil {
			x = p.getSlow()
		}
	}
	if race.Enabled {
		race.Enable()
		if x != nil {
			race.Acquire(poolRaceAddr(x))
		}
	}
	if x == nil && p.New != nil {
		x = p.New()
	}
	return x
}

func (p *Pool) getSlow() (x interface{}) {
	// See the comment in pin regarding ordering of the loads.
	size := atomic.LoadUintptr(&p.localSize) // load-acquire
	local := p.local                         // load-consume
	// Try to steal one element from other procs.
	pid := runtime_procPin()
	runtime_procUnpin()
	for i := 0; i < int(size); i++ {
		l := indexLocal(local, (pid+i+1)%int(size))
		l.Lock()
		last := len(l.shared) - 1
		if last >= 0 {
			x = l.shared[last]
			l.shared = l.shared[:last]
			l.Unlock()
			break
		}
		l.Unlock()
	}
	return x
}

// pin pins the current goroutine to P, disables preemption and returns poolLocal pool for the P.
// Caller must call runtime_procUnpin() when done with the pool.
func (p *Pool) pin() *poolLocal {
	pid := runtime_procPin()
	// In pinSlow we store to localSize and then to local, here we load in opposite order.
	// Since we've disabled preemption, GC cannot happen in between.
	// Thus here we must observe local at least as large localSize.
	// We can observe a newer/larger local, it is fine (we must observe its zero-initialized-ness).
	s := atomic.LoadUintptr(&p.localSize) // load-acquire
	l := p.local                          // load-consume
	if uintptr(pid) < s {
		return indexLocal(l, pid)
	}
	return p.pinSlow()
}

func (p *Pool) pinSlow() *poolLocal {
	// Retry under the mutex.
	// Can not lock the mutex while pinned.
	runtime_procUnpin()
	allPoolsMu.Lock()
	defer allPoolsMu.Unlock()
	pid := runtime_procPin()
	// poolCleanup won't be called while we are pinned.
	s := p.localSize
	l := p.local
	if uintptr(pid) < s {
		return indexLocal(l, pid)
	}
	if p.local == nil {
		allPools = append(allPools, p)
	}
	// If GOMAXPROCS changes between GCs, we re-allocate the array and lose the old one.
	size := runtime.GOMAXPROCS(0)
	local := make([]poolLocal, size)
	atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release
	atomic.StoreUintptr(&p.localSize, uintptr(size))         // store-release
	return &local[pid]
}

func poolCleanup() {
	// This function is called with the world stopped, at the beginning of a garbage collection.
	// It must not allocate and probably should not call any runtime functions.
	// Defensively zero out everything, 2 reasons:
	// 1. To prevent false retention of whole Pools.
	// 2. If GC happens while a goroutine works with l.shared in Put/Get,
	//    it will retain whole Pool. So next cycle memory consumption would be doubled.
	for i, p := range allPools {
		allPools[i] = nil
		for i := 0; i < int(p.localSize); i++ {
			l := indexLocal(p.local, i)
			l.private = nil
			for j := range l.shared {
				l.shared[j] = nil
			}
			l.shared = nil
		}
		p.local = nil
		p.localSize = 0
	}
	allPools = []*Pool{}
}

var (
	allPoolsMu Mutex
	allPools   []*Pool
)

func init() {
	runtime_registerPoolCleanup(poolCleanup)
}

func indexLocal(l unsafe.Pointer, i int) *poolLocal {
	lp := unsafe.Pointer(uintptr(l) + uintptr(i)*unsafe.Sizeof(poolLocal{}))
	return (*poolLocal)(lp)
}

// Implemented in runtime.
func runtime_registerPoolCleanup(cleanup func())
func runtime_procPin() int
func runtime_procUnpin()
<commit_msg>sync: align poolLocal to CPU cache line size<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sync

import (
	"internal/race"
	"runtime"
	"sync/atomic"
	"unsafe"
)

// A Pool is a set of temporary objects that may be individually saved and
// retrieved.
//
// Any item stored in the Pool may be removed automatically at any time without
// notification. If the Pool holds the only reference when this happens, the
// item might be deallocated.
//
// A Pool is safe for use by multiple goroutines simultaneously.
//
// Pool's purpose is to cache allocated but unused items for later reuse,
// relieving pressure on the garbage collector. That is, it makes it easy to
// build efficient, thread-safe free lists. However, it is not suitable for all
// free lists.
//
// An appropriate use of a Pool is to manage a group of temporary items
// silently shared among and potentially reused by concurrent independent
// clients of a package. Pool provides a way to amortize allocation overhead
// across many clients.
//
// An example of good use of a Pool is in the fmt package, which maintains a
// dynamically-sized store of temporary output buffers. The store scales under
// load (when many goroutines are actively printing) and shrinks when
// quiescent.
//
// On the other hand, a free list maintained as part of a short-lived object is
// not a suitable use for a Pool, since the overhead does not amortize well in
// that scenario. It is more efficient to have such objects implement their own
// free list.
//
// A Pool must not be copied after first use.
type Pool struct {
	noCopy noCopy

	local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
	localSize uintptr        // size of the local array

	// New optionally specifies a function to generate
	// a value when Get would otherwise return nil.
	// It may not be changed concurrently with calls to Get.
	New func() interface{}
}

// Local per-P Pool appendix.
type poolLocalInternal struct {
	private interface{}   // Can be used only by the respective P.
	shared  []interface{} // Can be used by any P.
	Mutex                 // Protects shared.
}

type poolLocal struct {
	poolLocalInternal

	// Prevents false sharing on widespread platforms with
	// 128 mod (cache line size) = 0 .
	pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}

// from runtime
func fastrand() uint32

var poolRaceHash [128]uint64

// poolRaceAddr returns an address to use as the synchronization point
// for race detector logic. We don't use the actual pointer stored in x
// directly, for fear of conflicting with other synchronization on that address.
// Instead, we hash the pointer to get an index into poolRaceHash.
// See discussion on golang.org/cl/31589.
func poolRaceAddr(x interface{}) unsafe.Pointer {
	ptr := uintptr((*[2]unsafe.Pointer)(unsafe.Pointer(&x))[1])
	h := uint32((uint64(uint32(ptr)) * 0x85ebca6b) >> 16)
	return unsafe.Pointer(&poolRaceHash[h%uint32(len(poolRaceHash))])
}

// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
	if x == nil {
		return
	}
	if race.Enabled {
		if fastrand()%4 == 0 {
			// Randomly drop x on floor.
			return
		}
		race.ReleaseMerge(poolRaceAddr(x))
		race.Disable()
	}
	l := p.pin()
	if l.private == nil {
		l.private = x
		x = nil
	}
	runtime_procUnpin()
	if x != nil {
		l.Lock()
		l.shared = append(l.shared, x)
		l.Unlock()
	}
	if race.Enabled {
		race.Enable()
	}
}

// Get selects an arbitrary item from the Pool, removes it from the
// Pool, and returns it to the caller.
// Get may choose to ignore the pool and treat it as empty.
// Callers should not assume any relation between values passed to Put and
// the values returned by Get.
//
// If Get would otherwise return nil and p.New is non-nil, Get returns
// the result of calling p.New.
func (p *Pool) Get() interface{} {
	if race.Enabled {
		race.Disable()
	}
	l := p.pin()
	x := l.private
	l.private = nil
	runtime_procUnpin()
	if x == nil {
		l.Lock()
		last := len(l.shared) - 1
		if last >= 0 {
			x = l.shared[last]
			l.shared = l.shared[:last]
		}
		l.Unlock()
		if x == nil {
			x = p.getSlow()
		}
	}
	if race.Enabled {
		race.Enable()
		if x != nil {
			race.Acquire(poolRaceAddr(x))
		}
	}
	if x == nil && p.New != nil {
		x = p.New()
	}
	return x
}

func (p *Pool) getSlow() (x interface{}) {
	// See the comment in pin regarding ordering of the loads.
	size := atomic.LoadUintptr(&p.localSize) // load-acquire
	local := p.local                         // load-consume
	// Try to steal one element from other procs.
	pid := runtime_procPin()
	runtime_procUnpin()
	for i := 0; i < int(size); i++ {
		l := indexLocal(local, (pid+i+1)%int(size))
		l.Lock()
		last := len(l.shared) - 1
		if last >= 0 {
			x = l.shared[last]
			l.shared = l.shared[:last]
			l.Unlock()
			break
		}
		l.Unlock()
	}
	return x
}

// pin pins the current goroutine to P, disables preemption and returns poolLocal pool for the P.
// Caller must call runtime_procUnpin() when done with the pool.
func (p *Pool) pin() *poolLocal {
	pid := runtime_procPin()
	// In pinSlow we store to localSize and then to local, here we load in opposite order.
	// Since we've disabled preemption, GC cannot happen in between.
	// Thus here we must observe local at least as large localSize.
	// We can observe a newer/larger local, it is fine (we must observe its zero-initialized-ness).
	s := atomic.LoadUintptr(&p.localSize) // load-acquire
	l := p.local                          // load-consume
	if uintptr(pid) < s {
		return indexLocal(l, pid)
	}
	return p.pinSlow()
}

func (p *Pool) pinSlow() *poolLocal {
	// Retry under the mutex.
	// Can not lock the mutex while pinned.
	runtime_procUnpin()
	allPoolsMu.Lock()
	defer allPoolsMu.Unlock()
	pid := runtime_procPin()
	// poolCleanup won't be called while we are pinned.
	s := p.localSize
	l := p.local
	if uintptr(pid) < s {
		return indexLocal(l, pid)
	}
	if p.local == nil {
		allPools = append(allPools, p)
	}
	// If GOMAXPROCS changes between GCs, we re-allocate the array and lose the old one.
	size := runtime.GOMAXPROCS(0)
	local := make([]poolLocal, size)
	atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release
	atomic.StoreUintptr(&p.localSize, uintptr(size))         // store-release
	return &local[pid]
}

func poolCleanup() {
	// This function is called with the world stopped, at the beginning of a garbage collection.
	// It must not allocate and probably should not call any runtime functions.
	// Defensively zero out everything, 2 reasons:
	// 1. To prevent false retention of whole Pools.
	// 2. If GC happens while a goroutine works with l.shared in Put/Get,
	//    it will retain whole Pool. So next cycle memory consumption would be doubled.
	for i, p := range allPools {
		allPools[i] = nil
		for i := 0; i < int(p.localSize); i++ {
			l := indexLocal(p.local, i)
			l.private = nil
			for j := range l.shared {
				l.shared[j] = nil
			}
			l.shared = nil
		}
		p.local = nil
		p.localSize = 0
	}
	allPools = []*Pool{}
}

var (
	allPoolsMu Mutex
	allPools   []*Pool
)

func init() {
	runtime_registerPoolCleanup(poolCleanup)
}

func indexLocal(l unsafe.Pointer, i int) *poolLocal {
	lp := unsafe.Pointer(uintptr(l) + uintptr(i)*unsafe.Sizeof(poolLocal{}))
	return (*poolLocal)(lp)
}

// Implemented in runtime.
func runtime_registerPoolCleanup(cleanup func())
func runtime_procPin() int
func runtime_procUnpin()
<|endoftext|>


<commit_before><commit_msg>fix etcd backward incompatible refactor Fixed #https://github.com/rekby/etcddir/issues/4<commit_after><|endoftext|>


<commit_before><commit_msg>fillBar bug fixes<commit_after><|endoftext|>


<commit_before><commit_msg>Pass *bState intead of bState<commit_after><|endoftext|>


<commit_before><commit_msg>add cliby.Exit to be used with panic instead of calling os.Exit directly<commit_after><|endoftext|>


<commit_before><commit_msg>Update godoc file (#238)<commit_after><|endoftext|>


<commit_before><commit_msg>rename and fix for godoc<commit_after><|endoftext|>


<commit_before><commit_msg>Number actually improved with the removal of that closure<commit_after><|endoftext|>


<commit_before>package main

import (
	"log"
	"sync"

	dep "github.com/hashicorp/consul-template/dependency"
)

// Brain is what Template uses to determine the values that are
// available for template parsing.
type Brain struct {
	sync.RWMutex

	// data is the map of individual dependencies (by HashCode()) and the most
	// recent data for that dependency.
	data map[string]interface{}

	// receivedData is an internal tracker of which dependencies have stored data
	// in the brain.
	receivedData map[string]struct{}
}

// NewBrain creates a new Brain with empty values for each
// of the key structs.
func NewBrain() *Brain {
	return &Brain{
		data:         make(map[string]interface{}),
		receivedData: make(map[string]struct{}),
	}
}

// Remember accepts a dependency and the data to store associated with that
// dep. This function converts the given data to a proper type and stores
// it interally.
func (b *Brain) Remember(d dep.Dependency, data interface{}) {
	b.Lock()
	defer b.Unlock()

	log.Printf("[INFO] (brain) remembering %s", d.Display())

	b.data[d.HashCode()] = data
	b.receivedData[d.HashCode()] = struct{}{}
}

// Recall gets the current value for the given dependency in the Brain.
func (b *Brain) Recall(d dep.Dependency) (interface{}, bool) {
	b.RLock()
	defer b.RUnlock()

	// If we have not received data for this dependency, return now.
	if _, ok := b.receivedData[d.HashCode()]; !ok {
		log.Printf("[DEBUG] (brain) does not have data for %s", d.Display())
		return nil, false
	}

	log.Printf("[INFO] (brain) recalling data for %s", d.Display())
	return b.data[d.HashCode()], true
}

// Forget accepts a dependency and removes all associated data with this
// dependency. It also resets the "receivedData" internal map.
func (b *Brain) Forget(d dep.Dependency) {
	b.Lock()
	defer b.Unlock()

	log.Printf("[INFO] (brain) forgetting %s", d.Display())

	delete(b.data, d.HashCode())
	delete(b.receivedData, d.HashCode())
}
<commit_msg>Remove log statements from the Brain<commit_after>package main

import (
	"sync"

	dep "github.com/hashicorp/consul-template/dependency"
)

// Brain is what Template uses to determine the values that are
// available for template parsing.
type Brain struct {
	sync.RWMutex

	// data is the map of individual dependencies (by HashCode()) and the most
	// recent data for that dependency.
	data map[string]interface{}

	// receivedData is an internal tracker of which dependencies have stored data
	// in the brain.
	receivedData map[string]struct{}
}

// NewBrain creates a new Brain with empty values for each
// of the key structs.
func NewBrain() *Brain {
	return &Brain{
		data:         make(map[string]interface{}),
		receivedData: make(map[string]struct{}),
	}
}

// Remember accepts a dependency and the data to store associated with that
// dep. This function converts the given data to a proper type and stores
// it interally.
func (b *Brain) Remember(d dep.Dependency, data interface{}) {
	b.Lock()
	defer b.Unlock()

	b.data[d.HashCode()] = data
	b.receivedData[d.HashCode()] = struct{}{}
}

// Recall gets the current value for the given dependency in the Brain.
func (b *Brain) Recall(d dep.Dependency) (interface{}, bool) {
	b.RLock()
	defer b.RUnlock()

	// If we have not received data for this dependency, return now.
	if _, ok := b.receivedData[d.HashCode()]; !ok {
		return nil, false
	}

	return b.data[d.HashCode()], true
}

// Forget accepts a dependency and removes all associated data with this
// dependency. It also resets the "receivedData" internal map.
func (b *Brain) Forget(d dep.Dependency) {
	b.Lock()
	defer b.Unlock()

	delete(b.data, d.HashCode())
	delete(b.receivedData, d.HashCode())
}
<|endoftext|>


<commit_before>package util

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"syscall"
	"text/template"
	"time"
	"unsafe"

	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/format"
	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/log"
	"github.com/Symantec/Dominator/lib/mbr"
	"github.com/Symantec/Dominator/lib/objectserver"
	"github.com/Symantec/Dominator/lib/wsyscall"
)

const BLKGETSIZE = 0x00001260

type bootInfoType struct {
	GrubConfigDirectory string
	InitrdImageFile     string
	KernelImageFile     string
	grubTemplate        *template.Template
}

func checkIfPartition(device string) (bool, error) {
	if isBlock, err := checkIsBlock(device); err != nil {
		if !os.IsNotExist(err) {
			return false, err
		}
		return false, nil
	} else if !isBlock {
		return false, fmt.Errorf("%s is not a block device", device)
	} else {
		return true, nil
	}
}

func checkIsBlock(filename string) (bool, error) {
	if fi, err := os.Stat(filename); err != nil {
		if !os.IsNotExist(err) {
			return false, fmt.Errorf("error stating: %s: %s", filename, err)
		}
		return false, err
	} else {
		return fi.Mode()&os.ModeDevice == os.ModeDevice, nil
	}
}

func fallocate(filename string, imageSize uint64) error {
	fd, err := syscall.Open(filename, syscall.O_RDWR, 0)
	if err != nil {
		return err
	}
	defer syscall.Close(fd)
	return syscall.Fallocate(fd, 0, 0, int64(imageSize))
}

func getBootDirectory(fs *filesystem.FileSystem) (
	*filesystem.DirectoryInode, error) {
	if fs.EntriesByName == nil {
		fs.BuildEntryMap()
	}
	dirent, ok := fs.EntriesByName["boot"]
	if !ok {
		return nil, errors.New("missing /boot directory")
	}
	bootDirectory, ok := dirent.Inode().(*filesystem.DirectoryInode)
	if !ok {
		return nil, errors.New("/boot is not a directory")
	}
	return bootDirectory, nil
}

func getRootOptions(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter) ([]string, error) {
	bootDirectory, err := getBootDirectory(fs)
	if err != nil {
		return nil, err
	}
	dirent, ok := bootDirectory.EntriesByName["ext4.unsupported-features"]
	fsOptions := []string{"has_journal"}
	if ok {
		if inode, ok := dirent.Inode().(*filesystem.RegularInode); ok {
			hashes := []hash.Hash{inode.Hash}
			objectsReader, err := objectsGetter.GetObjects(hashes)
			if err != nil {
				return nil, err
			}
			defer objectsReader.Close()
			size, reader, err := objectsReader.NextObject()
			if err != nil {
				return nil, err
			}
			defer reader.Close()
			if size > 1024 {
				return nil,
					errors.New("/boot/ext4.unsupported-features is too large")
			}
			for {
				var option string
				_, err := fmt.Fscanf(reader, "%s\n", &option)
				if err != nil {
					if err == io.EOF {
						break
					}
					return nil, err
				} else {
					fsOptions = append(fsOptions,
						"^"+strings.Map(sanitiseInput, option))
				}
			}
		}
	}
	return fsOptions, nil
}

func getRootPartition(bootDevice string) (string, error) {
	if isPartition, err := checkIfPartition(bootDevice + "p1"); err != nil {
		return "", err
	} else if isPartition {
		return bootDevice + "p1", nil
	}
	if isPartition, err := checkIfPartition(bootDevice + "1"); err != nil {
		return "", err
	} else if !isPartition {
		return "", errors.New("no root partition found")
	} else {
		return bootDevice + "1", nil
	}
}

func getBlocksize(device string) (uint64, error) {
	fd, err := syscall.Open(device, os.O_RDONLY|syscall.O_CLOEXEC, 0666)
	if err != nil {
		return 0, err
	}
	defer syscall.Close(fd)
	var blk uint64
	if err := ioctl(fd, BLKGETSIZE, uintptr(unsafe.Pointer(&blk))); err != nil {
		return 0, err
	}
	return blk << 9, nil
}

func ioctl(fd int, request, argp uintptr) error {
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request,
		argp)
	if errno != 0 {
		return os.NewSyscallError("ioctl", errno)
	}
	return nil
}

func makeAndWriteRoot(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter, bootDevice, rootDevice string,
	makeBootableFlag bool, logger log.Logger) error {
	fsOptions, err := getRootOptions(fs, objectsGetter)
	if err != nil {
		return err
	}
	var bootInfo *bootInfoType
	if makeBootableFlag {
		var err error
		bootInfo, err = getBootInfo(fs, objectsGetter)
		if err != nil {
			return err
		}
	}
	if err := makeRootFs(rootDevice, fsOptions, logger); err != nil {
		return err
	}
	mountPoint, err := ioutil.TempDir("", "write-raw-image")
	if err != nil {
		return err
	}
	defer os.RemoveAll(mountPoint)
	err = wsyscall.Mount(rootDevice, mountPoint, "ext4", 0, "")
	if err != nil {
		return fmt.Errorf("error mounting: %s", rootDevice)
	}
	defer syscall.Unmount(mountPoint, 0)
	os.RemoveAll(path.Join(mountPoint, "lost+found"))
	if err := Unpack(fs, objectsGetter, mountPoint, logger); err != nil {
		return err
	}
	if !makeBootableFlag {
		return nil
	}
	return bootInfo.makeBootable(bootDevice, mountPoint, logger)
}

func makeRootFs(deviceName string, options []string, logger log.Logger) error {
	size, err := getBlocksize(deviceName)
	if err != nil {
		return err
	} else {
		logger.Printf("Making %s file-system\n", format.FormatBytes(size))
	}
	startTime := time.Now()
	cmd := exec.Command("mkfs.ext4", "-L", "rootfs", "-i", "8192",
		"-O", strings.Join(options, ","),
		deviceName, strconv.FormatUint(size>>10, 10))
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error making file-system on: %s: %s: %s",
			deviceName, err, output)
	}
	logger.Printf("Made file-system in %s\n",
		format.Duration(time.Since(startTime)))
	return nil
}

func sanitiseInput(ch rune) rune {
	if 'a' <= ch && ch <= 'z' {
		return ch
	} else if '0' <= ch && ch <= '9' {
		return ch
	} else if ch == '_' {
		return ch
	} else {
		return -1
	}
}

func getBootInfo(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter) (*bootInfoType, error) {
	bootDirectory, err := getBootDirectory(fs)
	if err != nil {
		return nil, err
	}
	bootInfo := &bootInfoType{}
	if _, ok := bootDirectory.EntriesByName["grub2"]; ok {
		bootInfo.GrubConfigDirectory = "grub2"
	} else if _, ok := bootDirectory.EntriesByName["grub"]; ok {
		bootInfo.GrubConfigDirectory = "grub"
	} else {
		bootInfo.GrubConfigDirectory = "grub" // HACK. Maybe.
		// return nil, errors.New("no GRUB configuration directory")
	}
	for _, dirent := range bootDirectory.EntryList {
		if strings.HasPrefix(dirent.Name, "initrd.img-") ||
			strings.HasPrefix(dirent.Name, "initramfs-") {
			if bootInfo.InitrdImageFile != "" {
				return nil, errors.New("multiple initrd images")
			}
			bootInfo.InitrdImageFile = "/boot/" + dirent.Name
		}
		if strings.HasPrefix(dirent.Name, "vmlinuz-") {
			if bootInfo.KernelImageFile != "" {
				return nil, errors.New("multiple kernel images")
			}
			bootInfo.KernelImageFile = "/boot/" + dirent.Name
		}
	}
	bootInfo.grubTemplate, err = template.New("grub").Parse(
		grubTemplateString)
	if err != nil {
		return nil, err
	}
	return bootInfo, nil
}

func (bootInfo *bootInfoType) makeBootable(deviceName string, rootDir string,
	logger log.Logger) error {
	startTime := time.Now()
	cmd := exec.Command("grub-install", "--boot-directory="+rootDir+"/boot",
		deviceName)
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error installing GRUB on: %s: %s: %s",
			deviceName, err, output)
	}
	logger.Printf("Installed GRUB in %s\n",
		format.Duration(time.Since(startTime)))
	grubConfigFile := path.Join(rootDir, "boot", "grub", "grub.cfg")
	file, err := os.Create(grubConfigFile)
	if err != nil {
		return err
	}
	err = bootInfo.grubTemplate.Execute(file, bootInfo)
	if err != nil {
		file.Close()
		return err
	}
	if err := file.Close(); err != nil {
		return err
	}
	if bootInfo.GrubConfigDirectory != "grub" {
		// Be nice and make a copy for grub2.
		err := os.Link(grubConfigFile,
			path.Join(rootDir, "boot", bootInfo.GrubConfigDirectory,
				"grub.cfg"))
		if err != nil {
			return err
		}
	}
	return ioutil.WriteFile(path.Join(rootDir, "etc", "fstab"),
		[]byte("LABEL=rootfs / ext4 defaults 0 1"),
		0644)
}

func writeToBlock(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter, bootDevice string,
	tableType mbr.TableType, makeBootableFlag bool, logger log.Logger) error {
	if err := mbr.WriteDefault(bootDevice, tableType); err != nil {
		return err
	}
	if rootDevice, err := getRootPartition(bootDevice); err != nil {
		return err
	} else {
		return makeAndWriteRoot(fs, objectsGetter, bootDevice, rootDevice,
			makeBootableFlag, logger)
	}
}

func writeToFile(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter, rawFilename string,
	tableType mbr.TableType, minFreeSpace uint64, roundupPower uint64,
	makeBootableFlag, allocateBlocks bool, logger log.Logger) error {

	tmpFilename := rawFilename + "~"
	if file, err := os.Create(tmpFilename); err != nil {
		return err
	} else {
		file.Close()
		defer os.Remove(tmpFilename)
	}
	usageEstimate := fs.EstimateUsage(0)
	minBytes := usageEstimate + usageEstimate>>3 // 12% extra for good luck.
	minBytes += minFreeSpace
	if roundupPower < 24 {
		roundupPower = 24 // 16 MiB.
	}
	imageUnits := minBytes >> roundupPower
	if imageUnits<<roundupPower < minBytes {
		imageUnits++
	}
	imageSize := imageUnits << roundupPower
	if err := os.Truncate(tmpFilename, int64(imageSize)); err != nil {
		return err
	}
	if allocateBlocks {
		if err := fallocate(tmpFilename, imageSize); err != nil {
			return err
		}
	}
	if err := mbr.WriteDefault(tmpFilename, tableType); err != nil {
		return err
	}
	cmd := exec.Command("losetup", "-fP", "--show", tmpFilename)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("%s: %s", err, output)
	}
	if output[len(output)-1] == '\n' {
		output = output[0 : len(output)-1]
	}
	loopDevice := string(output)
	defer exec.Command("losetup", "-d", loopDevice).Run()
	err = makeAndWriteRoot(fs, objectsGetter, loopDevice, loopDevice+"p1",
		makeBootableFlag, logger)
	if err != nil {
		return err
	}
	return os.Rename(tmpFilename, rawFilename)
}

func writeRaw(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter, rawFilename string,
	tableType mbr.TableType, minFreeSpace uint64, roundupPower uint64,
	makeBootableFlag, allocateBlocks bool, logger log.Logger) error {
	if isBlock, err := checkIsBlock(rawFilename); err != nil {
		if !os.IsNotExist(err) {
			return err
		}
	} else if isBlock {
		return writeToBlock(fs, objectsGetter, rawFilename, tableType,
			makeBootableFlag, logger)
	}
	return writeToFile(fs, objectsGetter, rawFilename, tableType, minFreeSpace,
		roundupPower, makeBootableFlag, allocateBlocks, logger)
}

const grubTemplateString string = `# Generated from simple template.
set default="0"
if loadfont unicode ; then
  set gfxmode=auto
  insmod all_video
  insmod gfxterm
fi
terminal_output gfxterm
set timeout=0
set menu_color_normal=cyan/blue
set menu_color_highlight=white/blue

menuentry 'Linux' 'Solitary Linux' {
        insmod gzio
        insmod part_msdos
        insmod ext2
        echo    'Loading Linux {{.KernelImageFile}} ...'
        linux   {{.KernelImageFile}} root=LABEL=rootfs ro console=ttyS0,115200n8 console=tty0 net.ifnames=0
        echo    'Loading initial ramdisk ...'
        initrd  {{.InitrdImageFile}}
}
`
<commit_msg>Switch lib/filesystem/util.WriteRaw() to use new lib/fsutil.Fallocate().<commit_after>package util

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"syscall"
	"text/template"
	"time"
	"unsafe"

	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/format"
	"github.com/Symantec/Dominator/lib/fsutil"
	"github.com/Symantec/Dominator/lib/hash"
	"github.com/Symantec/Dominator/lib/log"
	"github.com/Symantec/Dominator/lib/mbr"
	"github.com/Symantec/Dominator/lib/objectserver"
	"github.com/Symantec/Dominator/lib/wsyscall"
)

const BLKGETSIZE = 0x00001260

type bootInfoType struct {
	GrubConfigDirectory string
	InitrdImageFile     string
	KernelImageFile     string
	grubTemplate        *template.Template
}

func checkIfPartition(device string) (bool, error) {
	if isBlock, err := checkIsBlock(device); err != nil {
		if !os.IsNotExist(err) {
			return false, err
		}
		return false, nil
	} else if !isBlock {
		return false, fmt.Errorf("%s is not a block device", device)
	} else {
		return true, nil
	}
}

func checkIsBlock(filename string) (bool, error) {
	if fi, err := os.Stat(filename); err != nil {
		if !os.IsNotExist(err) {
			return false, fmt.Errorf("error stating: %s: %s", filename, err)
		}
		return false, err
	} else {
		return fi.Mode()&os.ModeDevice == os.ModeDevice, nil
	}
}

func getBootDirectory(fs *filesystem.FileSystem) (
	*filesystem.DirectoryInode, error) {
	if fs.EntriesByName == nil {
		fs.BuildEntryMap()
	}
	dirent, ok := fs.EntriesByName["boot"]
	if !ok {
		return nil, errors.New("missing /boot directory")
	}
	bootDirectory, ok := dirent.Inode().(*filesystem.DirectoryInode)
	if !ok {
		return nil, errors.New("/boot is not a directory")
	}
	return bootDirectory, nil
}

func getRootOptions(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter) ([]string, error) {
	bootDirectory, err := getBootDirectory(fs)
	if err != nil {
		return nil, err
	}
	dirent, ok := bootDirectory.EntriesByName["ext4.unsupported-features"]
	fsOptions := []string{"has_journal"}
	if ok {
		if inode, ok := dirent.Inode().(*filesystem.RegularInode); ok {
			hashes := []hash.Hash{inode.Hash}
			objectsReader, err := objectsGetter.GetObjects(hashes)
			if err != nil {
				return nil, err
			}
			defer objectsReader.Close()
			size, reader, err := objectsReader.NextObject()
			if err != nil {
				return nil, err
			}
			defer reader.Close()
			if size > 1024 {
				return nil,
					errors.New("/boot/ext4.unsupported-features is too large")
			}
			for {
				var option string
				_, err := fmt.Fscanf(reader, "%s\n", &option)
				if err != nil {
					if err == io.EOF {
						break
					}
					return nil, err
				} else {
					fsOptions = append(fsOptions,
						"^"+strings.Map(sanitiseInput, option))
				}
			}
		}
	}
	return fsOptions, nil
}

func getRootPartition(bootDevice string) (string, error) {
	if isPartition, err := checkIfPartition(bootDevice + "p1"); err != nil {
		return "", err
	} else if isPartition {
		return bootDevice + "p1", nil
	}
	if isPartition, err := checkIfPartition(bootDevice + "1"); err != nil {
		return "", err
	} else if !isPartition {
		return "", errors.New("no root partition found")
	} else {
		return bootDevice + "1", nil
	}
}

func getBlocksize(device string) (uint64, error) {
	fd, err := syscall.Open(device, os.O_RDONLY|syscall.O_CLOEXEC, 0666)
	if err != nil {
		return 0, err
	}
	defer syscall.Close(fd)
	var blk uint64
	if err := ioctl(fd, BLKGETSIZE, uintptr(unsafe.Pointer(&blk))); err != nil {
		return 0, err
	}
	return blk << 9, nil
}

func ioctl(fd int, request, argp uintptr) error {
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request,
		argp)
	if errno != 0 {
		return os.NewSyscallError("ioctl", errno)
	}
	return nil
}

func makeAndWriteRoot(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter, bootDevice, rootDevice string,
	makeBootableFlag bool, logger log.Logger) error {
	fsOptions, err := getRootOptions(fs, objectsGetter)
	if err != nil {
		return err
	}
	var bootInfo *bootInfoType
	if makeBootableFlag {
		var err error
		bootInfo, err = getBootInfo(fs, objectsGetter)
		if err != nil {
			return err
		}
	}
	if err := makeRootFs(rootDevice, fsOptions, logger); err != nil {
		return err
	}
	mountPoint, err := ioutil.TempDir("", "write-raw-image")
	if err != nil {
		return err
	}
	defer os.RemoveAll(mountPoint)
	err = wsyscall.Mount(rootDevice, mountPoint, "ext4", 0, "")
	if err != nil {
		return fmt.Errorf("error mounting: %s", rootDevice)
	}
	defer syscall.Unmount(mountPoint, 0)
	os.RemoveAll(path.Join(mountPoint, "lost+found"))
	if err := Unpack(fs, objectsGetter, mountPoint, logger); err != nil {
		return err
	}
	if !makeBootableFlag {
		return nil
	}
	return bootInfo.makeBootable(bootDevice, mountPoint, logger)
}

func makeRootFs(deviceName string, options []string, logger log.Logger) error {
	size, err := getBlocksize(deviceName)
	if err != nil {
		return err
	} else {
		logger.Printf("Making %s file-system\n", format.FormatBytes(size))
	}
	startTime := time.Now()
	cmd := exec.Command("mkfs.ext4", "-L", "rootfs", "-i", "8192",
		"-O", strings.Join(options, ","),
		deviceName, strconv.FormatUint(size>>10, 10))
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error making file-system on: %s: %s: %s",
			deviceName, err, output)
	}
	logger.Printf("Made file-system in %s\n",
		format.Duration(time.Since(startTime)))
	return nil
}

func sanitiseInput(ch rune) rune {
	if 'a' <= ch && ch <= 'z' {
		return ch
	} else if '0' <= ch && ch <= '9' {
		return ch
	} else if ch == '_' {
		return ch
	} else {
		return -1
	}
}

func getBootInfo(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter) (*bootInfoType, error) {
	bootDirectory, err := getBootDirectory(fs)
	if err != nil {
		return nil, err
	}
	bootInfo := &bootInfoType{}
	if _, ok := bootDirectory.EntriesByName["grub2"]; ok {
		bootInfo.GrubConfigDirectory = "grub2"
	} else if _, ok := bootDirectory.EntriesByName["grub"]; ok {
		bootInfo.GrubConfigDirectory = "grub"
	} else {
		bootInfo.GrubConfigDirectory = "grub" // HACK. Maybe.
		// return nil, errors.New("no GRUB configuration directory")
	}
	for _, dirent := range bootDirectory.EntryList {
		if strings.HasPrefix(dirent.Name, "initrd.img-") ||
			strings.HasPrefix(dirent.Name, "initramfs-") {
			if bootInfo.InitrdImageFile != "" {
				return nil, errors.New("multiple initrd images")
			}
			bootInfo.InitrdImageFile = "/boot/" + dirent.Name
		}
		if strings.HasPrefix(dirent.Name, "vmlinuz-") {
			if bootInfo.KernelImageFile != "" {
				return nil, errors.New("multiple kernel images")
			}
			bootInfo.KernelImageFile = "/boot/" + dirent.Name
		}
	}
	bootInfo.grubTemplate, err = template.New("grub").Parse(
		grubTemplateString)
	if err != nil {
		return nil, err
	}
	return bootInfo, nil
}

func (bootInfo *bootInfoType) makeBootable(deviceName string, rootDir string,
	logger log.Logger) error {
	startTime := time.Now()
	cmd := exec.Command("grub-install", "--boot-directory="+rootDir+"/boot",
		deviceName)
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("error installing GRUB on: %s: %s: %s",
			deviceName, err, output)
	}
	logger.Printf("Installed GRUB in %s\n",
		format.Duration(time.Since(startTime)))
	grubConfigFile := path.Join(rootDir, "boot", "grub", "grub.cfg")
	file, err := os.Create(grubConfigFile)
	if err != nil {
		return err
	}
	err = bootInfo.grubTemplate.Execute(file, bootInfo)
	if err != nil {
		file.Close()
		return err
	}
	if err := file.Close(); err != nil {
		return err
	}
	if bootInfo.GrubConfigDirectory != "grub" {
		// Be nice and make a copy for grub2.
		err := os.Link(grubConfigFile,
			path.Join(rootDir, "boot", bootInfo.GrubConfigDirectory,
				"grub.cfg"))
		if err != nil {
			return err
		}
	}
	return ioutil.WriteFile(path.Join(rootDir, "etc", "fstab"),
		[]byte("LABEL=rootfs / ext4 defaults 0 1"),
		0644)
}

func writeToBlock(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter, bootDevice string,
	tableType mbr.TableType, makeBootableFlag bool, logger log.Logger) error {
	if err := mbr.WriteDefault(bootDevice, tableType); err != nil {
		return err
	}
	if rootDevice, err := getRootPartition(bootDevice); err != nil {
		return err
	} else {
		return makeAndWriteRoot(fs, objectsGetter, bootDevice, rootDevice,
			makeBootableFlag, logger)
	}
}

func writeToFile(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter, rawFilename string,
	tableType mbr.TableType, minFreeSpace uint64, roundupPower uint64,
	makeBootableFlag, allocateBlocks bool, logger log.Logger) error {

	tmpFilename := rawFilename + "~"
	if file, err := os.Create(tmpFilename); err != nil {
		return err
	} else {
		file.Close()
		defer os.Remove(tmpFilename)
	}
	usageEstimate := fs.EstimateUsage(0)
	minBytes := usageEstimate + usageEstimate>>3 // 12% extra for good luck.
	minBytes += minFreeSpace
	if roundupPower < 24 {
		roundupPower = 24 // 16 MiB.
	}
	imageUnits := minBytes >> roundupPower
	if imageUnits<<roundupPower < minBytes {
		imageUnits++
	}
	imageSize := imageUnits << roundupPower
	if err := os.Truncate(tmpFilename, int64(imageSize)); err != nil {
		return err
	}
	if allocateBlocks {
		if err := fsutil.Fallocate(tmpFilename, imageSize); err != nil {
			return err
		}
	}
	if err := mbr.WriteDefault(tmpFilename, tableType); err != nil {
		return err
	}
	cmd := exec.Command("losetup", "-fP", "--show", tmpFilename)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("%s: %s", err, output)
	}
	if output[len(output)-1] == '\n' {
		output = output[0 : len(output)-1]
	}
	loopDevice := string(output)
	defer exec.Command("losetup", "-d", loopDevice).Run()
	err = makeAndWriteRoot(fs, objectsGetter, loopDevice, loopDevice+"p1",
		makeBootableFlag, logger)
	if err != nil {
		return err
	}
	return os.Rename(tmpFilename, rawFilename)
}

func writeRaw(fs *filesystem.FileSystem,
	objectsGetter objectserver.ObjectsGetter, rawFilename string,
	tableType mbr.TableType, minFreeSpace uint64, roundupPower uint64,
	makeBootableFlag, allocateBlocks bool, logger log.Logger) error {
	if isBlock, err := checkIsBlock(rawFilename); err != nil {
		if !os.IsNotExist(err) {
			return err
		}
	} else if isBlock {
		return writeToBlock(fs, objectsGetter, rawFilename, tableType,
			makeBootableFlag, logger)
	}
	return writeToFile(fs, objectsGetter, rawFilename, tableType, minFreeSpace,
		roundupPower, makeBootableFlag, allocateBlocks, logger)
}

const grubTemplateString string = `# Generated from simple template.
set default="0"
if loadfont unicode ; then
  set gfxmode=auto
  insmod all_video
  insmod gfxterm
fi
terminal_output gfxterm
set timeout=0
set menu_color_normal=cyan/blue
set menu_color_highlight=white/blue

menuentry 'Linux' 'Solitary Linux' {
        insmod gzio
        insmod part_msdos
        insmod ext2
        echo    'Loading Linux {{.KernelImageFile}} ...'
        linux   {{.KernelImageFile}} root=LABEL=rootfs ro console=ttyS0,115200n8 console=tty0 net.ifnames=0
        echo    'Loading initial ramdisk ...'
        initrd  {{.InitrdImageFile}}
}
`
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/fatih/color"
	"github.com/google/go-github/github"
	"github.com/hoisie/mustache"
	git "github.com/libgit2/git2go"
	"gopkg.in/yaml.v2"
)

var (
	version        = "0.1"
	suffix         = "-gut-template"
	templateFile   = "gut.template"
	templateConfig configuration
	cwd            string
	name           string
	fullPath       string
)

var (
	boldCyan  *color.Color
	boldWhite *color.Color
)

type configuration struct {
	Variables map[string]string `yaml:"variables"`
	Options   map[string]string `yaml:"options"`
	Commands  [][]string        `yaml:"commands"`
}

func main() {
	boldCyan = color.New(color.FgCyan).Add(color.Bold)
	boldWhite = color.New(color.FgWhite).Add(color.Bold)

	cwd, _ = filepath.Abs(filepath.Dir(os.Args[0]))

	app := cli.NewApp()
	app.Name = "gut"
	app.Usage = "make an explosive entrance"

	app.Commands = []cli.Command{
		{
			Name:    "search",
			Aliases: []string{"s"},
			Usage:   "search templates",
			Action: func(c *cli.Context) {
				filter := c.Args().First()
				search(filter)
			},
		},
		{
			Name:    "new",
			Aliases: []string{"n"},
			Usage:   "new",
			Action: func(c *cli.Context) {
				if len(c.Args()) < 2 {
					fmt.Print("Not enough arguments")
				}
				templateName := c.Args().First()
				name = c.Args()[1]
				new(templateName)
			},
		},
		{
			Name:    "version",
			Aliases: []string{"v"},
			Usage:   "version",
			Action: func(c *cli.Context) {
				fmt.Printf("gut version %s\n", version)
			},
		},
	}

	app.Run(os.Args)
}

func search(filter string) {
	search := filter + suffix
	client := github.NewClient(nil)
	opt := &github.SearchOptions{
		Sort: "stars",
	}

	results, _, error := client.Search.Repositories(search, opt)

	if error != nil {
		log.Fatal(error)
	}

	for _, repository := range results.Repositories {
		repoName := *repository.Name
		repoUsername := *(*repository.Owner).Login
		repoDescription := *repository.Description
		repoStars := *repository.StargazersCount

		print := strings.HasSuffix(repoName, suffix)

		if print {
			repoName = strings.TrimSuffix(repoName, suffix)

			boldCyan.Printf("%s/", repoUsername)
			boldWhite.Printf("%s", repoName)
			fmt.Printf(" (%d)\n", repoStars)
			fmt.Printf("    %s\n", repoDescription)
		}
	}
}

func new(templateName string) {
	repoName := templateName + suffix
	repoURL := "https://github.com/" + repoName + ".git"

	fullPath = path.Join(cwd, name)
	gitopts := &git.CloneOptions{}

	boldWhite.Printf("Retrieving %s via %s\n", templateName, repoURL)
	_, err := git.Clone(repoURL, fullPath, gitopts)

	if err != nil {
		log.Fatal(err)
	} else {
		println()
	}

	config(name, path.Join(fullPath, templateFile))
	askForVariables()
	clean()
	compile()
	commands()
	fmt.Printf("Your %s project was created successfully.\n", templateName)
}

func config(name string, path string) {
	file, _ := ioutil.ReadFile(path)
	err := yaml.Unmarshal(file, &templateConfig) //TODO check for erros
	if err != nil {
		log.Fatal(err)
	}

	templateConfig.Variables["name"] = name
}

func askForVariables() {
	boldWhite.Println("Assign values to variables [default value]")
	for key, defaultValue := range templateConfig.Variables {
		if key != "name" {
			fmt.Printf("%s [%s]: ", key, defaultValue)

			input := ""
			fmt.Scanln(&input)

			if input != "" {
				templateConfig.Variables[key] = input
			} else {
				templateConfig.Variables[key] = defaultValue
			}
		}
	}
	println()
}

func clean() {
	os.Remove(path.Join(fullPath, templateFile))
	os.RemoveAll(path.Join(fullPath, ".git/"))
	os.Remove(path.Join(fullPath, ".gitignore"))
}

func compile() {
	boldWhite.Printf("Generating fresh 'gut new' %s project:\n", templateConfig.Variables["name"])
	filepath.Walk(fullPath, compileDirs)
	filepath.Walk(fullPath, compileFiles)
	println()
}

func compileDirs(fullPath string, f os.FileInfo, err error) error {
	if f.IsDir() {
		newPath := mustache.Render(fullPath, templateConfig.Variables)
		os.Remove(fullPath)
		os.Mkdir(newPath, f.Mode())
		printCreating(newPath)
	}

	return nil
}

func printCreating(path string) {
	relativePath, err := filepath.Rel(cwd, path)

	if err != nil {
		log.Fatal(err)
	}

	color.New(color.FgGreen).Print("* creating")
	fmt.Printf(" %s\n", relativePath)
}

func compileFiles(fullPath string, f os.FileInfo, err error) error {
	if !f.IsDir() {
		newPath := mustache.Render(fullPath, templateConfig.Variables)
		rendered := mustache.RenderFile(fullPath, templateConfig.Variables)

		os.Remove(fullPath)
		ioutil.WriteFile(newPath, []byte(rendered), f.Mode())

		printCreating(newPath)
	}

	return nil
}

func commands() {
	boldWhite.Println("Commands:")

	for n, v := range templateConfig.Commands {
		command := v[0]
		arguments := v[1:len(v)]

		fmt.Printf("%d. ", n+1)
		printCommand(command, arguments)
		println()
	}

	println()
	boldWhite.Printf("Do you want to run this commands? [N/y] ")
	input := ""
	fmt.Scanln(&input)

	if input == "y" || input == "Y" {
		for _, v := range templateConfig.Commands {
			cmdString := v[0]
			arguments := v[1:len(v)]

			color.New(color.FgGreen).Print("* running ")
			printCommand(cmdString, arguments)
			println()

			command := exec.Command(cmdString, arguments...)
			command.Dir = fullPath

			out, err := command.Output()
			if err != nil {
				log.Fatal(err)
			} else if string(out) != "" {
				fmt.Printf("> %s", out)
			}
		}
	}
	println()
}

func printCommand(command string, arguments []string) {
	fmt.Printf("%s", command)
	for _, argument := range arguments {
		fmt.Printf(" %s", argument)
	}
}

// inmediate tasks:
// add curl command in readme like docker compose has. generate gox builds
// work on temporary directory? if yes, then use os tempdir function. think if this is useful
// commands not be inside of an array in gut.template
// check that gut.template is present
// name in the string should not have a / slash. if it has, we should only use the first
// add describe template subcommand
// clone templates with depth one
// ask for specific tag or commit id of generator
// support templates that remove the cloned folder using the delete folder option
// add subcommand to compile current working dir. useful for developing templates
// store templates in /home/.gut/templates/. only fetch if template not found in that path
// update stored templates
// add option to fill variables from a file
// add homebrew formulas
// paginate to get all answers
// add documentation in the readme
// add version in gut help
<commit_msg>Reorder tasks<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/fatih/color"
	"github.com/google/go-github/github"
	"github.com/hoisie/mustache"
	git "github.com/libgit2/git2go"
	"gopkg.in/yaml.v2"
)

var (
	version        = "0.1"
	suffix         = "-gut-template"
	templateFile   = "gut.template"
	templateConfig configuration
	cwd            string
	name           string
	fullPath       string
)

var (
	boldCyan  *color.Color
	boldWhite *color.Color
)

type configuration struct {
	Variables map[string]string `yaml:"variables"`
	Options   map[string]string `yaml:"options"`
	Commands  [][]string        `yaml:"commands"`
}

func main() {
	boldCyan = color.New(color.FgCyan).Add(color.Bold)
	boldWhite = color.New(color.FgWhite).Add(color.Bold)

	cwd, _ = filepath.Abs(filepath.Dir(os.Args[0]))

	app := cli.NewApp()
	app.Name = "gut"
	app.Usage = "make an explosive entrance"

	app.Commands = []cli.Command{
		{
			Name:    "search",
			Aliases: []string{"s"},
			Usage:   "search templates",
			Action: func(c *cli.Context) {
				filter := c.Args().First()
				search(filter)
			},
		},
		{
			Name:    "new",
			Aliases: []string{"n"},
			Usage:   "new",
			Action: func(c *cli.Context) {
				if len(c.Args()) < 2 {
					fmt.Print("Not enough arguments")
				}
				templateName := c.Args().First()
				name = c.Args()[1]
				new(templateName)
			},
		},
		{
			Name:    "version",
			Aliases: []string{"v"},
			Usage:   "version",
			Action: func(c *cli.Context) {
				fmt.Printf("gut version %s\n", version)
			},
		},
	}

	app.Run(os.Args)
}

func search(filter string) {
	search := filter + suffix
	client := github.NewClient(nil)
	opt := &github.SearchOptions{
		Sort: "stars",
	}

	results, _, error := client.Search.Repositories(search, opt)

	if error != nil {
		log.Fatal(error)
	}

	for _, repository := range results.Repositories {
		repoName := *repository.Name
		repoUsername := *(*repository.Owner).Login
		repoDescription := *repository.Description
		repoStars := *repository.StargazersCount

		print := strings.HasSuffix(repoName, suffix)

		if print {
			repoName = strings.TrimSuffix(repoName, suffix)

			boldCyan.Printf("%s/", repoUsername)
			boldWhite.Printf("%s", repoName)
			fmt.Printf(" (%d)\n", repoStars)
			fmt.Printf("    %s\n", repoDescription)
		}
	}
}

func new(templateName string) {
	repoName := templateName + suffix
	repoURL := "https://github.com/" + repoName + ".git"

	fullPath = path.Join(cwd, name)
	gitopts := &git.CloneOptions{}

	boldWhite.Printf("Retrieving %s via %s\n", templateName, repoURL)
	_, err := git.Clone(repoURL, fullPath, gitopts)

	if err != nil {
		log.Fatal(err)
	} else {
		println()
	}

	config(name, path.Join(fullPath, templateFile))
	askForVariables()
	clean()
	compile()
	commands()
	fmt.Printf("Your %s project was created successfully.\n", templateName)
}

func config(name string, path string) {
	file, _ := ioutil.ReadFile(path)
	err := yaml.Unmarshal(file, &templateConfig) //TODO check for erros
	if err != nil {
		log.Fatal(err)
	}

	templateConfig.Variables["name"] = name
}

func askForVariables() {
	boldWhite.Println("Assign values to variables [default value]")
	for key, defaultValue := range templateConfig.Variables {
		if key != "name" {
			fmt.Printf("%s [%s]: ", key, defaultValue)

			input := ""
			fmt.Scanln(&input)

			if input != "" {
				templateConfig.Variables[key] = input
			} else {
				templateConfig.Variables[key] = defaultValue
			}
		}
	}
	println()
}

func clean() {
	os.Remove(path.Join(fullPath, templateFile))
	os.RemoveAll(path.Join(fullPath, ".git/"))
	os.Remove(path.Join(fullPath, ".gitignore"))
}

func compile() {
	boldWhite.Printf("Generating fresh 'gut new' %s project:\n", templateConfig.Variables["name"])
	filepath.Walk(fullPath, compileDirs)
	filepath.Walk(fullPath, compileFiles)
	println()
}

func compileDirs(fullPath string, f os.FileInfo, err error) error {
	if f.IsDir() {
		newPath := mustache.Render(fullPath, templateConfig.Variables)
		os.Remove(fullPath)
		os.Mkdir(newPath, f.Mode())
		printCreating(newPath)
	}

	return nil
}

func printCreating(path string) {
	relativePath, err := filepath.Rel(cwd, path)

	if err != nil {
		log.Fatal(err)
	}

	color.New(color.FgGreen).Print("* creating")
	fmt.Printf(" %s\n", relativePath)
}

func compileFiles(fullPath string, f os.FileInfo, err error) error {
	if !f.IsDir() {
		newPath := mustache.Render(fullPath, templateConfig.Variables)
		rendered := mustache.RenderFile(fullPath, templateConfig.Variables)

		os.Remove(fullPath)
		ioutil.WriteFile(newPath, []byte(rendered), f.Mode())

		printCreating(newPath)
	}

	return nil
}

func commands() {
	boldWhite.Println("Commands:")

	for n, v := range templateConfig.Commands {
		command := v[0]
		arguments := v[1:len(v)]

		fmt.Printf("%d. ", n+1)
		printCommand(command, arguments)
		println()
	}

	println()
	boldWhite.Printf("Do you want to run this commands? [N/y] ")
	input := ""
	fmt.Scanln(&input)

	if input == "y" || input == "Y" {
		for _, v := range templateConfig.Commands {
			cmdString := v[0]
			arguments := v[1:len(v)]

			color.New(color.FgGreen).Print("* running ")
			printCommand(cmdString, arguments)
			println()

			command := exec.Command(cmdString, arguments...)
			command.Dir = fullPath

			out, err := command.Output()
			if err != nil {
				log.Fatal(err)
			} else if string(out) != "" {
				fmt.Printf("> %s", out)
			}
		}
	}
	println()
}

func printCommand(command string, arguments []string) {
	fmt.Printf("%s", command)
	for _, argument := range arguments {
		fmt.Printf(" %s", argument)
	}
}

// inmediate tasks:
// add curl command in readme like docker compose has. generate gox builds
// work on temporary directory? if yes, then use os tempdir function. think if this is useful
// check that gut.template is present
// support templates that remove the cloned folder using the delete folder option
// commands not be inside of an array in gut.template
// name in the string should not have a / slash. if it has, we should only use the first
// add describe template subcommand
// clone templates with depth one
// option to specify tag or commit id of generator
// add homebrew formulas
// add documentation in the readme
// add version in gut help
// store templates in /home/.gut/templates/. only fetch if template not found in that path
// update stored templates
// paginate to get all answers
// add subcommand to compile current working dir. useful for developing templates
// add option to fill variables from a file
<|endoftext|>


<commit_before>package btrfs

import "sort"

/*
#cgo LDFLAGS: -lbtrfs

#include <stddef.h>
#include <btrfs/ioctl.h>
#include "btrfs.h"

// Required because Go has struct casting rules for negative numbers
const __u64 u64_BTRFS_LAST_FREE_OBJECTID = (__u64)BTRFS_LAST_FREE_OBJECTID;
const __u64 negative_one = (__u64)-1;

static char* get_name_btrfs_ioctl_vol_args_v2(struct btrfs_ioctl_vol_args_v2* btrfs_struct) {
	return btrfs_struct->name;
}
*/
import "C"

import (
	"fmt"
	"os"
	"path/filepath"
	"syscall"
	"unsafe"

	"github.com/pkg/errors"
)

// IsSubvolume returns nil if the path is a valid subvolume. An error is
// returned if the path does not exist or the path is not a valid subvolume.
func IsSubvolume(path string) error {
	fi, err := os.Lstat(path)
	if err != nil {
		return err
	}

	if err := isFileInfoSubvol(fi); err != nil {
		return err
	}

	var statfs syscall.Statfs_t
	if err := syscall.Statfs(path, &statfs); err != nil {
		return err
	}

	return isStatfsSubvol(&statfs)
}

// SubvolInfo returns information about the subvolume at the provided path.
func SubvolInfo(path string) (info Info, err error) {
	path, err = filepath.EvalSymlinks(path)
	if err != nil {
		return info, err
	}

	fp, err := openSubvolDir(path)
	if err != nil {
		return info, err
	}
	defer fp.Close()

	id, err := subvolID(fp.Fd())
	if err != nil {
		return info, err
	}

	subvolsByID, err := subvolMap(path)
	if err != nil {
		return info, err
	}

	if info, ok := subvolsByID[id]; ok {
		return info, nil
	}

	return info, errors.Errorf("%q not found", path)
}

func subvolMap(path string) (map[uint64]Info, error) {
	fp, err := openSubvolDir(path)
	if err != nil {
		return nil, err
	}
	defer fp.Close()

	var args C.struct_btrfs_ioctl_search_args

	args.key.tree_id = C.BTRFS_ROOT_TREE_OBJECTID
	args.key.min_type = C.BTRFS_ROOT_ITEM_KEY
	args.key.max_type = C.BTRFS_ROOT_BACKREF_KEY
	args.key.min_objectid = C.BTRFS_FS_TREE_OBJECTID
	args.key.max_objectid = C.u64_BTRFS_LAST_FREE_OBJECTID
	args.key.max_offset = C.negative_one
	args.key.max_transid = C.negative_one

	subvolsByID := map[uint64]Info{}

	for {
		args.key.nr_items = 4096
		if err := ioctl(fp.Fd(), C.BTRFS_IOC_TREE_SEARCH, uintptr(unsafe.Pointer(&args))); err != nil {
			return nil, err
		}

		if args.key.nr_items == 0 {
			break
		}

		var (
			sh     C.struct_btrfs_ioctl_search_header
			shSize = unsafe.Sizeof(sh)
			buf    = (*[1<<31 - 1]byte)(unsafe.Pointer(&args.buf[0]))[:C.BTRFS_SEARCH_ARGS_BUFSIZE]
		)

		for i := 0; i < int(args.key.nr_items); i++ {
			sh = (*(*C.struct_btrfs_ioctl_search_header)(unsafe.Pointer(&buf[0])))
			buf = buf[shSize:]

			info := subvolsByID[uint64(sh.objectid)]
			info.ID = uint64(sh.objectid)

			if sh._type == C.BTRFS_ROOT_BACKREF_KEY {
				rr := (*(*C.struct_btrfs_root_ref)(unsafe.Pointer(&buf[0])))

				// This branch processes the backrefs from the root object. We
				// get an entry of the objectid, with name, but the parent is
				// the offset.

				nname := C.btrfs_stack_root_ref_name_len(&rr)
				name := string(buf[C.sizeof_struct_btrfs_root_ref : C.sizeof_struct_btrfs_root_ref+uintptr(nname)])

				info.ID = uint64(sh.objectid)
				info.ParentID = uint64(sh.offset)
				info.Name = name
				info.DirID = uint64(C.btrfs_stack_root_ref_dirid(&rr))

				subvolsByID[uint64(sh.objectid)] = info
			} else if sh._type == C.BTRFS_ROOT_ITEM_KEY &&
				(sh.objectid >= C.BTRFS_ROOT_ITEM_KEY ||
					sh.objectid == C.BTRFS_FS_TREE_OBJECTID) {

				var (
					ri  = (*C.struct_btrfs_root_item)(unsafe.Pointer(&buf[0]))
					gri C.struct_gosafe_btrfs_root_item
				)

				C.unpack_root_item(&gri, ri)

				if gri.flags&C.BTRFS_ROOT_SUBVOL_RDONLY != 0 {
					info.Readonly = true
				}

				// in this case, the offset is the actual offset.
				info.Offset = uint64(sh.offset)

				info.UUID = uuidString(&gri.uuid)
				info.ParentUUID = uuidString(&gri.parent_uuid)
				info.ReceivedUUID = uuidString(&gri.received_uuid)

				info.Generation = uint64(gri.gen)
				info.OriginalGeneration = uint64(gri.ogen)

				subvolsByID[uint64(sh.objectid)] = info
			}

			args.key.min_objectid = sh.objectid
			args.key.min_offset = sh.offset
			args.key.min_type = sh._type //  this is very questionable.

			buf = buf[sh.len:]
		}

		args.key.min_offset++
		if args.key.min_offset == 0 {
			args.key.min_type++
		} else {
			continue
		}

		if args.key.min_type > C.BTRFS_ROOT_BACKREF_KEY {
			args.key.min_type = C.BTRFS_ROOT_ITEM_KEY
			args.key.min_objectid++
		} else {
			continue
		}

		if args.key.min_objectid > args.key.max_objectid {
			break
		}
	}

	mnt, err := findMountPoint(path)
	if err != nil {
		return nil, err
	}

	for _, sv := range subvolsByID {
		path := sv.Name
		parentID := sv.ParentID

		for parentID != 0 {
			parent, ok := subvolsByID[parentID]
			if !ok {
				break
			}

			parentID = parent.ParentID
			path = filepath.Join(parent.Name, path)
		}

		sv.Path = filepath.Join(mnt, path)
	}
	return subvolsByID, nil
}

// SubvolList will return the information for all subvolumes corresponding to
// the provided path.
func SubvolList(path string) ([]Info, error) {
	subvolsByID, err := subvolMap(path)
	if err != nil {
		return nil, err
	}

	subvols := make([]Info, 0, len(subvolsByID))
	for _, sv := range subvolsByID {
		subvols = append(subvols, sv)
	}

	sort.Sort(infosByID(subvols))

	return subvols, nil
}

// SubvolCreate creates a subvolume at the provided path.
func SubvolCreate(path string) error {
	dir, name := filepath.Split(path)

	fp, err := os.Open(dir)
	if err != nil {
		return err
	}
	defer fp.Close()

	var args C.struct_btrfs_ioctl_vol_args
	args.fd = C.__s64(fp.Fd())

	if len(name) > C.BTRFS_PATH_NAME_MAX {
		return errors.Errorf("%q too long for subvolume", name)
	}
	nameptr := (*[1<<31 - 1]byte)(unsafe.Pointer(&args.name[0]))
	copy(nameptr[:C.BTRFS_PATH_NAME_MAX], []byte(name))

	if err := ioctl(fp.Fd(), C.BTRFS_IOC_SUBVOL_CREATE, uintptr(unsafe.Pointer(&args))); err != nil {
		return errors.Wrap(err, "btrfs subvolume create failed")
	}

	return nil
}

// SubvolSnapshot creates a snapshot in dst from src. If readonly is true, the
// snapshot will be readonly.
func SubvolSnapshot(dst, src string, readonly bool) error {
	dstdir, dstname := filepath.Split(dst)

	dstfp, err := openSubvolDir(dstdir)
	if err != nil {
		return errors.Wrapf(err, "opening snapshot desination subvolume failed")
	}
	defer dstfp.Close()

	srcfp, err := openSubvolDir(src)
	if err != nil {
		return errors.Wrapf(err, "opening snapshot source subvolume failed")
	}

	// dstdir is the ioctl arg, wile srcdir gets set on the args
	var args C.struct_btrfs_ioctl_vol_args_v2
	args.fd = C.__s64(srcfp.Fd())
	name := C.get_name_btrfs_ioctl_vol_args_v2(&args)

	if len(dstname) > C.BTRFS_SUBVOL_NAME_MAX {
		return errors.Errorf("%q too long for subvolume", dstname)
	}

	nameptr := (*[1<<31 - 1]byte)(unsafe.Pointer(name))
	copy(nameptr[:C.BTRFS_SUBVOL_NAME_MAX], []byte(dstname))

	if readonly {
		args.flags |= C.BTRFS_SUBVOL_RDONLY
	}

	if err := ioctl(dstfp.Fd(), C.BTRFS_IOC_SNAP_CREATE_V2, uintptr(unsafe.Pointer(&args))); err != nil {
		return errors.Wrapf(err, "snapshot create failed")
	}

	return nil
}

// SubvolDelete deletes the subvolumes under the given path.
func SubvolDelete(path string) error {
	fmt.Println("delete", path)
	dir, name := filepath.Split(path)
	fp, err := openSubvolDir(dir)
	if err != nil {
		return errors.Wrapf(err, "failed opening %v", path)
	}
	defer fp.Close()

	// remove child subvolumes
	if err := filepath.Walk(path, func(p string, fi os.FileInfo, err error) error {
		if err != nil {
			if os.IsNotExist(err) || p == path {
				return nil
			}

			return errors.Wrapf(err, "failed walking subvolume %v", p)
		}

		if !fi.IsDir() {
			return nil // just ignore it!
		}

		if p == path {
			return nil
		}

		if err := isFileInfoSubvol(fi); err != nil {
			return err
		}

		if err := SubvolDelete(p); err != nil {
			return err
		}

		return filepath.SkipDir // children get walked by call above.
	}); err != nil {
		return err
	}

	var args C.struct_btrfs_ioctl_vol_args
	if len(name) > C.BTRFS_SUBVOL_NAME_MAX {
		return errors.Errorf("%q too long for subvolume", name)
	}

	nameptr := (*[1<<31 - 1]byte)(unsafe.Pointer(&args.name[0]))
	copy(nameptr[:C.BTRFS_SUBVOL_NAME_MAX], []byte(name))

	if err := ioctl(fp.Fd(), C.BTRFS_IOC_SNAP_DESTROY, uintptr(unsafe.Pointer(&args))); err != nil {
		return errors.Wrapf(err, "failed removing subvolume %v", path)
	}

	return nil
}

func openSubvolDir(path string) (*os.File, error) {
	fp, err := os.Open(path)
	if err != nil {
		return nil, errors.Wrapf(err, "opening %v as subvolume failed", path)
	}

	return fp, nil
}

func isStatfsSubvol(statfs *syscall.Statfs_t) error {
	if statfs.Type != C.BTRFS_SUPER_MAGIC {
		return errors.Errorf("not a btrfs filesystem")
	}

	return nil
}

func isFileInfoSubvol(fi os.FileInfo) error {
	if !fi.IsDir() {
		errors.Errorf("must be a directory")
	}

	stat := fi.Sys().(*syscall.Stat_t)

	if stat.Ino != C.BTRFS_FIRST_FREE_OBJECTID {
		return errors.Errorf("incorrect inode type")
	}

	return nil
}
<commit_msg>Close dangling srcfp in SubvolSnapshot<commit_after>package btrfs

import "sort"

/*
#cgo LDFLAGS: -lbtrfs

#include <stddef.h>
#include <btrfs/ioctl.h>
#include "btrfs.h"

// Required because Go has struct casting rules for negative numbers
const __u64 u64_BTRFS_LAST_FREE_OBJECTID = (__u64)BTRFS_LAST_FREE_OBJECTID;
const __u64 negative_one = (__u64)-1;

static char* get_name_btrfs_ioctl_vol_args_v2(struct btrfs_ioctl_vol_args_v2* btrfs_struct) {
	return btrfs_struct->name;
}
*/
import "C"

import (
	"fmt"
	"os"
	"path/filepath"
	"syscall"
	"unsafe"

	"github.com/pkg/errors"
)

// IsSubvolume returns nil if the path is a valid subvolume. An error is
// returned if the path does not exist or the path is not a valid subvolume.
func IsSubvolume(path string) error {
	fi, err := os.Lstat(path)
	if err != nil {
		return err
	}

	if err := isFileInfoSubvol(fi); err != nil {
		return err
	}

	var statfs syscall.Statfs_t
	if err := syscall.Statfs(path, &statfs); err != nil {
		return err
	}

	return isStatfsSubvol(&statfs)
}

// SubvolInfo returns information about the subvolume at the provided path.
func SubvolInfo(path string) (info Info, err error) {
	path, err = filepath.EvalSymlinks(path)
	if err != nil {
		return info, err
	}

	fp, err := openSubvolDir(path)
	if err != nil {
		return info, err
	}
	defer fp.Close()

	id, err := subvolID(fp.Fd())
	if err != nil {
		return info, err
	}

	subvolsByID, err := subvolMap(path)
	if err != nil {
		return info, err
	}

	if info, ok := subvolsByID[id]; ok {
		return info, nil
	}

	return info, errors.Errorf("%q not found", path)
}

func subvolMap(path string) (map[uint64]Info, error) {
	fp, err := openSubvolDir(path)
	if err != nil {
		return nil, err
	}
	defer fp.Close()

	var args C.struct_btrfs_ioctl_search_args

	args.key.tree_id = C.BTRFS_ROOT_TREE_OBJECTID
	args.key.min_type = C.BTRFS_ROOT_ITEM_KEY
	args.key.max_type = C.BTRFS_ROOT_BACKREF_KEY
	args.key.min_objectid = C.BTRFS_FS_TREE_OBJECTID
	args.key.max_objectid = C.u64_BTRFS_LAST_FREE_OBJECTID
	args.key.max_offset = C.negative_one
	args.key.max_transid = C.negative_one

	subvolsByID := map[uint64]Info{}

	for {
		args.key.nr_items = 4096
		if err := ioctl(fp.Fd(), C.BTRFS_IOC_TREE_SEARCH, uintptr(unsafe.Pointer(&args))); err != nil {
			return nil, err
		}

		if args.key.nr_items == 0 {
			break
		}

		var (
			sh     C.struct_btrfs_ioctl_search_header
			shSize = unsafe.Sizeof(sh)
			buf    = (*[1<<31 - 1]byte)(unsafe.Pointer(&args.buf[0]))[:C.BTRFS_SEARCH_ARGS_BUFSIZE]
		)

		for i := 0; i < int(args.key.nr_items); i++ {
			sh = (*(*C.struct_btrfs_ioctl_search_header)(unsafe.Pointer(&buf[0])))
			buf = buf[shSize:]

			info := subvolsByID[uint64(sh.objectid)]
			info.ID = uint64(sh.objectid)

			if sh._type == C.BTRFS_ROOT_BACKREF_KEY {
				rr := (*(*C.struct_btrfs_root_ref)(unsafe.Pointer(&buf[0])))

				// This branch processes the backrefs from the root object. We
				// get an entry of the objectid, with name, but the parent is
				// the offset.

				nname := C.btrfs_stack_root_ref_name_len(&rr)
				name := string(buf[C.sizeof_struct_btrfs_root_ref : C.sizeof_struct_btrfs_root_ref+uintptr(nname)])

				info.ID = uint64(sh.objectid)
				info.ParentID = uint64(sh.offset)
				info.Name = name
				info.DirID = uint64(C.btrfs_stack_root_ref_dirid(&rr))

				subvolsByID[uint64(sh.objectid)] = info
			} else if sh._type == C.BTRFS_ROOT_ITEM_KEY &&
				(sh.objectid >= C.BTRFS_ROOT_ITEM_KEY ||
					sh.objectid == C.BTRFS_FS_TREE_OBJECTID) {

				var (
					ri  = (*C.struct_btrfs_root_item)(unsafe.Pointer(&buf[0]))
					gri C.struct_gosafe_btrfs_root_item
				)

				C.unpack_root_item(&gri, ri)

				if gri.flags&C.BTRFS_ROOT_SUBVOL_RDONLY != 0 {
					info.Readonly = true
				}

				// in this case, the offset is the actual offset.
				info.Offset = uint64(sh.offset)

				info.UUID = uuidString(&gri.uuid)
				info.ParentUUID = uuidString(&gri.parent_uuid)
				info.ReceivedUUID = uuidString(&gri.received_uuid)

				info.Generation = uint64(gri.gen)
				info.OriginalGeneration = uint64(gri.ogen)

				subvolsByID[uint64(sh.objectid)] = info
			}

			args.key.min_objectid = sh.objectid
			args.key.min_offset = sh.offset
			args.key.min_type = sh._type //  this is very questionable.

			buf = buf[sh.len:]
		}

		args.key.min_offset++
		if args.key.min_offset == 0 {
			args.key.min_type++
		} else {
			continue
		}

		if args.key.min_type > C.BTRFS_ROOT_BACKREF_KEY {
			args.key.min_type = C.BTRFS_ROOT_ITEM_KEY
			args.key.min_objectid++
		} else {
			continue
		}

		if args.key.min_objectid > args.key.max_objectid {
			break
		}
	}

	mnt, err := findMountPoint(path)
	if err != nil {
		return nil, err
	}

	for _, sv := range subvolsByID {
		path := sv.Name
		parentID := sv.ParentID

		for parentID != 0 {
			parent, ok := subvolsByID[parentID]
			if !ok {
				break
			}

			parentID = parent.ParentID
			path = filepath.Join(parent.Name, path)
		}

		sv.Path = filepath.Join(mnt, path)
	}
	return subvolsByID, nil
}

// SubvolList will return the information for all subvolumes corresponding to
// the provided path.
func SubvolList(path string) ([]Info, error) {
	subvolsByID, err := subvolMap(path)
	if err != nil {
		return nil, err
	}

	subvols := make([]Info, 0, len(subvolsByID))
	for _, sv := range subvolsByID {
		subvols = append(subvols, sv)
	}

	sort.Sort(infosByID(subvols))

	return subvols, nil
}

// SubvolCreate creates a subvolume at the provided path.
func SubvolCreate(path string) error {
	dir, name := filepath.Split(path)

	fp, err := os.Open(dir)
	if err != nil {
		return err
	}
	defer fp.Close()

	var args C.struct_btrfs_ioctl_vol_args
	args.fd = C.__s64(fp.Fd())

	if len(name) > C.BTRFS_PATH_NAME_MAX {
		return errors.Errorf("%q too long for subvolume", name)
	}
	nameptr := (*[1<<31 - 1]byte)(unsafe.Pointer(&args.name[0]))
	copy(nameptr[:C.BTRFS_PATH_NAME_MAX], []byte(name))

	if err := ioctl(fp.Fd(), C.BTRFS_IOC_SUBVOL_CREATE, uintptr(unsafe.Pointer(&args))); err != nil {
		return errors.Wrap(err, "btrfs subvolume create failed")
	}

	return nil
}

// SubvolSnapshot creates a snapshot in dst from src. If readonly is true, the
// snapshot will be readonly.
func SubvolSnapshot(dst, src string, readonly bool) error {
	dstdir, dstname := filepath.Split(dst)

	dstfp, err := openSubvolDir(dstdir)
	if err != nil {
		return errors.Wrapf(err, "opening snapshot desination subvolume failed")
	}
	defer dstfp.Close()

	srcfp, err := openSubvolDir(src)
	if err != nil {
		return errors.Wrapf(err, "opening snapshot source subvolume failed")
	}
	defer srcfp.Close()

	// dstdir is the ioctl arg, wile srcdir gets set on the args
	var args C.struct_btrfs_ioctl_vol_args_v2
	args.fd = C.__s64(srcfp.Fd())
	name := C.get_name_btrfs_ioctl_vol_args_v2(&args)

	if len(dstname) > C.BTRFS_SUBVOL_NAME_MAX {
		return errors.Errorf("%q too long for subvolume", dstname)
	}

	nameptr := (*[1<<31 - 1]byte)(unsafe.Pointer(name))
	copy(nameptr[:C.BTRFS_SUBVOL_NAME_MAX], []byte(dstname))

	if readonly {
		args.flags |= C.BTRFS_SUBVOL_RDONLY
	}

	if err := ioctl(dstfp.Fd(), C.BTRFS_IOC_SNAP_CREATE_V2, uintptr(unsafe.Pointer(&args))); err != nil {
		return errors.Wrapf(err, "snapshot create failed")
	}

	return nil
}

// SubvolDelete deletes the subvolumes under the given path.
func SubvolDelete(path string) error {
	fmt.Println("delete", path)
	dir, name := filepath.Split(path)
	fp, err := openSubvolDir(dir)
	if err != nil {
		return errors.Wrapf(err, "failed opening %v", path)
	}
	defer fp.Close()

	// remove child subvolumes
	if err := filepath.Walk(path, func(p string, fi os.FileInfo, err error) error {
		if err != nil {
			if os.IsNotExist(err) || p == path {
				return nil
			}

			return errors.Wrapf(err, "failed walking subvolume %v", p)
		}

		if !fi.IsDir() {
			return nil // just ignore it!
		}

		if p == path {
			return nil
		}

		if err := isFileInfoSubvol(fi); err != nil {
			return err
		}

		if err := SubvolDelete(p); err != nil {
			return err
		}

		return filepath.SkipDir // children get walked by call above.
	}); err != nil {
		return err
	}

	var args C.struct_btrfs_ioctl_vol_args
	if len(name) > C.BTRFS_SUBVOL_NAME_MAX {
		return errors.Errorf("%q too long for subvolume", name)
	}

	nameptr := (*[1<<31 - 1]byte)(unsafe.Pointer(&args.name[0]))
	copy(nameptr[:C.BTRFS_SUBVOL_NAME_MAX], []byte(name))

	if err := ioctl(fp.Fd(), C.BTRFS_IOC_SNAP_DESTROY, uintptr(unsafe.Pointer(&args))); err != nil {
		return errors.Wrapf(err, "failed removing subvolume %v", path)
	}

	return nil
}

func openSubvolDir(path string) (*os.File, error) {
	fp, err := os.Open(path)
	if err != nil {
		return nil, errors.Wrapf(err, "opening %v as subvolume failed", path)
	}

	return fp, nil
}

func isStatfsSubvol(statfs *syscall.Statfs_t) error {
	if statfs.Type != C.BTRFS_SUPER_MAGIC {
		return errors.Errorf("not a btrfs filesystem")
	}

	return nil
}

func isFileInfoSubvol(fi os.FileInfo) error {
	if !fi.IsDir() {
		errors.Errorf("must be a directory")
	}

	stat := fi.Sys().(*syscall.Stat_t)

	if stat.Ino != C.BTRFS_FIRST_FREE_OBJECTID {
		return errors.Errorf("incorrect inode type")
	}

	return nil
}
<|endoftext|>