<commit_before>package main

import (
	"encoding/json"
	"github.com/gin-gonic/gin"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"html/template"
	"net/http"
	"strings"
)

type responseAdmin struct {
	Response
	Admin
}

func renderAdmin(c *gin.Context) {
	query := bson.M{}

	name, ok := c.GetQuery("name")
	if ok && len(name) != 0 {
		query["name"] = bson.RegEx{
			Pattern: `^.*?` + name + `.*$`,
			Options: "i",
		}
	}

	var results []Admin

	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)

	Result := getData(c, collection.Find(query), &results)

	Results, err := json.Marshal(Result)
	if err != nil {
		panic(err)
	}
	if XHR(c) {
		handleXHR(c, Results)
		return
	}

	c.Set("Results", template.JS(string(Results)))
	c.HTML(http.StatusOK, c.Request.URL.Path, c.Keys)
}

func createAdmin(c *gin.Context) {
	response := responseAdmin{}
	response.Init(c)
	admin := getAdmin(c)

	// validate
	ok := admin.IsMemberOf("root")
	if !ok {
		response.Errors = append(response.Errors, "You may not create administrators")
		response.Fail()
		return
	}

	response.Admin.DecodeRequest(c)
	if len(response.Name.Full) == 0 {
		response.Errors = append(response.Errors, "A name is required")
	}

	if response.HasErrors() {
		response.Fail()
		return
	}

	// handleName
	response.Name.Full = slugifyName(response.Name.Full)

	// duplicateAdministrator
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)
	err := collection.Find(bson.M{"name.full": response.Name.Full}).One(nil)
	// we expect err == mgo.ErrNotFound for success
	if err == nil {
		response.Errors = append(response.Errors, "That administrator already exists.")
		response.Fail()
		return
	} else if err != mgo.ErrNotFound {
		panic(err)
	}

	// handleName
	name := strings.Split(response.Name.Full, " ")
	response.Name.First = name[0]
	if len(name) == 2 {
		response.Name.Last = name[1]
		response.Admin.Name.Middle = ""
	}
	if len(name) == 3 {
		response.Name.Middle = name[2]
	}
	// todo maybe when we create first root user we lose it
	response.Admin.Search = []string{response.Name.First, response.Name.Middle, response.Name.Last}
	response.Admin.Permissions = []Permission{}
	response.Admin.Groups = []string{}

	// createAdministrator
	response.Admin.ID = bson.NewObjectId()
	err = collection.Insert(response.Admin)
	if err != nil {
		panic(err)
		return
	}
	response.Data["record"] = response
	response.Finish()
	//c.JSON(http.StatusOK, gin.H{"record": response, "success": true}) // todo necessary check
}

func readAdmin(c *gin.Context) {
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)
	admin := Admin{}
	err := collection.FindId(bson.ObjectIdHex(c.Param("id"))).One(&admin)
	if err != nil {
		if err == mgo.ErrNotFound {
			Status404Render(c)
			return
		}
		panic(err)
	}

	// populateGroups
	collection = db.C(ADMINGROUPS)
	adminGroups, err := admin.populateGroups(db)
	if err != nil {
		// mgo.ErrNotFound is not possible. "Root" group must be.
		panic(err)
	}

	results, err := json.Marshal(admin)
	if err != nil {
		panic(err)
	}
	if XHR(c) {
		c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
		c.Data(http.StatusOK, "application/json; charset=utf-8", results)
		return
	}

	// preparing for js.  Don't like it.
	// https://groups.google.com/forum/#!topic/golang-nuts/0HJoROz2TMo
	// https://play.golang.org/p/M_AoMQwtFt
	// 10 july 2016 wasn't expected
	// !!!  somehow label broke it too
	var adminGroupsS string
	for _, adminGroup := range adminGroups {
		adminGroupsS += `<option value="` + adminGroup.ID + `">` + adminGroup.Name + `</option>`
	}
	c.Set("Groups", template.JS(adminGroupsS))
	c.Set("Record", template.JS(getEscapedString(string(results))))
	c.HTML(http.StatusOK, "/admin/administrators/details/", c.Keys)
}

func updateAdmin(c *gin.Context) {
	response := responseAdmin{}
	response.Init(c)

	err := json.NewDecoder(c.Request.Body).Decode(&response.Admin.Name)
	if err != nil {
		panic(err)
	}

	if len(response.Name.First) == 0 {
		response.Errors = append(response.Errors, "A name is required")
	}

	if len(response.Name.Last) == 0 {
		response.Errors = append(response.Errors, "A lastname is required")
	}

	if response.HasErrors() {
		response.Fail()
		return
	}

	response.Admin.Name.Full = response.Admin.Name.First + " " + response.Admin.Name.Last

	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)

	// patchAdministrator
	err = collection.UpdateId(bson.ObjectIdHex(c.Param("id")), bson.M{
		"$set": bson.M{
			"name": response.Admin.Name,
		},
	})
	if err != nil {
		response.Errors = append(response.Errors, err.Error())
		response.Fail()
		return
	}

	response.Finish()
}

func updatePermissionsAdmin(c *gin.Context) {
	response := responseAdmin{}
	response.Init(c)
	//TODO there are not clear logic with populate of groups
	admin := getAdmin(c)

	// validate
	ok := admin.IsMemberOf("root")
	if !ok {
		response.Errors = append(response.Errors, "You may not change the permissions of admin groups.")
		response.Fail()
		return
	}

	response.Admin.DecodeRequest(c)
	response.ErrFor = map[string]string{} // in that handler it required (non standard behavior from node)
	if len(response.Permissions) == 0 {
		response.ErrFor["permissions"] = "required"
	}

	if response.HasErrors() {
		response.Fail()
		return
	}

	//patchAdmin
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)

	err := collection.UpdateId(bson.ObjectIdHex(c.Param("id")), bson.M{
		"$set": bson.M{
			"permissions": response.Admin.Permissions,
		},
	})
	if err != nil {
		println(err.Error())
		panic(err)
	}

	response.Finish()
}

func updateGroupsAdmin(c *gin.Context) {
	response := Response{}
	response.Init(c)

	admin := getAdmin(c)

	// validate
	ok := admin.IsMemberOf("root")
	if !ok {
		response.Errors = append(response.Errors, "You may not change the group memberships of admins.")
		response.Fail()
		return
	}

	var req struct {
		Groups []AdminGroup `json:"groups"`
	}
	err := json.NewDecoder(c.Request.Body).Decode(&req)
	if err != nil {
		panic(err)
	}

	if len(req.Groups) == 0 {
		response.ErrFor["groups"] = "required"
		response.Fail()
		return
	}

	setGroups := []string{}
	for _, group := range req.Groups {
		setGroups = append(setGroups, group.ID)
	}

	//patchAdmin
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)
	id := bson.ObjectIdHex(c.Param("id"))
	err = collection.UpdateId(id, bson.M{
		"$set": bson.M{
			"groups": setGroups,
		},
	})
	if err != nil {
		panic(err)
	}
	admin = &Admin{}
	err = collection.FindId(id).One(admin)
	if err != nil {
		panic(err)
	}

	admin.populateGroups(db)
	response.Data["admin"] = admin
	response.Finish()
}

func linkUser(c *gin.Context) {
	response := responseAdmin{}
	response.Init(c)

	admin := getAdmin(c)

	// validate
	ok := admin.IsMemberOf(ROOTGROUP)
	if !ok {
		response.Errors = append(response.Errors, "You may not link admins to users.")
		response.Fail()
		return
	}

	var req struct {
		NewUsername string `json:"newUsername"`
	}

	err := json.NewDecoder(c.Request.Body).Decode(&req)
	if err != nil {
		panic(err)
	}

	if len(req.NewUsername) == 0 {
		response.ErrFor["newUsername"] = "required"
		response.Errors = append(response.Errors, "required")
		response.Fail()
		return
	}

	//verifyUser
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(USERS)
	user := &User{}
	err = collection.Find(bson.M{"username": req.NewUsername}).One(&user)
	if err != nil {
		if err != mgo.ErrNotFound {
			panic(err)
		}
		response.Errors = append(response.Errors, "User not found.")
		response.Fail()
		return
	}
	id := c.Param("id")
	if user.Roles.Admin.String() == id {
		response.Errors = append(response.Errors, "User is already linked to a different admin.")
		response.Fail()
		return
	}

	// duplicateLinkCheck
	collection = db.C(ADMINS)
	err = collection.Find(
		bson.M{
			"user.id": id,
			"_id": bson.M{
				"user.id": id,
			},
		}).One(&admin) // reuse admin. If it will be used it mean that user already linked.

	if err == nil {
		response.Errors = append(response.Errors, "Another admin is already linked to that user.")
		response.Fail()
		return
	} else if err != mgo.ErrNotFound {
		panic(err)
	}

	admin.ID = bson.ObjectIdHex(id)
	// patchUser patchAdministrator
	err = admin.linkUser(db, user)

	if err != nil {

	}

	// getAdminForResponse  drywall require it // todo maybe bulk?
	err = collection.FindId(bson.ObjectIdHex(id)).One(&response.Admin)

	if err != nil {
		panic(err)
	}

	response.Data["admin"] = response.Admin
	response.Finish()
}

func unlinkUser(c *gin.Context) {
	response := responseAdmin{}
	response.Init(c)

	admin := getAdmin(c)

	// validate
	ok := admin.IsMemberOf(ROOTGROUP)
	if !ok {
		response.Errors = append(response.Errors, "You may not change the permissions of admin groups.")
		response.Fail()
		return
	}
	id := c.Param("id")
	if admin.ID.String() == id {
		response.Errors = append(response.Errors, "You may not unlink yourself from admin.")
		response.Fail()
		return
	} // todo  here is func for errors
	response.ErrFor = map[string]string{} // in that handler it required (non standard behavior from node)

	// patchUser
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(USERS)
	err := collection.Update(bson.M{"roles.admin": bson.ObjectIdHex(id)}, bson.M{
		"$set": bson.M{"roles.admin": ""},
	})
	if err != nil {
		if err != mgo.ErrNotFound {
			panic(err)
		}
		response.Errors = append(response.Errors, "User not found.")
		response.Fail()
		return
	}

	// patchAdministrator
	collection = db.C(ADMINS)
	err = collection.UpdateId(bson.ObjectIdHex(id), bson.M{
		"$set": bson.M{"user": bson.M{}},
	})

	if err != nil {
		panic(err)
	}

	response.Data["admin"] = response.Admin
	response.Finish()
}

func deleteAdmin(c *gin.Context) {
	response := Response{}
	response.Init(c)

	// validate
	if ok := getAdmin(c).IsMemberOf("root"); !ok {
		response.Errors = append(response.Errors, "You may not delete administrators.")
		response.Fail()
		return
	}

	// deleteUser
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)
	err := collection.RemoveId(bson.ObjectIdHex(c.Param("id")))
	if err != nil {
		response.Errors = append(response.Errors, err.Error())
		response.Fail()
		return
	}

	response.Finish()
}
<commit_msg>[admin] js permissions field error<commit_after>package main

import (
	"encoding/json"
	"github.com/gin-gonic/gin"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"html/template"
	"net/http"
	"strings"
)

type responseAdmin struct {
	Response
	Admin
}

func renderAdmin(c *gin.Context) {
	query := bson.M{}

	name, ok := c.GetQuery("name")
	if ok && len(name) != 0 {
		query["name"] = bson.RegEx{
			Pattern: `^.*?` + name + `.*$`,
			Options: "i",
		}
	}

	var results []Admin

	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)

	Result := getData(c, collection.Find(query), &results)

	Results, err := json.Marshal(Result)
	if err != nil {
		panic(err)
	}
	if XHR(c) {
		handleXHR(c, Results)
		return
	}

	c.Set("Results", template.JS(string(Results)))
	c.HTML(http.StatusOK, c.Request.URL.Path, c.Keys)
}

func createAdmin(c *gin.Context) {
	response := responseAdmin{}
	response.Init(c)
	admin := getAdmin(c)

	// validate
	ok := admin.IsMemberOf("root")
	if !ok {
		response.Errors = append(response.Errors, "You may not create administrators")
		response.Fail()
		return
	}

	response.Admin.DecodeRequest(c)
	if len(response.Name.Full) == 0 {
		response.Errors = append(response.Errors, "A name is required")
	}

	if response.HasErrors() {
		response.Fail()
		return
	}

	// handleName
	response.Name.Full = slugifyName(response.Name.Full)

	// duplicateAdministrator
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)
	err := collection.Find(bson.M{"name.full": response.Name.Full}).One(nil)
	// we expect err == mgo.ErrNotFound for success
	if err == nil {
		response.Errors = append(response.Errors, "That administrator already exists.")
		response.Fail()
		return
	} else if err != mgo.ErrNotFound {
		panic(err)
	}

	// handleName
	name := strings.Split(response.Name.Full, " ")
	response.Name.First = name[0]
	if len(name) == 2 {
		response.Name.Last = name[1]
		response.Admin.Name.Middle = ""
	}
	if len(name) == 3 {
		response.Name.Middle = name[2]
	}
	// todo maybe when we create first root user we lose it
	response.Admin.Search = []string{response.Name.First, response.Name.Middle, response.Name.Last}
	response.Admin.Permissions = []Permission{}
	response.Admin.Groups = []string{}

	// createAdministrator
	response.Admin.ID = bson.NewObjectId()
	err = collection.Insert(response.Admin)
	if err != nil {
		panic(err)
		return
	}
	response.Data["record"] = response
	response.Finish()
	//c.JSON(http.StatusOK, gin.H{"record": response, "success": true}) // todo necessary check
}

