<commit_before>// Copyright 2016 Stratumn SAS. All rights reserved.
// Use of this source code is governed by an Apache License 2.0
// that can be found in the LICENSE file.

// Package bcbatchfossilizer implements a fossilizer that fossilize batches of hashes on a blockchain.
package bcbatchfossilizer

import (
	"fmt"
	"log"

	"github.com/stratumn/go/fossilizer"
	"github.com/stratumn/goprivate/batchfossilizer"
	"github.com/stratumn/goprivate/blockchain"
	"github.com/stratumn/goprivate/types"
)

const (
	// Name is the name set in the fossilizer's information.
	Name = "bcbatch"

	// Description is the description set in the fossilizer's information.
	Description = "Stratumn Blockchain Batch Fossilizer"
)

// Config contains configuration options for the fossilizer.
type Config struct {
	HashTimestamper blockchain.HashTimestamper
}

// Info is the info returned by GetInfo.
type Info struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Version     string `json:"version"`
	Commit      string `json:"commit"`
	Blockchain  string `json:"blockchain"`
}

// Evidence is the evidence sent to the result channel.
type Evidence struct {
	*batchfossilizer.Evidence
	TransactionID blockchain.TransactionID `json:"txid"`
}

// Fossilizer is the type that implements github.com/stratumn/go/fossilizer.Adapter.
type Fossilizer struct {
	*batchfossilizer.Fossilizer
	config      *Config
	resultChans []chan *fossilizer.Result
	resultChan  chan *fossilizer.Result
}

// New creates an instance of a Fossilizer.
func New(config *Config, batchConfig *batchfossilizer.Config) (*Fossilizer, error) {
	b, err := batchfossilizer.New(batchConfig)
	if err != nil {
		return nil, err
	}

	return &Fossilizer{
		Fossilizer: b,
		config:     config,
	}, err
}

// GetInfo implements github.com/stratumn/go/fossilizer.Adapter.GetInfo.
func (a *Fossilizer) GetInfo() (interface{}, error) {
	batchInfo, err := a.Fossilizer.GetInfo()
	if err != nil {
		return nil, err
	}

	info, ok := batchInfo.(*batchfossilizer.Info)
	if !ok {
		return nil, fmt.Errorf("unexpected batchfossilizer info %#v", batchInfo)
	}

	return &Info{
		Name:        Name,
		Description: Description,
		Version:     info.Version,
		Commit:      info.Commit,
		Blockchain:  a.config.HashTimestamper.Network().String(),
	}, nil
}

// AddResultChan implements github.com/stratumn/go/fossilizer.Adapter.AddResultChan.
func (a *Fossilizer) AddResultChan(resultChan chan *fossilizer.Result) {
	a.resultChans = append(a.resultChans, resultChan)
}

// Start starts the fossilizer.
func (a *Fossilizer) Start() error {
	a.resultChan = make(chan *fossilizer.Result)
	a.Fossilizer.AddResultChan(a.resultChan)

	go func() {
		var (
			err               error
			lastRoot          *types.Bytes32
			lastTransactionID blockchain.TransactionID
		)

		for r := range a.resultChan {
			batchEvidenceWrapper, ok := r.Evidence.(*batchfossilizer.EvidenceWrapper)
			if !ok {
				log.Printf("Error: unexpected batchfossilizer evidence %#v\n", batchEvidenceWrapper)
				continue
			}

			root := batchEvidenceWrapper.Evidence.Root

			if lastRoot == nil || *root != *lastRoot {
				lastTransactionID, err = a.config.HashTimestamper.TimestampHash(root)
				if err != nil {
					log.Printf("Error: %s\n", err)
					continue
				}
			}

			evidenceWrapper := map[string]*Evidence{}
			evidenceWrapper[a.config.HashTimestamper.Network().String()] = &Evidence{
				Evidence:      batchEvidenceWrapper.Evidence,
				TransactionID: lastTransactionID,
			}
			r.Evidence = evidenceWrapper

			for _, c := range a.resultChans {
				c <- r
			}

			lastRoot = root
		}
	}()

	return a.Fossilizer.Start()
}

// Stop stops the fossilizer.
func (a *Fossilizer) Stop() error {
	err := a.Fossilizer.Stop()
	close(a.resultChan)
	return err
}
<commit_msg>bcbatchfossilizer: Clean output<commit_after>// Copyright 2016 Stratumn SAS. All rights reserved.
// Use of this source code is governed by an Apache License 2.0
// that can be found in the LICENSE file.

// Package bcbatchfossilizer implements a fossilizer that fossilize batches of hashes on a blockchain.
package bcbatchfossilizer

import (
	"fmt"
	"log"

	"github.com/stratumn/go/fossilizer"
	"github.com/stratumn/goprivate/batchfossilizer"
	"github.com/stratumn/goprivate/blockchain"
	"github.com/stratumn/goprivate/types"
)

const (
	// Name is the name set in the fossilizer's information.
	Name = "bcbatch"

	// Description is the description set in the fossilizer's information.
	Description = "Stratumn Blockchain Batch Fossilizer"
)

// Config contains configuration options for the fossilizer.
type Config struct {
	HashTimestamper blockchain.HashTimestamper
}

// Info is the info returned by GetInfo.
type Info struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Version     string `json:"version"`
	Commit      string `json:"commit"`
	Blockchain  string `json:"blockchain"`
}

// Evidence is the evidence sent to the result channel.
type Evidence struct {
	*batchfossilizer.Evidence
	TransactionID blockchain.TransactionID `json:"txid"`
}

// Fossilizer is the type that implements github.com/stratumn/go/fossilizer.Adapter.
type Fossilizer struct {
	*batchfossilizer.Fossilizer
	config      *Config
	resultChans []chan *fossilizer.Result
	resultChan  chan *fossilizer.Result
}

// New creates an instance of a Fossilizer.
func New(config *Config, batchConfig *batchfossilizer.Config) (*Fossilizer, error) {
	b, err := batchfossilizer.New(batchConfig)
	if err != nil {
		return nil, err
	}

	return &Fossilizer{
		Fossilizer: b,
		config:     config,
	}, err
}

