<commit_before>package cron

import (
	"bufio"
	"fmt"
	"github.com/aptible/supercronic/crontab"
	"github.com/sirupsen/logrus"
	"io"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"
)

func drainReader(wg sync.WaitGroup, readerLogger *logrus.Entry, reader io.Reader) {
	wg.Add(1)

	go func() {
		defer wg.Done()

		scanner := bufio.NewScanner(reader)

		for scanner.Scan() {
			readerLogger.Info(scanner.Text())
		}

		if err := scanner.Err(); err != nil {
			// The underlying reader might get closed by e.g. Wait(), or
			// even the process we're starting, so we don't log EOF-like
			// errors
			if strings.Contains(err.Error(), os.ErrClosed.Error()) {
				return
			}

			readerLogger.Error(err)
		}
	}()
}

func runJob(context *crontab.Context, command string, jobLogger *logrus.Entry) error {
	jobLogger.Info("starting")

	cmd := exec.Command(context.Shell, "-c", command)

	env := os.Environ()
	for k, v := range context.Environ {
		env = append(env, fmt.Sprintf("%s=%s", k, v))
	}
	cmd.Env = env

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	if err := cmd.Start(); err != nil {
		return err
	}

	var wg sync.WaitGroup

	stdoutLogger := jobLogger.WithFields(logrus.Fields{"channel": "stdout"})
	go drainReader(wg, stdoutLogger, stdout)

	stderrLogger := jobLogger.WithFields(logrus.Fields{"channel": "stderr"})
	go drainReader(wg, stderrLogger, stderr)

	wg.Wait()

	if err := cmd.Wait(); err != nil {
		return err
	}

	return nil
}

func StartJob(context *crontab.Context, job *crontab.Job, exitChan chan interface{}) {
	// NOTE: this (intentionally) does not run multiple instances of the
	// job concurrently
	cronLogger := logrus.WithFields(logrus.Fields{
		"job.schedule": job.Schedule,
		"job.command":  job.Command,
		"job.position": job.Position,
	})

	var cronIteration uint64 = 0
	nextRun := job.Expression.Next(time.Now())

	for {
		nextRun = job.Expression.Next(nextRun)
		cronLogger.Debugf("job will run next at %v", nextRun)

		delay := nextRun.Sub(time.Now())
		if delay < 0 {
			cronLogger.Warningf("job took too long to run: it should have started %v ago", -delay)
			nextRun = time.Now()
			continue
		}

		time.Sleep(delay)

		jobLogger := cronLogger.WithFields(logrus.Fields{
			"iteration": cronIteration,
		})

		err := runJob(context, job.Command, jobLogger)

		if err == nil {
			jobLogger.Info("job succeeded")
		} else {
			jobLogger.Error(err)
		}

		cronIteration++
	}
}
<commit_msg>Fix sync.WaitGroup pass-by-copy<commit_after>package cron

import (
	"bufio"
	"fmt"
	"github.com/aptible/supercronic/crontab"
	"github.com/sirupsen/logrus"
	"io"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"
)

func drainReader(wg *sync.WaitGroup, readerLogger *logrus.Entry, reader io.Reader) {
	wg.Add(1)

	go func() {
		defer wg.Done()

		scanner := bufio.NewScanner(reader)

		for scanner.Scan() {
			readerLogger.Info(scanner.Text())
		}

		if err := scanner.Err(); err != nil {
			// The underlying reader might get closed by e.g. Wait(), or
			// even the process we're starting, so we don't log EOF-like
			// errors
			if strings.Contains(err.Error(), os.ErrClosed.Error()) {
				return
			}

			readerLogger.Error(err)
		}
	}()
}

func runJob(context *crontab.Context, command string, jobLogger *logrus.Entry) error {
	jobLogger.Info("starting")

	cmd := exec.Command(context.Shell, "-c", command)

	env := os.Environ()
	for k, v := range context.Environ {
		env = append(env, fmt.Sprintf("%s=%s", k, v))
	}
	cmd.Env = env

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	if err := cmd.Start(); err != nil {
		return err
	}

	var wg sync.WaitGroup

	stdoutLogger := jobLogger.WithFields(logrus.Fields{"channel": "stdout"})
	go drainReader(&wg, stdoutLogger, stdout)

	stderrLogger := jobLogger.WithFields(logrus.Fields{"channel": "stderr"})
	go drainReader(&wg, stderrLogger, stderr)

	wg.Wait()

	if err := cmd.Wait(); err != nil {
		return err
	}

	return nil
}

func StartJob(context *crontab.Context, job *crontab.Job, exitChan chan interface{}) {
	// NOTE: this (intentionally) does not run multiple instances of the
	// job concurrently
	cronLogger := logrus.WithFields(logrus.Fields{
		"job.schedule": job.Schedule,
		"job.command":  job.Command,
		"job.position": job.Position,
	})

	var cronIteration uint64 = 0
	nextRun := job.Expression.Next(time.Now())

	for {
		nextRun = job.Expression.Next(nextRun)
		cronLogger.Debugf("job will run next at %v", nextRun)

		delay := nextRun.Sub(time.Now())
		if delay < 0 {
			cronLogger.Warningf("job took too long to run: it should have started %v ago", -delay)
			nextRun = time.Now()
			continue
		}

		time.Sleep(delay)

		jobLogger := cronLogger.WithFields(logrus.Fields{
			"iteration": cronIteration,
		})

		err := runJob(context, job.Command, jobLogger)

		if err == nil {
			jobLogger.Info("job succeeded")
		} else {
			jobLogger.Error(err)
		}

		cronIteration++
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Christian Muehlhaeuser
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *	Authors:
 *		Christian Muehlhaeuser <muesli@gmail.com>
 *		Michael Wendland <michael@michiwend.com>
 */

package smartcrop

import (
	"errors"
	"fmt"
	"image"
	_ "image/jpeg"
	_ "image/png"
	"io/ioutil"
	"os"
	"strings"
	"testing"
)

var (
	testFile = "./samples/test.png"
)

type SubImager interface {
	SubImage(r image.Rectangle) image.Image
}

func TestCrop(t *testing.T) {
	fi, _ := os.Open(testFile)
	defer fi.Close()

	img, _, err := image.Decode(fi)
	if err != nil {
		t.Error(err)
	}

	topCrop, scaledImg, err := SmartCrop(&img, 300, 300)
	if err != nil {
		t.Error(err)
	}
	fmt.Printf("Top crop: %+v\n", topCrop)

	sub, ok := scaledImg.(SubImager)
	if ok {
		cropImage := sub.SubImage(image.Rect(topCrop.X, topCrop.Y, topCrop.Width+topCrop.X, topCrop.Height+topCrop.Y))
		writeImageToJpeg(&cropImage, "./smartcrop.jpg")

	} else {
		t.Error(errors.New("No SubImage support"))
	}

}

func BenchmarkImageDir(b *testing.B) {

	b.SetParallelism(4)
	b.StopTimer()

	files, err := ioutil.ReadDir("./samples")
	if err != nil {
		b.Error(err)
	}

	b.StartTimer()
	for _, file := range files {
		if strings.Contains(file.Name(), ".jpg") {

			fi, _ := os.Open("./samples/" + file.Name())
			defer fi.Close()

			img, _, err := image.Decode(fi)
			if err != nil {
				b.Error(err)
			}

			topCrop, scaledImg, err := SmartCrop(&img, 300, 300)
			if err != nil {
				b.Error(err)
			}

			sub, ok := scaledImg.(SubImager)
			if ok {
				cropImage := sub.SubImage(image.Rect(topCrop.X, topCrop.Y, topCrop.Width+topCrop.X, topCrop.Height+topCrop.Y))
				writeImageToJpeg(&cropImage, "/tmp/smartcrop/smartcrop-"+file.Name())
			} else {
				b.Error(errors.New("No SubImage support"))
			}
		}
	}

	//fmt.Println("average time/image:", b.t

}
<commit_msg>fixes test<commit_after>/*
 * Copyright (c) 2014 Christian Muehlhaeuser
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *	Authors:
 *		Christian Muehlhaeuser <muesli@gmail.com>
 *		Michael Wendland <michael@michiwend.com>
 */

package smartcrop

import (
	"errors"
	"fmt"
	"image"
	_ "image/jpeg"
	_ "image/png"
	"io/ioutil"
	"os"
	"strings"
	"testing"
)

var (
	testFile = "./samples/gopher.jpg"
)

type SubImager interface {
	SubImage(r image.Rectangle) image.Image
}

func TestCrop(t *testing.T) {
	fi, _ := os.Open(testFile)
	defer fi.Close()

	img, _, err := image.Decode(fi)
	if err != nil {
		t.Error(err)
	}

	topCrop, scaledImg, err := SmartCrop(&img, 250, 250)
	if err != nil {
		t.Error(err)
	}
	fmt.Printf("Top crop: %+v\n", topCrop)

	sub, ok := scaledImg.(SubImager)
	if ok {
		cropImage := sub.SubImage(image.Rect(topCrop.X, topCrop.Y, topCrop.Width+topCrop.X, topCrop.Height+topCrop.Y))
		writeImageToJpeg(&cropImage, "./smartcrop.jpg")

	} else {
		t.Error(errors.New("No SubImage support"))
	}

}

func BenchmarkImageDir(b *testing.B) {

	b.SetParallelism(4)
	b.StopTimer()

	files, err := ioutil.ReadDir("./samples")
	if err != nil {
		b.Error(err)
	}

	b.StartTimer()
	for _, file := range files {
		if strings.Contains(file.Name(), ".jpg") {

			fi, _ := os.Open("./samples/" + file.Name())
			defer fi.Close()

			img, _, err := image.Decode(fi)
			if err != nil {
				b.Error(err)
			}

			topCrop, scaledImg, err := SmartCrop(&img, 250, 250)
			if err != nil {
				b.Error(err)
			}
			fmt.Printf("Top crop: %+v\n", topCrop)

			sub, ok := scaledImg.(SubImager)
			//sub, ok := img.(SubImager)
			if ok {
				cropImage := sub.SubImage(image.Rect(topCrop.X, topCrop.Y, topCrop.Width+topCrop.X, topCrop.Height+topCrop.Y))
				writeImageToJpeg(&cropImage, "/tmp/smartcrop/smartcrop-"+file.Name())
			} else {
				b.Error(errors.New("No SubImage support"))
			}
		}
	}
	//fmt.Println("average time/image:", b.t

}
<|endoftext|>


<commit_before>package client

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/caelifer/webtime/service"
	"github.com/gorilla/websocket"
)

// Public package variable to contol Websocket buffers
var WSUpgrader = &websocket.Upgrader{ReadBufferSize: 256, WriteBufferSize: 256}

type WSClient struct {
	service service.Service
}

func (cl *WSClient) log(msg string, args ...interface{}) {
	m := "[CLIENT] " + msg
	log.Printf(m, args...)
}

func NewWSClient(srv service.Service) *WSClient {
	return &WSClient{service: srv}
}

func (cl *WSClient) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Get remote address
	peer := r.RemoteAddr

	// Create websocket
	ws, err := WSUpgrader.Upgrade(w, r, nil)
	if err != nil {
		cl.log("(%s) - Failed to create WebSocket, exiting\n", peer)
		return // Need to better handle errors
	}

	// Connection object
	c := &connection{
		send: make(chan []byte, 256),
		ws:   ws,
	}

	// Init service

	// Make quit channel
	quit := make(chan bool)

	// Get service channel
	servchan := cl.service.Service(quit)

	cl.log("(%s) - initialized serivce\n", peer)

	// Loop in the separate goroutine
	go func() {
		for {
			select {
			case update := <-servchan:
				ts := update.(string) // Cast to the correct type
				cl.log("(%s) - sending update [%v]\n", peer, ts)

				msg, err := json.Marshal(&timeUpdate{ts})
				if err != nil {
					cl.log("Bad encoding for %q [%v]\n", ts, err)
					return
				}
				c.send <- []byte(msg)

			case <-quit:
				cl.log("(%s)- got quit signal\n", peer)
				close(quit)
				return
			}
		}
	}()

	// Enable by-directional communication via WS
	go c.write()

	// Reader will return when WS is closed on the remote client-side
	c.read()

	// Clean-up
	quit <- true
}

type connection struct {
	send chan []byte
	ws   *websocket.Conn
}

func (c *connection) read() {
	for {
		_, _, err := c.ws.ReadMessage()
		if err != nil {
			// Close on error
			break
		}
	}
	c.ws.Close()
}

func (c *connection) write() {
	for msg := range c.send {
		err := c.ws.WriteMessage(websocket.TextMessage, msg)
		if err != nil {
			log.Println(err)
			c.ws.Close()
			break
		}
	}
}

// Helper type to allow for fast JSON serialization
type timeUpdate struct {
	Time string `json:"time"`
}
<commit_msg>Use proper API for JSON autoencoding<commit_after>package client

import (
	"log"
	"net/http"

	"github.com/caelifer/webtime/service"
	"github.com/gorilla/websocket"
)

// Public package variable to contol Websocket buffers
var WSUpgrader = &websocket.Upgrader{ReadBufferSize: 256, WriteBufferSize: 256}

type WSClient struct {
	service service.Service
}

func (cl *WSClient) log(msg string, args ...interface{}) {
	m := "[CLIENT] " + msg
	log.Printf(m, args...)
}

func NewWSClient(srv service.Service) *WSClient {
	return &WSClient{service: srv}
}