func readAdmin(c *gin.Context) {
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)
	admin := Admin{}
	err := collection.FindId(bson.ObjectIdHex(c.Param("id"))).One(&admin)
	if err != nil {
		if err == mgo.ErrNotFound {
			Status404Render(c)
			return
		}
		panic(err)
	}

	// populateGroups
	collection = db.C(ADMINGROUPS)
	adminGroups, err := admin.populateGroups(db)
	admin.Permissions = []Permission{}
	if err != nil {
		// mgo.ErrNotFound is not possible. "Root" group must be.
		panic(err)
	}

	results, err := json.Marshal(admin)
	if err != nil {
		panic(err)
	}
	if XHR(c) {
		c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
		c.Data(http.StatusOK, "application/json; charset=utf-8", results)
		return
	}

	// preparing for js.  Don't like it.
	// https://groups.google.com/forum/#!topic/golang-nuts/0HJoROz2TMo
	// https://play.golang.org/p/M_AoMQwtFt
	// 10 july 2016 wasn't expected
	// !!!  somehow label broke it too
	var adminGroupsS string
	for _, adminGroup := range adminGroups {
		adminGroupsS += `<option value="` + adminGroup.ID + `">` + adminGroup.Name + `</option>`
	}
	c.Set("Groups", template.JS(adminGroupsS))
	c.Set("Record", template.JS(getEscapedString(string(results))))
	c.HTML(http.StatusOK, "/admin/administrators/details/", c.Keys)
}

func updateAdmin(c *gin.Context) {
	response := responseAdmin{}
	response.Init(c)

	err := json.NewDecoder(c.Request.Body).Decode(&response.Admin.Name)
	if err != nil {
		panic(err)
	}

	if len(response.Name.First) == 0 {
		response.Errors = append(response.Errors, "A name is required")
	}

	if len(response.Name.Last) == 0 {
		response.Errors = append(response.Errors, "A lastname is required")
	}

	if response.HasErrors() {
		response.Fail()
		return
	}

	response.Admin.Name.Full = response.Admin.Name.First + " " + response.Admin.Name.Last

	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)

	// patchAdministrator
	err = collection.UpdateId(bson.ObjectIdHex(c.Param("id")), bson.M{
		"$set": bson.M{
			"name": response.Admin.Name,
		},
	})
	if err != nil {
		response.Errors = append(response.Errors, err.Error())
		response.Fail()
		return
	}

	response.Finish()
}

func updatePermissionsAdmin(c *gin.Context) {
	response := responseAdmin{}
	response.Init(c)
	//TODO there are not clear logic with populate of groups
	admin := getAdmin(c)

	// validate
	ok := admin.IsMemberOf("root")
	if !ok {
		response.Errors = append(response.Errors, "You may not change the permissions of admin groups.")
		response.Fail()
		return
	}

	response.Admin.DecodeRequest(c)
	response.ErrFor = map[string]string{} // in that handler it required (non standard behavior from node)
	if len(response.Permissions) == 0 {
		response.ErrFor["permissions"] = "required"
	}

	if response.HasErrors() {
		response.Fail()
		return
	}

	//patchAdmin
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)

	err := collection.UpdateId(bson.ObjectIdHex(c.Param("id")), bson.M{
		"$set": bson.M{
			"permissions": response.Admin.Permissions,
		},
	})
	if err != nil {
		println(err.Error())
		panic(err)
	}

	response.Finish()
}

func updateGroupsAdmin(c *gin.Context) {
	response := Response{}
	response.Init(c)

	admin := getAdmin(c)

	// validate
	ok := admin.IsMemberOf("root")
	if !ok {
		response.Errors = append(response.Errors, "You may not change the group memberships of admins.")
		response.Fail()
		return
	}

	var req struct {
		Groups []AdminGroup `json:"groups"`
	}
	err := json.NewDecoder(c.Request.Body).Decode(&req)
	if err != nil {
		panic(err)
	}

	if len(req.Groups) == 0 {
		response.ErrFor["groups"] = "required"
		response.Fail()
		return
	}

	setGroups := []string{}
	for _, group := range req.Groups {
		setGroups = append(setGroups, group.ID)
	}

	//patchAdmin
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)
	id := bson.ObjectIdHex(c.Param("id"))
	err = collection.UpdateId(id, bson.M{
		"$set": bson.M{
			"groups": setGroups,
		},
	})
	if err != nil {
		panic(err)
	}
	admin = &Admin{}
	err = collection.FindId(id).One(admin)
	if err != nil {
		panic(err)
	}

	admin.populateGroups(db)
	response.Data["admin"] = admin
	response.Finish()
}

func linkUser(c *gin.Context) {
	response := responseAdmin{}
	response.Init(c)

	admin := getAdmin(c)

	// validate
	ok := admin.IsMemberOf(ROOTGROUP)
	if !ok {
		response.Errors = append(response.Errors, "You may not link admins to users.")
		response.Fail()
		return
	}

	var req struct {
		NewUsername string `json:"newUsername"`
	}

	err := json.NewDecoder(c.Request.Body).Decode(&req)
	if err != nil {
		panic(err)
	}

	if len(req.NewUsername) == 0 {
		response.ErrFor["newUsername"] = "required"
		response.Errors = append(response.Errors, "required")
		response.Fail()
		return
	}

	//verifyUser
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(USERS)
	user := &User{}
	err = collection.Find(bson.M{"username": req.NewUsername}).One(&user)
	if err != nil {
		if err != mgo.ErrNotFound {
			panic(err)
		}
		response.Errors = append(response.Errors, "User not found.")
		response.Fail()
		return
	}
	id := c.Param("id")
	if user.Roles.Admin.String() == id {
		response.Errors = append(response.Errors, "User is already linked to a different admin.")
		response.Fail()
		return
	}

	// duplicateLinkCheck
	collection = db.C(ADMINS)
	err = collection.Find(
		bson.M{
			"user.id": id,
			"_id": bson.M{
				"user.id": id,
			},
		}).One(&admin) // reuse admin. If it will be used it mean that user already linked.

	if err == nil {
		response.Errors = append(response.Errors, "Another admin is already linked to that user.")
		response.Fail()
		return
	} else if err != mgo.ErrNotFound {
		panic(err)
	}

	admin.ID = bson.ObjectIdHex(id)
	// patchUser patchAdministrator
	err = admin.linkUser(db, user)

	if err != nil {

	}

	// getAdminForResponse  drywall require it // todo maybe bulk?
	err = collection.FindId(bson.ObjectIdHex(id)).One(&response.Admin)

	if err != nil {
		panic(err)
	}

	response.Data["admin"] = response.Admin
	response.Finish()
}

func unlinkUser(c *gin.Context) {
	response := responseAdmin{}
	response.Init(c)

	admin := getAdmin(c)

	// validate
	ok := admin.IsMemberOf(ROOTGROUP)
	if !ok {
		response.Errors = append(response.Errors, "You may not change the permissions of admin groups.")
		response.Fail()
		return
	}
	id := c.Param("id")
	if admin.ID.String() == id {
		response.Errors = append(response.Errors, "You may not unlink yourself from admin.")
		response.Fail()
		return
	} // todo  here is func for errors
	response.ErrFor = map[string]string{} // in that handler it required (non standard behavior from node)

	// patchUser
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(USERS)
	err := collection.Update(bson.M{"roles.admin": bson.ObjectIdHex(id)}, bson.M{
		"$set": bson.M{"roles.admin": ""},
	})
	if err != nil {
		if err != mgo.ErrNotFound {
			panic(err)
		}
		response.Errors = append(response.Errors, "User not found.")
		response.Fail()
		return
	}

	// patchAdministrator
	collection = db.C(ADMINS)
	err = collection.UpdateId(bson.ObjectIdHex(id), bson.M{
		"$set": bson.M{"user": bson.M{}},
	})

	if err != nil {
		panic(err)
	}

	response.Data["admin"] = response.Admin
	response.Finish()
}

func deleteAdmin(c *gin.Context) {
	response := Response{}
	response.Init(c)

	// validate
	if ok := getAdmin(c).IsMemberOf("root"); !ok {
		response.Errors = append(response.Errors, "You may not delete administrators.")
		response.Fail()
		return
	}

	// deleteUser
	db := getMongoDBInstance()
	defer db.Session.Close()
	collection := db.C(ADMINS)
	err := collection.RemoveId(bson.ObjectIdHex(c.Param("id")))
	if err != nil {
		response.Errors = append(response.Errors, err.Error())
		response.Fail()
		return
	}

	response.Finish()
}
<|endoftext|>


<commit_before>package output

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/dustin/go-humanize"

	"github.com/thought-machine/please/src/cli"
	"github.com/thought-machine/please/src/core"
)

// We only set the terminal title for terminals that at least claim to be xterm
// (note that most terminals do for compatibility; some report as xterm-color, hence HasPrefix)
var terminalClaimsToBeXterm = strings.HasPrefix(os.Getenv("TERM"), "xterm")

type displayer struct {
	state                                               *core.BuildState
	targets                                             []buildingTarget
	numWorkers, maxWorkers, numRemote, maxRows, maxCols int
	stats                                               bool
	lines, lastLines                                    int // mutable - records how many rows we've printed this time
}

func display(ctx context.Context, state *core.BuildState, buildingTargets []buildingTarget) {
	backend := cli.NewLogBackend(state.Config.Display.MaxWorkers)
	go func() {
		sig := make(chan os.Signal, 10)
		signal.Notify(sig, syscall.SIGWINCH)
		for {
			<-sig
			recalcWindowSize(backend)
		}
	}()
	recalcWindowSize(backend)
	backend.SetActive()

	d := &displayer{
		state:      state,
		targets:    buildingTargets,
		numWorkers: state.Config.Please.NumThreads,
		maxWorkers: state.Config.Display.MaxWorkers,
		numRemote:  state.Config.NumRemoteExecutors(),
		maxRows:    backend.MaxInteractiveRows,
		maxCols:    backend.Cols,
		stats:      state.Config.Display.SystemStats,
	}

	d.printLines()
	d.run(ctx, backend)
	setWindowTitle(state, false)
	// Clear it all out.
	d.moveToFirstLine()
	printf("${CLEAR_END}")
	backend.Deactivate()
}

func (d *displayer) run(ctx context.Context, backend *cli.LogBackend) {
	ticker := time.NewTicker(50 * time.Millisecond)
	defer ticker.Stop()
	done := ctx.Done()
	for {
		select {
		case <-done:
			return
		case <-ticker.C:
			d.moveToFirstLine()
			d.printLines()
			for _, line := range backend.Output {
				printf("${ERASE_AFTER}%s\n", line)
				d.lines++
			}
			// Clean out any lines that were visible last time but are not now.
			if d.lines < d.lastLines {
				for i := d.lines; i < d.lastLines; i++ {
					printf("${ERASE_AFTER}\n")
				}
				printf("\x1b[%dA", d.lastLines-d.lines) // Move back up again
			}
			setWindowTitle(d.state, true)
		}
	}
}

// moveToFirstLine resets back to the first line.
func (d *displayer) moveToFirstLine() {
	printf("\x1b[%dA", d.lines)
	d.lastLines = d.lines
	d.lines = 0
}