// GetInfo implements github.com/stratumn/go/fossilizer.Adapter.GetInfo.
func (a *Fossilizer) GetInfo() (interface{}, error) {
	batchInfo, err := a.Fossilizer.GetInfo()
	if err != nil {
		return nil, err
	}

	info, ok := batchInfo.(*batchfossilizer.Info)
	if !ok {
		return nil, fmt.Errorf("unexpected batchfossilizer info %#v", batchInfo)
	}

	return &Info{
		Name:        Name,
		Description: Description,
		Version:     info.Version,
		Commit:      info.Commit,
		Blockchain:  a.config.HashTimestamper.Network().String(),
	}, nil
}

// AddResultChan implements github.com/stratumn/go/fossilizer.Adapter.AddResultChan.
func (a *Fossilizer) AddResultChan(resultChan chan *fossilizer.Result) {
	a.resultChans = append(a.resultChans, resultChan)
}

// Start starts the fossilizer.
func (a *Fossilizer) Start() error {
	a.resultChan = make(chan *fossilizer.Result)
	a.Fossilizer.AddResultChan(a.resultChan)

	go func() {
		var (
			err               error
			lastRoot          *types.Bytes32
			lastTransactionID blockchain.TransactionID
		)

		for r := range a.resultChan {
			batchEvidenceWrapper, ok := r.Evidence.(*batchfossilizer.EvidenceWrapper)
			if !ok {
				log.Printf("Error: unexpected batchfossilizer evidence %#v", batchEvidenceWrapper)
				continue
			}

			root := batchEvidenceWrapper.Evidence.Root

			if lastRoot == nil || *root != *lastRoot {
				lastTransactionID, err = a.config.HashTimestamper.TimestampHash(root)
				if err != nil {
					log.Printf("Error: %s", err)
					continue
				}
			}

			evidenceWrapper := map[string]*Evidence{}
			evidenceWrapper[a.config.HashTimestamper.Network().String()] = &Evidence{
				Evidence:      batchEvidenceWrapper.Evidence,
				TransactionID: lastTransactionID,
			}
			r.Evidence = evidenceWrapper

			for _, c := range a.resultChans {
				c <- r
			}

			lastRoot = root
		}
	}()

	return a.Fossilizer.Start()
}

// Stop stops the fossilizer.
func (a *Fossilizer) Stop() error {
	err := a.Fossilizer.Stop()
	close(a.resultChan)
	return err
}
<|endoftext|>


<commit_before>package memdtool

const version = "0.0.0"

var revision = "Devel"
<commit_msg>Checking in changes prior to tagging of version v0.1.0<commit_after>package memdtool

const version = "0.1.0"

var revision = "Devel"
<|endoftext|>


<commit_before>package main

/*
 * Copyright 2016 Albert P. Tobey <atobey@netflix.com>
 *
 * 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.
 */

import (
	"github.com/netflix/hal-9001/brokers/generic"
	"github.com/netflix/hal-9001/hal"
)

// This bot doesn't do anything except set up the generic broker and then
// block forever. The generic broker doesn't produce anything so nothing
// will happen and this is totally useless except to demonstrate the minimum
// amount of hal's API required to start the system.
//
// Most of hal's functionality is optional. It's still built along with the
// rest of hal but is not active unless it's used in main or a plugin.

func main() {
	conf := generic.Config{}
	broker := conf.NewBroker("generic")

	router := hal.Router()
	router.AddBroker(broker)
	router.Route()

	// TODO: maybe add a timer loop to inject some messages and exercise
	// the system a little.
}
<commit_msg>remove the no-long-existing generic broker<commit_after>package main

/*
 * Copyright 2016 Albert P. Tobey <atobey@netflix.com>
 *
 * 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.
 */

import "github.com/netflix/hal-9001/hal"

// This bot doesn't do anything except start the router and wait forever
// for messages that will never come.
//
// Most of hal's functionality is optional. It's still built along with the
// rest of hal but is not active unless it's used in main or a plugin.

func main() {
	router := hal.Router()
	router.Route()
}
<|endoftext|>


<commit_before>// Copyright 2014 James McGuire. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"log"
	"strings"
	"sync"
)

const baseTable = `CREATE TABLE Words (
    Nick VARCHAR(32),
    %s
    primary KEY (Nick)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    `

func updateWords(nick, message string) error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}
	for word, regex := range badWords {
		numwords := len(regex.FindAllString(strings.ToLower(message), -1))
		if numwords == 0 {
			continue
		}
		_, err = tx.Exec(fmt.Sprintf(`INSERT INTO Words (Nick, %[1]s) VALUES (?, ?)`+
			` ON DUPLICATE KEY UPDATE %[1]s=%[1]s+VALUES(%[1]s)`, word), nick, numwords)
		if err != nil {
			return err
		}
	}
	err = tx.Commit()
	if err != nil {
		return err
	}
	return nil
}

func genTables() {
	log.Println("Regenerating Words table")
	wordList := ""
	for _, word := range config.BadWords {
		wordList += word.Word + " INT(32) NOT NULL DEFAULT 0, "
	}

	_, err := db.Exec(`DROP TABLE IF EXISTS Words`)
	if err != nil {
		log.Fatal(err)
	}
	_, err = db.Exec(fmt.Sprintf(baseTable, wordList))
	if err != nil {
		log.Fatal(err)
	}

	genChan := make(chan struct {
		Nick    string
		Message string
	})
	wg := sync.WaitGroup{}
	for i := 0; i < 100; i++ {
		go func() {
			for post := range genChan {
				err := updateWords(post.Nick, post.Message)
				if err != nil {
					log.Fatal(err)
				}
				wg.Done()
			}
		}()
	}

	rows, err := db.Query(`SELECT Nick, Message from messages WHERE channel='#geekhack'`)
	if err != nil {
		log.Fatal(err)
	}
	for rows.Next() {
		var nick, message string
		err := rows.Scan(&nick, &message)
		if err != nil {
			log.Fatal(err)
		}
		wg.Add(1)
		genChan <- struct {
			Nick    string
			Message string
		}{nick, message}
	}

	wg.Wait()
	close(genChan)

	if err = rows.Err(); err != nil {
		log.Fatal(err)
	}
	log.Println("Finished generating Words!")
}
<commit_msg>rename words table<commit_after>// Copyright 2014 James McGuire. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"log"
	"strings"
	"sync"
)

const baseTable = `CREATE TABLE words (
    Nick VARCHAR(32),
    %s
    primary KEY (Nick)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    `