func (cl *WSClient) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Get remote address
	peer := r.RemoteAddr

	// Create websocket
	ws, err := WSUpgrader.Upgrade(w, r, nil)
	if err != nil {
		cl.log("(%s) - Failed to create WebSocket, exiting\n", peer)
		return // Need to better handle errors
	}

	// Connection object
	c := &connection{
		send: make(chan *timeUpdate, 256),
		ws:   ws,
	}

	// Init service

	// Make quit channel
	quit := make(chan bool)

	// Get service channel
	servchan := cl.service.Service(quit)

	cl.log("(%s) - initialized serivce\n", peer)

	// Loop in the separate goroutine
	go func() {
		for {
			select {
			case update := <-servchan:
				ts := update.(string) // Cast to the correct type
				cl.log("(%s) - sending update [%v]\n", peer, ts)
				c.send <- &timeUpdate{ts}

			case <-quit:
				cl.log("(%s)- got quit signal\n", peer)
				close(quit)
				return
			}
		}
	}()

	// Enable by-directional communication via WS
	go c.write()

	// Reader will return when WS is closed on the remote client-side
	c.read()

	// Clean-up
	quit <- true
}

type connection struct {
	send chan *timeUpdate
	ws   *websocket.Conn
}

func (c *connection) read() {
	for {
		_, _, err := c.ws.ReadMessage()
		if err != nil {
			// Close on error
			break
		}
	}
	c.ws.Close()
}

func (c *connection) write() {
	for tu := range c.send {
		err := c.ws.WriteJSON(tu)
		if err != nil {
			log.Println(err)
			c.ws.Close()
			break
		}
	}
}

// Helper type to allow for fast JSON serialization
type timeUpdate struct {
	Time string `json:"time"`
}
<|endoftext|>


<commit_before>package openrtb

// PtrInt8 returns pointer to passed argument.
func PtrInt8(n int8) *int8 {
	return &n
}

// PtrUint64 returns pointer to passed argument.
func PtrUint64(n uint64) *uint64 {
	return &n
}
<commit_msg>OCD: Ptr helpers: type name comes first<commit_after>package openrtb

// Int8Ptr returns pointer to passed argument.
func Int8Ptr(n int8) *int8 {
	return &n
}

// Uint64Ptr returns pointer to passed argument.
func Uint64Ptr(n uint64) *uint64 {
	return &n
}
<|endoftext|>


<commit_before>package bbhw

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"time"
)

//PWM lines

type PWMLine struct {
	fd_period   *os.File
	fd_duty     *os.File
	fd_polarity *os.File
}

func findPWMDir(bbb_pin string) (tdir string, err error) {
	foundit := fmt.Errorf("Success")
	path_base := "/sys/devices"
	path_re1 := "^" + path_base + "/ocp" + `\.\d+`
	path_re2 := path_re1 + "/pwm_test_" + bbb_pin
	re1 := regexp.MustCompile(path_re1 + "$")
	re2, err := regexp.Compile(path_re2)
	if err != nil {
		return
	}
	findPWMDirBBB := func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			return nil
		}
		if path == path_base {
			return nil
		}
		if re2.MatchString(path) {
			tdir = path
			return foundit //foundit
		}
		if !re1.MatchString(path) {
			return filepath.SkipDir //skipdir if not like path_re1
		}
		return nil //continue walking
	}
	err = filepath.Walk(path_base, findPWMDirBBB)
	if err == foundit {
		err = nil
	} else {
		err = fmt.Errorf("NotFound")
	}
	return
}

// Example: StepperPWM, err = NewBBBPWM("P9_16")
func NewBBBPWM(bbb_pin string) (pwm *PWMLine, err error) {
	var pwm_path string
	pwm_path, err = findPWMDir(bbb_pin)
	if err != nil {
		return
	}
	pwm = new(PWMLine)
	pwm.fd_period, err = os.OpenFile(pwm_path+"/period", os.O_RDWR|os.O_SYNC, 0666)
	if err != nil {
		return
	}
	pwm.fd_duty, err = os.OpenFile(pwm_path+"/duty", os.O_RDWR|os.O_SYNC, 0666)
	if err != nil {
		pwm.fd_period.Close()
		return
	}
	pwm.fd_polarity, err = os.OpenFile(pwm_path+"/polarity", os.O_RDWR|os.O_SYNC, 0666)
	if err != nil {
		pwm.fd_period.Close()
		pwm.fd_duty.Close()
		return
	}
	pwm.SetPolarity(false)
	return
}

func (pwm *PWMLine) SetPolarity(p bool) {
	var val byte = 0
	if p {
		val = 1
	}
	pwm.fd_polarity.Truncate(0)
	pwm.fd_polarity.Write([]byte{val, '\n'})
}

func (pwm *PWMLine) DisablePWM() {
	pwm.fd_duty.Truncate(0)
	pwm.fd_duty.Write([]byte{'0', '\n'})
	pwm.SetPolarity(false)
}

func (pwm *PWMLine) SetPWM(period, duty time.Duration) {
	if duty > period {
		return
	}
	var buffer []byte = make([]byte, 32)
	pwm.fd_period.Seek(0, 0)
	numread, err := pwm.fd_period.Read(buffer)
	oldperiod, err := strconv.ParseInt(string(buffer[0:numread-1]), 10, 64)
	if err != nil {
		pwm.fd_duty.Truncate(0)
		pwm.fd_duty.WriteString("0\n")
	}

	pwm.fd_duty.Truncate(0)
	pwm.fd_period.Truncate(0)

	if period.Nanoseconds() > oldperiod {
		pwm.fd_period.WriteString(fmt.Sprintf("%d\n", period.Nanoseconds()))
		pwm.fd_duty.WriteString(fmt.Sprintf("%d\n", duty.Nanoseconds()))
	} else {
		pwm.fd_duty.WriteString(fmt.Sprintf("%d\n", duty.Nanoseconds()))
		pwm.fd_period.WriteString(fmt.Sprintf("%d\n", period.Nanoseconds()))
	}
}

func (pwm *PWMLine) GetPWM() (period, duty time.Duration) {
	var buffer []byte = make([]byte, 32)
	pwm.fd_period.Seek(0, 0)
	numread, err := pwm.fd_period.Read(buffer)
	oldperiod, err := strconv.ParseInt(string(buffer[0:numread-1]), 10, 64)
	if err == nil {
		period = time.Duration(oldperiod) * time.Nanosecond
	}
	pwm.fd_duty.Seek(0, 0)
	numread, err = pwm.fd_duty.Read(buffer)
	oldduty, err := strconv.ParseInt(string(buffer[0:numread-1]), 10, 64)
	if err == nil {
		duty = time.Duration(oldduty) * time.Nanosecond
	}
	return
}

// set PWM duty to fraction between 0.0 and 1.0
func (pwm *PWMLine) SetDuty(fraction float64) {
	if fraction > 1.0 {
		fraction = 1.0
	} else if fraction < 0.0 {
		fraction = 0.0
	}
	var buffer []byte = make([]byte, 32)
	pwm.fd_period.Seek(0, 0)
	numread, err := pwm.fd_period.Read(buffer)
	period, err := strconv.ParseInt(string(buffer[0:numread-1]), 10, 64)
	if err != nil {
		panic(err)
	}
	pwm.fd_duty.Truncate(0)
	pwm.fd_duty.WriteString(fmt.Sprintf("%d\n", uint(float64(period)*fraction)))
}

func (pwm *PWMLine) SetPWMFreq(freq_hz float64) {
	pwm.SetPWMFreqDuty(freq_hz, 0.5)
}

func (pwm *PWMLine) SetPWMFreqDuty(freq_hz, fraction float64) {
	if fraction > 1.0 {
		fraction = 1.0
	} else if fraction < 0.0 {
		fraction = 0.0
	}
	period := float64(time.Second) / freq_hz
	pwm.SetPWM(time.Duration(period), time.Duration(period*fraction))
}

func (pwm *PWMLine) GetPWMFreqDuty() (freq_hz, fraction float64) {
	period, duty := pwm.GetPWM()
	freq_hz = float64(time.Second) / float64(period)
	fraction = float64(duty) / float64(period)
	return
}

func (pwm *PWMLine) SetStepperRPM(rpm, stepsperrot float64) {
	pwm.SetPWMFreqDuty(rpm*stepsperrot/60.0, 0.1)
}

func (pwm *PWMLine) Close() {
	pwm.fd_duty.Close()
	pwm.fd_period.Close()
	pwm.fd_polarity.Close()
	pwm = nil

}
<commit_msg>GetRPM<commit_after>package bbhw

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"time"
)

//PWM lines

type PWMLine struct {
	fd_period   *os.File
	fd_duty     *os.File
	fd_polarity *os.File
}

func findPWMDir(bbb_pin string) (tdir string, err error) {
	foundit := fmt.Errorf("Success")
	path_base := "/sys/devices"
	path_re1 := "^" + path_base + "/ocp" + `\.\d+`
	path_re2 := path_re1 + "/pwm_test_" + bbb_pin
	re1 := regexp.MustCompile(path_re1 + "$")
	re2, err := regexp.Compile(path_re2)
	if err != nil {
		return
	}
	findPWMDirBBB := func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			return nil
		}
		if path == path_base {
			return nil
		}
		if re2.MatchString(path) {
			tdir = path
			return foundit //foundit
		}
		if !re1.MatchString(path) {
			return filepath.SkipDir //skipdir if not like path_re1
		}
		return nil //continue walking
	}
	err = filepath.Walk(path_base, findPWMDirBBB)
	if err == foundit {
		err = nil
	} else {
		err = fmt.Errorf("NotFound")
	}
	return
}

// Example: StepperPWM, err = NewBBBPWM("P9_16")
func NewBBBPWM(bbb_pin string) (pwm *PWMLine, err error) {
	var pwm_path string
	pwm_path, err = findPWMDir(bbb_pin)
	if err != nil {
		return
	}
	pwm = new(PWMLine)
	pwm.fd_period, err = os.OpenFile(pwm_path+"/period", os.O_RDWR|os.O_SYNC, 0666)
	if err != nil {
		return
	}
	pwm.fd_duty, err = os.OpenFile(pwm_path+"/duty", os.O_RDWR|os.O_SYNC, 0666)
	if err != nil {
		pwm.fd_period.Close()
		return
	}
	pwm.fd_polarity, err = os.OpenFile(pwm_path+"/polarity", os.O_RDWR|os.O_SYNC, 0666)
	if err != nil {
		pwm.fd_period.Close()
		pwm.fd_duty.Close()
		return
	}
	pwm.SetPolarity(false)
	return
}

func (pwm *PWMLine) SetPolarity(p bool) {
	var val byte = 0
	if p {
		val = 1
	}
	pwm.fd_polarity.Truncate(0)
	pwm.fd_polarity.Write([]byte{val, '\n'})
}

func (pwm *PWMLine) DisablePWM() {
	pwm.fd_duty.Truncate(0)
	pwm.fd_duty.Write([]byte{'0', '\n'})
	pwm.SetPolarity(false)
}

func (pwm *PWMLine) SetPWM(period, duty time.Duration) {
	if duty > period {
		return
	}
	var buffer []byte = make([]byte, 32)
	pwm.fd_period.Seek(0, 0)
	numread, err := pwm.fd_period.Read(buffer)
	oldperiod, err := strconv.ParseInt(string(buffer[0:numread-1]), 10, 64)
	if err != nil {
		pwm.fd_duty.Truncate(0)
		pwm.fd_duty.WriteString("0\n")
	}

	pwm.fd_duty.Truncate(0)
	pwm.fd_period.Truncate(0)

	if period.Nanoseconds() > oldperiod {
		pwm.fd_period.WriteString(fmt.Sprintf("%d\n", period.Nanoseconds()))
		pwm.fd_duty.WriteString(fmt.Sprintf("%d\n", duty.Nanoseconds()))
	} else {
		pwm.fd_duty.WriteString(fmt.Sprintf("%d\n", duty.Nanoseconds()))
		pwm.fd_period.WriteString(fmt.Sprintf("%d\n", period.Nanoseconds()))
	}
}

func (pwm *PWMLine) GetPWM() (period, duty time.Duration) {
	var buffer []byte = make([]byte, 32)
	pwm.fd_period.Seek(0, 0)
	numread, err := pwm.fd_period.Read(buffer)
	oldperiod, err := strconv.ParseInt(string(buffer[0:numread-1]), 10, 64)
	if err == nil {
		period = time.Duration(oldperiod) * time.Nanosecond
	}
	pwm.fd_duty.Seek(0, 0)
	numread, err = pwm.fd_duty.Read(buffer)
	oldduty, err := strconv.ParseInt(string(buffer[0:numread-1]), 10, 64)
	if err == nil {
		duty = time.Duration(oldduty) * time.Nanosecond
	}
	return
}

// set PWM duty to fraction between 0.0 and 1.0
func (pwm *PWMLine) SetDuty(fraction float64) {
	if fraction > 1.0 {
		fraction = 1.0
	} else if fraction < 0.0 {
		fraction = 0.0
	}
	var buffer []byte = make([]byte, 32)
	pwm.fd_period.Seek(0, 0)
	numread, err := pwm.fd_period.Read(buffer)
	period, err := strconv.ParseInt(string(buffer[0:numread-1]), 10, 64)
	if err != nil {
		panic(err)
	}
	pwm.fd_duty.Truncate(0)
	pwm.fd_duty.WriteString(fmt.Sprintf("%d\n", uint(float64(period)*fraction)))
}

func (pwm *PWMLine) SetPWMFreq(freq_hz float64) {
	pwm.SetPWMFreqDuty(freq_hz, 0.5)
}