func (d *displayer) printLines() {
	now := time.Now()
	printf("Building [%d/%d, %3.1fs]:\n", d.state.NumDone(), d.state.NumActive(), time.Since(d.state.StartTime).Seconds())
	d.lines++
	if d.stats {
		printStat("CPU use", d.state.Stats.CPU.Used, d.state.Stats.CPU.Count)
		printStat("I/O", d.state.Stats.CPU.IOWait, d.state.Stats.CPU.Count)
		printStat("Mem use", d.state.Stats.Memory.UsedPercent, 1)
		if d.state.Stats.NumWorkerProcesses > 0 {
			printf("  ${BOLD_WHITE}Worker processes: %d${RESET}", d.state.Stats.NumWorkerProcesses)
		}
		if d.state.RemoteClient != nil {
			in, out, _, _ := d.state.RemoteClient.DataRate()
			printf("  ${BOLD_WHITE}RPC data in: %6s/s out: %6s/s${RESET}", humanize.Bytes(uint64(in)), humanize.Bytes(uint64(out)))
		}
		printf("${ERASE_AFTER}\n")
		d.lines++
	}
	workers := 0
	anyRemote := d.numRemote > 0
	for i := 0; i < d.numWorkers && i < d.maxRows && workers < d.maxWorkers; i++ {
		workers += d.printRow(i, now, anyRemote)
		d.lines++
	}
	if anyRemote {
		active := d.numRemoteActive()
		printf("Remote processes [%3d/%3d active]:   \n", active, d.numRemote)
		d.lines++
		for i := 0; i < d.numRemote && i < d.maxRows && workers < d.maxWorkers; i++ {
			workers += d.printRow(d.numWorkers+i, now, true)
			d.lines++
		}
		if workers < active {
			printf("${RESET}   [%2d more...]${ERASE_AFTER}\n", active-workers)
			d.lines++
		}
	}
	printf("${RESET}")
}

func (d *displayer) numRemoteActive() int {
	count := 0
	for i := 0; i < d.numRemote; i++ {
		if d.targets[d.numWorkers+i].Active {
			count++
		}
	}
	return count
}

func (d *displayer) printRow(i int, now time.Time, remote bool) int {
	d.targets[i].Lock()
	// Take a local copy of the structure, which isn't *that* big, so we don't need to retain the lock
	// while we do potentially blocking things like printing.
	target := d.targets[i].buildingTargetData
	d.targets[i].Unlock()
	label := target.Label.Parent()
	duration := now.Sub(target.Started).Seconds()
	if target.Active && target.Target != nil && target.Target.ShowProgress && target.Target.Progress > 0.0 {
		if target.Target.Progress > 1.0 && target.Target.Progress < 100.0 && target.Target.Progress != target.LastProgress {
			proportionDone := target.Target.Progress / 100.0
			perPercent := float32(duration) / proportionDone
			d.targets[i].Eta = time.Duration(perPercent * (1.0 - proportionDone) * float32(time.Second)).Truncate(time.Second)
			d.targets[i].LastProgress = target.Target.Progress
		}
		if target.Eta > 0 {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${RESET} (%.1f%%, est %s remaining)${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description, target.Target.Progress, target.Eta)
		} else {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${RESET} (%.1f%% complete)${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description, target.Target.Progress)
		}
	} else if target.Active {
		d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${ERASE_AFTER}\n",
			duration, target.Colour, label, target.Description)
	} else if time.Since(target.Finished).Seconds() < 0.5 {
		// Only display finished targets for half a second after they're done.
		duration := target.Finished.Sub(target.Started).Seconds()
		if target.Failed {
			d.printf("${BOLD_RED}=> [%4.1fs] ${RESET}%s%s ${BOLD_RED}Failed${ERASE_AFTER}\n",
				duration, target.Colour, label)
		} else if target.Cached {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_GREY}%s${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description)
		} else {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${WHITE}%s${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description)
		}
	} else if !remote {
		printf("${BOLD_GREY}=|${ERASE_AFTER}\n")
	} else {
		d.lines-- // Didn't print it
		return 0
	}
	return 1
}

// printStat prints a single statistic with appropriate colours.
func printStat(caption string, stat float64, multiplier int) {
	colour := "${BOLD_GREEN}"
	if stat > 80.0*float64(multiplier) {
		colour = "${BOLD_RED}"
	} else if stat > 60.0*float64(multiplier) {
		colour = "${BOLD_YELLOW}"
	}
	printf("  ${BOLD_WHITE}%s:${RESET} %s%5.1f%%${RESET}", caption, colour, stat)
}

func recalcWindowSize(backend *cli.LogBackend) {
	rows, cols, _ := cli.WindowSize()
	backend.Lock()
	defer backend.Unlock()
	backend.Rows = rows - 4 // Give a little space at the edge for any off-by-ones
	backend.Cols = cols
	backend.RecalcLines()
}

// Limited-length printf that respects current window width.
// Output is truncated at the middle to fit within 'cols'.
func (d *displayer) printf(format string, args ...interface{}) {
	fmt.Fprint(os.Stderr, lprintfPrepare(d.maxCols, os.Expand(fmt.Sprintf(format, args...), replace)))
}

func lprintfPrepare(cols int, s string) string {
	if len(s) < cols {
		return s // it's short enough, nice and simple
	}
	// Okay, it's too long. Tricky thing: ANSI escape codes don't count for width
	// so we need to count without those. Bonus: make an effort to be unicode-aware.
	var b bytes.Buffer
	written := 0
	inAnsiCode := false
	for _, rune := range s {
		if inAnsiCode {
			b.WriteRune(rune)
			if rune == 'm' {
				inAnsiCode = false
			}
		} else if rune == '\x1b' {
			b.WriteRune(rune)
			inAnsiCode = true
		} else if rune == '\n' {
			b.WriteRune(rune)
		} else if written == cols-3 {
			b.WriteString("...")
			written += 3
		} else if written < cols-3 {
			b.WriteRune(rune)
			written++
		}
	}
	return b.String()
}

// setWindowTitle sets the title of the current shell window based on the current build state.
func setWindowTitle(state *core.BuildState, running bool) {
	if !state.Config.Display.UpdateTitle {
		return
	}
	if running {
		SetWindowTitle("plz: finishing up")
	} else {
		SetWindowTitle(fmt.Sprintf("plz: %d / %d tasks, %3.1fs", state.NumDone(), state.NumActive(), time.Since(state.StartTime).Seconds()))
	}
}

// SetWindowTitle sets the title of the current shell window.
func SetWindowTitle(title string) {
	if cli.StdErrIsATerminal && terminalClaimsToBeXterm {
		os.Stderr.Write([]byte(fmt.Sprintf("\033]0;%s\007", title)))
	}
}
<commit_msg>another display tweak<commit_after>package output

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/dustin/go-humanize"

	"github.com/thought-machine/please/src/cli"
	"github.com/thought-machine/please/src/core"
)

// We only set the terminal title for terminals that at least claim to be xterm
// (note that most terminals do for compatibility; some report as xterm-color, hence HasPrefix)
var terminalClaimsToBeXterm = strings.HasPrefix(os.Getenv("TERM"), "xterm")

type displayer struct {
	state                                               *core.BuildState
	targets                                             []buildingTarget
	numWorkers, maxWorkers, numRemote, maxRows, maxCols int
	stats                                               bool
	lines, lastLines                                    int // mutable - records how many rows we've printed this time
}

func display(ctx context.Context, state *core.BuildState, buildingTargets []buildingTarget) {
	backend := cli.NewLogBackend(state.Config.Display.MaxWorkers)
	go func() {
		sig := make(chan os.Signal, 10)
		signal.Notify(sig, syscall.SIGWINCH)
		for {
			<-sig
			recalcWindowSize(backend)
		}
	}()
	recalcWindowSize(backend)
	backend.SetActive()

	d := &displayer{
		state:      state,
		targets:    buildingTargets,
		numWorkers: state.Config.Please.NumThreads,
		maxWorkers: state.Config.Display.MaxWorkers,
		numRemote:  state.Config.NumRemoteExecutors(),
		maxRows:    backend.MaxInteractiveRows,
		maxCols:    backend.Cols,
		stats:      state.Config.Display.SystemStats,
	}

	d.printLines()
	d.run(ctx, backend)
	setWindowTitle(state, false)
	// Clear it all out.
	d.moveToFirstLine()
	printf("${CLEAR_END}")
	backend.Deactivate()
}

func (d *displayer) run(ctx context.Context, backend *cli.LogBackend) {
	ticker := time.NewTicker(50 * time.Millisecond)
	defer ticker.Stop()
	done := ctx.Done()
	for {
		select {
		case <-done:
			return
		case <-ticker.C:
			d.moveToFirstLine()
			d.printLines()
			for _, line := range backend.Output {
				printf("${ERASE_AFTER}%s\n", line)
				d.lines++
			}
			// Clean out any lines that were visible last time but are not now.
			if d.lines < d.lastLines {
				for i := d.lines; i < d.lastLines; i++ {
					printf("${ERASE_AFTER}\n")
				}
				printf("\x1b[%dA", d.lastLines-d.lines) // Move back up again
			}
			setWindowTitle(d.state, true)
		}
	}
}

// moveToFirstLine resets back to the first line.
func (d *displayer) moveToFirstLine() {
	printf("\x1b[%dA", d.lines)
	d.lastLines = d.lines
	d.lines = 0
}

func (d *displayer) printLines() {
	now := time.Now()
	printf("Building [%d/%d, %3.1fs]:\n", d.state.NumDone(), d.state.NumActive(), time.Since(d.state.StartTime).Seconds())
	d.lines++
	if d.stats {
		printStat("CPU use", d.state.Stats.CPU.Used, d.state.Stats.CPU.Count)
		printStat("I/O", d.state.Stats.CPU.IOWait, d.state.Stats.CPU.Count)
		printStat("Mem use", d.state.Stats.Memory.UsedPercent, 1)
		if d.state.Stats.NumWorkerProcesses > 0 {
			printf("  ${BOLD_WHITE}Worker processes: %d${RESET}", d.state.Stats.NumWorkerProcesses)
		}
		if d.state.RemoteClient != nil {
			in, out, _, _ := d.state.RemoteClient.DataRate()
			printf("  ${BOLD_WHITE}RPC data in: %6s/s out: %6s/s${RESET}", humanize.Bytes(uint64(in)), humanize.Bytes(uint64(out)))
		}
		printf("${ERASE_AFTER}\n")
		d.lines++
	}
	workers := 0
	anyRemote := d.numRemote > 0
	for i := 0; i < d.numWorkers && i < d.maxRows && workers < d.maxWorkers; i++ {
		workers += d.printRow(i, now, anyRemote)
		d.lines++
	}
	if anyRemote {
		active := d.numRemoteActive()
		printf("Remote processes [%3d/%3d active]:   ${ERASE_AFTER}\n", active, d.numRemote)
		d.lines++
		for i := 0; i < d.numRemote && i < d.maxRows && workers < d.maxWorkers; i++ {
			workers += d.printRow(d.numWorkers+i, now, true)
			d.lines++
		}
		if workers < active {
			printf("${RESET}   [%2d more...]${ERASE_AFTER}\n", active-workers)
			d.lines++
		}
	}
	printf("${RESET}")
}

func (d *displayer) numRemoteActive() int {
	count := 0
	for i := 0; i < d.numRemote; i++ {
		if d.targets[d.numWorkers+i].Active {
			count++
		}
	}
	return count
}

func (d *displayer) printRow(i int, now time.Time, remote bool) int {
	d.targets[i].Lock()
	// Take a local copy of the structure, which isn't *that* big, so we don't need to retain the lock
	// while we do potentially blocking things like printing.
	target := d.targets[i].buildingTargetData
	d.targets[i].Unlock()
	label := target.Label.Parent()
	duration := now.Sub(target.Started).Seconds()
	if target.Active && target.Target != nil && target.Target.ShowProgress && target.Target.Progress > 0.0 {
		if target.Target.Progress > 1.0 && target.Target.Progress < 100.0 && target.Target.Progress != target.LastProgress {
			proportionDone := target.Target.Progress / 100.0
			perPercent := float32(duration) / proportionDone
			d.targets[i].Eta = time.Duration(perPercent * (1.0 - proportionDone) * float32(time.Second)).Truncate(time.Second)
			d.targets[i].LastProgress = target.Target.Progress
		}
		if target.Eta > 0 {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${RESET} (%.1f%%, est %s remaining)${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description, target.Target.Progress, target.Eta)
		} else {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${RESET} (%.1f%% complete)${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description, target.Target.Progress)
		}
	} else if target.Active {
		d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${ERASE_AFTER}\n",
			duration, target.Colour, label, target.Description)
	} else if time.Since(target.Finished).Seconds() < 0.5 {
		// Only display finished targets for half a second after they're done.
		duration := target.Finished.Sub(target.Started).Seconds()
		if target.Failed {
			d.printf("${BOLD_RED}=> [%4.1fs] ${RESET}%s%s ${BOLD_RED}Failed${ERASE_AFTER}\n",
				duration, target.Colour, label)
		} else if target.Cached {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_GREY}%s${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description)
		} else {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${WHITE}%s${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description)
		}
	} else if !remote {
		printf("${BOLD_GREY}=|${ERASE_AFTER}\n")
	} else {
		d.lines-- // Didn't print it
		return 0
	}
	return 1
}

// printStat prints a single statistic with appropriate colours.
func printStat(caption string, stat float64, multiplier int) {
	colour := "${BOLD_GREEN}"
	if stat > 80.0*float64(multiplier) {
		colour = "${BOLD_RED}"
	} else if stat > 60.0*float64(multiplier) {
		colour = "${BOLD_YELLOW}"
	}
	printf("  ${BOLD_WHITE}%s:${RESET} %s%5.1f%%${RESET}", caption, colour, stat)
}