func updateWords(nick, message string) error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}
	for word, regex := range badWords {
		numwords := len(regex.FindAllString(strings.ToLower(message), -1))
		if numwords == 0 {
			continue
		}
		_, err = tx.Exec(fmt.Sprintf(`INSERT INTO words (Nick, %[1]s) VALUES (?, ?)`+
			` ON DUPLICATE KEY UPDATE %[1]s=%[1]s+VALUES(%[1]s)`, word), nick, numwords)
		if err != nil {
			return err
		}
	}
	err = tx.Commit()
	if err != nil {
		return err
	}
	return nil
}

func genTables() {
	log.Println("Regenerating Words table")
	wordList := ""
	for _, word := range config.BadWords {
		wordList += word.Word + " INT(32) NOT NULL DEFAULT 0, "
	}

	_, err := db.Exec(`DROP TABLE IF EXISTS words`)
	if err != nil {
		log.Fatal(err)
	}
	_, err = db.Exec(fmt.Sprintf(baseTable, wordList))
	if err != nil {
		log.Fatal(err)
	}

	genChan := make(chan struct {
		Nick    string
		Message string
	})
	wg := sync.WaitGroup{}
	for i := 0; i < 100; i++ {
		go func() {
			for post := range genChan {
				err := updateWords(post.Nick, post.Message)
				if err != nil {
					log.Fatal(err)
				}
				wg.Done()
			}
		}()
	}

	rows, err := db.Query(`SELECT Nick, Message from messages WHERE channel='#geekhack'`)
	if err != nil {
		log.Fatal(err)
	}
	for rows.Next() {
		var nick, message string
		err := rows.Scan(&nick, &message)
		if err != nil {
			log.Fatal(err)
		}
		wg.Add(1)
		genChan <- struct {
			Nick    string
			Message string
		}{nick, message}
	}

	wg.Wait()
	close(genChan)

	if err = rows.Err(); err != nil {
		log.Fatal(err)
	}
	log.Println("Finished generating Words!")
}
<|endoftext|>


<commit_before>// Copyright 2014-2015 Apptimist, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"crypto/rand"
	"os"
	"strings"
	"syscall"
)

type Ses struct {
	name string
	ASN  *ASN
	srv  *Server
	Keys struct {
		Server struct {
			Ephemeral EncrPub
		}
		Client struct {
			Ephemeral, Login EncrPub
		}
	}

	Lat, Lon, Range int32

	asnsrv bool // true if: asnsrv CONFIG ...
}

var SesPool chan *Ses

func init() { SesPool = make(chan *Ses, 16) }

func NewSes() (ses *Ses) {
	select {
	case ses = <-SesPool:
	default:
		ses = &Ses{}
	}
	ses.ASN = NewASN()
	return
}

func SesPoolFlush() {
	for {
		select {
		case <-SesPool:
		default:
			return
		}
	}
}

func (ses *Ses) DN() string { return ses.srv.cmd.Cfg.Dir }

// dist pdu list to online sessions. Any sessions to other servers receive the
// first link which is the RESPO/SHA. All user sessions receive "asn/mark". Any
// other named blob named REPOS/USER/PATH goes to the associated USER sessions.
func (ses *Ses) dist(pdus []*PDU) {
	ses.srv.ForEachSession(func(x *Ses) {
		if x == ses {
			return
		}
		login := x.Keys.Client.Login
		slogin := login.String()
		server := x.srv.cmd.Cfg.Keys.Server.Pub.Encr
		if login.Equal(server) {
			if pdus[0] != nil {
				x.ASN.Tx(pdus[0])
			}
			return
		}
		for _, pdu := range pdus[1:] {
			if pdu != nil {
				suser, _ := x.srv.repos.ParsePath(pdu.FN)
				if suser != "" &&
					suser == slogin[:len(suser)] {
					x.ASN.Tx(pdu)
					// be sure to send only one per session
					return
				}
			}
		}
	})
	for i := range pdus {
		pdus[i].Free()
		pdus[i] = nil
	}
}

// Free the Ses by pooling or release it to GC if pool is full.
func (ses *Ses) Free() {
	if ses != nil {
		ses.name = ""
		ses.ASN.Free()
		ses.ASN = nil
		ses.srv = nil
		select {
		case SesPool <- ses:
		default:
		}
	}
}

func (ses *Ses) IsAdmin(key *EncrPub) bool {
	return *key == *ses.srv.cmd.Cfg.Keys.Admin.Pub.Encr
}

func (ses *Ses) IsService(key *EncrPub) bool {
	return *key == *ses.srv.cmd.Cfg.Keys.Server.Pub.Encr
}

func (ses *Ses) Rekey(req Requester) {
	var nonce Nonce
	rand.Reader.Read(nonce[:])
	pub, sec, _ := NewRandomEncrKeys()
	ses.Keys.Server.Ephemeral = *pub
	ses.ASN.Ack(req, pub[:], nonce[:])
	ses.ASN.SetStateEstablished()
	ses.ASN.SetBox(NewBox(2, &nonce, &ses.Keys.Client.Ephemeral,
		pub, sec))
	ses.ASN.Println("rekeyed with", pub.String()[:8]+"...")
}

// removals: if pdus[1] is a filename containing "asn/removals",
// remove the referenced files, then the "asn/removals/" link.
// However, keep the SUM file to distribute with clone.
func (ses *Ses) removals(pdus []*PDU) {
	if len(pdus) == 2 && pdus[1] != nil &&
		strings.Contains(pdus[1].FN, "asn/removals") {
		f, err := os.Open(pdus[1].FN)
		if err != nil {
			return
		}
		defer func() {
			f.Close()
			syscall.Unlink(pdus[1].FN)
			pdus[1].Free()
			pdus[1] = nil
		}()
		BlobSeek(f)
		scanner := bufio.NewScanner(f)
		for scanner.Scan() {
			fn := ses.srv.repos.Join(scanner.Text())
			ses.ASN.Diag("unlinked", fn)
			syscall.Unlink(fn)
		}
		scanner = nil
	}
}

func (ses *Ses) RxBlob(pdu *PDU) (err error) {
	blob, err := NewBlobFrom(pdu)
	if err != nil {
		return
	}
	defer func() {
		blob.Free()
		blob = nil
	}()
	sum, fn, err := ses.srv.repos.File(blob, pdu)
	if err != nil {
		return
	}
	links, err := ses.srv.repos.MkLinks(blob, sum, fn)
	if err != nil {
		return
	}
	ses.removals(links)
	ses.dist(links)
	links = nil
	return
}