func (pwm *PWMLine) SetPWMFreqDuty(freq_hz, fraction float64) {
	if fraction > 1.0 {
		fraction = 1.0
	} else if fraction < 0.0 {
		fraction = 0.0
	}
	period := float64(time.Second) / freq_hz
	pwm.SetPWM(time.Duration(period), time.Duration(period*fraction))
}

func (pwm *PWMLine) GetPWMFreqDuty() (freq_hz, fraction float64) {
	period, duty := pwm.GetPWM()
	freq_hz = float64(time.Second) / float64(period)
	fraction = float64(duty) / float64(period)
	return
}

func (pwm *PWMLine) SetStepperRPM(rpm, stepsperrot float64) {
	pwm.SetPWMFreqDuty(rpm*stepsperrot/60.0, 0.1)
}

func (pwm *PWMLine) GetStepperRPM(stepsperrot float64) float64 {
	freq_hz, _ := pwm.GetPWMFreqDuty()
	return freq_hz / stepsperrot * 60.0
}

func (pwm *PWMLine) Close() {
	pwm.fd_duty.Close()
	pwm.fd_period.Close()
	pwm.fd_polarity.Close()
	pwm = nil

}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. 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 cups

/*
#cgo LDFLAGS: -lcups
#include <cups/cups.h>
#include <stddef.h> // size_t
#include <stdlib.h> // free, malloc
#include <time.h>   // time_t

const char
    *POST_RESOURCE        = "/",
    *REQUESTED_ATTRIBUTES = "requested-attributes",
		*JOB_URI_ATTRIBUTE    = "job-uri",
		*IPP                  = "ipp";
*/
import "C"
import (
	"cups-connector/lib"
	"errors"
	"fmt"
	"runtime"
	"syscall"
	"time"
	"unsafe"

	"github.com/golang/glog"
)

const (
	maxConnectionAge = "2m"
	// jobURIFormat is the string format required by the CUPS API
	// to do things like query the state of a job.
	jobURIFormat = "/jobs/%d"
)

// cupsCore protects the CUPS C.http_t connection with a mutex. Although
// the CUPS API claims that it is thread-safe, this library panics under
// very little pressure without the mutex.
type cupsCore struct {
	http             *C.http_t
	lastConnect      time.Time
	maxConnectionAge time.Duration
	httpMutex        *lib.Semaphore
}

// newCUPSCore calls C.httpConnectEncrypt to create a new, open
// connection to the CUPS server specified by environment variables,
// client.conf, etc.
func newCUPSCore() (*cupsCore, error) {
	host := C.cupsServer()
	port := C.ippPort()
	encryption := C.cupsEncryption()

	http := C.httpConnectEncrypt(host, port, encryption)
	if http == nil {
		return nil, fmt.Errorf("Failed to connect to %s:%d", C.GoString(host), int(port))
	}

	var e string
	switch encryption {
	case C.HTTP_ENCRYPTION_ALWAYS:
		e = "encrypting ALWAYS"
	case C.HTTP_ENCRYPTION_IF_REQUESTED:
		e = "encrypting IF REQUESTED"
	case C.HTTP_ENCRYPTION_NEVER:
		e = "encrypting NEVER"
	case C.HTTP_ENCRYPTION_REQUIRED:
		e = "encryption REQUIRED"
	default:
		encryption = C.HTTP_ENCRYPTION_REQUIRED
		e = "encrypting REQUIRED"
	}

	parsedMaxConnectionAge, err := time.ParseDuration(maxConnectionAge)
	if err != nil {
		return nil, fmt.Errorf("Failed to parse max CUPS connection time: %s", err)
	}

	glog.Infof("connected to CUPS server %s:%d %s\n", C.GoString(host), int(port), e)

	cc := &cupsCore{
		http:             http,
		lastConnect:      time.Now(),
		maxConnectionAge: parsedMaxConnectionAge,
		httpMutex:        lib.NewSemaphore(1),
	}

	return cc, nil
}

// printFile prints by calling C.cupsPrintFile2().
// Returns the CUPS job ID, which is 0 (and meaningless) when err
// is not nil.
func (cc *cupsCore) printFile(user, printername, filename, title *C.char, numOptions C.int, options *C.cups_option_t) (C.int, error) {
	cc.httpMutex.Acquire()
	defer cc.httpMutex.Release()

	if err := cc.reconnectIfNeeded(); err != nil {
		return 0, err
	}

	// Lock the OS thread so that thread-local storage is available to
	// cupsLastError() and cupsLastErrorString().
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	C.cupsSetUser(user)
	jobID := C.cupsPrintFile2(cc.http, printername, filename, title, numOptions, options)
	if jobID == 0 {
		return 0, fmt.Errorf("Failed to call cupsPrintFile2(): %d %s",
			int(C.cupsLastError()), C.GoString(C.cupsLastErrorString()))
	}

	return jobID, nil
}

// getPrinters gets the current list and state of printers by calling
// C.doRequest (IPP_OP_CUPS_GET_PRINTERS).
//
// The caller is responsible to C.ippDelete the returned *C.ipp_t response.
func (cc *cupsCore) getPrinters(attributes **C.char, attrSize C.int) (*C.ipp_t, error) {
	// ippNewRequest() returns ipp_t pointer which does not need explicit free.
	request := C.ippNewRequest(C.IPP_OP_CUPS_GET_PRINTERS)
	C.ippAddStrings(request, C.IPP_TAG_OPERATION, C.IPP_TAG_KEYWORD, C.REQUESTED_ATTRIBUTES,
		attrSize, nil, attributes)

	cc.httpMutex.Acquire()
	defer cc.httpMutex.Release()

	if err := cc.reconnectIfNeeded(); err != nil {
		return nil, err
	}

	response, err := doRequestWithRetry(cc.http, request,
		[]C.ipp_status_t{C.IPP_STATUS_OK, C.IPP_STATUS_ERROR_NOT_FOUND})
	if err != nil {
		err = fmt.Errorf("Failed to call cupsDoRequest() [IPP_OP_CUPS_GET_PRINTERS]: %s", err)
		return nil, err
	}

	return response, nil
}

// getPPD gets the filename of the PPD for a printer by calling
// C.cupsGetPPD3. If the PPD hasn't changed since the time indicated
// by modtime, then the returned filename is a nil pointer.
//
// Note that modtime is a pointer whose value is changed by this
// function.
//
// The caller is responsible to C.free the returned *C.char filename.
func (cc *cupsCore) getPPD(printername *C.char, modtime *C.time_t) (*C.char, error) {
	bufsize := C.size_t(syscall.PathMax)
	buffer := (*C.char)(C.malloc(bufsize))
	if buffer == nil {
		return nil, errors.New("Failed to malloc; out of memory?")
	}
	C.memset(unsafe.Pointer(buffer), 0, bufsize)

	cc.httpMutex.Acquire()
	defer cc.httpMutex.Release()

	if err := cc.reconnectIfNeeded(); err != nil {
		return nil, fmt.Errorf("Failed to reconnect while serving a PPD for %s", printername)
	}

	// Lock the OS thread so that thread-local storage is available to
	// cupsLastError() and cupsLastErrorString().
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	httpStatus := C.cupsGetPPD3(cc.http, printername, modtime, buffer, bufsize)

	switch httpStatus {
	case C.HTTP_STATUS_NOT_MODIFIED:
		// Cache hit.
		return nil, nil

	case C.HTTP_STATUS_OK:
		// Cache miss.
		return buffer, nil

	default:
		cupsLastError := C.cupsLastError()
		if cupsLastError != C.IPP_STATUS_OK {
			return nil, fmt.Errorf("Failed to call cupsGetPPD3(): %d %s",
				int(cupsLastError), C.GoString(C.cupsLastErrorString()))
		}

		return nil, fmt.Errorf("Failed to call cupsGetPPD3(); HTTP status: %d", int(httpStatus))
	}
}

// getJobAttributes gets the requested attributes for a job by calling
// C.doRequest (IPP_OP_GET_JOB_ATTRIBUTES).
//
// The caller is responsible to C.ippDelete the returned *C.ipp_t response.
func (cc *cupsCore) getJobAttributes(jobID C.int, attributes **C.char) (*C.ipp_t, error) {
	uri, err := createJobURI(jobID)
	if err != nil {
		return nil, err
	}
	defer C.free(unsafe.Pointer(uri))

	// ippNewRequest() returns ipp_t pointer does not need explicit free.
	request := C.ippNewRequest(C.IPP_OP_GET_JOB_ATTRIBUTES)

	C.ippAddString(request, C.IPP_TAG_OPERATION, C.IPP_TAG_URI, C.JOB_URI_ATTRIBUTE, nil, uri)
	C.ippAddStrings(request, C.IPP_TAG_OPERATION, C.IPP_TAG_KEYWORD, C.REQUESTED_ATTRIBUTES,
		C.int(0), nil, attributes)

	cc.httpMutex.Acquire()
	defer cc.httpMutex.Release()

	if err := cc.reconnectIfNeeded(); err != nil {
		return nil, err
	}

	response, err := doRequestWithRetry(cc.http, request, []C.ipp_status_t{C.IPP_STATUS_OK})
	if err != nil {
		err = fmt.Errorf("Failed to call cupsDoRequest() [IPP_OP_GET_JOB_ATTRIBUTES]: %s", err)
		return nil, err
	}

	return response, nil
}

// createJobURI creates a uri string for the job-uri attribute, used to get the
// status of a CUPS job.
func createJobURI(jobID C.int) (*C.char, error) {
	length := C.size_t(urlMaxLength)
	uri := (*C.char)(C.malloc(length))
	if uri == nil {
		return nil, errors.New("Failed to malloc; out of memory?")
	}

	resource := C.CString(fmt.Sprintf(jobURIFormat, uint32(jobID)))
	defer C.free(unsafe.Pointer(resource))
	C.httpAssembleURI(C.HTTP_URI_CODING_ALL,
		uri, C.int(length), C.IPP, nil, C.cupsServer(), C.ippPort(), resource)

	return uri, nil
}

// doRequestWithRetry calls doRequest and retries once on failure.
func doRequestWithRetry(http *C.http_t, request *C.ipp_t, acceptableStatusCodes []C.ipp_status_t) (*C.ipp_t, error) {
	response, err := doRequest(http, request, acceptableStatusCodes)
	if err == nil {
		return response, err
	}

	return doRequest(http, request, acceptableStatusCodes)
}

// doRequest calls cupsDoRequest().
func doRequest(http *C.http_t, request *C.ipp_t, acceptableStatusCodes []C.ipp_status_t) (*C.ipp_t, error) {
	// Lock the OS thread so that thread-local storage is available to
	// cupsLastError() and cupsLastErrorString().
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	response := C.cupsDoRequest(http, request, C.POST_RESOURCE)
	if response == nil {
		return nil, fmt.Errorf("%d %s", int(C.cupsLastError()), C.GoString(C.cupsLastErrorString()))
	}
	statusCode := C.ippGetStatusCode(response)
	for _, sc := range acceptableStatusCodes {
		if statusCode == sc {
			return response, nil
		}
	}

	return nil, fmt.Errorf("IPP status code %d", int(statusCode))
}

// reconnectIfNeeded checks the age of the current CUPS connection.
// If too old, then it reconnects, else nothing happens.
func (cc *cupsCore) reconnectIfNeeded() error {
	if time.Since(cc.lastConnect) < cc.maxConnectionAge {
		return nil
	}

	if err := reconnect(cc.http); err != nil {
		return err
	}

	cc.lastConnect = time.Now()
	return nil
}

// reconnect calls C.httpReconnect, which re-starts the connection to
// the CUPS server.
func reconnect(http *C.http_t) error {
	// Lock the OS thread so that thread-local storage is available to
	// cupsLastErrorString().
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	ippStatus := C.httpReconnect(http)
	if ippStatus != C.IPP_STATUS_OK {
		return fmt.Errorf("Failed to call cupsReconnect(): %d %s",
			int(ippStatus), C.GoString(C.cupsLastErrorString()))
	}
	return nil
}
<commit_msg>Replace CUPS http_t connection sharing with connection-per-op.<commit_after>/*
Copyright 2014 Google Inc. 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 cups

/*
#cgo LDFLAGS: -lcups
#include <cups/cups.h>
#include <stddef.h> // size_t
#include <stdlib.h> // free, malloc
#include <time.h>   // time_t

const char
    *POST_RESOURCE        = "/",
    *REQUESTED_ATTRIBUTES = "requested-attributes",
		*JOB_URI_ATTRIBUTE    = "job-uri",
		*IPP                  = "ipp";
*/
import "C"
import (
	"errors"
	"fmt"
	"runtime"
	"syscall"
	"unsafe"

	"github.com/golang/glog"
)

const (
	// jobURIFormat is the string format required by the CUPS API
	// to do things like query the state of a job.
	jobURIFormat = "/jobs/%d"
)

// cupsCore protects the CUPS C.http_t connection with a mutex. Although
// the CUPS API claims that it is thread-safe, this library panics under
// very little pressure without the mutex.
type cupsCore struct {
	host       *C.char
	port       C.int
	encryption C.http_encryption_t
}

func newCUPSCore() (*cupsCore, error) {
	host := C.cupsServer()
	port := C.ippPort()
	encryption := C.cupsEncryption()

	cc := &cupsCore{host, port, encryption}

	// This connection isn't used, just checks that a connection is possible
	// before returning from the constructor.
	http, err := cc.connect()
	if err != nil {
		return nil, err
	}
	C.httpClose(http)

	var e string
	switch encryption {
	case C.HTTP_ENCRYPTION_ALWAYS:
		e = "encrypting ALWAYS"
	case C.HTTP_ENCRYPTION_IF_REQUESTED:
		e = "encrypting IF REQUESTED"
	case C.HTTP_ENCRYPTION_NEVER:
		e = "encrypting NEVER"
	case C.HTTP_ENCRYPTION_REQUIRED:
		e = "encryption REQUIRED"
	default:
		encryption = C.HTTP_ENCRYPTION_REQUIRED
		e = "encrypting REQUIRED"
	}

	glog.Infof("connected to CUPS server %s:%d %s\n", C.GoString(host), int(port), e)

	return cc, nil
}