func recalcWindowSize(backend *cli.LogBackend) {
	rows, cols, _ := cli.WindowSize()
	backend.Lock()
	defer backend.Unlock()
	backend.Rows = rows - 4 // Give a little space at the edge for any off-by-ones
	backend.Cols = cols
	backend.RecalcLines()
}

// Limited-length printf that respects current window width.
// Output is truncated at the middle to fit within 'cols'.
func (d *displayer) printf(format string, args ...interface{}) {
	fmt.Fprint(os.Stderr, lprintfPrepare(d.maxCols, os.Expand(fmt.Sprintf(format, args...), replace)))
}

func lprintfPrepare(cols int, s string) string {
	if len(s) < cols {
		return s // it's short enough, nice and simple
	}
	// Okay, it's too long. Tricky thing: ANSI escape codes don't count for width
	// so we need to count without those. Bonus: make an effort to be unicode-aware.
	var b bytes.Buffer
	written := 0
	inAnsiCode := false
	for _, rune := range s {
		if inAnsiCode {
			b.WriteRune(rune)
			if rune == 'm' {
				inAnsiCode = false
			}
		} else if rune == '\x1b' {
			b.WriteRune(rune)
			inAnsiCode = true
		} else if rune == '\n' {
			b.WriteRune(rune)
		} else if written == cols-3 {
			b.WriteString("...")
			written += 3
		} else if written < cols-3 {
			b.WriteRune(rune)
			written++
		}
	}
	return b.String()
}

// setWindowTitle sets the title of the current shell window based on the current build state.
func setWindowTitle(state *core.BuildState, running bool) {
	if !state.Config.Display.UpdateTitle {
		return
	}
	if running {
		SetWindowTitle("plz: finishing up")
	} else {
		SetWindowTitle(fmt.Sprintf("plz: %d / %d tasks, %3.1fs", state.NumDone(), state.NumActive(), time.Since(state.StartTime).Seconds()))
	}
}

// SetWindowTitle sets the title of the current shell window.
func SetWindowTitle(title string) {
	if cli.StdErrIsATerminal && terminalClaimsToBeXterm {
		os.Stderr.Write([]byte(fmt.Sprintf("\033]0;%s\007", title)))
	}
}
<|endoftext|>


<commit_before>package output

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/dustin/go-humanize"

	"github.com/thought-machine/please/src/cli"
	"github.com/thought-machine/please/src/core"
)

// We only set the terminal title for terminals that at least claim to be xterm
// (note that most terminals do for compatibility; some report as xterm-color, hence HasPrefix)
var terminalClaimsToBeXterm = strings.HasPrefix(os.Getenv("TERM"), "xterm")

type displayer struct {
	state                                               *core.BuildState
	targets                                             []buildingTarget
	numWorkers, maxWorkers, numRemote, maxRows, maxCols int
	stats                                               bool
	lines, lastLines                                    int // mutable - records how many rows we've printed this time
}

func display(ctx context.Context, state *core.BuildState, buildingTargets []buildingTarget) {
	backend := cli.NewLogBackend(state.Config.Display.MaxWorkers)
	go func() {
		sig := make(chan os.Signal, 10)
		signal.Notify(sig, syscall.SIGWINCH)
		for {
			<-sig
			recalcWindowSize(backend)
		}
	}()
	recalcWindowSize(backend)
	backend.SetActive()

	d := &displayer{
		state:      state,
		targets:    buildingTargets,
		numWorkers: state.Config.Please.NumThreads,
		maxWorkers: state.Config.Display.MaxWorkers,
		numRemote:  state.Config.NumRemoteExecutors(),
		maxRows:    backend.MaxInteractiveRows,
		maxCols:    backend.Cols,
		stats:      state.Config.Display.SystemStats,
	}

	d.printLines()
	d.run(ctx, backend)
	setWindowTitle(state, false)
	// Clear it all out.
	d.moveToFirstLine()
	printf("${CLEAR_END}")
	backend.Deactivate()
}

func (d *displayer) run(ctx context.Context, backend *cli.LogBackend) {
	ticker := time.NewTicker(50 * time.Millisecond)
	defer ticker.Stop()
	done := ctx.Done()
	for {
		select {
		case <-done:
			return
		case <-ticker.C:
			d.moveToFirstLine()
			d.printLines()
			for _, line := range backend.Output {
				printf("${ERASE_AFTER}%s\n", line)
				d.lines++
			}
			// Clean out any lines that were visible last time but are not now.
			if d.lines < d.lastLines {
				for i := d.lines; i < d.lastLines; i++ {
					printf("${ERASE_AFTER}\n")
				}
				printf("\x1b[%dA", d.lastLines-d.lines) // Move back up again
			}
			setWindowTitle(d.state, true)
		}
	}
}

// moveToFirstLine resets back to the first line.
func (d *displayer) moveToFirstLine() {
	printf("\x1b[%dA", d.lines)
	d.lastLines = d.lines
	d.lines = 0
}

func (d *displayer) printLines() {
	now := time.Now()
	printf("Building [%d/%d, %3.1fs]:\n", d.state.NumDone(), d.state.NumActive(), time.Since(d.state.StartTime).Seconds())
	d.lines++
	if d.stats {
		printStat("CPU use", d.state.Stats.CPU.Used, d.state.Stats.CPU.Count)
		printStat("I/O", d.state.Stats.CPU.IOWait, d.state.Stats.CPU.Count)
		printStat("Mem use", d.state.Stats.Memory.UsedPercent, 1)
		if d.state.Stats.NumWorkerProcesses > 0 {
			printf("  ${BOLD_WHITE}Worker processes: %d${RESET}", d.state.Stats.NumWorkerProcesses)
		}
		if d.state.RemoteClient != nil {
			in, out, _, _ := d.state.RemoteClient.DataRate()
			printf("  ${BOLD_WHITE}RPC data in: %6s/s out: %6s/s${RESET}", humanize.Bytes(uint64(in)), humanize.Bytes(uint64(out)))
		}
		printf("${ERASE_AFTER}\n")
		d.lines++
	}
	workers := 0
	anyRemote := d.numRemote > 0
	for i := 0; i < d.numWorkers && i < d.maxRows && workers < d.maxWorkers; i++ {
		workers += d.printRow(i, now, anyRemote)
		d.lines++
	}
	if anyRemote {
		active := d.numRemoteActive()
		printf("Remote processes [%d/%d active]:   \n", active, d.numRemote)
		d.lines++
		for i := 0; i < d.numRemote && i < d.maxRows && workers < d.maxWorkers; i++ {
			workers += d.printRow(d.numWorkers+i, now, true)
			d.lines++
		}
		if workers < active {
			printf("   [%d more...]\n", active-workers)
			d.lines++
		}
	}
	printf("${RESET}")
}

func (d *displayer) numRemoteActive() int {
	count := 0
	for i := 0; i < d.numRemote; i++ {
		if d.targets[d.numWorkers+i].Active {
			count++
		}
	}
	return count
}

func (d *displayer) printRow(i int, now time.Time, remote bool) int {
	d.targets[i].Lock()
	// Take a local copy of the structure, which isn't *that* big, so we don't need to retain the lock
	// while we do potentially blocking things like printing.
	target := d.targets[i].buildingTargetData
	d.targets[i].Unlock()
	label := target.Label.Parent()
	duration := now.Sub(target.Started).Seconds()
	if target.Active && target.Target != nil && target.Target.ShowProgress && target.Target.Progress > 0.0 {
		if target.Target.Progress > 1.0 && target.Target.Progress < 100.0 && target.Target.Progress != target.LastProgress {
			proportionDone := target.Target.Progress / 100.0
			perPercent := float32(duration) / proportionDone
			d.targets[i].Eta = time.Duration(perPercent * (1.0 - proportionDone) * float32(time.Second)).Truncate(time.Second)
			d.targets[i].LastProgress = target.Target.Progress
		}
		if target.Eta > 0 {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${RESET} (%.1f%%, est %s remaining)${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description, target.Target.Progress, target.Eta)
		} else {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${RESET} (%.1f%% complete)${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description, target.Target.Progress)
		}
	} else if target.Active {
		d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${ERASE_AFTER}\n",
			duration, target.Colour, label, target.Description)
	} else if time.Since(target.Finished).Seconds() < 0.5 {
		// Only display finished targets for half a second after they're done.
		duration := target.Finished.Sub(target.Started).Seconds()
		if target.Failed {
			d.printf("${BOLD_RED}=> [%4.1fs] ${RESET}%s%s ${BOLD_RED}Failed${ERASE_AFTER}\n",
				duration, target.Colour, label)
		} else if target.Cached {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_GREY}%s${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description)
		} else {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${WHITE}%s${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description)
		}
	} else if !remote {
		printf("${BOLD_GREY}=|${ERASE_AFTER}\n")
	} else {
		d.lines-- // Didn't print it
		return 0
	}
	return 1
}

// printStat prints a single statistic with appropriate colours.
func printStat(caption string, stat float64, multiplier int) {
	colour := "${BOLD_GREEN}"
	if stat > 80.0*float64(multiplier) {
		colour = "${BOLD_RED}"
	} else if stat > 60.0*float64(multiplier) {
		colour = "${BOLD_YELLOW}"
	}
	printf("  ${BOLD_WHITE}%s:${RESET} %s%5.1f%%${RESET}", caption, colour, stat)
}

func recalcWindowSize(backend *cli.LogBackend) {
	rows, cols, _ := cli.WindowSize()
	backend.Lock()
	defer backend.Unlock()
	backend.Rows = rows - 4 // Give a little space at the edge for any off-by-ones
	backend.Cols = cols
	backend.RecalcLines()
}

// Limited-length printf that respects current window width.
// Output is truncated at the middle to fit within 'cols'.
func (d *displayer) printf(format string, args ...interface{}) {
	fmt.Fprint(os.Stderr, lprintfPrepare(d.maxCols, os.Expand(fmt.Sprintf(format, args...), replace)))
}

func lprintfPrepare(cols int, s string) string {
	if len(s) < cols {
		return s // it's short enough, nice and simple
	}
	// Okay, it's too long. Tricky thing: ANSI escape codes don't count for width
	// so we need to count without those. Bonus: make an effort to be unicode-aware.
	var b bytes.Buffer
	written := 0
	inAnsiCode := false
	for _, rune := range s {
		if inAnsiCode {
			b.WriteRune(rune)
			if rune == 'm' {
				inAnsiCode = false
			}
		} else if rune == '\x1b' {
			b.WriteRune(rune)
			inAnsiCode = true
		} else if rune == '\n' {
			b.WriteRune(rune)
		} else if written == cols-3 {
			b.WriteString("...")
			written += 3
		} else if written < cols-3 {
			b.WriteRune(rune)
			written++
		}
	}
	return b.String()
}

// setWindowTitle sets the title of the current shell window based on the current build state.
func setWindowTitle(state *core.BuildState, running bool) {
	if !state.Config.Display.UpdateTitle {
		return
	}
	if running {
		SetWindowTitle("plz: finishing up")
	} else {
		SetWindowTitle(fmt.Sprintf("plz: %d / %d tasks, %3.1fs", state.NumDone(), state.NumActive(), time.Since(state.StartTime).Seconds()))
	}
}

// SetWindowTitle sets the title of the current shell window.
func SetWindowTitle(title string) {
	if cli.StdErrIsATerminal && terminalClaimsToBeXterm {
		os.Stderr.Write([]byte(fmt.Sprintf("\033]0;%s\007", title)))
	}
}
<commit_msg>Consistent coloured output<commit_after>package output

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/dustin/go-humanize"

	"github.com/thought-machine/please/src/cli"
	"github.com/thought-machine/please/src/core"
)

// We only set the terminal title for terminals that at least claim to be xterm
// (note that most terminals do for compatibility; some report as xterm-color, hence HasPrefix)
var terminalClaimsToBeXterm = strings.HasPrefix(os.Getenv("TERM"), "xterm")

type displayer struct {
	state                                               *core.BuildState
	targets                                             []buildingTarget
	numWorkers, maxWorkers, numRemote, maxRows, maxCols int
	stats                                               bool
	lines, lastLines                                    int // mutable - records how many rows we've printed this time
}

func display(ctx context.Context, state *core.BuildState, buildingTargets []buildingTarget) {
	backend := cli.NewLogBackend(state.Config.Display.MaxWorkers)
	go func() {
		sig := make(chan os.Signal, 10)
		signal.Notify(sig, syscall.SIGWINCH)
		for {
			<-sig
			recalcWindowSize(backend)
		}
	}()
	recalcWindowSize(backend)
	backend.SetActive()

	d := &displayer{
		state:      state,
		targets:    buildingTargets,
		numWorkers: state.Config.Please.NumThreads,
		maxWorkers: state.Config.Display.MaxWorkers,
		numRemote:  state.Config.NumRemoteExecutors(),
		maxRows:    backend.MaxInteractiveRows,
		maxCols:    backend.Cols,
		stats:      state.Config.Display.SystemStats,
	}

	d.printLines()
	d.run(ctx, backend)
	setWindowTitle(state, false)
	// Clear it all out.
	d.moveToFirstLine()
	printf("${CLEAR_END}")
	backend.Deactivate()
}