func (ses *Ses) RxLogin(pdu *PDU) (err error) {
	var (
		req Requester
		sig AuthSig
	)
	req.ReadFrom(pdu)
	_, err = pdu.Read(ses.Keys.Client.Login[:])
	if err == nil {
		_, err = pdu.Read(sig[:])
	}
	if err != nil {
		return
	}
	err = ErrFailure
	switch {
	case ses.Keys.Client.Login.Equal(ses.srv.cmd.Cfg.Keys.Admin.Pub.Encr):
		if sig.Verify(ses.srv.cmd.Cfg.Keys.Admin.Pub.Auth,
			ses.Keys.Client.Login[:]) {
			ses.ASN.Name.Remote = "admin"
			err = nil
		}
	case ses.Keys.Client.Login.Equal(ses.srv.cmd.Cfg.Keys.Server.Pub.Encr):
		if sig.Verify(ses.srv.cmd.Cfg.Keys.Server.Pub.Auth,
			ses.Keys.Client.Login[:]) {
			ses.ASN.Name.Remote = "server"
			err = nil
		}
	default:
		login := ses.Keys.Client.Login
		user := ses.srv.repos.Users.Search(login)
		if user != nil && sig.Verify(&user.ASN.Auth, login[:]) {
			ses.ASN.Name.Remote = login.String()[:8]
			err = nil
		}
	}
	ses.ASN.Name.Session = ses.ASN.Name.Local + ":" + ses.ASN.Name.Remote
	if err != nil {
		ses.ASN.Println("login", err)
	} else {
		ses.Rekey(req)
		ses.ASN.Println("login")
	}
	return
}

func (ses *Ses) RxPause(pdu *PDU) error {
	var req Requester
	req.ReadFrom(pdu)
	ses.ASN.Println("suspending")
	ses.ASN.Ack(req)
	ses.ASN.SetStateSuspended()
	return nil
}

func (ses *Ses) RxQuit(pdu *PDU) error {
	var req Requester
	req.ReadFrom(pdu)
	ses.ASN.Println("quitting")
	ses.ASN.Ack(req)
	ses.ASN.SetStateQuitting()
	return nil
}

func (ses *Ses) RxResume(pdu *PDU) error {
	var req Requester
	req.ReadFrom(pdu)
	ses.ASN.Println("resuming")
	ses.Rekey(req)
	return nil
}

func (ses *Ses) Send(fn string, keys ...*EncrPub) {
	// FIXME
}
<commit_msg>fix login crash<commit_after>// Copyright 2014-2015 Apptimist, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"crypto/rand"
	"os"
	"strings"
	"syscall"
)

type Ses struct {
	name string
	ASN  *ASN
	srv  *Server
	Keys struct {
		Server struct {
			Ephemeral EncrPub
		}
		Client struct {
			Ephemeral, Login EncrPub
		}
	}

	Lat, Lon, Range int32

	asnsrv bool // true if: asnsrv CONFIG ...
}

var SesPool chan *Ses

func init() { SesPool = make(chan *Ses, 16) }

func NewSes() (ses *Ses) {
	select {
	case ses = <-SesPool:
	default:
		ses = &Ses{}
	}
	ses.ASN = NewASN()
	return
}

func SesPoolFlush() {
	for {
		select {
		case <-SesPool:
		default:
			return
		}
	}
}

func (ses *Ses) DN() string { return ses.srv.cmd.Cfg.Dir }

// dist pdu list to online sessions. Any sessions to other servers receive the
// first link which is the RESPO/SHA. All user sessions receive "asn/mark". Any
// other named blob named REPOS/USER/PATH goes to the associated USER sessions.
func (ses *Ses) dist(pdus []*PDU) {
	ses.srv.ForEachSession(func(x *Ses) {
		if x == ses {
			return
		}
		login := x.Keys.Client.Login
		slogin := login.String()
		server := x.srv.cmd.Cfg.Keys.Server.Pub.Encr
		if login.Equal(server) {
			if pdus[0] != nil {
				x.ASN.Tx(pdus[0])
			}
			return
		}
		for _, pdu := range pdus[1:] {
			if pdu != nil {
				suser, _ := x.srv.repos.ParsePath(pdu.FN)
				if suser != "" &&
					suser == slogin[:len(suser)] {
					x.ASN.Tx(pdu)
					// be sure to send only one per session
					return
				}
			}
		}
	})
	for i := range pdus {
		pdus[i].Free()
		pdus[i] = nil
	}
}

// Free the Ses by pooling or release it to GC if pool is full.
func (ses *Ses) Free() {
	if ses != nil {
		ses.name = ""
		ses.ASN.Free()
		ses.ASN = nil
		ses.srv = nil
		select {
		case SesPool <- ses:
		default:
		}
	}
}

func (ses *Ses) IsAdmin(key *EncrPub) bool {
	return *key == *ses.srv.cmd.Cfg.Keys.Admin.Pub.Encr
}

func (ses *Ses) IsService(key *EncrPub) bool {
	return *key == *ses.srv.cmd.Cfg.Keys.Server.Pub.Encr
}

func (ses *Ses) Rekey(req Requester) {
	var nonce Nonce
	rand.Reader.Read(nonce[:])
	pub, sec, _ := NewRandomEncrKeys()
	ses.Keys.Server.Ephemeral = *pub
	ses.ASN.Ack(req, pub[:], nonce[:])
	ses.ASN.SetStateEstablished()
	ses.ASN.SetBox(NewBox(2, &nonce, &ses.Keys.Client.Ephemeral,
		pub, sec))
	ses.ASN.Println("rekeyed with", pub.String()[:8]+"...")
}

// removals: if pdus[1] is a filename containing "asn/removals",
// remove the referenced files, then the "asn/removals/" link.
// However, keep the SUM file to distribute with clone.
func (ses *Ses) removals(pdus []*PDU) {
	if len(pdus) == 2 && pdus[1] != nil &&
		strings.Contains(pdus[1].FN, "asn/removals") {
		f, err := os.Open(pdus[1].FN)
		if err != nil {
			return
		}
		defer func() {
			f.Close()
			syscall.Unlink(pdus[1].FN)
			pdus[1].Free()
			pdus[1] = nil
		}()
		BlobSeek(f)
		scanner := bufio.NewScanner(f)
		for scanner.Scan() {
			fn := ses.srv.repos.Join(scanner.Text())
			ses.ASN.Diag("unlinked", fn)
			syscall.Unlink(fn)
		}
		scanner = nil
	}
}