// printFile prints by calling C.cupsPrintFile2().
// Returns the CUPS job ID, which is 0 (and meaningless) when err
// is not nil.
func (cc *cupsCore) printFile(user, printername, filename, title *C.char, numOptions C.int, options *C.cups_option_t) (C.int, error) {
	// Lock the OS thread so that thread-local storage is available to
	// cupsLastError() and cupsLastErrorString().
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	http, err := cc.connect()
	if err != nil {
		return 0, err
	}
	defer C.httpClose(http)

	C.cupsSetUser(user)
	jobID := C.cupsPrintFile2(http, printername, filename, title, numOptions, options)
	if jobID == 0 {
		return 0, fmt.Errorf("Failed to call cupsPrintFile2(): %d %s",
			int(C.cupsLastError()), C.GoString(C.cupsLastErrorString()))
	}

	return jobID, nil
}

// getPrinters gets the current list and state of printers by calling
// C.doRequest (IPP_OP_CUPS_GET_PRINTERS).
//
// The caller is responsible to C.ippDelete the returned *C.ipp_t response.
func (cc *cupsCore) getPrinters(attributes **C.char, attrSize C.int) (*C.ipp_t, error) {
	// ippNewRequest() returns ipp_t pointer which does not need explicit free.
	request := C.ippNewRequest(C.IPP_OP_CUPS_GET_PRINTERS)
	C.ippAddStrings(request, C.IPP_TAG_OPERATION, C.IPP_TAG_KEYWORD, C.REQUESTED_ATTRIBUTES,
		attrSize, nil, attributes)

	response, err := cc.doRequestWithRetry(request,
		[]C.ipp_status_t{C.IPP_STATUS_OK, C.IPP_STATUS_ERROR_NOT_FOUND})
	if err != nil {
		err = fmt.Errorf("Failed to call cupsDoRequest() [IPP_OP_CUPS_GET_PRINTERS]: %s", err)
		return nil, err
	}

	return response, nil
}

// getPPD gets the filename of the PPD for a printer by calling
// C.cupsGetPPD3. If the PPD hasn't changed since the time indicated
// by modtime, then the returned filename is a nil pointer.
//
// Note that modtime is a pointer whose value is changed by this
// function.
//
// The caller is responsible to C.free the returned *C.char filename.
func (cc *cupsCore) getPPD(printername *C.char, modtime *C.time_t) (*C.char, error) {
	bufsize := C.size_t(syscall.PathMax)
	buffer := (*C.char)(C.malloc(bufsize))
	if buffer == nil {
		return nil, errors.New("Failed to malloc; out of memory?")
	}
	C.memset(unsafe.Pointer(buffer), 0, bufsize)

	// Lock the OS thread so that thread-local storage is available to
	// cupsLastError() and cupsLastErrorString().
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	http, err := cc.connect()
	if err != nil {
		return nil, err
	}
	defer C.httpClose(http)

	httpStatus := C.cupsGetPPD3(http, printername, modtime, buffer, bufsize)

	switch httpStatus {
	case C.HTTP_STATUS_NOT_MODIFIED:
		// Cache hit.
		return nil, nil

	case C.HTTP_STATUS_OK:
		// Cache miss.
		return buffer, nil

	default:
		cupsLastError := C.cupsLastError()
		if cupsLastError != C.IPP_STATUS_OK {
			return nil, fmt.Errorf("Failed to call cupsGetPPD3(): %d %s",
				int(cupsLastError), C.GoString(C.cupsLastErrorString()))
		}

		return nil, fmt.Errorf("Failed to call cupsGetPPD3(); HTTP status: %d", int(httpStatus))
	}
}

// getJobAttributes gets the requested attributes for a job by calling
// C.doRequest (IPP_OP_GET_JOB_ATTRIBUTES).
//
// The caller is responsible to C.ippDelete the returned *C.ipp_t response.
func (cc *cupsCore) getJobAttributes(jobID C.int, attributes **C.char) (*C.ipp_t, error) {
	uri, err := createJobURI(jobID)
	if err != nil {
		return nil, err
	}
	defer C.free(unsafe.Pointer(uri))

	// ippNewRequest() returns ipp_t pointer does not need explicit free.
	request := C.ippNewRequest(C.IPP_OP_GET_JOB_ATTRIBUTES)

	C.ippAddString(request, C.IPP_TAG_OPERATION, C.IPP_TAG_URI, C.JOB_URI_ATTRIBUTE, nil, uri)
	C.ippAddStrings(request, C.IPP_TAG_OPERATION, C.IPP_TAG_KEYWORD, C.REQUESTED_ATTRIBUTES,
		C.int(0), nil, attributes)

	response, err := cc.doRequestWithRetry(request, []C.ipp_status_t{C.IPP_STATUS_OK})
	if err != nil {
		err = fmt.Errorf("Failed to call cupsDoRequest() [IPP_OP_GET_JOB_ATTRIBUTES]: %s", err)
		return nil, err
	}

	return response, nil
}

// createJobURI creates a uri string for the job-uri attribute, used to get the
// status of a CUPS job.
func createJobURI(jobID C.int) (*C.char, error) {
	length := C.size_t(urlMaxLength)
	uri := (*C.char)(C.malloc(length))
	if uri == nil {
		return nil, errors.New("Failed to malloc; out of memory?")
	}

	resource := C.CString(fmt.Sprintf(jobURIFormat, uint32(jobID)))
	defer C.free(unsafe.Pointer(resource))
	C.httpAssembleURI(C.HTTP_URI_CODING_ALL,
		uri, C.int(length), C.IPP, nil, C.cupsServer(), C.ippPort(), resource)

	return uri, nil
}

// doRequestWithRetry calls doRequest and retries once on failure.
func (cc *cupsCore) doRequestWithRetry(request *C.ipp_t, acceptableStatusCodes []C.ipp_status_t) (*C.ipp_t, error) {
	response, err := cc.doRequest(request, acceptableStatusCodes)
	if err == nil {
		return response, err
	}

	return cc.doRequest(request, acceptableStatusCodes)
}

// doRequest calls cupsDoRequest().
func (cc *cupsCore) doRequest(request *C.ipp_t, acceptableStatusCodes []C.ipp_status_t) (*C.ipp_t, error) {
	// Lock the OS thread so that thread-local storage is available to
	// cupsLastError() and cupsLastErrorString().
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	http, err := cc.connect()
	if err != nil {
		return nil, err
	}
	defer C.httpClose(http)

	response := C.cupsDoRequest(http, request, C.POST_RESOURCE)
	if response == nil {
		return nil, fmt.Errorf("cupsDoRequest failed: %d %s", int(C.cupsLastError()), C.GoString(C.cupsLastErrorString()))
	}
	statusCode := C.ippGetStatusCode(response)
	for _, sc := range acceptableStatusCodes {
		if statusCode == sc {
			return response, nil
		}
	}

	return nil, fmt.Errorf("IPP status code %d", int(statusCode))
}