func (d *displayer) run(ctx context.Context, backend *cli.LogBackend) {
	ticker := time.NewTicker(50 * time.Millisecond)
	defer ticker.Stop()
	done := ctx.Done()
	for {
		select {
		case <-done:
			return
		case <-ticker.C:
			d.moveToFirstLine()
			d.printLines()
			for _, line := range backend.Output {
				printf("${ERASE_AFTER}%s\n", line)
				d.lines++
			}
			// Clean out any lines that were visible last time but are not now.
			if d.lines < d.lastLines {
				for i := d.lines; i < d.lastLines; i++ {
					printf("${ERASE_AFTER}\n")
				}
				printf("\x1b[%dA", d.lastLines-d.lines) // Move back up again
			}
			setWindowTitle(d.state, true)
		}
	}
}

// moveToFirstLine resets back to the first line.
func (d *displayer) moveToFirstLine() {
	printf("\x1b[%dA", d.lines)
	d.lastLines = d.lines
	d.lines = 0
}

func (d *displayer) printLines() {
	now := time.Now()
	printf("Building [%d/%d, %3.1fs]:\n", d.state.NumDone(), d.state.NumActive(), time.Since(d.state.StartTime).Seconds())
	d.lines++
	if d.stats {
		printStat("CPU use", d.state.Stats.CPU.Used, d.state.Stats.CPU.Count)
		printStat("I/O", d.state.Stats.CPU.IOWait, d.state.Stats.CPU.Count)
		printStat("Mem use", d.state.Stats.Memory.UsedPercent, 1)
		if d.state.Stats.NumWorkerProcesses > 0 {
			printf("  ${BOLD_WHITE}Worker processes: %d${RESET}", d.state.Stats.NumWorkerProcesses)
		}
		if d.state.RemoteClient != nil {
			in, out, _, _ := d.state.RemoteClient.DataRate()
			printf("  ${BOLD_WHITE}RPC data in: %6s/s out: %6s/s${RESET}", humanize.Bytes(uint64(in)), humanize.Bytes(uint64(out)))
		}
		printf("${ERASE_AFTER}\n")
		d.lines++
	}
	workers := 0
	anyRemote := d.numRemote > 0
	for i := 0; i < d.numWorkers && i < d.maxRows && workers < d.maxWorkers; i++ {
		workers += d.printRow(i, now, anyRemote)
		d.lines++
	}
	if anyRemote {
		active := d.numRemoteActive()
		printf("Remote processes [%d/%d active]:   \n", active, d.numRemote)
		d.lines++
		for i := 0; i < d.numRemote && i < d.maxRows && workers < d.maxWorkers; i++ {
			workers += d.printRow(d.numWorkers+i, now, true)
			d.lines++
		}
		if workers < active {
			printf("${RESET}   [%d more...]${ERASE_AFTER}\n", active-workers)
			d.lines++
		}
	}
	printf("${RESET}")
}

func (d *displayer) numRemoteActive() int {
	count := 0
	for i := 0; i < d.numRemote; i++ {
		if d.targets[d.numWorkers+i].Active {
			count++
		}
	}
	return count
}

func (d *displayer) printRow(i int, now time.Time, remote bool) int {
	d.targets[i].Lock()
	// Take a local copy of the structure, which isn't *that* big, so we don't need to retain the lock
	// while we do potentially blocking things like printing.
	target := d.targets[i].buildingTargetData
	d.targets[i].Unlock()
	label := target.Label.Parent()
	duration := now.Sub(target.Started).Seconds()
	if target.Active && target.Target != nil && target.Target.ShowProgress && target.Target.Progress > 0.0 {
		if target.Target.Progress > 1.0 && target.Target.Progress < 100.0 && target.Target.Progress != target.LastProgress {
			proportionDone := target.Target.Progress / 100.0
			perPercent := float32(duration) / proportionDone
			d.targets[i].Eta = time.Duration(perPercent * (1.0 - proportionDone) * float32(time.Second)).Truncate(time.Second)
			d.targets[i].LastProgress = target.Target.Progress
		}
		if target.Eta > 0 {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${RESET} (%.1f%%, est %s remaining)${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description, target.Target.Progress, target.Eta)
		} else {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${RESET} (%.1f%% complete)${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description, target.Target.Progress)
		}
	} else if target.Active {
		d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_WHITE}%s${ERASE_AFTER}\n",
			duration, target.Colour, label, target.Description)
	} else if time.Since(target.Finished).Seconds() < 0.5 {
		// Only display finished targets for half a second after they're done.
		duration := target.Finished.Sub(target.Started).Seconds()
		if target.Failed {
			d.printf("${BOLD_RED}=> [%4.1fs] ${RESET}%s%s ${BOLD_RED}Failed${ERASE_AFTER}\n",
				duration, target.Colour, label)
		} else if target.Cached {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${BOLD_GREY}%s${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description)
		} else {
			d.printf("${BOLD_WHITE}=> [%4.1fs] ${RESET}%s%s ${WHITE}%s${ERASE_AFTER}\n",
				duration, target.Colour, label, target.Description)
		}
	} else if !remote {
		printf("${BOLD_GREY}=|${ERASE_AFTER}\n")
	} else {
		d.lines-- // Didn't print it
		return 0
	}
	return 1
}

// printStat prints a single statistic with appropriate colours.
func printStat(caption string, stat float64, multiplier int) {
	colour := "${BOLD_GREEN}"
	if stat > 80.0*float64(multiplier) {
		colour = "${BOLD_RED}"
	} else if stat > 60.0*float64(multiplier) {
		colour = "${BOLD_YELLOW}"
	}
	printf("  ${BOLD_WHITE}%s:${RESET} %s%5.1f%%${RESET}", caption, colour, stat)
}

func recalcWindowSize(backend *cli.LogBackend) {
	rows, cols, _ := cli.WindowSize()
	backend.Lock()
	defer backend.Unlock()
	backend.Rows = rows - 4 // Give a little space at the edge for any off-by-ones
	backend.Cols = cols
	backend.RecalcLines()
}

// Limited-length printf that respects current window width.
// Output is truncated at the middle to fit within 'cols'.
func (d *displayer) printf(format string, args ...interface{}) {
	fmt.Fprint(os.Stderr, lprintfPrepare(d.maxCols, os.Expand(fmt.Sprintf(format, args...), replace)))
}

func lprintfPrepare(cols int, s string) string {
	if len(s) < cols {
		return s // it's short enough, nice and simple
	}
	// Okay, it's too long. Tricky thing: ANSI escape codes don't count for width
	// so we need to count without those. Bonus: make an effort to be unicode-aware.
	var b bytes.Buffer
	written := 0
	inAnsiCode := false
	for _, rune := range s {
		if inAnsiCode {
			b.WriteRune(rune)
			if rune == 'm' {
				inAnsiCode = false
			}
		} else if rune == '\x1b' {
			b.WriteRune(rune)
			inAnsiCode = true
		} else if rune == '\n' {
			b.WriteRune(rune)
		} else if written == cols-3 {
			b.WriteString("...")
			written += 3
		} else if written < cols-3 {
			b.WriteRune(rune)
			written++
		}
	}
	return b.String()
}

// setWindowTitle sets the title of the current shell window based on the current build state.
func setWindowTitle(state *core.BuildState, running bool) {
	if !state.Config.Display.UpdateTitle {
		return
	}
	if running {
		SetWindowTitle("plz: finishing up")
	} else {
		SetWindowTitle(fmt.Sprintf("plz: %d / %d tasks, %3.1fs", state.NumDone(), state.NumActive(), time.Since(state.StartTime).Seconds()))
	}
}

// SetWindowTitle sets the title of the current shell window.
func SetWindowTitle(title string) {
	if cli.StdErrIsATerminal && terminalClaimsToBeXterm {
		os.Stderr.Write([]byte(fmt.Sprintf("\033]0;%s\007", title)))
	}
}
<|endoftext|>


<commit_before>package runtime

import (
	"reflect"
	"testing"

	"github.com/apache/beam/sdks/go/pkg/beam/core/util/reflectx"
)

type S struct {
	a int
}

func TestKey(t *testing.T) {
	tests := []struct {
		T   reflect.Type
		Key string
		Ok  bool
	}{
		{reflectx.Int, "", false},                      // predeclared type
		{reflectx.String, "", false},                   // predeclared type
		{reflect.TypeOf(struct{ A int }{}), "", false}, // unnamed struct
		{reflect.TypeOf(S{}), "github.com/apache/beam/sdks/go/pkg/beam/core/runtime.S", true},
		{reflect.TypeOf(&S{}), "", false},  // ptr (= no name)
		{reflect.TypeOf([]S{}), "", false}, // slice (= no name)
	}

	for _, test := range tests {
		key, ok := TypeKey(test.T)
		if key != test.Key || ok != test.Ok {
			t.Errorf("TypeKey(%v) = (%v,%v), want (%v,%v)", test.T, key, ok, test.Key, test.Ok)
		}
	}
}

func TestRegister(t *testing.T) {
	s := reflect.TypeOf(&S{}) // *S

	RegisterType(s)

	for bad, key := range []string{"S", "graph.S", "foo", ""} {
		if _, ok := LookupType(key); ok {
			t.Fatalf("LookupType(%v) = (%v, true), want false", key, bad)
		}
	}

	actual, ok := LookupType("github.com/apache/beam/sdks/go/pkg/beam/core/runtime.S")
	if !ok {
		t.Fatalf("LookupType(S) failed")
	}
	if actual != s.Elem() {
		t.Fatalf("LookupType(S) = %v, want %v", actual, s.Elem())
	}
}
<commit_msg>Fix test to not use path of package.<commit_after>package runtime

import (
	"reflect"
	"testing"

	"github.com/apache/beam/sdks/go/pkg/beam/core/util/reflectx"
)

type S struct {
	a int
}

var sKey string

func init() {
	s := reflect.TypeOf(&S{}) // *S
	sKey = RegisterType(s)
}

func TestKey(t *testing.T) {
	tests := []struct {
		T   reflect.Type
		Key string
		Ok  bool
	}{
		{reflectx.Int, "", false},                      // predeclared type
		{reflectx.String, "", false},                   // predeclared type
		{reflect.TypeOf(struct{ A int }{}), "", false}, // unnamed struct
		{reflect.TypeOf(S{}), sKey, true},
		{reflect.TypeOf(&S{}), "", false},  // ptr (= no name)
		{reflect.TypeOf([]S{}), "", false}, // slice (= no name)
	}

	for _, test := range tests {
		key, ok := TypeKey(test.T)
		if key != test.Key || ok != test.Ok {
			t.Errorf("TypeKey(%v) = (%v,%v), want (%v,%v)", test.T, key, ok, test.Key, test.Ok)
		}
	}
}

func TestRegister(t *testing.T) {
	s := reflect.TypeOf(&S{}) // *S

	for bad, key := range []string{"S", "graph.S", "foo", ""} {
		if _, ok := LookupType(key); ok {
			t.Fatalf("LookupType(%v) = (%v, true), want false", key, bad)
		}
	}

	actual, ok := LookupType(sKey)
	if !ok {
		t.Fatalf("LookupType(S) failed")
	}
	if actual != s.Elem() {
		t.Fatalf("LookupType(S) = %v, want %v", actual, s.Elem())
	}
}
<|endoftext|>


<commit_before>package helm

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"time"

	"github.com/pkg/errors"
	"github.com/rancher/rancher/pkg/templatecontent"
	mgmtv3 "github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/rancher/types/apis/project.cattle.io/v3"
	"github.com/sirupsen/logrus"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	helmName = "helm"
	kubectl  = "kubectl"
	appLabel = "io.cattle.field/appId"
	kcEnv    = "KUBECONFIG"
)

func WriteTempDir(rootDir string, files map[string]string) (string, error) {
	for name, content := range files {
		fp := filepath.Join(rootDir, name)
		if err := os.MkdirAll(filepath.Dir(fp), 0755); err != nil {
			return "", err
		}
		if err := ioutil.WriteFile(fp, []byte(content), 0755); err != nil {
			return "", err
		}
	}
	for name := range files {
		parts := strings.Split(name, "/")
		if len(parts) > 0 {
			return filepath.Join(rootDir, parts[0]), nil
		}
	}
	return "", nil
}