func (ses *Ses) RxBlob(pdu *PDU) (err error) {
	blob, err := NewBlobFrom(pdu)
	if err != nil {
		return
	}
	defer func() {
		blob.Free()
		blob = nil
	}()
	sum, fn, err := ses.srv.repos.File(blob, pdu)
	if err != nil {
		return
	}
	links, err := ses.srv.repos.MkLinks(blob, sum, fn)
	if err != nil {
		return
	}
	ses.removals(links)
	ses.dist(links)
	links = nil
	return
}

func (ses *Ses) RxLogin(pdu *PDU) (err error) {
	var (
		req Requester
		sig AuthSig
	)
	req.ReadFrom(pdu)
	_, err = pdu.Read(ses.Keys.Client.Login[:])
	if err == nil {
		_, err = pdu.Read(sig[:])
	}
	if err != nil {
		return
	}
	err = ErrFailure
	switch {
	case ses.Keys.Client.Login.Equal(ses.srv.cmd.Cfg.Keys.Admin.Pub.Encr):
		if sig.Verify(ses.srv.cmd.Cfg.Keys.Admin.Pub.Auth,
			ses.Keys.Client.Login[:]) {
			ses.ASN.Name.Remote = "admin"
			err = nil
		}
	case ses.Keys.Client.Login.Equal(ses.srv.cmd.Cfg.Keys.Server.Pub.Encr):
		if sig.Verify(ses.srv.cmd.Cfg.Keys.Server.Pub.Auth,
			ses.Keys.Client.Login[:]) {
			ses.ASN.Name.Remote = "server"
			err = nil
		}
	default:
		login := ses.Keys.Client.Login
		user := ses.srv.repos.Users.Search(&login)
		if user != nil && sig.Verify(&user.ASN.Auth, login[:]) {
			ses.ASN.Name.Remote = login.String()[:8]
			err = nil
		}
	}
	ses.ASN.Name.Session = ses.ASN.Name.Local + ":" + ses.ASN.Name.Remote
	if err != nil {
		ses.ASN.Println("login", err)
	} else {
		ses.Rekey(req)
		ses.ASN.Println("login")
	}
	return
}

func (ses *Ses) RxPause(pdu *PDU) error {
	var req Requester
	req.ReadFrom(pdu)
	ses.ASN.Println("suspending")
	ses.ASN.Ack(req)
	ses.ASN.SetStateSuspended()
	return nil
}

func (ses *Ses) RxQuit(pdu *PDU) error {
	var req Requester
	req.ReadFrom(pdu)
	ses.ASN.Println("quitting")
	ses.ASN.Ack(req)
	ses.ASN.SetStateQuitting()
	return nil
}

func (ses *Ses) RxResume(pdu *PDU) error {
	var req Requester
	req.ReadFrom(pdu)
	ses.ASN.Println("resuming")
	ses.Rekey(req)
	return nil
}

func (ses *Ses) Send(fn string, keys ...*EncrPub) {
	// FIXME
}
<|endoftext|>


<commit_before>// Copyright 2018 The LUCI 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 main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"cloud.google.com/go/bigquery"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/protoc-gen-go/descriptor"
	"golang.org/x/net/context"
	"google.golang.org/api/option"

	"go.chromium.org/luci/auth"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/flag/stringlistflag"
	"go.chromium.org/luci/common/proto/google/descutil"
	"go.chromium.org/luci/hardcoded/chromeinfra"
)

var (
	canceledByUser = errors.BoolTag{
		Key: errors.NewTagKey("operation canceled by user"),
	}
	errCanceledByUser = errors.Reason("operation canceled by user").Tag(canceledByUser).Err()
)

type tableDef struct {
	ProjectID              string
	DataSetID              string
	TableID                string
	FriendlyName           string
	Description            string
	PartitioningDisabled   bool
	PartitioningExpiration time.Duration
	PartitioningField      string
	Schema                 bigquery.Schema
}

func updateFromTableDef(ctx context.Context, force bool, ts tableStore, td tableDef) error {
	tableID := fmt.Sprintf("%s.%s.%s", td.ProjectID, td.DataSetID, td.TableID)
	shouldContinue := func() bool {
		if force {
			return true
		}
		return confirm("Continue")
	}

	md, err := ts.getTableMetadata(ctx, td.DataSetID, td.TableID)
	switch {
	case isNotFound(err): // new table
		fmt.Printf("Table %q does not exist.\n", tableID)
		fmt.Println("It will be created with the following schema:")
		fmt.Println(strings.Repeat("=", 80))
		fmt.Println(schemaString(td.Schema))
		fmt.Println(strings.Repeat("=", 80))
		if !shouldContinue() {
			return errCanceledByUser
		}

		md := &bigquery.TableMetadata{
			Name:        td.FriendlyName,
			Description: td.Description,
			Schema:      td.Schema,
		}
		if !td.PartitioningDisabled {
			md.TimePartitioning = &bigquery.TimePartitioning{
				Expiration: td.PartitioningExpiration,
				Field:      td.PartitioningField,
			}
		}
		err := ts.createTable(ctx, td.DataSetID, td.TableID, md)
		if err != nil {
			return err
		}
		fmt.Println("Table is created.")
		fmt.Println("Please update the documentation in https://chromium.googlesource.com/infra/infra/+/master/doc/bigquery_tables.md or the internal equivalent.")
		return nil

	case err != nil:
		return err

	default: // existing table
		fmt.Printf("Updating table %q\n", tableID)
		if diff := schemaDiff(md.Schema, td.Schema); diff == "" {
			fmt.Println("No changes to schema detected.")
		} else {
			fmt.Println("The following changes to the schema will be made:")
			fmt.Println(strings.Repeat("=", 80))
			fmt.Println(diff)
			fmt.Println(strings.Repeat("=", 80))
			if !shouldContinue() {
				return errCanceledByUser
			}
		}

		update := bigquery.TableMetadataToUpdate{
			Name:        td.FriendlyName,
			Description: td.Description,
			Schema:      td.Schema,
		}
		if err := ts.updateTable(ctx, td.DataSetID, td.TableID, update); err != nil {
			return err
		}
		fmt.Println("Finished updating the table.")
		return nil
	}
}

type flags struct {
	tableDef
	protoDir    string
	messageName string
	force       bool
	importPaths stringlistflag.Flag
}