// connect calls C.httpConnectEncrypt to create a new, open
// connection to the CUPS server specified by environment variables,
// client.conf, etc.
//
// The caller is responsible to close the connection when finished
// using C.httpClose.
func (cc *cupsCore) connect() (*C.http_t, error) {
	http := C.httpConnectEncrypt(cc.host, cc.port, cc.encryption)
	if http == nil {
		return nil, fmt.Errorf("Failed to connect to CUPS server %s:%d because %d %s",
			C.GoString(cc.host), int(cc.port), int(C.cupsLastError()), C.GoString(C.cupsLastErrorString()))
	}

	return http, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2015 Google Inc. All rights reserved.

Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file or at
https://developers.google.com/open-source/licenses/bsd
*/

package cups

/*
#include "cups.h"
*/
import "C"
import (
	"errors"
	"fmt"
	"os"
	"runtime"
	"strings"
	"time"
	"unsafe"

	"github.com/google/cups-connector/lib"
	"github.com/google/cups-connector/log"
)

const (
	// jobURIFormat is the string format required by the CUPS API
	// to do things like query the state of a job.
	jobURIFormat = "/jobs/%d"

	// filePathMaxLength varies by operating system and file system.
	// This value should be large enough to be useful and small enough
	// to work on any platform.
	filePathMaxLength = 1024
)

// cupsCore handles CUPS API interaction and connection management.
type cupsCore struct {
	host           *C.char
	port           C.int
	encryption     C.http_encryption_t
	connectTimeout C.int
	// connectionSemaphore limits the quantity of open CUPS connections.
	connectionSemaphore *lib.Semaphore
	// connectionPool allows a connection to be reused instead of closed.
	connectionPool chan *C.http_t
	hostIsLocal    bool
}

func newCUPSCore(maxConnections uint, connectTimeout time.Duration) (*cupsCore, error) {
	host := C.cupsServer()
	port := C.ippPort()
	encryption := C.cupsEncryption()
	timeout := C.int(connectTimeout / time.Millisecond)

	var e string
	switch encryption {
	case C.HTTP_ENCRYPTION_ALWAYS:
		e = "encrypting ALWAYS"
	case C.HTTP_ENCRYPTION_IF_REQUESTED:
		e = "encrypting IF REQUESTED"
	case C.HTTP_ENCRYPTION_NEVER:
		e = "encrypting NEVER"
	case C.HTTP_ENCRYPTION_REQUIRED:
		e = "encryption REQUIRED"
	default:
		encryption = C.HTTP_ENCRYPTION_REQUIRED
		e = "encrypting REQUIRED"
	}

	var hostIsLocal bool
	if h := C.GoString(host); strings.HasPrefix(h, "/") || h == "localhost" {
		hostIsLocal = true
	}

	cs := lib.NewSemaphore(maxConnections)
	cp := make(chan *C.http_t)

	cc := &cupsCore{host, port, encryption, timeout, cs, cp, hostIsLocal}

	// This connection isn't used, just checks that a connection is possible
	// before returning from the constructor.
	http, err := cc.connect()
	if err != nil {
		return nil, err
	}
	cc.disconnect(http)

	log.Infof("connected to CUPS server %s:%d %s\n", C.GoString(host), int(port), e)

	return cc, nil
}

// printFile prints by calling C.cupsPrintFile2().
// Returns the CUPS job ID, which is 0 (and meaningless) when err
// is not nil.
func (cc *cupsCore) printFile(user, printername, filename, title *C.char, numOptions C.int, options *C.cups_option_t) (C.int, error) {
	http, err := cc.connect()
	if err != nil {
		return 0, err
	}
	defer cc.disconnect(http)

	C.cupsSetUser(user)
	jobID := C.cupsPrintFile2(http, printername, filename, title, numOptions, options)
	if jobID == 0 {
		return 0, fmt.Errorf("Failed to call cupsPrintFile2() for file %s: %d %s",
			C.GoString(filename), int(C.cupsLastError()), C.GoString(C.cupsLastErrorString()))
	}

	return jobID, nil
}

// getPrinters gets the current list and state of printers by calling
// C.doRequest (IPP_OP_CUPS_GET_PRINTERS).
//
// The caller is responsible to C.ippDelete the returned *C.ipp_t response.
func (cc *cupsCore) getPrinters(attributes **C.char, attrSize C.int) (*C.ipp_t, error) {
	// ippNewRequest() returns ipp_t pointer which does not need explicit free.
	request := C.ippNewRequest(C.IPP_OP_CUPS_GET_PRINTERS)
	C.ippAddStrings(request, C.IPP_TAG_OPERATION, C.IPP_TAG_KEYWORD, C.REQUESTED_ATTRIBUTES,
		attrSize, nil, attributes)

	response, err := cc.doRequest(request,
		[]C.ipp_status_t{C.IPP_STATUS_OK, C.IPP_STATUS_ERROR_NOT_FOUND})
	if err != nil {
		err = fmt.Errorf("Failed to call cupsDoRequest() [IPP_OP_CUPS_GET_PRINTERS]: %s", err)
		return nil, err
	}

	return response, nil
}

// getPPD gets the filename of the PPD for a printer by calling
// C.cupsGetPPD3. If the PPD hasn't changed since the time indicated
// by modtime, then the returned filename is a nil pointer.
//
// Note that modtime is a pointer whose value is changed by this
// function.
//
// The caller is responsible to C.free the returned *C.char filename
// if the returned filename is not nil.
func (cc *cupsCore) getPPD(printername *C.char, modtime *C.time_t) (*C.char, error) {
	bufsize := C.size_t(filePathMaxLength)
	buffer := (*C.char)(C.malloc(bufsize))
	if buffer == nil {
		return nil, errors.New("Failed to malloc; out of memory?")
	}
	C.memset(unsafe.Pointer(buffer), 0, bufsize)

	var http *C.http_t
	if !cc.hostIsLocal {
		// Don't need a connection or corresponding semaphore if the PPD
		// is on the local filesystem.
		// Still need OS thread lock; see else.
		var err error
		http, err = cc.connect()
		if err != nil {
			return nil, err
		}
		defer cc.disconnect(http)

	} else {
		// Lock the OS thread so that thread-local storage is available to
		// cupsLastError() and cupsLastErrorString().
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()
	}

	httpStatus := C.cupsGetPPD3(http, printername, modtime, buffer, bufsize)

	switch httpStatus {
	case C.HTTP_STATUS_NOT_MODIFIED:
		// Cache hit.
		if len(C.GoString(buffer)) > 0 {
			os.Remove(C.GoString(buffer))
		}
		C.free(unsafe.Pointer(buffer))
		return nil, nil

	case C.HTTP_STATUS_OK:
		// Cache miss.
		return buffer, nil

	default:
		if len(C.GoString(buffer)) > 0 {
			os.Remove(C.GoString(buffer))
		}
		C.free(unsafe.Pointer(buffer))
		cupsLastError := C.cupsLastError()
		if cupsLastError != C.IPP_STATUS_OK {
			return nil, fmt.Errorf("Failed to call cupsGetPPD3(): %d %s",
				int(cupsLastError), C.GoString(C.cupsLastErrorString()))
		}

		return nil, fmt.Errorf("Failed to call cupsGetPPD3(); HTTP status: %d", int(httpStatus))
	}
}

// getJobAttributes gets the requested attributes for a job by calling
// C.doRequest (IPP_OP_GET_JOB_ATTRIBUTES).
//
// The caller is responsible to C.ippDelete the returned *C.ipp_t response.
func (cc *cupsCore) getJobAttributes(jobID C.int, attributes **C.char) (*C.ipp_t, error) {
	uri, err := createJobURI(jobID)
	if err != nil {
		return nil, err
	}
	defer C.free(unsafe.Pointer(uri))

	// ippNewRequest() returns ipp_t pointer does not need explicit free.
	request := C.ippNewRequest(C.IPP_OP_GET_JOB_ATTRIBUTES)

	C.ippAddString(request, C.IPP_TAG_OPERATION, C.IPP_TAG_URI, C.JOB_URI_ATTRIBUTE, nil, uri)
	C.ippAddStrings(request, C.IPP_TAG_OPERATION, C.IPP_TAG_KEYWORD, C.REQUESTED_ATTRIBUTES,
		C.int(0), nil, attributes)

	response, err := cc.doRequest(request, []C.ipp_status_t{C.IPP_STATUS_OK})
	if err != nil {
		err = fmt.Errorf("Failed to call cupsDoRequest() [IPP_OP_GET_JOB_ATTRIBUTES]: %s", err)
		return nil, err
	}

	return response, nil
}

// createJobURI creates a uri string for the job-uri attribute, used to get the
// state of a CUPS job.
func createJobURI(jobID C.int) (*C.char, error) {
	length := C.size_t(urlMaxLength)
	uri := (*C.char)(C.malloc(length))
	if uri == nil {
		return nil, errors.New("Failed to malloc; out of memory?")
	}

	resource := C.CString(fmt.Sprintf(jobURIFormat, uint32(jobID)))
	defer C.free(unsafe.Pointer(resource))
	C.httpAssembleURI(C.HTTP_URI_CODING_ALL,
		uri, C.int(length), C.IPP, nil, C.cupsServer(), C.ippPort(), resource)

	return uri, nil
}

// doRequest calls cupsDoRequest().
func (cc *cupsCore) doRequest(request *C.ipp_t, acceptableStatusCodes []C.ipp_status_t) (*C.ipp_t, error) {
	http, err := cc.connect()
	if err != nil {
		return nil, err
	}
	defer cc.disconnect(http)

	if C.ippValidateAttributes(request) != 1 {
		return nil, fmt.Errorf("Bad IPP request: %s", C.GoString(C.cupsLastErrorString()))
	}

	response := C.cupsDoRequest(http, request, C.POST_RESOURCE)
	if response == nil {
		return nil, fmt.Errorf("cupsDoRequest failed: %d %s", int(C.cupsLastError()), C.GoString(C.cupsLastErrorString()))
	}
	statusCode := C.getIPPRequestStatusCode(response)
	for _, sc := range acceptableStatusCodes {
		if statusCode == sc {
			return response, nil
		}
	}

	return nil, fmt.Errorf("IPP status code %d", int(statusCode))
}

// connect calls C.httpConnect2 to create a new, open connection to
// the CUPS server specified by environment variables, client.conf, etc.
//
// connect also acquires the connection semaphore and locks the OS
// thread to allow the CUPS API to use thread-local storage cleanly.
//
// The caller is responsible to close the connection when finished
// using cupsCore.disconnect.
func (cc *cupsCore) connect() (*C.http_t, error) {
	cc.connectionSemaphore.Acquire()

	// Lock the OS thread so that thread-local storage is available to
	// cupsLastError() and cupsLastErrorString().
	runtime.LockOSThread()

	var http *C.http_t

	select {
	case h := <-cc.connectionPool:
		// Reuse another connection.
		http = h
	default:
		// No connection available for reuse; create a new one.
		http = C.httpConnect2(cc.host, cc.port, nil, C.AF_UNSPEC, cc.encryption, 1, cc.connectTimeout, nil)
		if http == nil {
			defer cc.disconnect(http)
			return nil, fmt.Errorf("Failed to connect to CUPS server %s:%d because %d %s",
				C.GoString(cc.host), int(cc.port), int(C.cupsLastError()), C.GoString(C.cupsLastErrorString()))
		}
	}

	return http, nil
}

// disconnect calls C.httpClose to close an open CUPS connection, then
// unlocks the OS thread and the connection semaphore.
//
// The http argument may be nil; the OS thread and semaphore are still
// treated the same as described above.
func (cc *cupsCore) disconnect(http *C.http_t) {
	go func() {
		select {
		case cc.connectionPool <- http:
			// Hand this connection to the next guy who needs it.
		case <-time.After(time.Second):
			// Don't wait very long; stale connections are no fun.
			C.httpClose(http)
		}
	}()
	runtime.UnlockOSThread()
	cc.connectionSemaphore.Release()
}

func (cc *cupsCore) connQtyOpen() uint {
	return cc.connectionSemaphore.Count()
}

func (cc *cupsCore) connQtyMax() uint {
	return cc.connectionSemaphore.Size()
}
<commit_msg>Remove unwanted newline from log entry.<commit_after>/*
Copyright 2015 Google Inc. All rights reserved.

Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file or at
https://developers.google.com/open-source/licenses/bsd
*/

package cups

/*
#include "cups.h"
*/
import "C"
import (
	"errors"
	"fmt"
	"os"
	"runtime"
	"strings"
	"time"
	"unsafe"

	"github.com/google/cups-connector/lib"
	"github.com/google/cups-connector/log"
)

const (
	// jobURIFormat is the string format required by the CUPS API
	// to do things like query the state of a job.
	jobURIFormat = "/jobs/%d"

	// filePathMaxLength varies by operating system and file system.
	// This value should be large enough to be useful and small enough
	// to work on any platform.
	filePathMaxLength = 1024
)

// cupsCore handles CUPS API interaction and connection management.
type cupsCore struct {
	host           *C.char
	port           C.int
	encryption     C.http_encryption_t
	connectTimeout C.int
	// connectionSemaphore limits the quantity of open CUPS connections.
	connectionSemaphore *lib.Semaphore
	// connectionPool allows a connection to be reused instead of closed.
	connectionPool chan *C.http_t
	hostIsLocal    bool
}

func newCUPSCore(maxConnections uint, connectTimeout time.Duration) (*cupsCore, error) {
	host := C.cupsServer()
	port := C.ippPort()
	encryption := C.cupsEncryption()
	timeout := C.int(connectTimeout / time.Millisecond)

	var e string
	switch encryption {
	case C.HTTP_ENCRYPTION_ALWAYS:
		e = "encrypting ALWAYS"
	case C.HTTP_ENCRYPTION_IF_REQUESTED:
		e = "encrypting IF REQUESTED"
	case C.HTTP_ENCRYPTION_NEVER:
		e = "encrypting NEVER"
	case C.HTTP_ENCRYPTION_REQUIRED:
		e = "encryption REQUIRED"
	default:
		encryption = C.HTTP_ENCRYPTION_REQUIRED
		e = "encrypting REQUIRED"
	}

	var hostIsLocal bool
	if h := C.GoString(host); strings.HasPrefix(h, "/") || h == "localhost" {
		hostIsLocal = true
	}

	cs := lib.NewSemaphore(maxConnections)
	cp := make(chan *C.http_t)

	cc := &cupsCore{host, port, encryption, timeout, cs, cp, hostIsLocal}

	// This connection isn't used, just checks that a connection is possible
	// before returning from the constructor.
	http, err := cc.connect()
	if err != nil {
		return nil, err
	}
	cc.disconnect(http)

	log.Infof("connected to CUPS server %s:%d %s", C.GoString(host), int(port), e)

	return cc, nil
}

// printFile prints by calling C.cupsPrintFile2().
// Returns the CUPS job ID, which is 0 (and meaningless) when err
// is not nil.
func (cc *cupsCore) printFile(user, printername, filename, title *C.char, numOptions C.int, options *C.cups_option_t) (C.int, error) {
	http, err := cc.connect()
	if err != nil {
		return 0, err
	}
	defer cc.disconnect(http)

	C.cupsSetUser(user)
	jobID := C.cupsPrintFile2(http, printername, filename, title, numOptions, options)
	if jobID == 0 {
		return 0, fmt.Errorf("Failed to call cupsPrintFile2() for file %s: %d %s",
			C.GoString(filename), int(C.cupsLastError()), C.GoString(C.cupsLastErrorString()))
	}

	return jobID, nil
}

// getPrinters gets the current list and state of printers by calling
// C.doRequest (IPP_OP_CUPS_GET_PRINTERS).
//
// The caller is responsible to C.ippDelete the returned *C.ipp_t response.
func (cc *cupsCore) getPrinters(attributes **C.char, attrSize C.int) (*C.ipp_t, error) {
	// ippNewRequest() returns ipp_t pointer which does not need explicit free.
	request := C.ippNewRequest(C.IPP_OP_CUPS_GET_PRINTERS)
	C.ippAddStrings(request, C.IPP_TAG_OPERATION, C.IPP_TAG_KEYWORD, C.REQUESTED_ATTRIBUTES,
		attrSize, nil, attributes)

	response, err := cc.doRequest(request,
		[]C.ipp_status_t{C.IPP_STATUS_OK, C.IPP_STATUS_ERROR_NOT_FOUND})
	if err != nil {
		err = fmt.Errorf("Failed to call cupsDoRequest() [IPP_OP_CUPS_GET_PRINTERS]: %s", err)
		return nil, err
	}

	return response, nil
}

// getPPD gets the filename of the PPD for a printer by calling
// C.cupsGetPPD3. If the PPD hasn't changed since the time indicated
// by modtime, then the returned filename is a nil pointer.
//
// Note that modtime is a pointer whose value is changed by this
// function.
//
// The caller is responsible to C.free the returned *C.char filename
// if the returned filename is not nil.
func (cc *cupsCore) getPPD(printername *C.char, modtime *C.time_t) (*C.char, error) {
	bufsize := C.size_t(filePathMaxLength)
	buffer := (*C.char)(C.malloc(bufsize))
	if buffer == nil {
		return nil, errors.New("Failed to malloc; out of memory?")
	}
	C.memset(unsafe.Pointer(buffer), 0, bufsize)

	var http *C.http_t
	if !cc.hostIsLocal {
		// Don't need a connection or corresponding semaphore if the PPD
		// is on the local filesystem.
		// Still need OS thread lock; see else.
		var err error
		http, err = cc.connect()
		if err != nil {
			return nil, err
		}
		defer cc.disconnect(http)

	} else {
		// Lock the OS thread so that thread-local storage is available to
		// cupsLastError() and cupsLastErrorString().
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()
	}

	httpStatus := C.cupsGetPPD3(http, printername, modtime, buffer, bufsize)

	switch httpStatus {
	case C.HTTP_STATUS_NOT_MODIFIED:
		// Cache hit.
		if len(C.GoString(buffer)) > 0 {
			os.Remove(C.GoString(buffer))
		}
		C.free(unsafe.Pointer(buffer))
		return nil, nil

	case C.HTTP_STATUS_OK:
		// Cache miss.
		return buffer, nil

	default:
		if len(C.GoString(buffer)) > 0 {
			os.Remove(C.GoString(buffer))
		}
		C.free(unsafe.Pointer(buffer))
		cupsLastError := C.cupsLastError()
		if cupsLastError != C.IPP_STATUS_OK {
			return nil, fmt.Errorf("Failed to call cupsGetPPD3(): %d %s",
				int(cupsLastError), C.GoString(C.cupsLastErrorString()))
		}

		return nil, fmt.Errorf("Failed to call cupsGetPPD3(); HTTP status: %d", int(httpStatus))
	}
}

// getJobAttributes gets the requested attributes for a job by calling
// C.doRequest (IPP_OP_GET_JOB_ATTRIBUTES).
//
// The caller is responsible to C.ippDelete the returned *C.ipp_t response.
func (cc *cupsCore) getJobAttributes(jobID C.int, attributes **C.char) (*C.ipp_t, error) {
	uri, err := createJobURI(jobID)
	if err != nil {
		return nil, err
	}
	defer C.free(unsafe.Pointer(uri))

	// ippNewRequest() returns ipp_t pointer does not need explicit free.
	request := C.ippNewRequest(C.IPP_OP_GET_JOB_ATTRIBUTES)

	C.ippAddString(request, C.IPP_TAG_OPERATION, C.IPP_TAG_URI, C.JOB_URI_ATTRIBUTE, nil, uri)
	C.ippAddStrings(request, C.IPP_TAG_OPERATION, C.IPP_TAG_KEYWORD, C.REQUESTED_ATTRIBUTES,
		C.int(0), nil, attributes)

	response, err := cc.doRequest(request, []C.ipp_status_t{C.IPP_STATUS_OK})
	if err != nil {
		err = fmt.Errorf("Failed to call cupsDoRequest() [IPP_OP_GET_JOB_ATTRIBUTES]: %s", err)
		return nil, err
	}

	return response, nil
}

// createJobURI creates a uri string for the job-uri attribute, used to get the
// state of a CUPS job.
func createJobURI(jobID C.int) (*C.char, error) {
	length := C.size_t(urlMaxLength)
	uri := (*C.char)(C.malloc(length))
	if uri == nil {
		return nil, errors.New("Failed to malloc; out of memory?")
	}

	resource := C.CString(fmt.Sprintf(jobURIFormat, uint32(jobID)))
	defer C.free(unsafe.Pointer(resource))
	C.httpAssembleURI(C.HTTP_URI_CODING_ALL,
		uri, C.int(length), C.IPP, nil, C.cupsServer(), C.ippPort(), resource)

	return uri, nil
}

// doRequest calls cupsDoRequest().
func (cc *cupsCore) doRequest(request *C.ipp_t, acceptableStatusCodes []C.ipp_status_t) (*C.ipp_t, error) {
	http, err := cc.connect()
	if err != nil {
		return nil, err
	}
	defer cc.disconnect(http)

	if C.ippValidateAttributes(request) != 1 {
		return nil, fmt.Errorf("Bad IPP request: %s", C.GoString(C.cupsLastErrorString()))
	}

	response := C.cupsDoRequest(http, request, C.POST_RESOURCE)
	if response == nil {
		return nil, fmt.Errorf("cupsDoRequest failed: %d %s", int(C.cupsLastError()), C.GoString(C.cupsLastErrorString()))
	}
	statusCode := C.getIPPRequestStatusCode(response)
	for _, sc := range acceptableStatusCodes {
		if statusCode == sc {
			return response, nil
		}
	}

	return nil, fmt.Errorf("IPP status code %d", int(statusCode))
}

// connect calls C.httpConnect2 to create a new, open connection to
// the CUPS server specified by environment variables, client.conf, etc.
//
// connect also acquires the connection semaphore and locks the OS
// thread to allow the CUPS API to use thread-local storage cleanly.
//
// The caller is responsible to close the connection when finished
// using cupsCore.disconnect.
func (cc *cupsCore) connect() (*C.http_t, error) {
	cc.connectionSemaphore.Acquire()

	// Lock the OS thread so that thread-local storage is available to
	// cupsLastError() and cupsLastErrorString().
	runtime.LockOSThread()

	var http *C.http_t

	select {
	case h := <-cc.connectionPool:
		// Reuse another connection.
		http = h
	default:
		// No connection available for reuse; create a new one.
		http = C.httpConnect2(cc.host, cc.port, nil, C.AF_UNSPEC, cc.encryption, 1, cc.connectTimeout, nil)
		if http == nil {
			defer cc.disconnect(http)
			return nil, fmt.Errorf("Failed to connect to CUPS server %s:%d because %d %s",
				C.GoString(cc.host), int(cc.port), int(C.cupsLastError()), C.GoString(C.cupsLastErrorString()))
		}
	}

	return http, nil
}

// disconnect calls C.httpClose to close an open CUPS connection, then
// unlocks the OS thread and the connection semaphore.
//
// The http argument may be nil; the OS thread and semaphore are still
// treated the same as described above.
func (cc *cupsCore) disconnect(http *C.http_t) {
	go func() {
		select {
		case cc.connectionPool <- http:
			// Hand this connection to the next guy who needs it.
		case <-time.After(time.Second):
			// Don't wait very long; stale connections are no fun.
			C.httpClose(http)
		}
	}()
	runtime.UnlockOSThread()
	cc.connectionSemaphore.Release()
}

func (cc *cupsCore) connQtyOpen() uint {
	return cc.connectionSemaphore.Count()
}

func (cc *cupsCore) connQtyMax() uint {
	return cc.connectionSemaphore.Size()
}
<|endoftext|>


<commit_before>package cmd_teamfolder

import (
	"flag"
	"github.com/watermint/toolbox/app/app_report"
	"github.com/watermint/toolbox/cmd"
	"github.com/watermint/toolbox/domain/infra/api_auth_impl"
	"github.com/watermint/toolbox/domain/usecase/uc_teamfolder_mirror"
)

type CmdTeamFolderMirror struct {
	*cmd.SimpleCommandlet

	optSrcTeamAlias   string
	optDstTeamAlias   string
	optAllTeamFolders bool

	report app_report.Factory
}

func (CmdTeamFolderMirror) Name() string {
	return "mirror"
}

func (CmdTeamFolderMirror) Desc() string {
	return "cmd.teamfolder.mirror.desc"
}

func (CmdTeamFolderMirror) Usage() func(usage cmd.CommandUsage) {
	return nil
}

func (z *CmdTeamFolderMirror) FlagConfig(f *flag.FlagSet) {
	z.report.ExecContext = z.ExecContext
	z.report.FlagConfig(f)

	descFromAccount := z.ExecContext.Msg("cmd.teamfolder.mirror.flag.src_account").T()
	f.StringVar(&z.optSrcTeamAlias, "alias-src", "mirror-src", descFromAccount)

	descToAccount := z.ExecContext.Msg("cmd.teamfolder.mirror.flag.dst_account").T()
	f.StringVar(&z.optDstTeamAlias, "alias-dest", "mirror-dst", descToAccount)

	descAll := z.ExecContext.Msg("cmd.teamfolder.mirror.flag.all").T()
	f.BoolVar(&z.optAllTeamFolders, "all", false, descAll)
}

func (z *CmdTeamFolderMirror) Exec(args []string) {
	if z.optSrcTeamAlias == "" ||
		z.optDstTeamAlias == "" {

		z.ExecContext.Msg("cmd.teamfolder.mirror.err.not_enough_params").TellError()
		return
	}
	if z.optSrcTeamAlias == z.optDstTeamAlias {
		z.ExecContext.Msg("cmd.teamfolder.mirror.err.same_team").TellError()
		return
	}
	if len(args) < 1 && !z.optAllTeamFolders {
		z.ExecContext.Msg("cmd.teamfolder.mirror.err.not_enough_arguments").TellError()
		return
	}
	var err error

	// Ask for SRC account authentication
	z.ExecContext.Msg("cmd.teamfolder.mirror.prompt.ask_src_file_account_auth").WithData(struct {
		Alias string
	}{
		Alias: z.optSrcTeamAlias,
	}).Tell()
	ctxFileSrc, err := api_auth_impl.Auth(z.ExecContext, api_auth_impl.PeerName(z.optSrcTeamAlias), api_auth_impl.BusinessFile())
	if err != nil {
		return
	}

	// Ask for SRC account authentication
	z.ExecContext.Msg("cmd.teamfolder.mirror.prompt.ask_src_mgmt_account_auth").WithData(struct {
		Alias string
	}{
		Alias: z.optSrcTeamAlias,
	}).Tell()
	ctxMgtSrc, err := api_auth_impl.Auth(z.ExecContext, api_auth_impl.PeerName(z.optSrcTeamAlias), api_auth_impl.BusinessManagement())
	if err != nil {
		return
	}

	// Ask for DST account authentication
	z.ExecContext.Msg("cmd.teamfolder.mirror.prompt.ask_dst_file_account_auth").WithData(struct {
		Alias string
	}{
		Alias: z.optDstTeamAlias,
	}).Tell()
	ctxFileDst, err := api_auth_impl.Auth(z.ExecContext, api_auth_impl.PeerName(z.optDstTeamAlias), api_auth_impl.BusinessFile())
	if err != nil {
		return
	}

	// Ask for DST account authentication
	z.ExecContext.Msg("cmd.teamfolder.mirror.prompt.ask_dst_mgmt_account_auth").WithData(struct {
		Alias string
	}{
		Alias: z.optDstTeamAlias,
	}).Tell()
	ctxMgtDst, err := api_auth_impl.Auth(z.ExecContext, api_auth_impl.PeerName(z.optDstTeamAlias), api_auth_impl.BusinessManagement())
	if err != nil {
		return
	}

	z.report.Init(z.ExecContext)
	defer z.report.Close()

	ucm := uc_teamfolder_mirror.New(ctxFileSrc, ctxMgtSrc, ctxFileDst, ctxMgtDst, z.report)

	if z.optAllTeamFolders {
		uc, err := ucm.AllFolderScope()
		if err != nil {
			ctxFileSrc.ErrorMsg(err).TellError()
			return
		}
		if err = ucm.Mirror(uc); err != nil {
			ctxFileSrc.ErrorMsg(err).TellError()
		}
	} else {
		uc, err := ucm.PartialScope(args)
		if err != nil {
			ctxFileSrc.ErrorMsg(err).TellError()
			return
		}
		if err = ucm.Mirror(uc); err != nil {
			ctxFileSrc.ErrorMsg(err).TellError()
		}
	}
}
<commit_msg>#59 : fix mirror command<commit_after>package cmd_teamfolder

import (
	"flag"
	"github.com/watermint/toolbox/app/app_report"
	"github.com/watermint/toolbox/cmd"
	"github.com/watermint/toolbox/domain/infra/api_auth_impl"
	"github.com/watermint/toolbox/domain/usecase/uc_teamfolder_mirror"
)

type CmdTeamFolderMirror struct {
	*cmd.SimpleCommandlet

	optSrcTeamAlias   string
	optDstTeamAlias   string
	optAllTeamFolders bool

	report app_report.Factory
}

func (CmdTeamFolderMirror) Name() string {
	return "mirror"
}

func (CmdTeamFolderMirror) Desc() string {
	return "cmd.teamfolder.mirror.desc"
}

func (CmdTeamFolderMirror) Usage() func(usage cmd.CommandUsage) {
	return nil
}

func (z *CmdTeamFolderMirror) FlagConfig(f *flag.FlagSet) {
	z.report.ExecContext = z.ExecContext
	z.report.FlagConfig(f)

	descFromAccount := z.ExecContext.Msg("cmd.teamfolder.mirror.flag.src_account").T()
	f.StringVar(&z.optSrcTeamAlias, "alias-src", "mirror-src", descFromAccount)

	descToAccount := z.ExecContext.Msg("cmd.teamfolder.mirror.flag.dst_account").T()
	f.StringVar(&z.optDstTeamAlias, "alias-dest", "mirror-dst", descToAccount)

	descAll := z.ExecContext.Msg("cmd.teamfolder.mirror.flag.all").T()
	f.BoolVar(&z.optAllTeamFolders, "all", false, descAll)
}

func (z *CmdTeamFolderMirror) Exec(args []string) {
	if z.optSrcTeamAlias == "" ||
		z.optDstTeamAlias == "" {

		z.ExecContext.Msg("cmd.teamfolder.mirror.err.not_enough_params").TellError()
		return
	}
	if z.optSrcTeamAlias == z.optDstTeamAlias {
		z.ExecContext.Msg("cmd.teamfolder.mirror.err.same_team").TellError()
		return
	}
	if len(args) < 1 && !z.optAllTeamFolders {
		z.ExecContext.Msg("cmd.teamfolder.mirror.err.not_enough_arguments").TellError()
		return
	}
	var err error

	// Ask for SRC account authentication
	z.ExecContext.Msg("cmd.teamfolder.mirror.prompt.ask_src_file_account_auth").WithData(struct {
		Alias string
	}{
		Alias: z.optSrcTeamAlias,
	}).Tell()
	ctxFileSrc, err := api_auth_impl.Auth(z.ExecContext, api_auth_impl.PeerName(z.optSrcTeamAlias), api_auth_impl.BusinessFile())
	if err != nil {
		return
	}

	// Ask for SRC account authentication
	z.ExecContext.Msg("cmd.teamfolder.mirror.prompt.ask_src_mgmt_account_auth").WithData(struct {
		Alias string
	}{
		Alias: z.optSrcTeamAlias,
	}).Tell()
	ctxMgtSrc, err := api_auth_impl.Auth(z.ExecContext, api_auth_impl.PeerName(z.optSrcTeamAlias), api_auth_impl.BusinessManagement())
	if err != nil {
		return
	}

	// Ask for DST account authentication
	z.ExecContext.Msg("cmd.teamfolder.mirror.prompt.ask_dst_file_account_auth").WithData(struct {
		Alias string
	}{
		Alias: z.optDstTeamAlias,
	}).Tell()
	ctxFileDst, err := api_auth_impl.Auth(z.ExecContext, api_auth_impl.PeerName(z.optDstTeamAlias), api_auth_impl.BusinessFile())
	if err != nil {
		return
	}

	// Ask for DST account authentication
	z.ExecContext.Msg("cmd.teamfolder.mirror.prompt.ask_dst_mgmt_account_auth").WithData(struct {
		Alias string
	}{
		Alias: z.optDstTeamAlias,
	}).Tell()
	ctxMgtDst, err := api_auth_impl.Auth(z.ExecContext, api_auth_impl.PeerName(z.optDstTeamAlias), api_auth_impl.BusinessManagement())
	if err != nil {
		return
	}

	z.report.Init(z.ExecContext)
	defer z.report.Close()

	ucm := uc_teamfolder_mirror.New(ctxFileSrc, ctxMgtSrc, ctxFileDst, ctxMgtDst, &z.report)

	if z.optAllTeamFolders {
		uc, err := ucm.AllFolderScope()
		if err != nil {
			ctxFileSrc.ErrorMsg(err).TellError()
			return
		}
		if err = ucm.Mirror(uc); err != nil {
			ctxFileSrc.ErrorMsg(err).TellError()
		}
	} else {
		uc, err := ucm.PartialScope(args)
		if err != nil {
			ctxFileSrc.ErrorMsg(err).TellError()
			return
		}
		if err = ucm.Mirror(uc); err != nil {
			ctxFileSrc.ErrorMsg(err).TellError()
		}
	}
}
<|endoftext|>


<commit_before>/*
   Copyright 2020 Docker Compose CLI authors

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

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

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

package compatibility

import (
	"fmt"
	"os"

	"github.com/docker/compose/v2/cmd/compose"
)

func getBoolFlags() []string {
	return []string{
		"--debug", "-D",
		"--verbose",
		"--tls",
		"--tlsverify",
	}
}

func getStringFlags() []string {
	return []string{
		"--tlscacert",
		"--tlscert",
		"--tlskey",
		"--host", "-H",
		"--context",
		"--log-level",
	}
}

// Convert transforms standalone docker-compose args into CLI plugin compliant ones
func Convert(args []string) []string {
	var rootFlags []string
	command := []string{compose.PluginName}
	l := len(args)
	for i := 0; i < l; i++ {
		arg := args[i]
		if arg[0] != '-' {
			// not a top-level flag anymore, keep the rest of the command unmodified
			if arg == compose.PluginName {
				i++
			}
			command = append(command, args[i:]...)
			break
		}
		if arg == "--verbose" {
			arg = "--debug"
		}
		if arg == "-h" {
			// docker cli has deprecated -h to avoid ambiguity with -H, while docker-compose still support it
			arg = "--help"
		}
		if arg == "--version" || arg == "-v" {
			// redirect --version pseudo-command to actual command
			arg = "version"
		}
		if contains(getBoolFlags(), arg) {
			rootFlags = append(rootFlags, arg)
			continue
		}
		if contains(getStringFlags(), arg) {
			i++
			if i >= l {
				fmt.Fprintf(os.Stderr, "flag needs an argument: '%s'\n", arg)
				os.Exit(1)
			}
			rootFlags = append(rootFlags, arg, args[i])
			continue
		}
		command = append(command, arg)
	}
	return append(rootFlags, command...)
}

func contains(array []string, needle string) bool {
	for _, val := range array {
		if val == needle {
			return true
		}
	}
	return false
}
<commit_msg>TC: Use map to simplify flag conversion and avoid multilple if statements<commit_after>/*
   Copyright 2020 Docker Compose CLI authors

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

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

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

package compatibility

import (
	"fmt"
	"os"

	"github.com/docker/compose/v2/cmd/compose"
)

func getBoolFlags() []string {
	return []string{
		"--debug", "-D",
		"--verbose",
		"--tls",
		"--tlsverify",
	}
}

func getStringFlags() []string {
	return []string{
		"--tlscacert",
		"--tlscert",
		"--tlskey",
		"--host", "-H",
		"--context",
		"--log-level",
	}
}

var argToActualFlag = map[string]string{
	"--verbose": "--debug",
	// docker cli has deprecated -h to avoid ambiguity with -H, while docker-compose still support it
	"-h": "--help",
	// redirect --version pseudo-command to actual command
	"--version": "version",
	"-v":        "version",
}

// Convert transforms standalone docker-compose args into CLI plugin compliant ones
func Convert(args []string) []string {
	var rootFlags []string
	command := []string{compose.PluginName}
	l := len(args)
	for i := 0; i < l; i++ {
		arg := args[i]
		if arg[0] != '-' {
			// not a top-level flag anymore, keep the rest of the command unmodified
			if arg == compose.PluginName {
				i++
			}
			command = append(command, args[i:]...)
			break
		}
		if actualFlag, ok := argToActualFlag[arg]; ok {
			arg = actualFlag
		}
		if contains(getBoolFlags(), arg) {
			rootFlags = append(rootFlags, arg)
			continue
		}
		if contains(getStringFlags(), arg) {
			i++
			if i >= l {
				fmt.Fprintf(os.Stderr, "flag needs an argument: '%s'\n", arg)
				os.Exit(1)
			}
			rootFlags = append(rootFlags, arg, args[i])
			continue
		}
		command = append(command, arg)
	}
	return append(rootFlags, command...)
}

func contains(array []string, needle string) bool {
	for _, val := range array {
		if val == needle {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package entrypoint

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"testing"
	"time"
)

// The Queue struct implements a multi-writer/multi-reader concurrent queue where the dequeue
// operation (the Get() method) takes a predicate that allows it to skip past queue entries until it
// finds one that satisfies the specified predicate.
type Queue struct {
	T       *testing.T
	timeout time.Duration
	cond    *sync.Cond
	entries []interface{}
	offset  int
}

// NewQueue constructs a new queue with the supplied timeout.
func NewQueue(t *testing.T, timeout time.Duration) *Queue {
	q := &Queue{
		T:       t,
		timeout: timeout,
		cond:    sync.NewCond(&sync.Mutex{}),
	}
	ctx, cancel := context.WithCancel(context.Background())
	t.Cleanup(cancel)
	// Broadcast on Queue.cond every three seconds so that anyone waiting on the condition has a
	// chance to timeout. (Go doesn't support timed wait on conditions.)
	go func() {
		ticker := time.NewTicker(3 * time.Second)
		for {
			select {
			case <-ticker.C:
				q.cond.Broadcast()
			case <-ctx.Done():
				return
			}
		}
	}()
	return q
}

// Add an entry to the queue.
func (q *Queue) Add(obj interface{}) {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	q.entries = append(q.entries, obj)
	q.cond.Broadcast()
}

// Get will return the next entry that satisfies the supplied predicate.
func (q *Queue) Get(predicate func(interface{}) bool) interface{} {
	start := time.Now()
	q.cond.L.Lock()
	defer q.cond.L.Unlock()

	for {
		for idx, obj := range q.entries[q.offset:] {
			if predicate(obj) {
				q.offset += idx + 1
				return obj
			}
		}

		if time.Since(start) > q.timeout {
			fmt.Println("GET TIMED OUT")

			if q.offset >= len(q.entries) {
				fmt.Println("--- Queue is empty ---")
			} else {
				// Walk the outstanding entries in the queue and dump them as
				// JSON, so that the test writer has a fighting chance of
				// figuring out _why_ the get has timed out.
				for i := q.offset; i < len(q.entries); i++ {
					bytes, err := json.MarshalIndent(q.entries[i], "", "  ")

					if err != nil {
						panic(err)
					}

					fmt.Printf("--- Queue Entry %d ---\n", i)
					fmt.Println(string(bytes))
				}
			}

			q.T.Fatal("Get timed out!")
		}
		q.cond.Wait()
	}
}
<commit_msg>(from AES) Make queue timeouts more useful.<commit_after>package entrypoint

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"sync"
	"testing"
	"time"
)

// The Queue struct implements a multi-writer/multi-reader concurrent queue where the dequeue
// operation (the Get() method) takes a predicate that allows it to skip past queue entries until it
// finds one that satisfies the specified predicate.
type Queue struct {
	T       *testing.T
	timeout time.Duration
	cond    *sync.Cond
	entries []interface{}
	offset  int
}

// NewQueue constructs a new queue with the supplied timeout.
func NewQueue(t *testing.T, timeout time.Duration) *Queue {
	q := &Queue{
		T:       t,
		timeout: timeout,
		cond:    sync.NewCond(&sync.Mutex{}),
	}
	ctx, cancel := context.WithCancel(context.Background())
	t.Cleanup(cancel)
	// Broadcast on Queue.cond every three seconds so that anyone waiting on the condition has a
	// chance to timeout. (Go doesn't support timed wait on conditions.)
	go func() {
		ticker := time.NewTicker(3 * time.Second)
		for {
			select {
			case <-ticker.C:
				q.cond.Broadcast()
			case <-ctx.Done():
				return
			}
		}
	}()
	return q
}

// Add an entry to the queue.
func (q *Queue) Add(obj interface{}) {
	q.cond.L.Lock()
	defer q.cond.L.Unlock()
	q.entries = append(q.entries, obj)
	q.cond.Broadcast()
}

// Get will return the next entry that satisfies the supplied predicate.
func (q *Queue) Get(predicate func(interface{}) bool) interface{} {
	start := time.Now()
	q.cond.L.Lock()
	defer q.cond.L.Unlock()

	for {
		for idx, obj := range q.entries[q.offset:] {
			if predicate(obj) {
				q.offset += idx + 1
				return obj
			}
		}

		if time.Since(start) > q.timeout {
			msg := &strings.Builder{}
			for idx, entry := range q.entries {
				bytes, err := json.MarshalIndent(entry, "", "  ")
				if err != nil {
					panic(err)
				}
				var extra string
				if idx < q.offset {
					extra = "(Before Offset)"
				} else if idx == q.offset {
					extra = "(Offset Here)"
				} else {
					extra = "(After Offset)"
				}
				msg.WriteString(fmt.Sprintf("\n--- Queue Entry[%d] %s---\n%s\n", idx, extra, string(bytes)))
			}

			q.T.Fatal(fmt.Sprintf("Get timed out!\n%s", msg))
		}
		q.cond.Wait()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/binary"
	"fmt"
	"os"

	flags "github.com/jessevdk/go-flags"
	"github.com/monochromegane/gannoy"
)

type Options struct {
	Dim  int    `short:"d" long:"dim" default:"2" description:"Specify size of feature dimention."`
	Tree int    `short:"t" long:"tree" default:"1" description:"Specify size of index tree."`
	K    int    `short:"K" long:"K" default:"50" description:"Specify max node size in a bucket node."`
	Path string `short:"p" long:"path" default:"." description:"Build meta file into this directory."`
	Maps string `short:"m" long:"map-path" default:"" description:"Specify key and index mapping CSV file, if exist."`
}

var opts Options

func main() {
	parser := flags.NewParser(&opts, flags.Default)
	parser.Usage = "[OPTIONS] SRC_ANNOY_FILE DEST_DATABASE_NAME"
	args, err := parser.Parse()
	if err != nil {
		os.Exit(1)
	}
	if len(args) != 2 {
		fmt.Fprintf(os.Stderr, "source annoy file and destination database name not specified.\n")
		os.Exit(1)
	}

	converter := gannoy.NewConverter(opts.Dim, opts.Tree, opts.K, binary.LittleEndian)
	err = converter.Convert(args[0], opts.Path, args[1], opts.Maps)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}
<commit_msg>Since K came to lead from Dim, I deleted it from the option.<commit_after>package main

import (
	"encoding/binary"
	"fmt"
	"os"

	flags "github.com/jessevdk/go-flags"
	"github.com/monochromegane/gannoy"
)

type Options struct {
	Dim  int    `short:"d" long:"dim" default:"2" description:"Specify size of feature dimention."`
	Tree int    `short:"t" long:"tree" default:"1" description:"Specify size of index tree."`
	Path string `short:"p" long:"path" default:"." description:"Build meta file into this directory."`
	Maps string `short:"m" long:"map-path" default:"" description:"Specify key and index mapping CSV file, if exist."`
}

var opts Options

func main() {
	parser := flags.NewParser(&opts, flags.Default)
	parser.Usage = "[OPTIONS] SRC_ANNOY_FILE DEST_DATABASE_NAME"
	args, err := parser.Parse()
	if err != nil {
		os.Exit(1)
	}
	if len(args) != 2 {
		fmt.Fprintf(os.Stderr, "source annoy file and destination database name not specified.\n")
		os.Exit(1)
	}

	converter := gannoy.NewConverter(opts.Dim, opts.Tree, opts.Dim*2, binary.LittleEndian)
	err = converter.Convert(args[0], opts.Path, args[1], opts.Maps)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package cmd

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

	"github.com/spf13/cobra"

	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
	"k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
	"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
	"k8s.io/kubernetes/pkg/util/initsystem"
)

// NewCmdReset returns the "kubeadm reset" command
func NewCmdReset(out io.Writer) *cobra.Command {
	var skipPreFlight, removeNode bool
	cmd := &cobra.Command{
		Use:   "reset",
		Short: "Run this to revert any changes made to this host by 'kubeadm init' or 'kubeadm join'.",
		Run: func(cmd *cobra.Command, args []string) {
			r, err := NewReset(skipPreFlight, removeNode)
			kubeadmutil.CheckErr(err)
			kubeadmutil.CheckErr(r.Run(out))
		},
	}

	cmd.PersistentFlags().BoolVar(
		&skipPreFlight, "skip-preflight-checks", false,
		"Skip preflight checks normally run before modifying the system",
	)

	cmd.PersistentFlags().BoolVar(
		&removeNode, "remove-node", true,
		"Remove this node from the pool of nodes in this cluster",
	)

	return cmd
}

type Reset struct {
	removeNode bool
}

func NewReset(skipPreFlight, removeNode bool) (*Reset, error) {
	if !skipPreFlight {
		fmt.Println("[preflight] Running pre-flight checks")

		if err := preflight.RunRootCheckOnly(); err != nil {
			return nil, err
		}
	} else {
		fmt.Println("[preflight] Skipping pre-flight checks")
	}

	return &Reset{
		removeNode: removeNode,
	}, nil
}

// Run reverts any changes made to this host by "kubeadm init" or "kubeadm join".
func (r *Reset) Run(out io.Writer) error {

	// Try to drain and remove the node from the cluster
	err := drainAndRemoveNode(r.removeNode)
	if err != nil {
		fmt.Printf("[reset] Failed to cleanup node: [%v]\n", err)
	}

	// Try to stop the kubelet service
	initSystem, err := initsystem.GetInitSystem()
	if err != nil {
		fmt.Println("[reset] WARNING: The kubelet service couldn't be stopped by kubeadm because no supported init system was detected.")
		fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.")
	} else {
		fmt.Println("[reset] Stopping the kubelet service")
		if err := initSystem.ServiceStop("kubelet"); err != nil {
			fmt.Printf("[reset] WARNING: The kubelet service couldn't be stopped by kubeadm: [%v]\n", err)
			fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.")
		}
	}

	// Try to unmount mounted directories under /var/lib/kubelet in order to be able to remove the /var/lib/kubelet directory later
	fmt.Printf("[reset] Unmounting mounted directories in %q\n", "/var/lib/kubelet")
	umountDirsCmd := "cat /proc/mounts | awk '{print $2}' | grep '/var/lib/kubelet' | xargs -r umount"
	umountOutputBytes, err := exec.Command("sh", "-c", umountDirsCmd).Output()
	if err != nil {
		fmt.Printf("[reset] Failed to unmount mounted directories in /var/lib/kubelet: %s\n", string(umountOutputBytes))
	}

	dockerCheck := preflight.ServiceCheck{Service: "docker", CheckIfActive: true}
	if warnings, errors := dockerCheck.Check(); len(warnings) == 0 && len(errors) == 0 {
		fmt.Println("[reset] Removing kubernetes-managed containers")
		if err := exec.Command("sh", "-c", "docker ps | grep 'k8s_' | awk '{print $1}' | xargs -r docker rm --force --volumes").Run(); err != nil {
			fmt.Println("[reset] Failed to stop the running containers")
		}
	} else {
		fmt.Println("[reset] docker doesn't seem to be running, skipping the removal of running kubernetes containers")
	}

	dirsToClean := []string{"/var/lib/kubelet", "/etc/cni/net.d"}

	// Only clear etcd data when the etcd manifest is found. In case it is not found, we must assume that the user
	// provided external etcd endpoints. In that case, it is his own responsibility to reset etcd
	etcdManifestPath := filepath.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests/etcd.json")
	if _, err := os.Stat(etcdManifestPath); err == nil {
		dirsToClean = append(dirsToClean, "/var/lib/etcd")
	} else {
		fmt.Printf("[reset] No etcd manifest found in %q, assuming external etcd.\n", etcdManifestPath)
	}

	// Then clean contents from the stateful kubelet, etcd and cni directories
	fmt.Printf("[reset] Deleting contents of stateful directories: %v\n", dirsToClean)
	for _, dir := range dirsToClean {
		cleanDir(dir)
	}

	// Remove contents from the config and pki directories
	resetConfigDir(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmapi.GlobalEnvParams.HostPKIPath)

	return nil
}

func drainAndRemoveNode(removeNode bool) error {

	hostname, err := os.Hostname()
	if err != nil {
		return fmt.Errorf("failed to detect node hostname")
	}
	hostname = strings.ToLower(hostname)

	// TODO: Use the "native" k8s client for this once we're confident the versioned is working
	kubeConfigPath := filepath.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.KubeletKubeConfigFileName)

	getNodesCmd := fmt.Sprintf("kubectl --kubeconfig %s get nodes | grep %s", kubeConfigPath, hostname)
	output, err := exec.Command("sh", "-c", getNodesCmd).Output()
	if err != nil || len(output) == 0 {
		// kubeadm shouldn't drain and/or remove the node when it doesn't exist anymore
		return nil
	}

	fmt.Printf("[reset] Draining node: %q\n", hostname)

	output, err = exec.Command("kubectl", "--kubeconfig", kubeConfigPath, "drain", hostname, "--delete-local-data", "--force", "--ignore-daemonsets").Output()
	if err != nil {
		return fmt.Errorf("failed to drain node %q [%s]", hostname, output)
	}

	if removeNode {
		fmt.Printf("[reset] Removing node: %q\n", hostname)

		output, err = exec.Command("kubectl", "--kubeconfig", kubeConfigPath, "delete", "node", hostname).Output()
		if err != nil {
			return fmt.Errorf("failed to remove node %q [%s]", hostname, output)
		}
	}

	return nil
}

// cleanDir removes everything in a directory, but not the directory itself
func cleanDir(filePath string) error {
	// If the directory doesn't even exist there's nothing to do, and we do
	// not consider this an error
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return nil
	}

	d, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer d.Close()
	names, err := d.Readdirnames(-1)
	if err != nil {
		return err
	}
	for _, name := range names {
		err = os.RemoveAll(filepath.Join(filePath, name))
		if err != nil {
			return err
		}
	}
	return nil
}

// resetConfigDir is used to cleanup the files kubeadm writes in /etc/kubernetes/.
func resetConfigDir(configPathDir, pkiPathDir string) {
	dirsToClean := []string{
		filepath.Join(configPathDir, "manifests"),
		pkiPathDir,
	}
	fmt.Printf("[reset] Deleting contents of config directories: %v\n", dirsToClean)
	for _, dir := range dirsToClean {
		err := cleanDir(dir)
		if err != nil {
			fmt.Printf("[reset] Failed to remove directory: %q [%v]\n", dir, err)
		}
	}

	filesToClean := []string{
		filepath.Join(configPathDir, kubeconfig.AdminKubeConfigFileName),
		filepath.Join(configPathDir, kubeconfig.KubeletKubeConfigFileName),
		filepath.Join(configPathDir, kubeadmconstants.CACertName),
	}
	fmt.Printf("[reset] Deleting files: %v\n", filesToClean)
	for _, path := range filesToClean {
		err := os.RemoveAll(path)
		if err != nil {
			fmt.Printf("[reset] Failed to remove file: %q [%v]\n", path, err)
		}
	}
}
<commit_msg>kubeadm: fix reset error logging.<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package cmd

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

	"github.com/spf13/cobra"

	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
	kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
	"k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
	"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
	"k8s.io/kubernetes/pkg/util/initsystem"
)

// NewCmdReset returns the "kubeadm reset" command
func NewCmdReset(out io.Writer) *cobra.Command {
	var skipPreFlight, removeNode bool
	cmd := &cobra.Command{
		Use:   "reset",
		Short: "Run this to revert any changes made to this host by 'kubeadm init' or 'kubeadm join'.",
		Run: func(cmd *cobra.Command, args []string) {
			r, err := NewReset(skipPreFlight, removeNode)
			kubeadmutil.CheckErr(err)
			kubeadmutil.CheckErr(r.Run(out))
		},
	}

	cmd.PersistentFlags().BoolVar(
		&skipPreFlight, "skip-preflight-checks", false,
		"Skip preflight checks normally run before modifying the system",
	)

	cmd.PersistentFlags().BoolVar(
		&removeNode, "remove-node", true,
		"Remove this node from the pool of nodes in this cluster",
	)

	return cmd
}

type Reset struct {
	removeNode bool
}

func NewReset(skipPreFlight, removeNode bool) (*Reset, error) {
	if !skipPreFlight {
		fmt.Println("[preflight] Running pre-flight checks")

		if err := preflight.RunRootCheckOnly(); err != nil {
			return nil, err
		}
	} else {
		fmt.Println("[preflight] Skipping pre-flight checks")
	}

	return &Reset{
		removeNode: removeNode,
	}, nil
}

// Run reverts any changes made to this host by "kubeadm init" or "kubeadm join".
func (r *Reset) Run(out io.Writer) error {

	// Try to drain and remove the node from the cluster
	err := drainAndRemoveNode(r.removeNode)
	if err != nil {
		fmt.Printf("[reset] Failed to cleanup node: [%v]\n", err)
	}

	// Try to stop the kubelet service
	initSystem, err := initsystem.GetInitSystem()
	if err != nil {
		fmt.Println("[reset] WARNING: The kubelet service couldn't be stopped by kubeadm because no supported init system was detected.")
		fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.")
	} else {
		fmt.Println("[reset] Stopping the kubelet service")
		if err := initSystem.ServiceStop("kubelet"); err != nil {
			fmt.Printf("[reset] WARNING: The kubelet service couldn't be stopped by kubeadm: [%v]\n", err)
			fmt.Println("[reset] WARNING: Please ensure kubelet is stopped manually.")
		}
	}

	// Try to unmount mounted directories under /var/lib/kubelet in order to be able to remove the /var/lib/kubelet directory later
	fmt.Printf("[reset] Unmounting mounted directories in %q\n", "/var/lib/kubelet")
	umountDirsCmd := "cat /proc/mounts | awk '{print $2}' | grep '/var/lib/kubelet' | xargs -r umount"
	umountOutputBytes, err := exec.Command("sh", "-c", umountDirsCmd).Output()
	if err != nil {
		fmt.Printf("[reset] Failed to unmount mounted directories in /var/lib/kubelet: %s\n", string(umountOutputBytes))
	}

	dockerCheck := preflight.ServiceCheck{Service: "docker", CheckIfActive: true}
	if warnings, errors := dockerCheck.Check(); len(warnings) == 0 && len(errors) == 0 {
		fmt.Println("[reset] Removing kubernetes-managed containers")
		if err := exec.Command("sh", "-c", "docker ps | grep 'k8s_' | awk '{print $1}' | xargs -r docker rm --force --volumes").Run(); err != nil {
			fmt.Println("[reset] Failed to stop the running containers")
		}
	} else {
		fmt.Println("[reset] docker doesn't seem to be running, skipping the removal of running kubernetes containers")
	}

	dirsToClean := []string{"/var/lib/kubelet", "/etc/cni/net.d"}

	// Only clear etcd data when the etcd manifest is found. In case it is not found, we must assume that the user
	// provided external etcd endpoints. In that case, it is his own responsibility to reset etcd
	etcdManifestPath := filepath.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, "manifests/etcd.json")
	if _, err := os.Stat(etcdManifestPath); err == nil {
		dirsToClean = append(dirsToClean, "/var/lib/etcd")
	} else {
		fmt.Printf("[reset] No etcd manifest found in %q, assuming external etcd.\n", etcdManifestPath)
	}

	// Then clean contents from the stateful kubelet, etcd and cni directories
	fmt.Printf("[reset] Deleting contents of stateful directories: %v\n", dirsToClean)
	for _, dir := range dirsToClean {
		cleanDir(dir)
	}

	// Remove contents from the config and pki directories
	resetConfigDir(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeadmapi.GlobalEnvParams.HostPKIPath)

	return nil
}

func drainAndRemoveNode(removeNode bool) error {
	hostname, err := os.Hostname()
	if err != nil {
		return fmt.Errorf("failed to detect node hostname")
	}
	hostname = strings.ToLower(hostname)

	// TODO: Use the "native" k8s client for this once we're confident the versioned is working
	kubeConfigPath := filepath.Join(kubeadmapi.GlobalEnvParams.KubernetesDir, kubeconfig.KubeletKubeConfigFileName)

	getNodesCmd := fmt.Sprintf("kubectl --kubeconfig %s get nodes | grep %s", kubeConfigPath, hostname)
	output, err := exec.Command("sh", "-c", getNodesCmd).Output()
	if err != nil {
		// kubeadm shouldn't drain and/or remove the node when it doesn't exist anymore
		return fmt.Errorf("failed to list nodes: %v", err)
	}
	if len(output) == 0 {
		return fmt.Errorf("list nodes request returned zero entries")
	}

	fmt.Printf("[reset] Draining node: %q\n", hostname)

	_, err = exec.Command("kubectl", "--kubeconfig", kubeConfigPath, "drain", hostname, "--delete-local-data", "--force", "--ignore-daemonsets").Output()
	if err != nil {
		return fmt.Errorf("failed to drain node %q: %v", hostname, err)
	}

	if removeNode {
		fmt.Printf("[reset] Removing node: %q\n", hostname)
		_, err = exec.Command("kubectl", "--kubeconfig", kubeConfigPath, "delete", "node", hostname).Output()
		if err != nil {
			return fmt.Errorf("failed to remove node %q: %v", hostname, err)
		}
	}

	return nil
}

// cleanDir removes everything in a directory, but not the directory itself
func cleanDir(filePath string) error {
	// If the directory doesn't even exist there's nothing to do, and we do
	// not consider this an error
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return nil
	}

	d, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer d.Close()
	names, err := d.Readdirnames(-1)
	if err != nil {
		return err
	}
	for _, name := range names {
		err = os.RemoveAll(filepath.Join(filePath, name))
		if err != nil {
			return err
		}
	}
	return nil
}

// resetConfigDir is used to cleanup the files kubeadm writes in /etc/kubernetes/.
func resetConfigDir(configPathDir, pkiPathDir string) {
	dirsToClean := []string{
		filepath.Join(configPathDir, "manifests"),
		pkiPathDir,
	}
	fmt.Printf("[reset] Deleting contents of config directories: %v\n", dirsToClean)
	for _, dir := range dirsToClean {
		err := cleanDir(dir)
		if err != nil {
			fmt.Printf("[reset] Failed to remove directory: %q [%v]\n", dir, err)
		}
	}

	filesToClean := []string{
		filepath.Join(configPathDir, kubeconfig.AdminKubeConfigFileName),
		filepath.Join(configPathDir, kubeconfig.KubeletKubeConfigFileName),
		filepath.Join(configPathDir, kubeadmconstants.CACertName),
	}
	fmt.Printf("[reset] Deleting files: %v\n", filesToClean)
	for _, path := range filesToClean {
		err := os.RemoveAll(path)
		if err != nil {
			fmt.Printf("[reset] Failed to remove file: %q [%v]\n", path, err)
		}
	}
}
<|endoftext|>