func kubectlApply(template, kubeconfig string, app *v3.App) error {
	file, err := ioutil.TempFile("", "app-template")
	if err != nil {
		return err
	}
	defer os.RemoveAll(file.Name())
	if err := ioutil.WriteFile(file.Name(), []byte(template), 0755); err != nil {
		return err
	}

	command := []string{"apply", "--all", "--overwrite", "-n", app.Spec.TargetNamespace, "-f", file.Name()}
	if app.Spec.Prune {
		command = append(command, "--prune")
	}
	cmd := exec.Command(kubectl, command...)
	cmd.Env = []string{fmt.Sprintf("%s=%s", kcEnv, kubeconfig)}
	sbErr := &bytes.Buffer{}
	cmd.Stdout = os.Stdout
	cmd.Stderr = sbErr
	if err := cmd.Start(); err != nil {
		return err
	}
	if err := cmd.Wait(); err != nil {
		return errors.Wrapf(err, fmt.Sprintf("Kubectl apply failed. Error: %s", sbErr.String()))
	}

	// wait for resources to be created and try 5 times
	// todo: there is a bug around rolebindings that are being deleted if created outside rancher
	start := 250 * time.Millisecond
	for i := 0; i < 5; i++ {
		time.Sleep(start)
		command = []string{"label", "-n", app.Spec.TargetNamespace, "--overwrite", "-f", file.Name(), fmt.Sprintf("%s=%s", appLabel, app.Name)}
		cmd = exec.Command(kubectl, command...)
		cmd.Env = []string{fmt.Sprintf("%s=%s", kcEnv, kubeconfig)}
		buf := &bytes.Buffer{}
		cmd.Stdout = os.Stdout
		cmd.Stderr = buf
		if err := cmd.Start(); err != nil {
			return err
		}
		if err := cmd.Wait(); err != nil {
			if i == 4 {
				logrus.Warnf("tried 4 times and kubectl label failed. Error: %s", buf.String())
				break
			}
			start = start * 2
			continue
		}
		break
	}

	return nil
}

func kubectlDelete(template, kubeconfig, namespace string) error {
	file, err := ioutil.TempFile("", "app-template")
	if err != nil {
		return err
	}
	defer os.RemoveAll(file.Name())
	if err := ioutil.WriteFile(file.Name(), []byte(template), 0755); err != nil {
		return err
	}
	command := []string{"delete", "--all", "-n", namespace, "-f", file.Name()}
	cmd := exec.Command(kubectl, command...)
	cmd.Env = []string{fmt.Sprintf("%s=%s", kcEnv, kubeconfig)}
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		return err
	}
	return cmd.Wait()
}

func convertTemplates(files map[string]string, templateContentClient mgmtv3.TemplateContentInterface) (map[string]string, error) {
	templates := map[string]string{}
	for name, tag := range files {
		data, err := templatecontent.GetTemplateFromTag(tag, templateContentClient)
		if err != nil {
			continue
		}
		templates[name] = data
	}
	return templates, nil
}

func generateTemplates(obj *v3.App, templateVersionClient mgmtv3.TemplateVersionInterface, templateContentClient mgmtv3.TemplateContentInterface) (string, string, error) {
	templateVersionID, err := parseExternalID(obj.Spec.ExternalID)
	if err != nil {
		return "", "", err
	}
	templateVersion, err := templateVersionClient.Get(templateVersionID, metav1.GetOptions{})
	if err != nil {
		return "", "", err
	}
	files, err := convertTemplates(templateVersion.Spec.Files, templateContentClient)
	if err != nil {
		return "", "", err
	}
	tempDir, err := ioutil.TempDir("", "helm-")
	if err != nil {
		return "", "", err
	}
	dir, err := WriteTempDir(tempDir, files)
	defer os.RemoveAll(dir)
	if err != nil {
		return "", "", err
	}

	setValues := []string{}
	if obj.Spec.Answers != nil {
		answers := obj.Spec.Answers
		result := []string{}
		for k, v := range answers {
			result = append(result, fmt.Sprintf("%s=%s", k, v))
		}
		setValues = append([]string{"--set"}, strings.Join(result, ","))
	}
	commands := append([]string{"template", dir, "--name", obj.Name, "--namespace", obj.Spec.TargetNamespace}, setValues...)

	cmd := exec.Command(helmName, commands...)
	sbOut := &bytes.Buffer{}
	sbErr := &bytes.Buffer{}
	cmd.Stdout = sbOut
	cmd.Stderr = sbErr
	if err := cmd.Start(); err != nil {
		return "", "", errors.Wrapf(err, "helm template failed. %s", sbErr.String())
	}
	if err := cmd.Wait(); err != nil {
		return "", "", errors.Wrapf(err, "helm template failed. %s", sbErr.String())
	}

	// notes.txt
	commands = []string{"template", dir, "--name", obj.Name, "--namespace", obj.Spec.TargetNamespace, "--notes"}
	cmd = exec.Command(helmName, commands...)
	noteOut := &bytes.Buffer{}
	sbErr = &bytes.Buffer{}
	cmd.Stdout = noteOut
	cmd.Stderr = sbErr
	if err := cmd.Start(); err != nil {
		return "", "", errors.Wrapf(err, "helm template --notes failed. %s", sbErr.String())
	}
	if err := cmd.Wait(); err != nil {
		return "", "", errors.Wrapf(err, "helm template --notes failed. %s", sbErr.String())
	}
	template := sbOut.String()
	notes := noteOut.String()
	return template, notes, nil
}

func parseExternalID(externalID string) (string, error) {
	values, err := url.Parse(externalID)
	if err != nil {
		return "", err
	}
	catalog := values.Query().Get("catalog")
	template := values.Query().Get("template")
	version := values.Query().Get("version")
	return strings.Join([]string{catalog, template, version}, "-"), nil
}
<commit_msg>try three times and succeed<commit_after>package helm

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"time"

	"github.com/pkg/errors"
	"github.com/rancher/rancher/pkg/templatecontent"
	mgmtv3 "github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/rancher/types/apis/project.cattle.io/v3"
	"github.com/sirupsen/logrus"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	helmName = "helm"
	kubectl  = "kubectl"
	appLabel = "io.cattle.field/appId"
	kcEnv    = "KUBECONFIG"
)

func WriteTempDir(rootDir string, files map[string]string) (string, error) {
	for name, content := range files {
		fp := filepath.Join(rootDir, name)
		if err := os.MkdirAll(filepath.Dir(fp), 0755); err != nil {
			return "", err
		}
		if err := ioutil.WriteFile(fp, []byte(content), 0755); err != nil {
			return "", err
		}
	}
	for name := range files {
		parts := strings.Split(name, "/")
		if len(parts) > 0 {
			return filepath.Join(rootDir, parts[0]), nil
		}
	}
	return "", nil
}

func kubectlApply(template, kubeconfig string, app *v3.App) error {
	file, err := ioutil.TempFile("", "app-template")
	if err != nil {
		return err
	}
	defer os.RemoveAll(file.Name())
	if err := ioutil.WriteFile(file.Name(), []byte(template), 0755); err != nil {
		return err
	}

	command := []string{"apply", "--all", "--overwrite", "-n", app.Spec.TargetNamespace, "-f", file.Name()}
	if app.Spec.Prune {
		command = append(command, "--prune")
	}
	cmd := exec.Command(kubectl, command...)
	cmd.Env = []string{fmt.Sprintf("%s=%s", kcEnv, kubeconfig)}
	sbErr := &bytes.Buffer{}
	cmd.Stdout = os.Stdout
	cmd.Stderr = sbErr
	if err := cmd.Start(); err != nil {
		return err
	}
	if err := cmd.Wait(); err != nil {
		return errors.Wrapf(err, fmt.Sprintf("Kubectl apply failed. Error: %s", sbErr.String()))
	}

	// wait for resources to be created and try 5 times
	// todo: there is a bug around rolebindings that are being deleted if created outside rancher
	start := 250 * time.Millisecond
	for i := 0; i < 5; i++ {
		time.Sleep(start)
		command = []string{"label", "-n", app.Spec.TargetNamespace, "--overwrite", "-f", file.Name(), fmt.Sprintf("%s=%s", appLabel, app.Name)}
		cmd = exec.Command(kubectl, command...)
		cmd.Env = []string{fmt.Sprintf("%s=%s", kcEnv, kubeconfig)}
		buf := &bytes.Buffer{}
		cmd.Stdout = os.Stdout
		cmd.Stderr = buf
		if err := cmd.Start(); err != nil {
			return err
		}
		if err := cmd.Wait(); err != nil {
			if i == 4 {
				logrus.Warnf("tried 4 times and kubectl label failed. Error: %s", buf.String())
				break
			}
			start = start * 2
			continue
		}
		break
	}

	return nil
}

func kubectlDelete(template, kubeconfig, namespace string) error {
	file, err := ioutil.TempFile("", "app-template")
	if err != nil {
		return err
	}
	defer os.RemoveAll(file.Name())
	if err := ioutil.WriteFile(file.Name(), []byte(template), 0755); err != nil {
		return err
	}
	command := []string{"delete", "--all", "-n", namespace, "-f", file.Name()}
	// try three times and succeed
	start := time.Second * 1
	for i := 0; i < 3; i++ {
		cmd := exec.Command(kubectl, command...)
		cmd.Env = []string{fmt.Sprintf("%s=%s", kcEnv, kubeconfig)}
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Start(); err != nil {
			return err
		}
		if err := cmd.Wait(); err != nil {
			time.Sleep(start)
			start = start * 2
			continue
		}
		break
	}
	return nil
}

func convertTemplates(files map[string]string, templateContentClient mgmtv3.TemplateContentInterface) (map[string]string, error) {
	templates := map[string]string{}
	for name, tag := range files {
		data, err := templatecontent.GetTemplateFromTag(tag, templateContentClient)
		if err != nil {
			continue
		}
		templates[name] = data
	}
	return templates, nil
}

func generateTemplates(obj *v3.App, templateVersionClient mgmtv3.TemplateVersionInterface, templateContentClient mgmtv3.TemplateContentInterface) (string, string, error) {
	templateVersionID, err := parseExternalID(obj.Spec.ExternalID)
	if err != nil {
		return "", "", err
	}
	templateVersion, err := templateVersionClient.Get(templateVersionID, metav1.GetOptions{})
	if err != nil {
		return "", "", err
	}
	files, err := convertTemplates(templateVersion.Spec.Files, templateContentClient)
	if err != nil {
		return "", "", err
	}
	tempDir, err := ioutil.TempDir("", "helm-")
	if err != nil {
		return "", "", err
	}
	dir, err := WriteTempDir(tempDir, files)
	defer os.RemoveAll(dir)
	if err != nil {
		return "", "", err
	}

	setValues := []string{}
	if obj.Spec.Answers != nil {
		answers := obj.Spec.Answers
		result := []string{}
		for k, v := range answers {
			result = append(result, fmt.Sprintf("%s=%s", k, v))
		}
		setValues = append([]string{"--set"}, strings.Join(result, ","))
	}
	commands := append([]string{"template", dir, "--name", obj.Name, "--namespace", obj.Spec.TargetNamespace}, setValues...)

	cmd := exec.Command(helmName, commands...)
	sbOut := &bytes.Buffer{}
	sbErr := &bytes.Buffer{}
	cmd.Stdout = sbOut
	cmd.Stderr = sbErr
	if err := cmd.Start(); err != nil {
		return "", "", errors.Wrapf(err, "helm template failed. %s", sbErr.String())
	}
	if err := cmd.Wait(); err != nil {
		return "", "", errors.Wrapf(err, "helm template failed. %s", sbErr.String())
	}

	// notes.txt
	commands = []string{"template", dir, "--name", obj.Name, "--namespace", obj.Spec.TargetNamespace, "--notes"}
	cmd = exec.Command(helmName, commands...)
	noteOut := &bytes.Buffer{}
	sbErr = &bytes.Buffer{}
	cmd.Stdout = noteOut
	cmd.Stderr = sbErr
	if err := cmd.Start(); err != nil {
		return "", "", errors.Wrapf(err, "helm template --notes failed. %s", sbErr.String())
	}
	if err := cmd.Wait(); err != nil {
		return "", "", errors.Wrapf(err, "helm template --notes failed. %s", sbErr.String())
	}
	template := sbOut.String()
	notes := noteOut.String()
	return template, notes, nil
}