func parseFlags() (*flags, error) {
	var f flags
	table := flag.String("table", "", `Table name with format "<project id>.<dataset id>.<table id>"`)
	flag.StringVar(&f.FriendlyName, "friendly-name", "", "Friendly name for the table.")
	flag.StringVar(&f.PartitioningField, "partitioning-field", "", "Name of a timestamp field to use for table partitioning (beta).")
	flag.BoolVar(&f.PartitioningDisabled, "disable-partitioning", false, "Makes the table not time-partitioned.")
	flag.DurationVar(&f.PartitioningExpiration, "partition-expiration", 0, "Expiration for partitions. 0 for no expiration.")
	flag.StringVar(&f.protoDir, "message-dir", ".", "path to directory with the .proto file that defines the schema message.")
	flag.BoolVar(&f.force, "force", false, "proceed without a user confirmation.")
	// -I matches protoc's flag and its error message suggesting to pass -I.
	flag.Var(&f.importPaths, "I", "path to directory with the imported .proto file; can be specified multiple times.")

	flag.StringVar(&f.messageName,
		"message",
		"",
		"Full name of the protobuf message that defines the table schema. The name must contain proto package name.")

	flag.Parse()

	switch {
	case len(flag.Args()) > 0:
		return nil, fmt.Errorf("unexpected arguments: %q", flag.Args())
	case *table == "":
		return nil, fmt.Errorf("-table is required")
	case f.messageName == "":
		return nil, fmt.Errorf("-message is required (the name must contain the proto package name)")
	case f.PartitioningField != "" && f.PartitioningDisabled:
		return nil, fmt.Errorf("partitioning field cannot be non-empty with disabled partitioning")
	}
	if parts := strings.Split(*table, "."); len(parts) == 3 {
		f.ProjectID = parts[0]
		f.DataSetID = parts[1]
		f.TableID = parts[2]
	} else {
		return nil, fmt.Errorf("expected exactly 2 dots in table name %q", *table)
	}

	return &f, nil
}

func run(ctx context.Context) error {
	flags, err := parseFlags()
	if err != nil {
		return errors.Annotate(err, "failed to parse flags").Err()
	}

	td := flags.tableDef

	desc, err := loadProtoDescription(flags.protoDir, flags.importPaths)
	if err != nil {
		return errors.Annotate(err, "failed to load proto descriptor").Err()
	}
	td.Schema, td.Description, err = schemaFromMessage(desc, flags.messageName)
	if err != nil {
		return errors.Annotate(err, "could not derive schema from message %q at path %q", flags.messageName, flags.protoDir).Err()
	}
	file, _, _ := descutil.Resolve(desc, flags.messageName)
	td.Description = fmt.Sprintf(
		"Proto: https://cs.chromium.org/%s\nTable Description:\n%s",
		url.PathEscape(fmt.Sprintf("%s file:%s", flags.messageName, file.GetName())),
		td.Description)

	// Create an Authenticator and use it for BigQuery operations.
	authOpts := chromeinfra.DefaultAuthOptions()
	authOpts.Scopes = []string{bigquery.Scope}
	authenticator := auth.NewAuthenticator(ctx, auth.InteractiveLogin, authOpts)

	authTS, err := authenticator.TokenSource()
	if err != nil {
		return errors.Annotate(err, "could not get authentication credentials").Err()
	}

	c, err := bigquery.NewClient(ctx, td.ProjectID, option.WithTokenSource(authTS))
	if err != nil {
		return errors.Annotate(err, "could not create BigQuery client").Err()
	}
	return updateFromTableDef(ctx, flags.force, bqTableStore{c}, td)
}

func main() {
	switch err := run(context.Background()); {
	case canceledByUser.In(err):
		os.Exit(1)
	case err != nil:
		log.Fatal(err)
	}
}

// schemaFromMessage loads a message by name from .proto files in dir
// and converts the message to a bigquery schema.
func schemaFromMessage(desc *descriptor.FileDescriptorSet, messageName string) (schema bigquery.Schema, description string, err error) {
	conv := schemaConverter{
		desc:           desc,
		sourceCodeInfo: make(map[*descriptor.FileDescriptorProto]sourceCodeInfoMap, len(desc.File)),
	}
	for _, f := range desc.File {
		conv.sourceCodeInfo[f], err = descutil.IndexSourceCodeInfo(f)
		if err != nil {
			return nil, "", errors.Annotate(err, "failed to index source code info in file %q", f.GetName()).Err()
		}
	}
	return conv.schema(messageName)
}

func protoImportPaths(dir string, userDefinedImportPaths []string) ([]string, error) {
	// In Go mode, import paths are all $GOPATH/src directories because we like
	// go-style absolute import paths,
	// e.g. "go.chromium.org/luci/logdog/api/logpb/log.proto"
	var goSources []string
	inGopath := false
	for _, p := range goPaths() {
		src := filepath.Join(p, "src")
		switch info, err := os.Stat(src); {
		case os.IsNotExist(err):

		case err != nil:
			return nil, err

		case !info.IsDir():

		default:
			goSources = append(goSources, src)
			// note: does not respect case insensitive file systems (e.g. on windows)
			inGopath = inGopath || strings.HasPrefix(dir, src)
		}
	}

	switch {
	case !inGopath:
		// Python mode.

		// loadProtoDescription passes absolute paths to .proto files,
		// so unless we pass -I with a directory containing them,
		// protoc will complain. Do that for the user.
		return append([]string{dir}, userDefinedImportPaths...), nil

	case len(userDefinedImportPaths) > 0:
		return nil, fmt.Errorf(
			"%q is in $GOPATH. "+
				"Please do not use -I flag. "+
				"Use go-style absolute paths to imported .proto files, "+
				"e.g. github.com/user/repo/path/to/file.proto", dir)
	default:
		return goSources, nil
	}
}

// loadProtoDescription compiles .proto files in the dir
// and returns their descriptor.
func loadProtoDescription(dir string, importPaths []string) (*descriptor.FileDescriptorSet, error) {
	dir, err := filepath.Abs(dir)
	if err != nil {
		return nil, errors.Annotate(err, "could make path %q absolute", dir).Err()
	}

	tempDir, err := ioutil.TempDir("", "")
	if err != nil {
		return nil, err
	}
	defer os.RemoveAll(tempDir)
	descFile := filepath.Join(tempDir, "desc")

	importPaths, err = protoImportPaths(dir, importPaths)
	if err != nil {
		return nil, err
	}

	args := []string{
		"--descriptor_set_out=" + descFile,
		"--include_imports",
		"--include_source_info",
	}
	for _, p := range importPaths {
		args = append(args, "-I="+p)
	}
	protoFiles, err := filepath.Glob(filepath.Join(dir, "*.proto"))
	if err != nil {
		return nil, err
	}
	if len(protoFiles) == 0 {
		return nil, fmt.Errorf("no .proto files found in directory %q", dir)
	}
	args = append(args, protoFiles...)

	protoc := exec.Command("protoc", args...)
	protoc.Stderr = os.Stderr
	if err := protoc.Run(); err != nil {
		return nil, errors.Annotate(err, "protoc run failed").Err()
	}

	descBytes, err := ioutil.ReadFile(descFile)
	if err != nil {
		return nil, err
	}
	var desc descriptor.FileDescriptorSet
	err = proto.Unmarshal(descBytes, &desc)
	return &desc, err
}

func goPaths() []string {
	gopath := strings.TrimSpace(os.Getenv("GOPATH"))
	if gopath == "" {
		return nil
	}
	return strings.Split(gopath, string(filepath.ListSeparator))
}

// confirm asks for a user confirmation for an action, with No as default.
// Only "y" or "Y" responses is treated as yes.
func confirm(action string) (response bool) {
	fmt.Printf("%s? [y/N] ", action)
	var res string
	fmt.Scanln(&res)
	return res == "y" || res == "Y"
}
<commit_msg>[bqschemaupdater] rename -parition-expiration flag<commit_after>// Copyright 2018 The LUCI 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 main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"cloud.google.com/go/bigquery"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/protoc-gen-go/descriptor"
	"golang.org/x/net/context"
	"google.golang.org/api/option"

	"go.chromium.org/luci/auth"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/flag/stringlistflag"
	"go.chromium.org/luci/common/proto/google/descutil"
	"go.chromium.org/luci/hardcoded/chromeinfra"
)

var (
	canceledByUser = errors.BoolTag{
		Key: errors.NewTagKey("operation canceled by user"),
	}
	errCanceledByUser = errors.Reason("operation canceled by user").Tag(canceledByUser).Err()
)

type tableDef struct {
	ProjectID              string
	DataSetID              string
	TableID                string
	FriendlyName           string
	Description            string
	PartitioningDisabled   bool
	PartitioningExpiration time.Duration
	PartitioningField      string
	Schema                 bigquery.Schema
}

func updateFromTableDef(ctx context.Context, force bool, ts tableStore, td tableDef) error {
	tableID := fmt.Sprintf("%s.%s.%s", td.ProjectID, td.DataSetID, td.TableID)
	shouldContinue := func() bool {
		if force {
			return true
		}
		return confirm("Continue")
	}

	md, err := ts.getTableMetadata(ctx, td.DataSetID, td.TableID)
	switch {
	case isNotFound(err): // new table
		fmt.Printf("Table %q does not exist.\n", tableID)
		fmt.Println("It will be created with the following schema:")
		fmt.Println(strings.Repeat("=", 80))
		fmt.Println(schemaString(td.Schema))
		fmt.Println(strings.Repeat("=", 80))
		if !shouldContinue() {
			return errCanceledByUser
		}

		md := &bigquery.TableMetadata{
			Name:        td.FriendlyName,
			Description: td.Description,
			Schema:      td.Schema,
		}
		if !td.PartitioningDisabled {
			md.TimePartitioning = &bigquery.TimePartitioning{
				Expiration: td.PartitioningExpiration,
				Field:      td.PartitioningField,
			}
		}
		err := ts.createTable(ctx, td.DataSetID, td.TableID, md)
		if err != nil {
			return err
		}
		fmt.Println("Table is created.")
		fmt.Println("Please update the documentation in https://chromium.googlesource.com/infra/infra/+/master/doc/bigquery_tables.md or the internal equivalent.")
		return nil

	case err != nil:
		return err

	default: // existing table
		fmt.Printf("Updating table %q\n", tableID)
		if diff := schemaDiff(md.Schema, td.Schema); diff == "" {
			fmt.Println("No changes to schema detected.")
		} else {
			fmt.Println("The following changes to the schema will be made:")
			fmt.Println(strings.Repeat("=", 80))
			fmt.Println(diff)
			fmt.Println(strings.Repeat("=", 80))
			if !shouldContinue() {
				return errCanceledByUser
			}
		}

		update := bigquery.TableMetadataToUpdate{
			Name:        td.FriendlyName,
			Description: td.Description,
			Schema:      td.Schema,
		}
		if err := ts.updateTable(ctx, td.DataSetID, td.TableID, update); err != nil {
			return err
		}
		fmt.Println("Finished updating the table.")
		return nil
	}
}

type flags struct {
	tableDef
	protoDir    string
	messageName string
	force       bool
	importPaths stringlistflag.Flag
}

func parseFlags() (*flags, error) {
	var f flags
	table := flag.String("table", "", `Table name with format "<project id>.<dataset id>.<table id>"`)
	flag.StringVar(&f.FriendlyName, "friendly-name", "", "Friendly name for the table.")
	flag.StringVar(&f.PartitioningField, "partitioning-field", "", "Name of a timestamp field to use for table partitioning (beta).")
	flag.BoolVar(&f.PartitioningDisabled, "disable-partitioning", false, "Makes the table not time-partitioned.")
	flag.DurationVar(&f.PartitioningExpiration, "partitioning-expiration", 0, "Expiration for partitions. 0 for no expiration.")
	flag.StringVar(&f.protoDir, "message-dir", ".", "path to directory with the .proto file that defines the schema message.")
	flag.BoolVar(&f.force, "force", false, "proceed without a user confirmation.")
	// -I matches protoc's flag and its error message suggesting to pass -I.
	flag.Var(&f.importPaths, "I", "path to directory with the imported .proto file; can be specified multiple times.")

	flag.StringVar(&f.messageName,
		"message",
		"",
		"Full name of the protobuf message that defines the table schema. The name must contain proto package name.")

	flag.Parse()

	switch {
	case len(flag.Args()) > 0:
		return nil, fmt.Errorf("unexpected arguments: %q", flag.Args())
	case *table == "":
		return nil, fmt.Errorf("-table is required")
	case f.messageName == "":
		return nil, fmt.Errorf("-message is required (the name must contain the proto package name)")
	case f.PartitioningField != "" && f.PartitioningDisabled:
		return nil, fmt.Errorf("partitioning field cannot be non-empty with disabled partitioning")
	}
	if parts := strings.Split(*table, "."); len(parts) == 3 {
		f.ProjectID = parts[0]
		f.DataSetID = parts[1]
		f.TableID = parts[2]
	} else {
		return nil, fmt.Errorf("expected exactly 2 dots in table name %q", *table)
	}

	return &f, nil
}