func parseExternalID(externalID string) (string, error) {
	values, err := url.Parse(externalID)
	if err != nil {
		return "", err
	}
	catalog := values.Query().Get("catalog")
	template := values.Query().Get("template")
	version := values.Query().Get("version")
	return strings.Join([]string{catalog, template, version}, "-"), nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors All rights reserved.

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

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

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

package tunnel

import (
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"regexp"
	"strings"

	"github.com/golang/glog"
)

func (router *osRouter) EnsureRouteIsAdded(route *Route) error {
	exists, err := isValidToAddOrDelete(router, route)
	if err != nil {
		return err
	}
	if exists {
		return nil
	}
	if err := writeResolverFile(route); err != nil {
		return fmt.Errorf("could not write /etc/resolver/{cluster_domain} file: %s", err)
	}

	serviceCIDR := route.DestCIDR.String()
	gatewayIP := route.Gateway.String()

	glog.Infof("Adding route for CIDR %s to gateway %s", serviceCIDR, gatewayIP)
	command := exec.Command("sudo", "route", "-n", "add", serviceCIDR, gatewayIP)
	glog.Infof("About to run command: %s", command.Args)
	stdInAndOut, err := command.CombinedOutput()
	message := fmt.Sprintf("%s", stdInAndOut)
	re := regexp.MustCompile(fmt.Sprintf("add net (.*): gateway %s\n", gatewayIP))
	if !re.MatchString(message) {
		return fmt.Errorf("error adding Route: %s, %d", message, len(strings.Split(message, "\n")))
	}
	glog.Infof("%s", stdInAndOut)

	return err
}

func (router *osRouter) Inspect(route *Route) (exists bool, conflict string, overlaps []string, err error) {
	cmd := exec.Command("netstat", "-nr", "-f", "inet")
	cmd.Env = append(cmd.Env, "LC_ALL=C")
	stdInAndOut, err := cmd.CombinedOutput()
	if err != nil {
		err = fmt.Errorf("error running '%v': %s", cmd, err)
		return
	}

	rt := router.parseTable(stdInAndOut)

	exists, conflict, overlaps = rt.Check(route)

	return
}

func (router *osRouter) parseTable(table []byte) routingTable {
	t := routingTable{}
	skip := true
	for _, line := range strings.Split(string(table), "\n") {
		// header
		if strings.HasPrefix(line, "Destination") {
			skip = false
			continue
		}
		// don't care about the 0.0.0.0 routes
		if skip || strings.HasPrefix(line, "default") {
			continue
		}
		fields := strings.Fields(line)

		if len(fields) <= 2 {
			continue
		}
		dstCIDRString := router.padCIDR(fields[0])
		gatewayIPString := fields[1]
		gatewayIP := net.ParseIP(gatewayIPString)

		_, ipNet, err := net.ParseCIDR(dstCIDRString)
		if err != nil {
			glog.V(4).Infof("skipping line: can't parse CIDR from routing table: %s", dstCIDRString)
		} else if gatewayIP == nil {
			glog.V(4).Infof("skipping line: can't parse IP from routing table: %s", gatewayIPString)
		} else {
			tableLine := routingTableLine{
				route: &Route{
					DestCIDR: ipNet,
					Gateway:  gatewayIP,
				},
				line: line,
			}
			t = append(t, tableLine)
		}
	}

	return t
}

func (router *osRouter) padCIDR(origCIDR string) string {
	s := ""
	dots := 0
	slash := false
	for i, c := range origCIDR {
		if c == '.' {
			dots++
		}
		if c == '/' {
			for dots < 3 {
				s += ".0"
				dots++
			}
			slash = true
		}
		if i == len(origCIDR)-1 {
			s += string(c)
			bits := 32 - 8*(3-dots)
			for dots < 3 {
				s += ".0"
				dots++
			}
			if !slash {
				s += fmt.Sprintf("/%d", bits)
			}
		} else {
			s += string(c)
		}
	}
	return s
}

func (router *osRouter) Cleanup(route *Route) error {
	glog.V(3).Infof("Cleaning up %s\n", route)
	exists, err := isValidToAddOrDelete(router, route)
	if err != nil {
		return err
	}
	if !exists {
		return nil
	}
	cmd := exec.Command("sudo", "route", "-n", "delete", route.DestCIDR.String())
	stdInAndOut, err := cmd.CombinedOutput()
	if err != nil {
		return err
	}
	msg := fmt.Sprintf("%s", stdInAndOut)
	glog.V(4).Infof("%s", msg)
	re := regexp.MustCompile("^delete net ([^:]*)$")
	if !re.MatchString(msg) {
		return fmt.Errorf("error deleting route: %s, %d", msg, len(strings.Split(msg, "\n")))
	}
	// idempotent removal of cluster domain dns
	resolverFile := fmt.Sprintf("/etc/resolver/%s", route.ClusterDomain)
	cmd = exec.Command("sudo", "rm", "-f", resolverFile)
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("could not remove %s: %s", resolverFile, err)
	}
	return nil
}

func writeResolverFile(route *Route) error {
	resolverFile := "/etc/resolver/" + route.ClusterDomain
	content := fmt.Sprintf("nameserver %s\nsearch_order 1\n", route.ClusterDNSIP)
	// write resolver content into tmpFile, then copy it to /etc/resolver/clusterDomain
	tmpFile, err := ioutil.TempFile("", "minikube-tunnel-resolver-")
	if err != nil {
		return err
	}
	defer os.Remove(tmpFile.Name())
	if _, err = tmpFile.WriteString(content); err != nil {
		return err
	}
	if err = tmpFile.Close(); err != nil {
		return err
	}
	cmd := exec.Command("sudo", "mkdir", "-p", "/etc/resolver")
	if err := cmd.Run(); err != nil {
		return err
	}
	cmd = exec.Command("sudo", "cp", "-f", tmpFile.Name(), resolverFile)
	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}