func run(ctx context.Context) error {
	flags, err := parseFlags()
	if err != nil {
		return errors.Annotate(err, "failed to parse flags").Err()
	}

	td := flags.tableDef

	desc, err := loadProtoDescription(flags.protoDir, flags.importPaths)
	if err != nil {
		return errors.Annotate(err, "failed to load proto descriptor").Err()
	}
	td.Schema, td.Description, err = schemaFromMessage(desc, flags.messageName)
	if err != nil {
		return errors.Annotate(err, "could not derive schema from message %q at path %q", flags.messageName, flags.protoDir).Err()
	}
	file, _, _ := descutil.Resolve(desc, flags.messageName)
	td.Description = fmt.Sprintf(
		"Proto: https://cs.chromium.org/%s\nTable Description:\n%s",
		url.PathEscape(fmt.Sprintf("%s file:%s", flags.messageName, file.GetName())),
		td.Description)

	// Create an Authenticator and use it for BigQuery operations.
	authOpts := chromeinfra.DefaultAuthOptions()
	authOpts.Scopes = []string{bigquery.Scope}
	authenticator := auth.NewAuthenticator(ctx, auth.InteractiveLogin, authOpts)

	authTS, err := authenticator.TokenSource()
	if err != nil {
		return errors.Annotate(err, "could not get authentication credentials").Err()
	}

	c, err := bigquery.NewClient(ctx, td.ProjectID, option.WithTokenSource(authTS))
	if err != nil {
		return errors.Annotate(err, "could not create BigQuery client").Err()
	}
	return updateFromTableDef(ctx, flags.force, bqTableStore{c}, td)
}

func main() {
	switch err := run(context.Background()); {
	case canceledByUser.In(err):
		os.Exit(1)
	case err != nil:
		log.Fatal(err)
	}
}

// schemaFromMessage loads a message by name from .proto files in dir
// and converts the message to a bigquery schema.
func schemaFromMessage(desc *descriptor.FileDescriptorSet, messageName string) (schema bigquery.Schema, description string, err error) {
	conv := schemaConverter{
		desc:           desc,
		sourceCodeInfo: make(map[*descriptor.FileDescriptorProto]sourceCodeInfoMap, len(desc.File)),
	}
	for _, f := range desc.File {
		conv.sourceCodeInfo[f], err = descutil.IndexSourceCodeInfo(f)
		if err != nil {
			return nil, "", errors.Annotate(err, "failed to index source code info in file %q", f.GetName()).Err()
		}
	}
	return conv.schema(messageName)
}

func protoImportPaths(dir string, userDefinedImportPaths []string) ([]string, error) {
	// In Go mode, import paths are all $GOPATH/src directories because we like
	// go-style absolute import paths,
	// e.g. "go.chromium.org/luci/logdog/api/logpb/log.proto"
	var goSources []string
	inGopath := false
	for _, p := range goPaths() {
		src := filepath.Join(p, "src")
		switch info, err := os.Stat(src); {
		case os.IsNotExist(err):

		case err != nil:
			return nil, err

		case !info.IsDir():

		default:
			goSources = append(goSources, src)
			// note: does not respect case insensitive file systems (e.g. on windows)
			inGopath = inGopath || strings.HasPrefix(dir, src)
		}
	}

	switch {
	case !inGopath:
		// Python mode.

		// loadProtoDescription passes absolute paths to .proto files,
		// so unless we pass -I with a directory containing them,
		// protoc will complain. Do that for the user.
		return append([]string{dir}, userDefinedImportPaths...), nil

	case len(userDefinedImportPaths) > 0:
		return nil, fmt.Errorf(
			"%q is in $GOPATH. "+
				"Please do not use -I flag. "+
				"Use go-style absolute paths to imported .proto files, "+
				"e.g. github.com/user/repo/path/to/file.proto", dir)
	default:
		return goSources, nil
	}
}

// loadProtoDescription compiles .proto files in the dir
// and returns their descriptor.
func loadProtoDescription(dir string, importPaths []string) (*descriptor.FileDescriptorSet, error) {
	dir, err := filepath.Abs(dir)
	if err != nil {
		return nil, errors.Annotate(err, "could make path %q absolute", dir).Err()
	}

	tempDir, err := ioutil.TempDir("", "")
	if err != nil {
		return nil, err
	}
	defer os.RemoveAll(tempDir)
	descFile := filepath.Join(tempDir, "desc")

	importPaths, err = protoImportPaths(dir, importPaths)
	if err != nil {
		return nil, err
	}

	args := []string{
		"--descriptor_set_out=" + descFile,
		"--include_imports",
		"--include_source_info",
	}
	for _, p := range importPaths {
		args = append(args, "-I="+p)
	}
	protoFiles, err := filepath.Glob(filepath.Join(dir, "*.proto"))
	if err != nil {
		return nil, err
	}
	if len(protoFiles) == 0 {
		return nil, fmt.Errorf("no .proto files found in directory %q", dir)
	}
	args = append(args, protoFiles...)

	protoc := exec.Command("protoc", args...)
	protoc.Stderr = os.Stderr
	if err := protoc.Run(); err != nil {
		return nil, errors.Annotate(err, "protoc run failed").Err()
	}

	descBytes, err := ioutil.ReadFile(descFile)
	if err != nil {
		return nil, err
	}
	var desc descriptor.FileDescriptorSet
	err = proto.Unmarshal(descBytes, &desc)
	return &desc, err
}

func goPaths() []string {
	gopath := strings.TrimSpace(os.Getenv("GOPATH"))
	if gopath == "" {
		return nil
	}
	return strings.Split(gopath, string(filepath.ListSeparator))
}

// confirm asks for a user confirmation for an action, with No as default.
// Only "y" or "Y" responses is treated as yes.
func confirm(action string) (response bool) {
	fmt.Printf("%s? [y/N] ", action)
	var res string
	fmt.Scanln(&res)
	return res == "y" || res == "Y"
}
<|endoftext|>