<commit_msg>Make resolver file world-readable, improve logging and errors<commit_after>/*
Copyright 2018 The Kubernetes Authors All rights reserved.

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

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

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

package tunnel

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

	"github.com/golang/glog"
	"github.com/pkg/errors"
)

func (router *osRouter) EnsureRouteIsAdded(route *Route) error {
	exists, err := isValidToAddOrDelete(router, route)
	if err != nil {
		return err
	}
	if exists {
		return nil
	}
	if err := writeResolverFile(route); err != nil {
		glog.Errorf("DNS forwarding unavailable: %v", err)
	}

	serviceCIDR := route.DestCIDR.String()
	gatewayIP := route.Gateway.String()

	glog.Infof("Adding route for CIDR %s to gateway %s", serviceCIDR, gatewayIP)
	command := exec.Command("sudo", "route", "-n", "add", serviceCIDR, gatewayIP)
	glog.Infof("About to run command: %s", command.Args)
	stdInAndOut, err := command.CombinedOutput()
	message := fmt.Sprintf("%s", stdInAndOut)
	re := regexp.MustCompile(fmt.Sprintf("add net (.*): gateway %s\n", gatewayIP))
	if !re.MatchString(message) {
		return fmt.Errorf("error adding Route: %s, %d", message, len(strings.Split(message, "\n")))
	}
	glog.Infof("%s", stdInAndOut)

	return err
}

func (router *osRouter) Inspect(route *Route) (exists bool, conflict string, overlaps []string, err error) {
	cmd := exec.Command("netstat", "-nr", "-f", "inet")
	cmd.Env = append(cmd.Env, "LC_ALL=C")
	stdInAndOut, err := cmd.CombinedOutput()
	if err != nil {
		err = fmt.Errorf("error running '%v': %s", cmd, err)
		return
	}

	rt := router.parseTable(stdInAndOut)

	exists, conflict, overlaps = rt.Check(route)

	return
}

func (router *osRouter) parseTable(table []byte) routingTable {
	t := routingTable{}
	skip := true
	for _, line := range strings.Split(string(table), "\n") {
		// header
		if strings.HasPrefix(line, "Destination") {
			skip = false
			continue
		}
		// don't care about the 0.0.0.0 routes
		if skip || strings.HasPrefix(line, "default") {
			continue
		}
		fields := strings.Fields(line)

		if len(fields) <= 2 {
			continue
		}
		dstCIDRString := router.padCIDR(fields[0])
		gatewayIPString := fields[1]
		gatewayIP := net.ParseIP(gatewayIPString)

		_, ipNet, err := net.ParseCIDR(dstCIDRString)
		if err != nil {
			glog.V(4).Infof("skipping line: can't parse CIDR from routing table: %s", dstCIDRString)
		} else if gatewayIP == nil {
			glog.V(4).Infof("skipping line: can't parse IP from routing table: %s", gatewayIPString)
		} else {
			tableLine := routingTableLine{
				route: &Route{
					DestCIDR: ipNet,
					Gateway:  gatewayIP,
				},
				line: line,
			}
			t = append(t, tableLine)
		}
	}

	return t
}

func (router *osRouter) padCIDR(origCIDR string) string {
	s := ""
	dots := 0
	slash := false
	for i, c := range origCIDR {
		if c == '.' {
			dots++
		}
		if c == '/' {
			for dots < 3 {
				s += ".0"
				dots++
			}
			slash = true
		}
		if i == len(origCIDR)-1 {
			s += string(c)
			bits := 32 - 8*(3-dots)
			for dots < 3 {
				s += ".0"
				dots++
			}
			if !slash {
				s += fmt.Sprintf("/%d", bits)
			}
		} else {
			s += string(c)
		}
	}
	return s
}

func (router *osRouter) Cleanup(route *Route) error {
	glog.V(3).Infof("Cleaning up %s\n", route)
	exists, err := isValidToAddOrDelete(router, route)
	if err != nil {
		return err
	}
	if !exists {
		return nil
	}
	cmd := exec.Command("sudo", "route", "-n", "delete", route.DestCIDR.String())
	stdInAndOut, err := cmd.CombinedOutput()
	if err != nil {
		return err
	}
	msg := fmt.Sprintf("%s", stdInAndOut)
	glog.V(4).Infof("%s", msg)
	re := regexp.MustCompile("^delete net ([^:]*)$")
	if !re.MatchString(msg) {
		return fmt.Errorf("error deleting route: %s, %d", msg, len(strings.Split(msg, "\n")))
	}
	// idempotent removal of cluster domain dns
	resolverFile := fmt.Sprintf("/etc/resolver/%s", route.ClusterDomain)
	cmd = exec.Command("sudo", "rm", "-f", resolverFile)
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("could not remove %s: %s", resolverFile, err)
	}
	return nil
}

func writeResolverFile(route *Route) error {
	resolverFile := "/etc/resolver/" + route.ClusterDomain

	content := fmt.Sprintf("nameserver %s\nsearch_order 1\n", route.ClusterDNSIP)

	glog.Infof("preparing DNS forwarding config in %q:\n%s", resolverFile, content)

	// write resolver content into tf, then copy it to /etc/resolver/clusterDomain
	tf, err := ioutil.TempFile("", "minikube-tunnel-resolver-")
	if err != nil {
		return errors.Wrap(err, "tempfile")
	}
	defer os.Remove(tf.Name())

	if _, err = tf.WriteString(content); err != nil {
		return errors.Wrap(err, "write")
	}

	if err = tf.Close(); err != nil {
		return errors.Wrap(err, "close")
	}

	if err = os.Chmod(tf.Name(), 0644); err != nil {
		return errors.Wrap(err, "chmod")
	}

	cmd := exec.Command("sudo", "mkdir", "-p", filepath.Dir(resolverFile))
	_, err = cmd.Output()
	if err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			return fmt.Errorf("%q failed: %v: %q", strings.Join(cmd.Args, " "), exitErr, exitErr.Stderr)
		}
		return errors.Wrap(err, "mkdir")
	}

	cmd = exec.Command("sudo", "cp", "-fp", tf.Name(), resolverFile)

	_, err = cmd.Output()
	if err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			return fmt.Errorf("%q failed: %v: %q", strings.Join(cmd.Args, " "), exitErr, exitErr.Stderr)
		}
		return errors.Wrap(err, "copy")
	}
	glog.Infof("DNS forwarding now configured in %q", resolverFile)
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2011 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.

// +build linux

package syscall

import (
	"unsafe"
)

type SysProcAttr struct {
	Chroot     string      // Chroot.
	Credential *Credential // Credential.
	Ptrace     bool        // Enable tracing.
	Setsid     bool        // Create session.
	Setpgid    bool        // Set process group ID to new pid (SYSV setpgrp)
	Setctty    bool        // Set controlling terminal to fd 0
	Noctty     bool        // Detach fd 0 from controlling terminal
	Pdeathsig  Signal      // Signal that the process will get when its parent dies (Linux only)
}

// Fork, dup fd onto 0..len(fd), and exec(argv0, argvv, envv) in child.
// If a dup or exec fails, write the errno error to pipe.
// (Pipe is close-on-exec so if exec succeeds, it will be closed.)
// In the child, this function must not acquire any locks, because
// they might have been locked at the time of the fork.  This means
// no rescheduling, no malloc calls, and no new stack segments.
// The calls to RawSyscall are okay because they are assembly
// functions that do not grow the stack.
func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) {
	// Declare all variables at top in case any
	// declarations require heap allocation (e.g., err1).
	var (
		r1     uintptr
		err1   Errno
		nextfd int
		i      int
	)

	// guard against side effects of shuffling fds below.
	fd := make([]int, len(attr.Files))
	for i, ufd := range attr.Files {
		fd[i] = int(ufd)
	}

	// About to call fork.
	// No more allocation or calls of non-assembly functions.
	r1, _, err1 = RawSyscall(SYS_FORK, 0, 0, 0)
	if err1 != 0 {
		return 0, err1
	}

	if r1 != 0 {
		// parent; return PID
		return int(r1), 0
	}

	// Fork succeeded, now in child.

	// Parent death signal
	if sys.Pdeathsig != 0 {
		_, _, err1 = RawSyscall6(SYS_PRCTL, PR_SET_PDEATHSIG, uintptr(sys.Pdeathsig), 0, 0, 0, 0)
		if err1 != 0 {
			goto childerror
		}

		// Signal self if parent is already dead. This might cause a
		// duplicate signal in rare cases, but it won't matter when
		// using SIGKILL.
		r1, _, _ = RawSyscall(SYS_GETPPID, 0, 0, 0)
		if r1 == 1 {
			pid, _, _ := RawSyscall(SYS_GETPID, 0, 0, 0)
			_, _, err1 := RawSyscall(SYS_KILL, pid, uintptr(sys.Pdeathsig), 0)
			if err1 != 0 {
				goto childerror
			}
		}
	}

	// Enable tracing if requested.
	if sys.Ptrace {
		_, _, err1 = RawSyscall(SYS_PTRACE, uintptr(PTRACE_TRACEME), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Session ID
	if sys.Setsid {
		_, _, err1 = RawSyscall(SYS_SETSID, 0, 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Set process group
	if sys.Setpgid {
		_, _, err1 = RawSyscall(SYS_SETPGID, 0, 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Chroot
	if chroot != nil {
		_, _, err1 = RawSyscall(SYS_CHROOT, uintptr(unsafe.Pointer(chroot)), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// User and groups
	if cred := sys.Credential; cred != nil {
		ngroups := uintptr(len(cred.Groups))
		groups := uintptr(0)
		if ngroups > 0 {
			groups = uintptr(unsafe.Pointer(&cred.Groups[0]))
		}
		_, _, err1 = RawSyscall(SYS_SETGROUPS, ngroups, groups, 0)
		if err1 != 0 {
			goto childerror
		}
		_, _, err1 = RawSyscall(SYS_SETGID, uintptr(cred.Gid), 0, 0)
		if err1 != 0 {
			goto childerror
		}
		_, _, err1 = RawSyscall(SYS_SETUID, uintptr(cred.Uid), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Chdir
	if dir != nil {
		_, _, err1 = RawSyscall(SYS_CHDIR, uintptr(unsafe.Pointer(dir)), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Pass 1: look for fd[i] < i and move those up above len(fd)
	// so that pass 2 won't stomp on an fd it needs later.
	nextfd = int(len(fd))
	if pipe < nextfd {
		_, _, err1 = RawSyscall(SYS_DUP2, uintptr(pipe), uintptr(nextfd), 0)
		if err1 != 0 {
			goto childerror
		}
		RawSyscall(SYS_FCNTL, uintptr(nextfd), F_SETFD, FD_CLOEXEC)
		pipe = nextfd
		nextfd++
	}
	for i = 0; i < len(fd); i++ {
		if fd[i] >= 0 && fd[i] < int(i) {
			_, _, err1 = RawSyscall(SYS_DUP2, uintptr(fd[i]), uintptr(nextfd), 0)
			if err1 != 0 {
				goto childerror
			}
			RawSyscall(SYS_FCNTL, uintptr(nextfd), F_SETFD, FD_CLOEXEC)
			fd[i] = nextfd
			nextfd++
			if nextfd == pipe { // don't stomp on pipe
				nextfd++
			}
		}
	}

	// Pass 2: dup fd[i] down onto i.
	for i = 0; i < len(fd); i++ {
		if fd[i] == -1 {
			RawSyscall(SYS_CLOSE, uintptr(i), 0, 0)
			continue
		}
		if fd[i] == int(i) {
			// dup2(i, i) won't clear close-on-exec flag on Linux,
			// probably not elsewhere either.
			_, _, err1 = RawSyscall(SYS_FCNTL, uintptr(fd[i]), F_SETFD, 0)
			if err1 != 0 {
				goto childerror
			}
			continue
		}
		// The new fd is created NOT close-on-exec,
		// which is exactly what we want.
		_, _, err1 = RawSyscall(SYS_DUP2, uintptr(fd[i]), uintptr(i), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// By convention, we don't close-on-exec the fds we are
	// started with, so if len(fd) < 3, close 0, 1, 2 as needed.
	// Programs that know they inherit fds >= 3 will need
	// to set them close-on-exec.
	for i = len(fd); i < 3; i++ {
		RawSyscall(SYS_CLOSE, uintptr(i), 0, 0)
	}

	// Detach fd 0 from tty
	if sys.Noctty {
		_, _, err1 = RawSyscall(SYS_IOCTL, 0, uintptr(TIOCNOTTY), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Make fd 0 the tty
	if sys.Setctty {
		_, _, err1 = RawSyscall(SYS_IOCTL, 0, uintptr(TIOCSCTTY), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Time to exec.
	_, _, err1 = RawSyscall(SYS_EXECVE,
		uintptr(unsafe.Pointer(argv0)),
		uintptr(unsafe.Pointer(&argv[0])),
		uintptr(unsafe.Pointer(&envv[0])))

childerror:
	// send error code on pipe
	RawSyscall(SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&err1)), unsafe.Sizeof(err1))
	for {
		RawSyscall(SYS_EXIT, 253, 0, 0)
	}

	// Calling panic is not actually safe,
	// but the for loop above won't break
	// and this shuts up the compiler.
	panic("unreached")
}
<commit_msg>syscall/exec_linux: enable changing controlling tty<commit_after>// Copyright 2011 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.

// +build linux

package syscall

import (
	"unsafe"
)

type SysProcAttr struct {
	Chroot     string      // Chroot.
	Credential *Credential // Credential.
	Ptrace     bool        // Enable tracing.
	Setsid     bool        // Create session.
	Setpgid    bool        // Set process group ID to new pid (SYSV setpgrp)
	Setctty    bool        // Set controlling terminal to fd Ctty (only meaningful if Setsid is set)
	Noctty     bool        // Detach fd 0 from controlling terminal
	Ctty       int         // Controlling TTY fd (Linux only)
	Pdeathsig  Signal      // Signal that the process will get when its parent dies (Linux only)
}

// Fork, dup fd onto 0..len(fd), and exec(argv0, argvv, envv) in child.
// If a dup or exec fails, write the errno error to pipe.
// (Pipe is close-on-exec so if exec succeeds, it will be closed.)
// In the child, this function must not acquire any locks, because
// they might have been locked at the time of the fork.  This means
// no rescheduling, no malloc calls, and no new stack segments.
// The calls to RawSyscall are okay because they are assembly
// functions that do not grow the stack.
func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) {
	// Declare all variables at top in case any
	// declarations require heap allocation (e.g., err1).
	var (
		r1     uintptr
		err1   Errno
		nextfd int
		i      int
	)

	// guard against side effects of shuffling fds below.
	fd := make([]int, len(attr.Files))
	for i, ufd := range attr.Files {
		fd[i] = int(ufd)
	}

	// About to call fork.
	// No more allocation or calls of non-assembly functions.
	r1, _, err1 = RawSyscall(SYS_FORK, 0, 0, 0)
	if err1 != 0 {
		return 0, err1
	}

	if r1 != 0 {
		// parent; return PID
		return int(r1), 0
	}

	// Fork succeeded, now in child.

	// Parent death signal
	if sys.Pdeathsig != 0 {
		_, _, err1 = RawSyscall6(SYS_PRCTL, PR_SET_PDEATHSIG, uintptr(sys.Pdeathsig), 0, 0, 0, 0)
		if err1 != 0 {
			goto childerror
		}

		// Signal self if parent is already dead. This might cause a
		// duplicate signal in rare cases, but it won't matter when
		// using SIGKILL.
		r1, _, _ = RawSyscall(SYS_GETPPID, 0, 0, 0)
		if r1 == 1 {
			pid, _, _ := RawSyscall(SYS_GETPID, 0, 0, 0)
			_, _, err1 := RawSyscall(SYS_KILL, pid, uintptr(sys.Pdeathsig), 0)
			if err1 != 0 {
				goto childerror
			}
		}
	}

	// Enable tracing if requested.
	if sys.Ptrace {
		_, _, err1 = RawSyscall(SYS_PTRACE, uintptr(PTRACE_TRACEME), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Session ID
	if sys.Setsid {
		_, _, err1 = RawSyscall(SYS_SETSID, 0, 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Set process group
	if sys.Setpgid {
		_, _, err1 = RawSyscall(SYS_SETPGID, 0, 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Chroot
	if chroot != nil {
		_, _, err1 = RawSyscall(SYS_CHROOT, uintptr(unsafe.Pointer(chroot)), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// User and groups
	if cred := sys.Credential; cred != nil {
		ngroups := uintptr(len(cred.Groups))
		groups := uintptr(0)
		if ngroups > 0 {
			groups = uintptr(unsafe.Pointer(&cred.Groups[0]))
		}
		_, _, err1 = RawSyscall(SYS_SETGROUPS, ngroups, groups, 0)
		if err1 != 0 {
			goto childerror
		}
		_, _, err1 = RawSyscall(SYS_SETGID, uintptr(cred.Gid), 0, 0)
		if err1 != 0 {
			goto childerror
		}
		_, _, err1 = RawSyscall(SYS_SETUID, uintptr(cred.Uid), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Chdir
	if dir != nil {
		_, _, err1 = RawSyscall(SYS_CHDIR, uintptr(unsafe.Pointer(dir)), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Pass 1: look for fd[i] < i and move those up above len(fd)
	// so that pass 2 won't stomp on an fd it needs later.
	nextfd = int(len(fd))
	if pipe < nextfd {
		_, _, err1 = RawSyscall(SYS_DUP2, uintptr(pipe), uintptr(nextfd), 0)
		if err1 != 0 {
			goto childerror
		}
		RawSyscall(SYS_FCNTL, uintptr(nextfd), F_SETFD, FD_CLOEXEC)
		pipe = nextfd
		nextfd++
	}
	for i = 0; i < len(fd); i++ {
		if fd[i] >= 0 && fd[i] < int(i) {
			_, _, err1 = RawSyscall(SYS_DUP2, uintptr(fd[i]), uintptr(nextfd), 0)
			if err1 != 0 {
				goto childerror
			}
			RawSyscall(SYS_FCNTL, uintptr(nextfd), F_SETFD, FD_CLOEXEC)
			fd[i] = nextfd
			nextfd++
			if nextfd == pipe { // don't stomp on pipe
				nextfd++
			}
		}
	}

	// Pass 2: dup fd[i] down onto i.
	for i = 0; i < len(fd); i++ {
		if fd[i] == -1 {
			RawSyscall(SYS_CLOSE, uintptr(i), 0, 0)
			continue
		}
		if fd[i] == int(i) {
			// dup2(i, i) won't clear close-on-exec flag on Linux,
			// probably not elsewhere either.
			_, _, err1 = RawSyscall(SYS_FCNTL, uintptr(fd[i]), F_SETFD, 0)
			if err1 != 0 {
				goto childerror
			}
			continue
		}
		// The new fd is created NOT close-on-exec,
		// which is exactly what we want.
		_, _, err1 = RawSyscall(SYS_DUP2, uintptr(fd[i]), uintptr(i), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// By convention, we don't close-on-exec the fds we are
	// started with, so if len(fd) < 3, close 0, 1, 2 as needed.
	// Programs that know they inherit fds >= 3 will need
	// to set them close-on-exec.
	for i = len(fd); i < 3; i++ {
		RawSyscall(SYS_CLOSE, uintptr(i), 0, 0)
	}

	// Detach fd 0 from tty
	if sys.Noctty {
		_, _, err1 = RawSyscall(SYS_IOCTL, 0, uintptr(TIOCNOTTY), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Set the controlling TTY to Ctty
	if sys.Setctty && sys.Ctty >= 0 {
		_, _, err1 = RawSyscall(SYS_IOCTL, uintptr(sys.Ctty), uintptr(TIOCSCTTY), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Time to exec.
	_, _, err1 = RawSyscall(SYS_EXECVE,
		uintptr(unsafe.Pointer(argv0)),
		uintptr(unsafe.Pointer(&argv[0])),
		uintptr(unsafe.Pointer(&envv[0])))

childerror:
	// send error code on pipe
	RawSyscall(SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&err1)), unsafe.Sizeof(err1))
	for {
		RawSyscall(SYS_EXIT, 253, 0, 0)
	}

	// Calling panic is not actually safe,
	// but the for loop above won't break
	// and this shuts up the compiler.
	panic("unreached")
}
<|endoftext|>