<commit_before>package commands

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"

	"github.com/authelia/authelia/internal/authentication"
	"github.com/authelia/authelia/internal/authorization"
	"github.com/authelia/authelia/internal/configuration/schema"
	"github.com/authelia/authelia/internal/logging"
	"github.com/authelia/authelia/internal/middlewares"
	"github.com/authelia/authelia/internal/notification"
	"github.com/authelia/authelia/internal/oidc"
	"github.com/authelia/authelia/internal/regulation"
	"github.com/authelia/authelia/internal/server"
	"github.com/authelia/authelia/internal/session"
	"github.com/authelia/authelia/internal/storage"
	"github.com/authelia/authelia/internal/utils"
)

// NewRootCmd returns a new Root Cmd.
func NewRootCmd() (cmd *cobra.Command) {
	version := utils.Version()

	cmd = &cobra.Command{
		Use:     "authelia",
		Example: cmdAutheliaExample,
		Short:   fmt.Sprintf("authelia %s", version),
		Long:    fmt.Sprintf(fmtAutheliaLong, version),
		Version: version,
		Args:    cobra.NoArgs,
		PreRun:  newCmdWithConfigPreRun(true, true, true),
		Run:     cmdRootRun,
	}

	cmdWithConfigFlags(cmd)

	cmd.AddCommand(
		newBuildInfoCmd(),
		NewCertificatesCmd(),
		newCompletionCmd(),
		NewHashPasswordCmd(),
		NewRSACmd(),
		newValidateConfigCmd(),
	)

	return cmd
}

func cmdRootRun(_ *cobra.Command, _ []string) {
	logger := logging.Logger()

	logger.Infof("Authelia %s is starting", utils.Version())

	if os.Getenv("ENVIRONMENT") == "dev" {
		logger.Info("===> Authelia is running in development mode. <===")
	}

	if err := logging.InitializeLogger(config.Log, true); err != nil {
		logger.Fatalf("Cannot initialize logger: %v", err)
	}

	providers, warnings, errors := getProviders(config)
	if len(warnings) != 0 {
		for _, err := range warnings {
			logger.Warn(err)
		}
	}

	if len(errors) != 0 {
		for _, err := range errors {
			logger.Error(err)
		}

		logger.Fatalf("Errors occurred provisioning providers.")
	}

	server.Start(*config, providers)
}

func getProviders(config *schema.Configuration) (providers middlewares.Providers, warnings []error, errors []error) {
	autheliaCertPool, warnings, errors := utils.NewX509CertPool(config.CertificatesDirectory)
	if len(warnings) != 0 || len(errors) != 0 {
		return providers, warnings, errors
	}

	var storageProvider storage.Provider

	switch {
	case config.Storage.PostgreSQL != nil:
		storageProvider = storage.NewPostgreSQLProvider(*config.Storage.PostgreSQL)
	case config.Storage.MySQL != nil:
		storageProvider = storage.NewMySQLProvider(*config.Storage.MySQL)
	case config.Storage.Local != nil:
		storageProvider = storage.NewSQLiteProvider(config.Storage.Local.Path)
	default:
		errors = append(errors, fmt.Errorf("unrecognized storage provider"))
	}

	var (
		userProvider authentication.UserProvider
		err          error
	)

	switch {
	case config.AuthenticationBackend.File != nil:
		userProvider = authentication.NewFileUserProvider(config.AuthenticationBackend.File)
	case config.AuthenticationBackend.LDAP != nil:
		userProvider, err = authentication.NewLDAPUserProvider(config.AuthenticationBackend, autheliaCertPool)
		if err != nil {
			errors = append(errors, fmt.Errorf("failed to check LDAP authentication backend: %w", err))
		}
	default:
		errors = append(errors, fmt.Errorf("unrecognized user provider"))
	}

	var notifier notification.Notifier

	switch {
	case config.Notifier.SMTP != nil:
		notifier = notification.NewSMTPNotifier(*config.Notifier.SMTP, autheliaCertPool)
	case config.Notifier.FileSystem != nil:
		notifier = notification.NewFileNotifier(*config.Notifier.FileSystem)
	default:
		errors = append(errors, fmt.Errorf("unrecognized notifier provider"))
	}

	if notifier != nil {
		if _, err := notifier.StartupCheck(); err != nil {
			errors = append(errors, fmt.Errorf("failed to check notification provider: %w", err))
		}
	}

	clock := utils.RealClock{}
	authorizer := authorization.NewAuthorizer(config)
	sessionProvider := session.NewProvider(config.Session, autheliaCertPool)
	regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock)

	oidcProvider, err := oidc.NewOpenIDConnectProvider(config.IdentityProviders.OIDC)
	if err != nil {
		errors = append(errors, err)
	}

	return middlewares.Providers{
		Authorizer:      authorizer,
		UserProvider:    userProvider,
		Regulator:       regulator,
		OpenIDConnect:   oidcProvider,
		StorageProvider: storageProvider,
		Notifier:        notifier,
		SessionProvider: sessionProvider,
	}, warnings, errors
}
<commit_msg>fix(notifier): regression in startup check (#2248)<commit_after>package commands

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"

	"github.com/authelia/authelia/internal/authentication"
	"github.com/authelia/authelia/internal/authorization"
	"github.com/authelia/authelia/internal/configuration/schema"
	"github.com/authelia/authelia/internal/logging"
	"github.com/authelia/authelia/internal/middlewares"
	"github.com/authelia/authelia/internal/notification"
	"github.com/authelia/authelia/internal/oidc"
	"github.com/authelia/authelia/internal/regulation"
	"github.com/authelia/authelia/internal/server"
	"github.com/authelia/authelia/internal/session"
	"github.com/authelia/authelia/internal/storage"
	"github.com/authelia/authelia/internal/utils"
)

// NewRootCmd returns a new Root Cmd.
func NewRootCmd() (cmd *cobra.Command) {
	version := utils.Version()

	cmd = &cobra.Command{
		Use:     "authelia",
		Example: cmdAutheliaExample,
		Short:   fmt.Sprintf("authelia %s", version),
		Long:    fmt.Sprintf(fmtAutheliaLong, version),
		Version: version,
		Args:    cobra.NoArgs,
		PreRun:  newCmdWithConfigPreRun(true, true, true),
		Run:     cmdRootRun,
	}

	cmdWithConfigFlags(cmd)

	cmd.AddCommand(
		newBuildInfoCmd(),
		NewCertificatesCmd(),
		newCompletionCmd(),
		NewHashPasswordCmd(),
		NewRSACmd(),
		newValidateConfigCmd(),
	)

	return cmd
}

func cmdRootRun(_ *cobra.Command, _ []string) {
	logger := logging.Logger()

	logger.Infof("Authelia %s is starting", utils.Version())

	if os.Getenv("ENVIRONMENT") == "dev" {
		logger.Info("===> Authelia is running in development mode. <===")
	}

	if err := logging.InitializeLogger(config.Log, true); err != nil {
		logger.Fatalf("Cannot initialize logger: %v", err)
	}

	providers, warnings, errors := getProviders(config)
	if len(warnings) != 0 {
		for _, err := range warnings {
			logger.Warn(err)
		}
	}

	if len(errors) != 0 {
		for _, err := range errors {
			logger.Error(err)
		}

		logger.Fatalf("Errors occurred provisioning providers.")
	}

	server.Start(*config, providers)
}

func getProviders(config *schema.Configuration) (providers middlewares.Providers, warnings []error, errors []error) {
	autheliaCertPool, warnings, errors := utils.NewX509CertPool(config.CertificatesDirectory)
	if len(warnings) != 0 || len(errors) != 0 {
		return providers, warnings, errors
	}

	var storageProvider storage.Provider

	switch {
	case config.Storage.PostgreSQL != nil:
		storageProvider = storage.NewPostgreSQLProvider(*config.Storage.PostgreSQL)
	case config.Storage.MySQL != nil:
		storageProvider = storage.NewMySQLProvider(*config.Storage.MySQL)
	case config.Storage.Local != nil:
		storageProvider = storage.NewSQLiteProvider(config.Storage.Local.Path)
	default:
		errors = append(errors, fmt.Errorf("unrecognized storage provider"))
	}

	var (
		userProvider authentication.UserProvider
		err          error
	)

	switch {
	case config.AuthenticationBackend.File != nil:
		userProvider = authentication.NewFileUserProvider(config.AuthenticationBackend.File)
	case config.AuthenticationBackend.LDAP != nil:
		userProvider, err = authentication.NewLDAPUserProvider(config.AuthenticationBackend, autheliaCertPool)
		if err != nil {
			errors = append(errors, fmt.Errorf("failed to check LDAP authentication backend: %w", err))
		}
	default:
		errors = append(errors, fmt.Errorf("unrecognized user provider"))
	}

	var notifier notification.Notifier

	switch {
	case config.Notifier.SMTP != nil:
		notifier = notification.NewSMTPNotifier(*config.Notifier.SMTP, autheliaCertPool)
	case config.Notifier.FileSystem != nil:
		notifier = notification.NewFileNotifier(*config.Notifier.FileSystem)
	default:
		errors = append(errors, fmt.Errorf("unrecognized notifier provider"))
	}

	if notifier != nil && !config.Notifier.DisableStartupCheck {
		if _, err := notifier.StartupCheck(); err != nil {
			errors = append(errors, fmt.Errorf("failed to check notification provider: %w", err))
		}
	}

	clock := utils.RealClock{}
	authorizer := authorization.NewAuthorizer(config)
	sessionProvider := session.NewProvider(config.Session, autheliaCertPool)
	regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock)

	oidcProvider, err := oidc.NewOpenIDConnectProvider(config.IdentityProviders.OIDC)
	if err != nil {
		errors = append(errors, err)
	}

	return middlewares.Providers{
		Authorizer:      authorizer,
		UserProvider:    userProvider,
		Regulator:       regulator,
		OpenIDConnect:   oidcProvider,
		StorageProvider: storageProvider,
		Notifier:        notifier,
		SessionProvider: sessionProvider,
	}, warnings, errors
}
<|endoftext|>


<commit_before>// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

// Package events provides event subscription and polling functionality.
package events

import (
	"errors"
	stdsync "sync"
	"time"

	"github.com/syncthing/syncthing/internal/sync"
)

type EventType int

const (
	Ping EventType = 1 << iota
	Starting
	StartupComplete
	DeviceDiscovered
	DeviceConnected
	DeviceDisconnected
	DeviceRejected
	LocalIndexUpdated
	RemoteIndexUpdated
	ItemStarted
	ItemFinished
	StateChanged
	FolderRejected
	ConfigSaved
	DownloadProgress
	FolderSummary
	FolderCompletion

	AllEvents = (1 << iota) - 1
)

func (t EventType) String() string {
	switch t {
	case Ping:
		return "Ping"
	case Starting:
		return "Starting"
	case StartupComplete:
		return "StartupComplete"
	case DeviceDiscovered:
		return "DeviceDiscovered"
	case DeviceConnected:
		return "DeviceConnected"
	case DeviceDisconnected:
		return "DeviceDisconnected"
	case DeviceRejected:
		return "DeviceRejected"
	case LocalIndexUpdated:
		return "LocalIndexUpdated"
	case RemoteIndexUpdated:
		return "RemoteIndexUpdated"
	case ItemStarted:
		return "ItemStarted"
	case ItemFinished:
		return "ItemFinished"
	case StateChanged:
		return "StateChanged"
	case FolderRejected:
		return "FolderRejected"
	case ConfigSaved:
		return "ConfigSaved"
	case DownloadProgress:
		return "DownloadProgress"
	case FolderSummary:
		return "FolderSummary"
	case FolderCompletion:
		return "FolderCompletion"
	default:
		return "Unknown"
	}
}

func (t EventType) MarshalText() ([]byte, error) {
	return []byte(t.String()), nil
}

const BufferSize = 64

type Logger struct {
	subs   map[int]*Subscription
	nextID int
	mutex  sync.Mutex
}

type Event struct {
	ID   int         `json:"id"`
	Time time.Time   `json:"time"`
	Type EventType   `json:"type"`
	Data interface{} `json:"data"`
}

type Subscription struct {
	mask   EventType
	id     int
	events chan Event
	mutex  sync.Mutex
}

var Default = NewLogger()

var (
	ErrTimeout = errors.New("timeout")
	ErrClosed  = errors.New("closed")
)

func NewLogger() *Logger {
	return &Logger{
		subs:  make(map[int]*Subscription),
		mutex: sync.NewMutex(),
	}
}

func (l *Logger) Log(t EventType, data interface{}) {
	l.mutex.Lock()
	if debug {
		dl.Debugln("log", l.nextID, t.String(), data)
	}
	e := Event{
		ID:   l.nextID,
		Time: time.Now(),
		Type: t,
		Data: data,
	}
	l.nextID++
	for _, s := range l.subs {
		if s.mask&t != 0 {
			select {
			case s.events <- e:
			default:
				// if s.events is not ready, drop the event
			}
		}
	}
	l.mutex.Unlock()
}

func (l *Logger) Subscribe(mask EventType) *Subscription {
	l.mutex.Lock()
	if debug {
		dl.Debugln("subscribe", mask)
	}
	s := &Subscription{
		mask:   mask,
		id:     l.nextID,
		events: make(chan Event, BufferSize),
		mutex:  sync.NewMutex(),
	}
	l.nextID++
	l.subs[s.id] = s
	l.mutex.Unlock()
	return s
}

func (l *Logger) Unsubscribe(s *Subscription) {
	l.mutex.Lock()
	if debug {
		dl.Debugln("unsubscribe")
	}
	delete(l.subs, s.id)
	close(s.events)
	l.mutex.Unlock()
}

func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if debug {
		dl.Debugln("poll", timeout)
	}

	to := time.After(timeout)
	select {
	case e, ok := <-s.events:
		if !ok {
			return e, ErrClosed
		}
		return e, nil
	case <-to:
		return Event{}, ErrTimeout
	}
}

func (s *Subscription) C() <-chan Event {
	return s.events
}

type BufferedSubscription struct {
	sub  *Subscription
	buf  []Event
	next int
	cur  int
	mut  sync.Mutex
	cond *stdsync.Cond
}

func NewBufferedSubscription(s *Subscription, size int) *BufferedSubscription {
	bs := &BufferedSubscription{
		sub: s,
		buf: make([]Event, size),
		mut: sync.NewMutex(),
	}
	bs.cond = stdsync.NewCond(bs.mut)
	go bs.pollingLoop()
	return bs
}

func (s *BufferedSubscription) pollingLoop() {
	for {
		ev, err := s.sub.Poll(60 * time.Second)
		if err == ErrTimeout {
			continue
		}
		if err == ErrClosed {
			return
		}
		if err != nil {
			panic("unexpected error: " + err.Error())
		}

		s.mut.Lock()
		s.buf[s.next] = ev
		s.next = (s.next + 1) % len(s.buf)
		s.cur = ev.ID
		s.cond.Broadcast()
		s.mut.Unlock()
	}
}

func (s *BufferedSubscription) Since(id int, into []Event) []Event {
	s.mut.Lock()
	defer s.mut.Unlock()

	for id >= s.cur {
		s.cond.Wait()
	}

	for i := s.next; i < len(s.buf); i++ {
		if s.buf[i].ID > id {
			into = append(into, s.buf[i])
		}
	}
	for i := 0; i < s.next; i++ {
		if s.buf[i].ID > id {
			into = append(into, s.buf[i])
		}
	}

	return into
}
<commit_msg>events.Subscription.Poll does not seem to require locking<commit_after>// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

// Package events provides event subscription and polling functionality.
package events

import (
	"errors"
	stdsync "sync"
	"time"

	"github.com/syncthing/syncthing/internal/sync"
)

type EventType int

const (
	Ping EventType = 1 << iota
	Starting
	StartupComplete
	DeviceDiscovered
	DeviceConnected
	DeviceDisconnected
	DeviceRejected
	LocalIndexUpdated
	RemoteIndexUpdated
	ItemStarted
	ItemFinished
	StateChanged
	FolderRejected
	ConfigSaved
	DownloadProgress
	FolderSummary
	FolderCompletion

	AllEvents = (1 << iota) - 1
)

func (t EventType) String() string {
	switch t {
	case Ping:
		return "Ping"
	case Starting:
		return "Starting"
	case StartupComplete:
		return "StartupComplete"
	case DeviceDiscovered:
		return "DeviceDiscovered"
	case DeviceConnected:
		return "DeviceConnected"
	case DeviceDisconnected:
		return "DeviceDisconnected"
	case DeviceRejected:
		return "DeviceRejected"
	case LocalIndexUpdated:
		return "LocalIndexUpdated"
	case RemoteIndexUpdated:
		return "RemoteIndexUpdated"
	case ItemStarted:
		return "ItemStarted"
	case ItemFinished:
		return "ItemFinished"
	case StateChanged:
		return "StateChanged"
	case FolderRejected:
		return "FolderRejected"
	case ConfigSaved:
		return "ConfigSaved"
	case DownloadProgress:
		return "DownloadProgress"
	case FolderSummary:
		return "FolderSummary"
	case FolderCompletion:
		return "FolderCompletion"
	default:
		return "Unknown"
	}
}

func (t EventType) MarshalText() ([]byte, error) {
	return []byte(t.String()), nil
}

const BufferSize = 64

type Logger struct {
	subs   map[int]*Subscription
	nextID int
	mutex  sync.Mutex
}

type Event struct {
	ID   int         `json:"id"`
	Time time.Time   `json:"time"`
	Type EventType   `json:"type"`
	Data interface{} `json:"data"`
}

type Subscription struct {
	mask   EventType
	id     int
	events chan Event
}

var Default = NewLogger()

var (
	ErrTimeout = errors.New("timeout")
	ErrClosed  = errors.New("closed")
)

func NewLogger() *Logger {
	return &Logger{
		subs:  make(map[int]*Subscription),
		mutex: sync.NewMutex(),
	}
}

func (l *Logger) Log(t EventType, data interface{}) {
	l.mutex.Lock()
	if debug {
		dl.Debugln("log", l.nextID, t.String(), data)
	}
	e := Event{
		ID:   l.nextID,
		Time: time.Now(),
		Type: t,
		Data: data,
	}
	l.nextID++
	for _, s := range l.subs {
		if s.mask&t != 0 {
			select {
			case s.events <- e:
			default:
				// if s.events is not ready, drop the event
			}
		}
	}
	l.mutex.Unlock()
}

func (l *Logger) Subscribe(mask EventType) *Subscription {
	l.mutex.Lock()
	if debug {
		dl.Debugln("subscribe", mask)
	}
	s := &Subscription{
		mask:   mask,
		id:     l.nextID,
		events: make(chan Event, BufferSize),
	}
	l.nextID++
	l.subs[s.id] = s
	l.mutex.Unlock()
	return s
}

func (l *Logger) Unsubscribe(s *Subscription) {
	l.mutex.Lock()
	if debug {
		dl.Debugln("unsubscribe")
	}
	delete(l.subs, s.id)
	close(s.events)
	l.mutex.Unlock()
}

func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
	if debug {
		dl.Debugln("poll", timeout)
	}

	to := time.After(timeout)
	select {
	case e, ok := <-s.events:
		if !ok {
			return e, ErrClosed
		}
		return e, nil
	case <-to:
		return Event{}, ErrTimeout
	}
}

func (s *Subscription) C() <-chan Event {
	return s.events
}

type BufferedSubscription struct {
	sub  *Subscription
	buf  []Event
	next int
	cur  int
	mut  sync.Mutex
	cond *stdsync.Cond
}

func NewBufferedSubscription(s *Subscription, size int) *BufferedSubscription {
	bs := &BufferedSubscription{
		sub: s,
		buf: make([]Event, size),
		mut: sync.NewMutex(),
	}
	bs.cond = stdsync.NewCond(bs.mut)
	go bs.pollingLoop()
	return bs
}

func (s *BufferedSubscription) pollingLoop() {
	for {
		ev, err := s.sub.Poll(60 * time.Second)
		if err == ErrTimeout {
			continue
		}
		if err == ErrClosed {
			return
		}
		if err != nil {
			panic("unexpected error: " + err.Error())
		}

		s.mut.Lock()
		s.buf[s.next] = ev
		s.next = (s.next + 1) % len(s.buf)
		s.cur = ev.ID
		s.cond.Broadcast()
		s.mut.Unlock()
	}
}

func (s *BufferedSubscription) Since(id int, into []Event) []Event {
	s.mut.Lock()
	defer s.mut.Unlock()

	for id >= s.cur {
		s.cond.Wait()
	}

	for i := s.next; i < len(s.buf); i++ {
		if s.buf[i].ID > id {
			into = append(into, s.buf[i])
		}
	}
	for i := 0; i < s.next; i++ {
		if s.buf[i].ID > id {
			into = append(into, s.buf[i])
		}
	}

	return into
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"path"
	"runtime"

	"google.golang.org/api/logging/v2"

	"go.skia.org/infra/go/auth"
	"go.skia.org/infra/go/gce"
	"go.skia.org/infra/go/gce/server"
)

func FuzzerBase(name string) *gce.Instance {
	vm := server.Server20170613(name)
	vm.BootDisk.Type = gce.DISK_TYPE_PERSISTENT_SSD
	vm.DataDisk.SizeGb = 375
	vm.DataDisk.Type = gce.DISK_TYPE_LOCAL_SSD
	vm.Metadata["owner_primary"] = "kjlubick"
	vm.Metadata["owner_secondary"] = "jcgregorio"
	vm.Scopes = append(vm.Scopes, auth.SCOPE_READ_WRITE, logging.LoggingWriteScope)

	_, filename, _, _ := runtime.Caller(0)
	dir := path.Dir(filename)
	vm.SetupScript = path.Join(dir, "setup-script.sh")
	vm.StartupScript = path.Join(dir, "startup-script.sh")
	return vm
}

func FrontEnd() *gce.Instance {
	vm := FuzzerBase("skia-fuzzer-fe")
	vm.ExternalIpAddress = "104.154.112.170"
	vm.MachineType = gce.MACHINE_TYPE_STANDARD_8
	return vm
}

func BackEnd(num int) *gce.Instance {
	vm := FuzzerBase(fmt.Sprintf("skia-fuzzer-be-%d", num))
	vm.MachineType = gce.MACHINE_TYPE_STANDARD_32
	return vm
}

func main() {
	server.Main(gce.ZONE_DEFAULT, map[string]*gce.Instance{
		"fe":   FrontEnd(),
		"be-1": BackEnd(1),
		"be-2": BackEnd(2),
		"be-3": BackEnd(3),
	})
}
<commit_msg>Make fuzzer use ephemeral IP<commit_after>package main

import (
	"fmt"
	"path"
	"runtime"

	"google.golang.org/api/logging/v2"

	"go.skia.org/infra/go/auth"
	"go.skia.org/infra/go/gce"
	"go.skia.org/infra/go/gce/server"
)

func FuzzerBase(name string) *gce.Instance {
	vm := server.Server20170613(name)
	vm.BootDisk.Type = gce.DISK_TYPE_PERSISTENT_SSD
	vm.DataDisk.SizeGb = 375
	vm.DataDisk.Type = gce.DISK_TYPE_LOCAL_SSD
	vm.Metadata["owner_primary"] = "kjlubick"
	vm.Metadata["owner_secondary"] = "jcgregorio"
	vm.Scopes = append(vm.Scopes, auth.SCOPE_READ_WRITE, logging.LoggingWriteScope)

	_, filename, _, _ := runtime.Caller(0)
	dir := path.Dir(filename)
	vm.SetupScript = path.Join(dir, "setup-script.sh")
	vm.StartupScript = path.Join(dir, "startup-script.sh")
	return vm
}

func FrontEnd() *gce.Instance {
	vm := FuzzerBase("skia-fuzzer-fe")
	vm.MachineType = gce.MACHINE_TYPE_STANDARD_8
	return vm
}

func BackEnd(num int) *gce.Instance {
	vm := FuzzerBase(fmt.Sprintf("skia-fuzzer-be-%d", num))
	vm.MachineType = gce.MACHINE_TYPE_STANDARD_32
	return vm
}

func main() {
	server.Main(gce.ZONE_DEFAULT, map[string]*gce.Instance{
		"fe":   FrontEnd(),
		"be-1": BackEnd(1),
		"be-2": BackEnd(2),
		"be-3": BackEnd(3),
	})
}
<|endoftext|>


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

// Package fetch provides a way to fetch modules from a proxy.
package fetch

import (
	"context"
	"errors"
	"fmt"
	"io/fs"
	"path"
	"runtime/debug"
	"strings"

	"go.opencensus.io/trace"
	"golang.org/x/mod/module"
	"golang.org/x/pkgsite/internal"
	"golang.org/x/pkgsite/internal/derrors"
	"golang.org/x/pkgsite/internal/godoc"
	"golang.org/x/pkgsite/internal/licenses"
	"golang.org/x/pkgsite/internal/log"
	"golang.org/x/pkgsite/internal/source"
)

// A goPackage is a group of one or more Go source files with the same
// package header. Packages are part of a module.
type goPackage struct {
	path              string
	name              string
	imports           []string
	isRedistributable bool
	licenseMeta       []*licenses.Metadata // metadata of applicable licenses
	// v1path is the package path of a package with major version 1 in a given
	// series.
	v1path string
	docs   []*internal.Documentation // doc for different build contexts
	err    error                     // non-fatal error when loading the package (e.g. documentation is too large)
}

// extractPackages returns a slice of packages from a filesystem arranged like a
// module zip.
// It matches against the given licenses to determine the subset of licenses
// that applies to each package.
// The second return value says whether any packages are "incomplete," meaning
// that they contained .go files but couldn't be processed due to current
// limitations of this site. The limitations are:
// * a maximum file size (MaxFileSize)
// * the particular set of build contexts we consider (goEnvs)
// * whether the import path is valid.
func extractPackages(ctx context.Context, modulePath, resolvedVersion string, contentDir fs.FS, d *licenses.Detector, sourceInfo *source.Info) (_ []*goPackage, _ []*internal.PackageVersionState, err error) {
	defer derrors.Wrap(&err, "extractPackages(ctx, %q, %q, r, d)", modulePath, resolvedVersion)
	ctx, span := trace.StartSpan(ctx, "fetch.extractPackages")
	defer span.End()
	defer func() {
		if e := recover(); e != nil {
			// The package processing code performs some sanity checks along the way.
			// None of the panics should occur, but if they do, we want to log them and
			// be able to find them. So, convert internal panics to internal errors here.
			err = fmt.Errorf("internal panic: %v\n\n%s", e, debug.Stack())
		}
	}()

	// The high-level approach is to split the processing of the zip file
	// into two phases:
	//
	// 	1. loop over all files, looking at file metadata only
	// 	2. process all files by reading their contents
	//
	// During phase 1, we populate the dirs map for each directory
	// that contains at least one .go file.

	var (
		// dirs is the set of directories with at least one .go file,
		// to be populated during phase 1 and used during phase 2.
		//
		// The map key is the directory path, with the modulePrefix trimmed.
		// The map value is a slice of all .go file paths, and no other files.
		dirs = make(map[string][]string)

		// modInfo contains all the module information a package in the module
		// needs to render its documentation, to be populated during phase 1
		// and used during phase 2.
		modInfo = &godoc.ModuleInfo{
			ModulePath:      modulePath,
			ResolvedVersion: resolvedVersion,
			ModulePackages:  make(map[string]bool),
		}

		// incompleteDirs tracks directories for which we have incomplete
		// information, due to a problem processing one of the go files contained
		// therein. We use this so that a single unprocessable package does not
		// prevent processing of other packages in the module.
		incompleteDirs       = make(map[string]bool)
		packageVersionStates = []*internal.PackageVersionState{}
	)

	// Phase 1.
	// Loop over zip files preemptively and check for problems
	// that can be detected by looking at metadata alone.
	// We'll be looking at file contents starting with phase 2 only,
	// only after we're sure this phase passed without errors.
	err = fs.WalkDir(contentDir, ".", func(pathname string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			// Skip directories.
			return nil
		}
		innerPath := path.Dir(pathname)
		if incompleteDirs[innerPath] {
			// We already know this directory cannot be processed, so skip.
			return nil
		}
		importPath := path.Join(modulePath, innerPath)
		if ignoredByGoTool(importPath) || isVendored(importPath) {
			// File is in a directory we're not looking to process at this time, so skip it.
			return nil
		}
		if !strings.HasSuffix(pathname, ".go") {
			// We care about .go files only.
			return nil
		}
		// It's possible to have a Go package in a directory that does not result in a valid import path.
		// That package cannot be imported, but that may be fine if it's a main package, intended to built
		// and run from that directory.
		// Example:  https://github.com/postmannen/go-learning/blob/master/concurrency/01-sending%20numbers%20and%20receving%20numbers%20from%20a%20channel/main.go
		// We're not set up to handle invalid import paths, so skip these packages.
		if err := module.CheckImportPath(importPath); err != nil {
			incompleteDirs[innerPath] = true
			packageVersionStates = append(packageVersionStates, &internal.PackageVersionState{
				ModulePath:  modulePath,
				PackagePath: importPath,
				Version:     resolvedVersion,
				Status:      derrors.ToStatus(derrors.PackageBadImportPath),
				Error:       err.Error(),
			})
			return nil
		}
		info, err := d.Info()
		if err != nil {
			return err
		}
		if info.Size() > MaxFileSize {
			incompleteDirs[innerPath] = true
			status := derrors.ToStatus(derrors.PackageMaxFileSizeLimitExceeded)
			err := fmt.Sprintf("Unable to process %s: file size %d exceeds max limit %d",
				pathname, info.Size(), MaxFileSize)
			packageVersionStates = append(packageVersionStates, &internal.PackageVersionState{
				ModulePath:  modulePath,
				PackagePath: importPath,
				Version:     resolvedVersion,
				Status:      status,
				Error:       err,
			})
			return nil
		}
		dirs[innerPath] = append(dirs[innerPath], pathname)
		if len(dirs) > maxPackagesPerModule {
			return fmt.Errorf("%d packages found in %q; exceeds limit %d for maxPackagePerModule", len(dirs), modulePath, maxPackagesPerModule)
		}
		return nil
	})
	if errors.Is(err, fs.ErrNotExist) {
		return nil, nil, fmt.Errorf("no files: %w", ErrModuleContainsNoPackages)
	}
	if err != nil {
		return nil, nil, err
	}

	for pkgName := range dirs {
		modInfo.ModulePackages[path.Join(modulePath, pkgName)] = true
	}

	// Phase 2.
	// If we got this far, the file metadata was okay.
	// Start reading the file contents now to extract information
	// about Go packages.
	var pkgs []*goPackage
	for innerPath, goFiles := range dirs {
		if incompleteDirs[innerPath] {
			// Something went wrong when processing this directory, so we skip.
			log.Infof(ctx, "Skipping %q because it is incomplete", innerPath)
			continue
		}

		var (
			status error
			errMsg string
		)
		pkg, err := loadPackage(ctx, contentDir, goFiles, innerPath, sourceInfo, modInfo)
		if bpe := (*BadPackageError)(nil); errors.As(err, &bpe) {
			log.Infof(ctx, "Error loading %s: %v", innerPath, err)
			incompleteDirs[innerPath] = true
			status = derrors.PackageInvalidContents
			errMsg = err.Error()
		} else if err != nil {
			return nil, nil, fmt.Errorf("unexpected error loading package: %v", err)
		}
		var pkgPath string
		if pkg == nil {
			// No package.
			if len(goFiles) > 0 {
				// There were go files, but no build contexts matched them.
				incompleteDirs[innerPath] = true
				status = derrors.PackageBuildContextNotSupported
			}
			pkgPath = path.Join(modulePath, innerPath)
		} else {
			if errors.Is(pkg.err, godoc.ErrTooLarge) {
				status = derrors.PackageDocumentationHTMLTooLarge
				errMsg = pkg.err.Error()
			} else if pkg.err != nil {
				// ErrTooLarge is the only valid value of pkg.err.
				return nil, nil, fmt.Errorf("bad package error for %s: %v", pkg.path, pkg.err)
			}
			if d != nil { //  should only be nil for tests
				isRedist, lics := d.PackageInfo(innerPath)
				pkg.isRedistributable = isRedist
				for _, l := range lics {
					pkg.licenseMeta = append(pkg.licenseMeta, l.Metadata)
				}
			}
			pkgs = append(pkgs, pkg)
			pkgPath = pkg.path
		}
		packageVersionStates = append(packageVersionStates, &internal.PackageVersionState{
			ModulePath:  modulePath,
			PackagePath: pkgPath,
			Version:     resolvedVersion,
			Status:      derrors.ToStatus(status),
			Error:       errMsg,
		})
	}
	if len(pkgs) == 0 {
		return nil, packageVersionStates, ErrModuleContainsNoPackages
	}
	return pkgs, packageVersionStates, nil
}

// ignoredByGoTool reports whether the given import path corresponds
// to a directory that would be ignored by the go tool.
//
// The logic of the go tool for ignoring directories is documented at
// https://golang.org/cmd/go/#hdr-Package_lists_and_patterns:
//
//	Directory and file names that begin with "." or "_" are ignored
//	by the go tool, as are directories named "testdata".
//
// However, even though `go list` and other commands that take package
// wildcards will ignore these, they can still be imported and used in
// working Go programs. We continue to ignore the "." and "testdata"
// cases, but we've seen valid Go packages with "_", so we accept those.
func ignoredByGoTool(importPath string) bool {
	for _, el := range strings.Split(importPath, "/") {
		if strings.HasPrefix(el, ".") || el == "testdata" {
			return true
		}
	}
	return false
}

// isVendored reports whether the given import path corresponds
// to a Go package that is inside a vendor directory.
//
// The logic for what is considered a vendor directory is documented at
// https://golang.org/cmd/go/#hdr-Vendor_Directories.
func isVendored(importPath string) bool {
	return strings.HasPrefix(importPath, "vendor/") ||
		strings.Contains(importPath, "/vendor/")
}
<commit_msg>internal/fetch: make too many packages a ModuleTooLarge error<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package fetch provides a way to fetch modules from a proxy.
package fetch

import (
	"context"
	"errors"
	"fmt"
	"io/fs"
	"path"
	"runtime/debug"
	"strings"

	"go.opencensus.io/trace"
	"golang.org/x/mod/module"
	"golang.org/x/pkgsite/internal"
	"golang.org/x/pkgsite/internal/derrors"
	"golang.org/x/pkgsite/internal/godoc"
	"golang.org/x/pkgsite/internal/licenses"
	"golang.org/x/pkgsite/internal/log"
	"golang.org/x/pkgsite/internal/source"
)

// A goPackage is a group of one or more Go source files with the same
// package header. Packages are part of a module.
type goPackage struct {
	path              string
	name              string
	imports           []string
	isRedistributable bool
	licenseMeta       []*licenses.Metadata // metadata of applicable licenses
	// v1path is the package path of a package with major version 1 in a given
	// series.
	v1path string
	docs   []*internal.Documentation // doc for different build contexts
	err    error                     // non-fatal error when loading the package (e.g. documentation is too large)
}

// extractPackages returns a slice of packages from a filesystem arranged like a
// module zip.
// It matches against the given licenses to determine the subset of licenses
// that applies to each package.
// The second return value says whether any packages are "incomplete," meaning
// that they contained .go files but couldn't be processed due to current
// limitations of this site. The limitations are:
// * a maximum file size (MaxFileSize)
// * the particular set of build contexts we consider (goEnvs)
// * whether the import path is valid.
func extractPackages(ctx context.Context, modulePath, resolvedVersion string, contentDir fs.FS, d *licenses.Detector, sourceInfo *source.Info) (_ []*goPackage, _ []*internal.PackageVersionState, err error) {
	defer derrors.Wrap(&err, "extractPackages(ctx, %q, %q, r, d)", modulePath, resolvedVersion)
	ctx, span := trace.StartSpan(ctx, "fetch.extractPackages")
	defer span.End()
	defer func() {
		if e := recover(); e != nil {
			// The package processing code performs some sanity checks along the way.
			// None of the panics should occur, but if they do, we want to log them and
			// be able to find them. So, convert internal panics to internal errors here.
			err = fmt.Errorf("internal panic: %v\n\n%s", e, debug.Stack())
		}
	}()

	// The high-level approach is to split the processing of the zip file
	// into two phases:
	//
	// 	1. loop over all files, looking at file metadata only
	// 	2. process all files by reading their contents
	//
	// During phase 1, we populate the dirs map for each directory
	// that contains at least one .go file.

	var (
		// dirs is the set of directories with at least one .go file,
		// to be populated during phase 1 and used during phase 2.
		//
		// The map key is the directory path, with the modulePrefix trimmed.
		// The map value is a slice of all .go file paths, and no other files.
		dirs = make(map[string][]string)

		// modInfo contains all the module information a package in the module
		// needs to render its documentation, to be populated during phase 1
		// and used during phase 2.
		modInfo = &godoc.ModuleInfo{
			ModulePath:      modulePath,
			ResolvedVersion: resolvedVersion,
			ModulePackages:  make(map[string]bool),
		}

		// incompleteDirs tracks directories for which we have incomplete
		// information, due to a problem processing one of the go files contained
		// therein. We use this so that a single unprocessable package does not
		// prevent processing of other packages in the module.
		incompleteDirs       = make(map[string]bool)
		packageVersionStates = []*internal.PackageVersionState{}
	)

	// Phase 1.
	// Loop over zip files preemptively and check for problems
	// that can be detected by looking at metadata alone.
	// We'll be looking at file contents starting with phase 2 only,
	// only after we're sure this phase passed without errors.
	err = fs.WalkDir(contentDir, ".", func(pathname string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			// Skip directories.
			return nil
		}
		innerPath := path.Dir(pathname)
		if incompleteDirs[innerPath] {
			// We already know this directory cannot be processed, so skip.
			return nil
		}
		importPath := path.Join(modulePath, innerPath)
		if ignoredByGoTool(importPath) || isVendored(importPath) {
			// File is in a directory we're not looking to process at this time, so skip it.
			return nil
		}
		if !strings.HasSuffix(pathname, ".go") {
			// We care about .go files only.
			return nil
		}
		// It's possible to have a Go package in a directory that does not result in a valid import path.
		// That package cannot be imported, but that may be fine if it's a main package, intended to built
		// and run from that directory.
		// Example:  https://github.com/postmannen/go-learning/blob/master/concurrency/01-sending%20numbers%20and%20receving%20numbers%20from%20a%20channel/main.go
		// We're not set up to handle invalid import paths, so skip these packages.
		if err := module.CheckImportPath(importPath); err != nil {
			incompleteDirs[innerPath] = true
			packageVersionStates = append(packageVersionStates, &internal.PackageVersionState{
				ModulePath:  modulePath,
				PackagePath: importPath,
				Version:     resolvedVersion,
				Status:      derrors.ToStatus(derrors.PackageBadImportPath),
				Error:       err.Error(),
			})
			return nil
		}
		info, err := d.Info()
		if err != nil {
			return err
		}
		if info.Size() > MaxFileSize {
			incompleteDirs[innerPath] = true
			status := derrors.ToStatus(derrors.PackageMaxFileSizeLimitExceeded)
			err := fmt.Sprintf("Unable to process %s: file size %d exceeds max limit %d",
				pathname, info.Size(), MaxFileSize)
			packageVersionStates = append(packageVersionStates, &internal.PackageVersionState{
				ModulePath:  modulePath,
				PackagePath: importPath,
				Version:     resolvedVersion,
				Status:      status,
				Error:       err,
			})
			return nil
		}
		dirs[innerPath] = append(dirs[innerPath], pathname)
		if len(dirs) > maxPackagesPerModule {
			return fmt.Errorf("%d packages found in %q; exceeds limit %d for maxPackagePerModule: %w",
				len(dirs), modulePath, maxPackagesPerModule, derrors.ModuleTooLarge)
		}
		return nil
	})
	if errors.Is(err, fs.ErrNotExist) {
		return nil, nil, fmt.Errorf("no files: %w", ErrModuleContainsNoPackages)
	}
	if err != nil {
		return nil, nil, err
	}

	for pkgName := range dirs {
		modInfo.ModulePackages[path.Join(modulePath, pkgName)] = true
	}

	// Phase 2.
	// If we got this far, the file metadata was okay.
	// Start reading the file contents now to extract information
	// about Go packages.
	var pkgs []*goPackage
	for innerPath, goFiles := range dirs {
		if incompleteDirs[innerPath] {
			// Something went wrong when processing this directory, so we skip.
			log.Infof(ctx, "Skipping %q because it is incomplete", innerPath)
			continue
		}

		var (
			status error
			errMsg string
		)
		pkg, err := loadPackage(ctx, contentDir, goFiles, innerPath, sourceInfo, modInfo)
		if bpe := (*BadPackageError)(nil); errors.As(err, &bpe) {
			log.Infof(ctx, "Error loading %s: %v", innerPath, err)
			incompleteDirs[innerPath] = true
			status = derrors.PackageInvalidContents
			errMsg = err.Error()
		} else if err != nil {
			return nil, nil, fmt.Errorf("unexpected error loading package: %v", err)
		}
		var pkgPath string
		if pkg == nil {
			// No package.
			if len(goFiles) > 0 {
				// There were go files, but no build contexts matched them.
				incompleteDirs[innerPath] = true
				status = derrors.PackageBuildContextNotSupported
			}
			pkgPath = path.Join(modulePath, innerPath)
		} else {
			if errors.Is(pkg.err, godoc.ErrTooLarge) {
				status = derrors.PackageDocumentationHTMLTooLarge
				errMsg = pkg.err.Error()
			} else if pkg.err != nil {
				// ErrTooLarge is the only valid value of pkg.err.
				return nil, nil, fmt.Errorf("bad package error for %s: %v", pkg.path, pkg.err)
			}
			if d != nil { //  should only be nil for tests
				isRedist, lics := d.PackageInfo(innerPath)
				pkg.isRedistributable = isRedist
				for _, l := range lics {
					pkg.licenseMeta = append(pkg.licenseMeta, l.Metadata)
				}
			}
			pkgs = append(pkgs, pkg)
			pkgPath = pkg.path
		}
		packageVersionStates = append(packageVersionStates, &internal.PackageVersionState{
			ModulePath:  modulePath,
			PackagePath: pkgPath,
			Version:     resolvedVersion,
			Status:      derrors.ToStatus(status),
			Error:       errMsg,
		})
	}
	if len(pkgs) == 0 {
		return nil, packageVersionStates, ErrModuleContainsNoPackages
	}
	return pkgs, packageVersionStates, nil
}

// ignoredByGoTool reports whether the given import path corresponds
// to a directory that would be ignored by the go tool.
//
// The logic of the go tool for ignoring directories is documented at
// https://golang.org/cmd/go/#hdr-Package_lists_and_patterns:
//
//	Directory and file names that begin with "." or "_" are ignored
//	by the go tool, as are directories named "testdata".
//
// However, even though `go list` and other commands that take package
// wildcards will ignore these, they can still be imported and used in
// working Go programs. We continue to ignore the "." and "testdata"
// cases, but we've seen valid Go packages with "_", so we accept those.
func ignoredByGoTool(importPath string) bool {
	for _, el := range strings.Split(importPath, "/") {
		if strings.HasPrefix(el, ".") || el == "testdata" {
			return true
		}
	}
	return false
}

// isVendored reports whether the given import path corresponds
// to a Go package that is inside a vendor directory.
//
// The logic for what is considered a vendor directory is documented at
// https://golang.org/cmd/go/#hdr-Vendor_Directories.
func isVendored(importPath string) bool {
	return strings.HasPrefix(importPath, "vendor/") ||
		strings.Contains(importPath, "/vendor/")
}
<|endoftext|>


<commit_before>// inomap translates (Dev, Flags, Ino) tuples to unique uint64
// inode numbers.
//
// Format of the returned inode numbers:
//
//   [spill bit = 0][15 bit namespace id][48 bit passthru inode number]
//   [spill bit = 1][63 bit spill inode number                        ]
//
// Each (Dev, Flags) tuple gets a namespace id assigned. The original inode
// number is then passed through in the lower 48 bits.
//
// If namespace ids are exhaused, or the original id is larger than 48 bits,
// the whole (Dev, Flags, Ino) tuple gets mapped in the spill map, and the
// spill bit is set to 1.
package inomap

import (
	"fmt"
	"log"
	"sync"
	"syscall"
)

const (
	// max value of 15 bit namespace id
	maxNamespaceId = 1<<15 - 1
	// max value of 48 bit passthru inode number
	maxPassthruIno = 1<<48 - 1
	// max value of 63 bit spill inode number
	maxSpillIno = 1<<63 - 1
)

// InoMap stores the maps using for inode number translation.
// See package comment for details.
type InoMap struct {
	sync.Mutex
	// namespaces keeps the mapping of (Dev,Flags) tuples to
	// 15-bit identifiers (stored in an uint16 with the high bit always zero)
	namespaceMap map[namespaceData]uint16
	// spillNext is the next free namespace number in the namespaces map
	namespaceNext uint16
	// spill is used once the namespaces map is full
	spillMap map[QIno]uint64
	// spillNext is the next free inode number in the spill map
	spillNext uint64
}

// New returns a new InoMap.
func New() *InoMap {
	return &InoMap{
		namespaceMap:  make(map[namespaceData]uint16),
		namespaceNext: 0,
		spillMap:      make(map[QIno]uint64),
		spillNext:     0,
	}
}

func (m *InoMap) spill(in QIno) (out uint64) {
	out, found := m.spillMap[in]
	if found {
		return out
	}
	if m.spillNext >= maxSpillIno {
		log.Panicf("spillMap overflow: spillNext = 0x%x", m.spillNext)
	}
	out = m.spillNext
	m.spillNext++
	m.spillMap[in] = out
	return 1<<63 | out
}

// Translate maps the passed-in (device, inode) pair to a unique inode number.
func (m *InoMap) Translate(in QIno) (out uint64) {
	m.Lock()
	defer m.Unlock()
	defer func() {
		fmt.Printf("Translate: %v -> %d\n", in, out)
	}()

	if in.Ino > maxPassthruIno {
		out = m.spill(in)
		return out
	}
	ns, found := m.namespaceMap[in.namespaceData]
	// Use existing namespace
	if found {
		out = uint64(ns)<<48 | in.Ino
		return out
	}
	// No free namespace slots?
	if m.namespaceNext >= maxNamespaceId {
		out = m.spill(in)
		return out
	}
	ns = m.namespaceNext
	m.namespaceNext++
	m.namespaceMap[in.namespaceData] = ns
	out = uint64(ns)<<48 | in.Ino
	return out
}

// TranslateStat translates the inode number contained in "st" if neccessary.
// Convience wrapper around Translate().
func (m *InoMap) TranslateStat(st *syscall.Stat_t) {
	in := QInoFromStat(st)
	st.Ino = m.Translate(in)
}
<commit_msg>inomap: remove leftover debug output<commit_after>// inomap translates (Dev, Flags, Ino) tuples to unique uint64
// inode numbers.
//
// Format of the returned inode numbers:
//
//   [spill bit = 0][15 bit namespace id][48 bit passthru inode number]
//   [spill bit = 1][63 bit spill inode number                        ]
//
// Each (Dev, Flags) tuple gets a namespace id assigned. The original inode
// number is then passed through in the lower 48 bits.
//
// If namespace ids are exhaused, or the original id is larger than 48 bits,
// the whole (Dev, Flags, Ino) tuple gets mapped in the spill map, and the
// spill bit is set to 1.
package inomap

import (
	"log"
	"sync"
	"syscall"
)

const (
	// max value of 15 bit namespace id
	maxNamespaceId = 1<<15 - 1
	// max value of 48 bit passthru inode number
	maxPassthruIno = 1<<48 - 1
	// max value of 63 bit spill inode number
	maxSpillIno = 1<<63 - 1
)

// InoMap stores the maps using for inode number translation.
// See package comment for details.
type InoMap struct {
	sync.Mutex
	// namespaces keeps the mapping of (Dev,Flags) tuples to
	// 15-bit identifiers (stored in an uint16 with the high bit always zero)
	namespaceMap map[namespaceData]uint16
	// spillNext is the next free namespace number in the namespaces map
	namespaceNext uint16
	// spill is used once the namespaces map is full
	spillMap map[QIno]uint64
	// spillNext is the next free inode number in the spill map
	spillNext uint64
}

// New returns a new InoMap.
func New() *InoMap {
	return &InoMap{
		namespaceMap:  make(map[namespaceData]uint16),
		namespaceNext: 0,
		spillMap:      make(map[QIno]uint64),
		spillNext:     0,
	}
}

func (m *InoMap) spill(in QIno) (out uint64) {
	out, found := m.spillMap[in]
	if found {
		return out
	}
	if m.spillNext >= maxSpillIno {
		log.Panicf("spillMap overflow: spillNext = 0x%x", m.spillNext)
	}
	out = m.spillNext
	m.spillNext++
	m.spillMap[in] = out
	return 1<<63 | out
}

// Translate maps the passed-in (device, inode) pair to a unique inode number.
func (m *InoMap) Translate(in QIno) (out uint64) {
	m.Lock()
	defer m.Unlock()

	if in.Ino > maxPassthruIno {
		out = m.spill(in)
		return out
	}
	ns, found := m.namespaceMap[in.namespaceData]
	// Use existing namespace
	if found {
		out = uint64(ns)<<48 | in.Ino
		return out
	}
	// No free namespace slots?
	if m.namespaceNext >= maxNamespaceId {
		out = m.spill(in)
		return out
	}
	ns = m.namespaceNext
	m.namespaceNext++
	m.namespaceMap[in.namespaceData] = ns
	out = uint64(ns)<<48 | in.Ino
	return out
}

// TranslateStat translates the inode number contained in "st" if neccessary.
// Convience wrapper around Translate().
func (m *InoMap) TranslateStat(st *syscall.Stat_t) {
	in := QInoFromStat(st)
	st.Ino = m.Translate(in)
}
<|endoftext|>


<commit_before>package internal

import (
	"os/exec"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

type SnakeTest struct {
	input  string
	output string
}

var tests = []SnakeTest{
	{"a", "a"},
	{"snake", "snake"},
	{"A", "a"},
	{"ID", "id"},
	{"MOTD", "motd"},
	{"Snake", "snake"},
	{"SnakeTest", "snake_test"},
	{"APIResponse", "api_response"},
	{"SnakeID", "snake_id"},
	{"SnakeIDGoogle", "snake_id_google"},
	{"LinuxMOTD", "linux_motd"},
	{"OMGWTFBBQ", "omgwtfbbq"},
	{"omg_wtf_bbq", "omg_wtf_bbq"},
}

func TestSnakeCase(t *testing.T) {
	for _, test := range tests {
		if SnakeCase(test.input) != test.output {
			t.Errorf(`SnakeCase("%s"), wanted "%s", got \%s"`, test.input, test.output, SnakeCase(test.input))
		}
	}
}

var (
	sleepbin, _ = exec.LookPath("sleep")
	echobin, _  = exec.LookPath("echo")
)

func TestRunTimeout(t *testing.T) {
	if sleepbin == "" {
		t.Skip("'sleep' binary not available on OS, skipping.")
	}
	cmd := exec.Command(sleepbin, "10")
	start := time.Now()
	err := RunTimeout(cmd, time.Millisecond*20)
	elapsed := time.Since(start)

	assert.Equal(t, TimeoutErr, err)
	// Verify that command gets killed in 20ms, with some breathing room
	assert.True(t, elapsed < time.Millisecond*75)
}

func TestCombinedOutputTimeout(t *testing.T) {
	if sleepbin == "" {
		t.Skip("'sleep' binary not available on OS, skipping.")
	}
	cmd := exec.Command(sleepbin, "10")
	start := time.Now()
	_, err := CombinedOutputTimeout(cmd, time.Millisecond*20)
	elapsed := time.Since(start)

	assert.Equal(t, TimeoutErr, err)
	// Verify that command gets killed in 20ms, with some breathing room
	assert.True(t, elapsed < time.Millisecond*75)
}

func TestCombinedOutput(t *testing.T) {
	if echobin == "" {
		t.Skip("'echo' binary not available on OS, skipping.")
	}
	cmd := exec.Command(echobin, "foo")
	out, err := CombinedOutputTimeout(cmd, time.Second)

	assert.NoError(t, err)
	assert.Equal(t, "foo\n", string(out))
}

// test that CombinedOutputTimeout and exec.Cmd.CombinedOutput return
// the same output from a failed command.
func TestCombinedOutputError(t *testing.T) {
	if sleepbin == "" {
		t.Skip("'sleep' binary not available on OS, skipping.")
	}
	cmd := exec.Command(sleepbin, "foo")
	expected, err := cmd.CombinedOutput()

	cmd2 := exec.Command(sleepbin, "foo")
	actual, err := CombinedOutputTimeout(cmd2, time.Second)

	assert.Error(t, err)
	assert.Equal(t, expected, actual)
}

func TestRunError(t *testing.T) {
	if sleepbin == "" {
		t.Skip("'sleep' binary not available on OS, skipping.")
	}
	cmd := exec.Command(sleepbin, "foo")
	err := RunTimeout(cmd, time.Second)

	assert.Error(t, err)
}

func TestRandomSleep(t *testing.T) {
	// test that zero max returns immediately
	s := time.Now()
	RandomSleep(time.Duration(0), make(chan struct{}))
	elapsed := time.Since(s)
	assert.True(t, elapsed < time.Millisecond)

	// test that max sleep is respected
	s = time.Now()
	RandomSleep(time.Millisecond*50, make(chan struct{}))
	elapsed = time.Since(s)
	assert.True(t, elapsed < time.Millisecond*100)

	// test that shutdown is respected
	s = time.Now()
	shutdown := make(chan struct{})
	go func() {
		time.Sleep(time.Millisecond * 100)
		close(shutdown)
	}()
	RandomSleep(time.Second, shutdown)
	elapsed = time.Since(s)
	assert.True(t, elapsed < time.Millisecond*150)
}

func TestDuration(t *testing.T) {
	var d Duration

	d.UnmarshalTOML([]byte(`"1s"`))
	assert.Equal(t, time.Second, d.Duration)

	d = Duration{}
	d.UnmarshalTOML([]byte(`1s`))
	assert.Equal(t, time.Second, d.Duration)

	d = Duration{}
	d.UnmarshalTOML([]byte(`'1s'`))
	assert.Equal(t, time.Second, d.Duration)

	d = Duration{}
	d.UnmarshalTOML([]byte(`10`))
	assert.Equal(t, 10*time.Second, d.Duration)

	d = Duration{}
	d.UnmarshalTOML([]byte(`1.5`))
	assert.Equal(t, time.Second, d.Duration)
}
<commit_msg>Fix short tests on darwin (#3099)<commit_after>package internal

import (
	"os/exec"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

type SnakeTest struct {
	input  string
	output string
}

var tests = []SnakeTest{
	{"a", "a"},
	{"snake", "snake"},
	{"A", "a"},
	{"ID", "id"},
	{"MOTD", "motd"},
	{"Snake", "snake"},
	{"SnakeTest", "snake_test"},
	{"APIResponse", "api_response"},
	{"SnakeID", "snake_id"},
	{"SnakeIDGoogle", "snake_id_google"},
	{"LinuxMOTD", "linux_motd"},
	{"OMGWTFBBQ", "omgwtfbbq"},
	{"omg_wtf_bbq", "omg_wtf_bbq"},
}

func TestSnakeCase(t *testing.T) {
	for _, test := range tests {
		if SnakeCase(test.input) != test.output {
			t.Errorf(`SnakeCase("%s"), wanted "%s", got \%s"`, test.input, test.output, SnakeCase(test.input))
		}
	}
}

var (
	sleepbin, _ = exec.LookPath("sleep")
	echobin, _  = exec.LookPath("echo")
	shell, _    = exec.LookPath("sh")
)

func TestRunTimeout(t *testing.T) {
	if sleepbin == "" {
		t.Skip("'sleep' binary not available on OS, skipping.")
	}
	cmd := exec.Command(sleepbin, "10")
	start := time.Now()
	err := RunTimeout(cmd, time.Millisecond*20)
	elapsed := time.Since(start)

	assert.Equal(t, TimeoutErr, err)
	// Verify that command gets killed in 20ms, with some breathing room
	assert.True(t, elapsed < time.Millisecond*75)
}

func TestCombinedOutputTimeout(t *testing.T) {
	if sleepbin == "" {
		t.Skip("'sleep' binary not available on OS, skipping.")
	}
	cmd := exec.Command(sleepbin, "10")
	start := time.Now()
	_, err := CombinedOutputTimeout(cmd, time.Millisecond*20)
	elapsed := time.Since(start)

	assert.Equal(t, TimeoutErr, err)
	// Verify that command gets killed in 20ms, with some breathing room
	assert.True(t, elapsed < time.Millisecond*75)
}

func TestCombinedOutput(t *testing.T) {
	if echobin == "" {
		t.Skip("'echo' binary not available on OS, skipping.")
	}
	cmd := exec.Command(echobin, "foo")
	out, err := CombinedOutputTimeout(cmd, time.Second)

	assert.NoError(t, err)
	assert.Equal(t, "foo\n", string(out))
}

// test that CombinedOutputTimeout and exec.Cmd.CombinedOutput return
// the same output from a failed command.
func TestCombinedOutputError(t *testing.T) {
	if shell == "" {
		t.Skip("'sh' binary not available on OS, skipping.")
	}
	cmd := exec.Command(shell, "-c", "false")
	expected, err := cmd.CombinedOutput()

	cmd2 := exec.Command(shell, "-c", "false")
	actual, err := CombinedOutputTimeout(cmd2, time.Second)

	assert.Error(t, err)
	assert.Equal(t, expected, actual)
}

func TestRunError(t *testing.T) {
	if shell == "" {
		t.Skip("'sh' binary not available on OS, skipping.")
	}
	cmd := exec.Command(shell, "-c", "false")
	err := RunTimeout(cmd, time.Second)

	assert.Error(t, err)
}

func TestRandomSleep(t *testing.T) {
	// test that zero max returns immediately
	s := time.Now()
	RandomSleep(time.Duration(0), make(chan struct{}))
	elapsed := time.Since(s)
	assert.True(t, elapsed < time.Millisecond)

	// test that max sleep is respected
	s = time.Now()
	RandomSleep(time.Millisecond*50, make(chan struct{}))
	elapsed = time.Since(s)
	assert.True(t, elapsed < time.Millisecond*100)

	// test that shutdown is respected
	s = time.Now()
	shutdown := make(chan struct{})
	go func() {
		time.Sleep(time.Millisecond * 100)
		close(shutdown)
	}()
	RandomSleep(time.Second, shutdown)
	elapsed = time.Since(s)
	assert.True(t, elapsed < time.Millisecond*150)
}

func TestDuration(t *testing.T) {
	var d Duration

	d.UnmarshalTOML([]byte(`"1s"`))
	assert.Equal(t, time.Second, d.Duration)

	d = Duration{}
	d.UnmarshalTOML([]byte(`1s`))
	assert.Equal(t, time.Second, d.Duration)

	d = Duration{}
	d.UnmarshalTOML([]byte(`'1s'`))
	assert.Equal(t, time.Second, d.Duration)

	d = Duration{}
	d.UnmarshalTOML([]byte(`10`))
	assert.Equal(t, 10*time.Second, d.Duration)

	d = Duration{}
	d.UnmarshalTOML([]byte(`1.5`))
	assert.Equal(t, time.Second, d.Duration)
}
<|endoftext|>


<commit_before>package mapper

import (
	"fmt"
	"io"

	"github.com/cloudflare/unsee/internal/models"
)

var (
	alertMappers   = []AlertMapper{}
	silenceMappers = []SilenceMapper{}
)

// Mapper converts Alertmanager response body and maps to unsee data structures
type Mapper interface {
	IsSupported(version string) bool
	AbsoluteURL(baseURI string) (string, error)
}

// AlertMapper handles mapping of Alertmanager alert information to unsee AlertGroup models
type AlertMapper interface {
	Mapper
	Decode(io.ReadCloser) ([]models.AlertGroup, error)
}

// SilenceMapper handles mapping of Alertmanager silence information to unsee Silence models
type SilenceMapper interface {
	Mapper
	Decode(io.ReadCloser) ([]models.Silence, error)
	Release() string
}

// RegisterAlertMapper allows to register mapper implementing alert data
// handling for specific Alertmanager versions
func RegisterAlertMapper(m AlertMapper) {
	alertMappers = append(alertMappers, m)
}

// GetAlertMapper returns mapper for given version
func GetAlertMapper(version string) (AlertMapper, error) {
	for _, m := range alertMappers {
		if m.IsSupported(version) {
			return m, nil
		}
	}
	return nil, fmt.Errorf("Can't find alert mapper for Alertmanager %s", version)
}

// RegisterSilenceMapper allows to register mapper implementing silence data
// handling for specific Alertmanager versions
func RegisterSilenceMapper(m SilenceMapper) {
	silenceMappers = append(silenceMappers, m)
}

// GetSilenceMapper returns mapper for given version
func GetSilenceMapper(version string) (SilenceMapper, error) {
	for _, m := range silenceMappers {
		if m.IsSupported(version) {
			return m, nil
		}
	}
	return nil, fmt.Errorf("Can't find silence mapper for Alertmanager %s", version)
}
<commit_msg>Drop Release() from SilenceMapper Not used<commit_after>package mapper

import (
	"fmt"
	"io"

	"github.com/cloudflare/unsee/internal/models"
)

var (
	alertMappers   = []AlertMapper{}
	silenceMappers = []SilenceMapper{}
)

// Mapper converts Alertmanager response body and maps to unsee data structures
type Mapper interface {
	IsSupported(version string) bool
	AbsoluteURL(baseURI string) (string, error)
}

// AlertMapper handles mapping of Alertmanager alert information to unsee AlertGroup models
type AlertMapper interface {
	Mapper
	Decode(io.ReadCloser) ([]models.AlertGroup, error)
}

// SilenceMapper handles mapping of Alertmanager silence information to unsee Silence models
type SilenceMapper interface {
	Mapper
	Decode(io.ReadCloser) ([]models.Silence, error)
}

// RegisterAlertMapper allows to register mapper implementing alert data
// handling for specific Alertmanager versions
func RegisterAlertMapper(m AlertMapper) {
	alertMappers = append(alertMappers, m)
}

// GetAlertMapper returns mapper for given version
func GetAlertMapper(version string) (AlertMapper, error) {
	for _, m := range alertMappers {
		if m.IsSupported(version) {
			return m, nil
		}
	}
	return nil, fmt.Errorf("Can't find alert mapper for Alertmanager %s", version)
}

// RegisterSilenceMapper allows to register mapper implementing silence data
// handling for specific Alertmanager versions
func RegisterSilenceMapper(m SilenceMapper) {
	silenceMappers = append(silenceMappers, m)
}

// GetSilenceMapper returns mapper for given version
func GetSilenceMapper(version string) (SilenceMapper, error) {
	for _, m := range silenceMappers {
		if m.IsSupported(version) {
			return m, nil
		}
	}
	return nil, fmt.Errorf("Can't find silence mapper for Alertmanager %s", version)
}
<|endoftext|>


<commit_before>/**
 * @file game.go
 * @author Mikhail Klementyev jollheef<AT>riseup.net
 * @license GNU AGPLv3
 * @date November, 2015
 * @brief game api
 *
 * Contain functions for calculate score, check flag etc.
 */

package game

import (
	"database/sql"
	"log"
	"regexp"
	"sort"
	"sync"
	"time"

	"github.com/jollheef/henhouse/db"
)

// Game struct
type Game struct {
	db              *sql.DB
	Start           time.Time
	End             time.Time
	OpenTimeout     time.Duration // after solve task
	AutoOpen        bool
	AutoOpenTimeout time.Duration // if task does not solved
	scoreboardLock  *sync.Mutex
	TaskPrice       struct {
		TeamsBase              float64
		P500, P400, P300, P200 float64
	}
}

// TaskInfo provide information about task
type TaskInfo struct {
	ID         int
	Name       string
	Desc       string
	Author     string
	Price      int
	Opened     bool
	Level      int
	SolvedBy   []int
	OpenedTime time.Time
}

// CategoryInfo provide information about categories and tasks
type CategoryInfo struct {
	Name      string
	TasksInfo []TaskInfo
}

// TeamScoreInfo provide information about team score
type TeamScoreInfo struct {
	ID    int
	Name  string
	Desc  string
	Score int
}

type byScore []TeamScoreInfo

func (tr byScore) Len() int           { return len(tr) }
func (tr byScore) Swap(i, j int)      { tr[i], tr[j] = tr[j], tr[i] }
func (tr byScore) Less(i, j int) bool { return tr[i].Score > tr[j].Score }

type byLevel []TaskInfo

func (ti byLevel) Len() int           { return len(ti) }
func (ti byLevel) Swap(i, j int)      { ti[i], ti[j] = ti[j], ti[i] }
func (ti byLevel) Less(i, j int) bool { return ti[i].Level < ti[j].Level }

// TaskPrice provide task price info

// NewGame create new game
func NewGame(database *sql.DB, start, end time.Time) (g Game, err error) {

	g.db = database
	g.Start = start
	g.End = end

	// Default values
	g.TaskPrice.P200 = 0.50
	g.TaskPrice.P300 = 0.30
	g.TaskPrice.P400 = 0.15
	g.TaskPrice.P500 = 0.10

	g.scoreboardLock = &sync.Mutex{}

	tasks, err := db.GetTasks(g.db)
	if err != nil {
		return
	}
	g.TaskPrice.TeamsBase = float64(len(tasks))

	err = g.RecalcScoreboard()
	if err != nil {
		return
	}

	return
}

// SetTaskPrice convert and set price of tasks
func (g Game) SetTaskPrice(p500, p400, p300, p200 int) {
	g.TaskPrice.P200 = float64(p200) / 100
	g.TaskPrice.P300 = float64(p300) / 100
	g.TaskPrice.P400 = float64(p400) / 100
	g.TaskPrice.P500 = float64(p500) / 100
}

// SetTeamsBase force set amount of teams for calc price task
func (g *Game) SetTeamsBase(teams int) {
	g.TaskPrice.TeamsBase = float64(teams)
}

// Run open first level tasks and start auto open routine
func (g Game) Run() (err error) {

	for time.Now().Before(g.Start) {
		time.Sleep(time.Second)
	}

	cats, err := g.Tasks()
	if err != nil {
		return
	}

	for _, c := range cats {
		for _, t := range c.TasksInfo {
			log.Println("Open task", t.Name, t.Level)
			err = db.SetOpened(g.db, t.ID, true)
			if err != nil {
				return
			}

			break
		}
	}

	go func() {
		for {
			time.Sleep(time.Second)
			err = g.autoOpenTasks()
			if err != nil {
				log.Println("Auto open tasks fail:", err)
			}
		}
	}()

	return
}

func (g Game) autoOpenTasks() (err error) {

	now := time.Now()

	cats, err := g.Tasks()
	if err != nil {
		return
	}

	for _, c := range cats {
		prev := TaskInfo{Opened: true}
		for i, t := range c.TasksInfo {
			if i == 0 || t.Opened || !prev.Opened {
				prev = t
				continue
			}

			if now.After(prev.OpenedTime.Add(g.AutoOpenTimeout)) {
				log.Println("Open task", t.Name, t.Level)
				err = db.SetOpened(g.db, t.ID, true)
				if err != nil {
					return
				}
			}

			prev = t
		}

	}

	return
}

func (g Game) taskPrice(database *sql.DB, taskID int) (price int, err error) {

	count, err := db.GetSolvedCount(database, taskID)

	fprice := float64(count) / g.TaskPrice.TeamsBase

	if fprice <= g.TaskPrice.P500 {
		price = 500
	} else if fprice <= g.TaskPrice.P400 {
		price = 400
	} else if fprice <= g.TaskPrice.P300 {
		price = 300
	} else if fprice <= g.TaskPrice.P200 {
		price = 200
	} else {
		price = 100
	}

	return
}

// Tasks returns categories with tasks
func (g Game) Tasks() (cats []CategoryInfo, err error) {

	tasks, err := db.GetTasks(g.db)
	if err != nil {
		return
	}

	categories, err := db.GetCategories(g.db)
	if err != nil {
		return
	}

	for _, category := range categories {

		cat := CategoryInfo{Name: category.Name}

		for _, task := range tasks {

			if task.CategoryID == category.ID {

				var price int
				price, err = g.taskPrice(g.db, task.ID)
				if err != nil {
					return
				}

				var solvedBy []int
				solvedBy, err = db.GetSolvedBy(g.db, task.ID)
				if err != nil {
					return
				}

				if !task.Opened {
					task.Desc = ""
				}

				tInfo := TaskInfo{
					ID:         task.ID,
					Name:       task.Name,
					Desc:       task.Desc,
					Price:      price,
					Opened:     task.Opened,
					SolvedBy:   solvedBy,
					Author:     task.Author,
					Level:      task.Level,
					OpenedTime: task.OpenedTime,
				}

				cat.TasksInfo = append(cat.TasksInfo, tInfo)
			}
		}

		sort.Sort(byLevel(cat.TasksInfo))

		cats = append(cats, cat)
	}

	return
}

// Scoreboard returns sorted scoreboard
func (g Game) Scoreboard() (scores []TeamScoreInfo, err error) {

	g.scoreboardLock.Lock()
	defer g.scoreboardLock.Unlock()

	teams, err := db.GetTeams(g.db)
	if err != nil {
		return
	}

	for _, team := range teams {

		if team.Test {
			continue
		}

		var s db.Score
		s, err = db.GetLastScore(g.db, team.ID)
		if err != nil {
			return
		}

		scores = append(scores,
			TeamScoreInfo{team.ID, team.Name, team.Desc, s.Score})
	}

	sort.Sort(byScore(scores))

	return
}

// RecalcScoreboard update scoreboard
func (g Game) RecalcScoreboard() (err error) {

	g.scoreboardLock.Lock()
	defer g.scoreboardLock.Unlock()

	teams, err := db.GetTeams(g.db)
	if err != nil {
		return
	}

	tasks, err := db.GetTasks(g.db)
	if err != nil {
		return
	}

	for _, team := range teams {

		if team.Test {
			continue
		}

		score := 0

		for _, task := range tasks {

			var price int
			price, err = g.taskPrice(g.db, task.ID)
			if err != nil {
				return
			}

			var solved bool
			solved, err = db.IsSolved(g.db, team.ID, task.ID)
			if err != nil {
				return
			}

			if solved {
				score += price
			}
		}

		err = db.AddScore(g.db, &db.Score{TeamID: team.ID, Score: score})
		if err != nil {
			return
		}
	}

	return
}

// OpenNextTask open next task by level
func (g Game) OpenNextTask(t db.Task) (err error) {

	time.Sleep(g.OpenTimeout)

	tasks, err := db.GetTasks(g.db)
	if err != nil {
		return
	}

	for _, task := range tasks {
		// If same category and next level
		if t.CategoryID == task.CategoryID && t.Level+1 == task.Level {
			// If not already opened
			if !task.Opened {
				// Open it!
				log.Println("Open task", t.Name, t.Level)
				err = db.SetOpened(g.db, task.ID, true)
				if err != nil {
					return
				}
			}
		}
	}

	return
}

func (g Game) isTestTeam(teamID int) bool {

	teams, err := db.GetTeams(g.db)
	if err != nil {
		log.Println("Get teams fail:", err)
		return true
	}

	for _, team := range teams {
		if team.ID == teamID {
			return team.Test
		}
	}

	return false
}

// Solve check flag for task and recalc scoreboard if flag correct
func (g Game) Solve(teamID, taskID int, flag string) (solved bool, err error) {

	tasks, err := db.GetTasks(g.db)
	if err != nil {
		return
	}

	for _, task := range tasks {
		if task.ID == taskID {

			solved, err = regexp.MatchString("^"+task.Flag+"$", flag)
			if err != nil {
				log.Println("Match regex fail:", err)
				return
			}

			if solved {

				if g.isTestTeam(teamID) {
					return
				}

				var isSolv bool // if already solved
				isSolv, err = db.IsSolved(g.db, teamID, taskID)
				if isSolv {
					return
				}

				now := time.Now()

				if now.After(g.Start) && now.Before(g.End) {
					err = db.AddFlag(g.db, &db.Flag{
						TeamID: teamID,
						TaskID: taskID,
						Flag:   flag,
						Solved: solved,
					})
					if err != nil {
						return
					}

					go g.OpenNextTask(task)
				}
			}

			break
		}
	}

	return
}
<commit_msg>Now price change work<commit_after>/**
 * @file game.go
 * @author Mikhail Klementyev jollheef<AT>riseup.net
 * @license GNU AGPLv3
 * @date November, 2015
 * @brief game api
 *
 * Contain functions for calculate score, check flag etc.
 */

package game

import (
	"database/sql"
	"log"
	"regexp"
	"sort"
	"sync"
	"time"

	"github.com/jollheef/henhouse/db"
)

// Game struct
type Game struct {
	db              *sql.DB
	Start           time.Time
	End             time.Time
	OpenTimeout     time.Duration // after solve task
	AutoOpen        bool
	AutoOpenTimeout time.Duration // if task does not solved
	scoreboardLock  *sync.Mutex
	TaskPrice       struct {
		TeamsBase              float64
		P500, P400, P300, P200 float64
	}
}

// TaskInfo provide information about task
type TaskInfo struct {
	ID         int
	Name       string
	Desc       string
	Author     string
	Price      int
	Opened     bool
	Level      int
	SolvedBy   []int
	OpenedTime time.Time
}

// CategoryInfo provide information about categories and tasks
type CategoryInfo struct {
	Name      string
	TasksInfo []TaskInfo
}

// TeamScoreInfo provide information about team score
type TeamScoreInfo struct {
	ID    int
	Name  string
	Desc  string
	Score int
}

type byScore []TeamScoreInfo

func (tr byScore) Len() int           { return len(tr) }
func (tr byScore) Swap(i, j int)      { tr[i], tr[j] = tr[j], tr[i] }
func (tr byScore) Less(i, j int) bool { return tr[i].Score > tr[j].Score }

type byLevel []TaskInfo

func (ti byLevel) Len() int           { return len(ti) }
func (ti byLevel) Swap(i, j int)      { ti[i], ti[j] = ti[j], ti[i] }
func (ti byLevel) Less(i, j int) bool { return ti[i].Level < ti[j].Level }

// TaskPrice provide task price info

// NewGame create new game
func NewGame(database *sql.DB, start, end time.Time) (g Game, err error) {

	g.db = database
	g.Start = start
	g.End = end

	// Default values
	g.TaskPrice.P200 = 0.50
	g.TaskPrice.P300 = 0.30
	g.TaskPrice.P400 = 0.15
	g.TaskPrice.P500 = 0.10

	g.scoreboardLock = &sync.Mutex{}

	tasks, err := db.GetTasks(g.db)
	if err != nil {
		return
	}
	g.TaskPrice.TeamsBase = float64(len(tasks))

	err = g.RecalcScoreboard()
	if err != nil {
		return
	}

	return
}

// SetTaskPrice convert and set price of tasks
func (g *Game) SetTaskPrice(p500, p400, p300, p200 int) {
	g.TaskPrice.P200 = float64(p200) / 100
	g.TaskPrice.P300 = float64(p300) / 100
	g.TaskPrice.P400 = float64(p400) / 100
	g.TaskPrice.P500 = float64(p500) / 100
}

// SetTeamsBase force set amount of teams for calc price task
func (g *Game) SetTeamsBase(teams int) {
	g.TaskPrice.TeamsBase = float64(teams)
}

// Run open first level tasks and start auto open routine
func (g Game) Run() (err error) {

	for time.Now().Before(g.Start) {
		time.Sleep(time.Second)
	}

	cats, err := g.Tasks()
	if err != nil {
		return
	}

	for _, c := range cats {
		for _, t := range c.TasksInfo {
			log.Println("Open task", t.Name, t.Level)
			err = db.SetOpened(g.db, t.ID, true)
			if err != nil {
				return
			}

			break
		}
	}

	go func() {
		for {
			time.Sleep(time.Second)
			err = g.autoOpenTasks()
			if err != nil {
				log.Println("Auto open tasks fail:", err)
			}
		}
	}()

	return
}

func (g Game) autoOpenTasks() (err error) {

	now := time.Now()

	cats, err := g.Tasks()
	if err != nil {
		return
	}

	for _, c := range cats {
		prev := TaskInfo{Opened: true}
		for i, t := range c.TasksInfo {
			if i == 0 || t.Opened || !prev.Opened {
				prev = t
				continue
			}

			if now.After(prev.OpenedTime.Add(g.AutoOpenTimeout)) {
				log.Println("Open task", t.Name, t.Level)
				err = db.SetOpened(g.db, t.ID, true)
				if err != nil {
					return
				}
			}

			prev = t
		}

	}

	return
}

func (g Game) taskPrice(database *sql.DB, taskID int) (price int, err error) {

	count, err := db.GetSolvedCount(database, taskID)

	fprice := float64(count) / g.TaskPrice.TeamsBase

	if fprice <= g.TaskPrice.P500 {
		price = 500
	} else if fprice <= g.TaskPrice.P400 {
		price = 400
	} else if fprice <= g.TaskPrice.P300 {
		price = 300
	} else if fprice <= g.TaskPrice.P200 {
		price = 200
	} else {
		price = 100
	}

	return
}

// Tasks returns categories with tasks
func (g Game) Tasks() (cats []CategoryInfo, err error) {

	tasks, err := db.GetTasks(g.db)
	if err != nil {
		return
	}

	categories, err := db.GetCategories(g.db)
	if err != nil {
		return
	}

	for _, category := range categories {

		cat := CategoryInfo{Name: category.Name}

		for _, task := range tasks {

			if task.CategoryID == category.ID {

				var price int
				price, err = g.taskPrice(g.db, task.ID)
				if err != nil {
					return
				}

				var solvedBy []int
				solvedBy, err = db.GetSolvedBy(g.db, task.ID)
				if err != nil {
					return
				}

				if !task.Opened {
					task.Desc = ""
				}

				tInfo := TaskInfo{
					ID:         task.ID,
					Name:       task.Name,
					Desc:       task.Desc,
					Price:      price,
					Opened:     task.Opened,
					SolvedBy:   solvedBy,
					Author:     task.Author,
					Level:      task.Level,
					OpenedTime: task.OpenedTime,
				}

				cat.TasksInfo = append(cat.TasksInfo, tInfo)
			}
		}

		sort.Sort(byLevel(cat.TasksInfo))

		cats = append(cats, cat)
	}

	return
}

// Scoreboard returns sorted scoreboard
func (g Game) Scoreboard() (scores []TeamScoreInfo, err error) {

	g.scoreboardLock.Lock()
	defer g.scoreboardLock.Unlock()

	teams, err := db.GetTeams(g.db)
	if err != nil {
		return
	}

	for _, team := range teams {

		if team.Test {
			continue
		}

		var s db.Score
		s, err = db.GetLastScore(g.db, team.ID)
		if err != nil {
			return
		}

		scores = append(scores,
			TeamScoreInfo{team.ID, team.Name, team.Desc, s.Score})
	}

	sort.Sort(byScore(scores))

	return
}

// RecalcScoreboard update scoreboard
func (g Game) RecalcScoreboard() (err error) {

	g.scoreboardLock.Lock()
	defer g.scoreboardLock.Unlock()

	teams, err := db.GetTeams(g.db)
	if err != nil {
		return
	}

	tasks, err := db.GetTasks(g.db)
	if err != nil {
		return
	}

	for _, team := range teams {

		if team.Test {
			continue
		}

		score := 0

		for _, task := range tasks {

			var price int
			price, err = g.taskPrice(g.db, task.ID)
			if err != nil {
				return
			}

			var solved bool
			solved, err = db.IsSolved(g.db, team.ID, task.ID)
			if err != nil {
				return
			}

			if solved {
				score += price
			}
		}

		err = db.AddScore(g.db, &db.Score{TeamID: team.ID, Score: score})
		if err != nil {
			return
		}
	}

	return
}

// OpenNextTask open next task by level
func (g Game) OpenNextTask(t db.Task) (err error) {

	time.Sleep(g.OpenTimeout)

	tasks, err := db.GetTasks(g.db)
	if err != nil {
		return
	}

	for _, task := range tasks {
		// If same category and next level
		if t.CategoryID == task.CategoryID && t.Level+1 == task.Level {
			// If not already opened
			if !task.Opened {
				// Open it!
				log.Println("Open task", t.Name, t.Level)
				err = db.SetOpened(g.db, task.ID, true)
				if err != nil {
					return
				}
			}
		}
	}

	return
}

func (g Game) isTestTeam(teamID int) bool {

	teams, err := db.GetTeams(g.db)
	if err != nil {
		log.Println("Get teams fail:", err)
		return true
	}

	for _, team := range teams {
		if team.ID == teamID {
			return team.Test
		}
	}

	return false
}

// Solve check flag for task and recalc scoreboard if flag correct
func (g Game) Solve(teamID, taskID int, flag string) (solved bool, err error) {

	tasks, err := db.GetTasks(g.db)
	if err != nil {
		return
	}

	for _, task := range tasks {
		if task.ID == taskID {

			solved, err = regexp.MatchString("^"+task.Flag+"$", flag)
			if err != nil {
				log.Println("Match regex fail:", err)
				return
			}

			if solved {

				if g.isTestTeam(teamID) {
					return
				}

				var isSolv bool // if already solved
				isSolv, err = db.IsSolved(g.db, teamID, taskID)
				if isSolv {
					return
				}

				now := time.Now()

				if now.After(g.Start) && now.Before(g.End) {
					err = db.AddFlag(g.db, &db.Flag{
						TeamID: teamID,
						TaskID: taskID,
						Flag:   flag,
						Solved: solved,
					})
					if err != nil {
						return
					}

					go g.OpenNextTask(task)
				}
			}

			break
		}
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"io"
	"io/ioutil"
	"time"

	"github.com/vbauerster/mpb/v7"
	"github.com/vbauerster/mpb/v7/decor"
)

func main() {
	var total int64 = 1024 * 1024 * 500
	reader := io.LimitReader(rand.Reader, total)

	p := mpb.New(
		mpb.WithWidth(60),
		mpb.WithRefreshRate(180*time.Millisecond),
	)

	bar := p.New(total,
		mpb.BarStyle().Rbound("|"),
		mpb.PrependDecorators(
			decor.CountersKibiByte("% .2f / % .2f"),
		),
		mpb.AppendDecorators(
			decor.EwmaETA(decor.ET_STYLE_GO, 90),
			decor.Name(" ] "),
			decor.EwmaSpeed(decor.UnitKiB, "% .2f", 60),
		),
	)

	// create proxy reader
	proxyReader := bar.ProxyReader(reader)
	defer proxyReader.Close()

	// copy from proxyReader, ignoring errors
	io.Copy(ioutil.Discard, proxyReader)

	p.Wait()
}
<commit_msg>explicit error ignore<commit_after>package main

import (
	"crypto/rand"
	"io"
	"io/ioutil"
	"time"

	"github.com/vbauerster/mpb/v7"
	"github.com/vbauerster/mpb/v7/decor"
)

func main() {
	var total int64 = 1024 * 1024 * 500
	reader := io.LimitReader(rand.Reader, total)

	p := mpb.New(
		mpb.WithWidth(60),
		mpb.WithRefreshRate(180*time.Millisecond),
	)

	bar := p.New(total,
		mpb.BarStyle().Rbound("|"),
		mpb.PrependDecorators(
			decor.CountersKibiByte("% .2f / % .2f"),
		),
		mpb.AppendDecorators(
			decor.EwmaETA(decor.ET_STYLE_GO, 90),
			decor.Name(" ] "),
			decor.EwmaSpeed(decor.UnitKiB, "% .2f", 60),
		),
	)

	// create proxy reader
	proxyReader := bar.ProxyReader(reader)
	defer proxyReader.Close()

	// copy from proxyReader, ignoring errors
	_, _ = io.Copy(ioutil.Discard, proxyReader)

	p.Wait()
}
<|endoftext|>


<commit_before>// Copyright William Schwartz 2014. See the LICENSE file for more information.

// Package pigosat is a Go binding for the PicoSAT satisfiability solver.
//
// Designing your model is beyond the scope of this document, but Googling
// "satisfiability problem", "conjunctive normal form", and "DIMACS" are good
// places to start. Once you have your model, create a Pigosat instance p with
// pigosat.NewPigosat, add the model to the instance with p.AddClauses, and
// solve with p.Solve.
package pigosat

// picosat/libpicosat.a must exist to build this file. See README.md.

// #cgo CFLAGS: -I picosat
// #cgo LDFLAGS: -l picosat -L picosat
// #include "picosat.h"
import "C"
import (
	"fmt"
	"os"
	"runtime"
	"sync"
	"syscall"
	"time"
	"unsafe"
)

var Version = SemanticVersion{0, 3, 0, "", 0}

// PicosatVersion returns the version string from the underlying Picosat
// library.
func PicosatVersion() string {
	return C.GoString(C.picosat_version())
}

// Return values for Pigosat.Solve's status.
const (
	// NotReady is a solver status only used in PiGoSAT and it means the
	// underlying data structures of the solver have not been initialized or
	// their memory was previously freed.
	NotReady = -1
	// PicoSAT cannot determine the satisfiability of the formula.
	Unknown = 0
	// The formula is satisfiable.
	Satisfiable = 10
	// The formula cannot be satisfied.
	Unsatisfiable = 20
)

// Struct Pigosat must be created with NewPigosat and stores the state of the
// solver. Once initialized by NewPigosat, it is safe for concurrent use.
type Pigosat struct {
	// Pointer to the underlying C struct.
	p    *C.PicoSAT
	lock *sync.RWMutex
}

// Settings that must be decided before adding any clauses. Zero values indicate
// default behavior.
type Options struct {
	// Set PropagationLimit to a positive value to limit how long the solver
	// tries to find a solution.
	PropagationLimit uint64

	// Default (nil value) output file stdout
	OutputFile *os.File

	/* Set verbosity level. A verbosity level of 1 and above prints more and
	* more detailed progress reports on the output file, set by
	* 'picosat_set_output'. Verbose messages are prefixed with the string set
	* by 'picosat_set_prefix'.
	 */
	Verbosity uint

	// Set the prefix used for printing verbose messages and statistics.
	// Default is "c ".
	Prefix string

	/* Measure all time spent in all calls in the solver.  By default only the
	 * time spent in 'picosat_sat' is measured.  Enabling this function may for
	 * instance triple the time needed to add large CNFs, since every call to
	 * 'picosat_add' will trigger a call to 'getrusage'.
	 */
	MeasureAllCalls bool
}

// cfdopen returns a C-level FILE*. mode should be as described in fdopen(3).
func cfdopen(file *os.File, mode string) (*C.FILE, error) {
	cmode := C.CString(mode)
	defer C.free(unsafe.Pointer(cmode))
	// FILE * fdopen(int fildes, const char *mode);
	cfile, err := C.fdopen(C.int(file.Fd()), cmode)
	if err != nil {
		return nil, err
	}
	if cfile == nil {
		return nil, syscall.EINVAL
	}
	return cfile, nil
}

// NewPigosat returns a new Pigosat instance, ready to have literals added to
// it. The error return value need only be checked if the OutputFile option is
// non-nil.
func NewPigosat(options *Options) (*Pigosat, error) {
	// PicoSAT * picosat_init (void);
	p := C.picosat_init()
	if options != nil {
		// void picosat_set_propagation_limit (PicoSAT *, unsigned long long limit);
		C.picosat_set_propagation_limit(p, C.ulonglong(options.PropagationLimit))
		if options.OutputFile != nil {
			cfile, err := cfdopen(options.OutputFile, "a")
			if err != nil {
				C.picosat_reset(p)
				return nil, &os.PathError{Op: "fdopen",
					Path: options.OutputFile.Name(), Err: err}
			}
			// void picosat_set_output (PicoSAT *, FILE *);
			C.picosat_set_output(p, cfile)
		}
		// void picosat_set_verbosity (PicoSAT *, int new_verbosity_level);
		C.picosat_set_verbosity(p, C.int(options.Verbosity))
		if options.Prefix != "" {
			// void picosat_set_prefix (PicoSAT *, const char *);
			prefix := C.CString(options.Prefix)
			defer C.free(unsafe.Pointer(prefix))
			C.picosat_set_prefix(p, prefix)
		}
		if options.MeasureAllCalls {
			// void picosat_measure_all_calls (PicoSAT *);
			C.picosat_measure_all_calls(p)
		}
	}
	pgo := &Pigosat{p: p, lock: new(sync.RWMutex)}
	runtime.SetFinalizer(pgo, (*Pigosat).Delete)
	return pgo, nil
}

// Delete may be called when you are done using a Pigosat instance, after which
// it cannot be used again. However, you only need to call this method if the
// instance's finalizer was reset using runtime.SetFinalizer (if you're not
// sure, it's always safe to call Delete again). Most users will not need this
// method.
func (p *Pigosat) Delete() {
	if p == nil || p.p == nil {
		return
	}
	p.lock.Lock()
	defer p.lock.Unlock()
	// void picosat_reset (PicoSAT *);
	C.picosat_reset(p.p)
	p.p = nil
	// No longer need a finalizer. See file.close (not File.close) in the os
	// package: http://golang.org/src/pkg/os/file_unix.go#L115 (sorry if the
	// line number ends up wrong).
	runtime.SetFinalizer(p, nil)
}

// Variables returns the number of variables in the formula: The m in the DIMACS
// header "p cnf <m> n".
func (p *Pigosat) Variables() int {
	if p == nil || p.p == nil {
		return 0
	}
	p.lock.RLock()
	defer p.lock.RUnlock()
	// int picosat_variables (PicoSAT *);
	return int(C.picosat_variables(p.p))
}

// AddedOriginalClauses returns the number of clauses in the formula: The n in
// the DIMACS header "p cnf m <n>".
func (p *Pigosat) AddedOriginalClauses() int {
	if p == nil || p.p == nil {
		return 0
	}
	p.lock.RLock()
	defer p.lock.RUnlock()
	// int picosat_added_original_clauses (PicoSAT *);
	return int(C.picosat_added_original_clauses(p.p))
}

// Seconds returns the time spent in the PicoSAT library.
func (p *Pigosat) Seconds() time.Duration {
	if p == nil || p.p == nil {
		return 0
	}
	p.lock.RLock()
	defer p.lock.RUnlock()
	// double picosat_seconds (PicoSAT *);
	return time.Duration(float64(C.picosat_seconds(p.p)) * float64(time.Second))
}

// AddClauses adds a list (as a slice) of clauses (the sub-slices). Each clause
// is a list of integers called literals. The absolute value of the literal i is
// the subscript for some variable x_i. If the literal is positive, x_i must end
// up being true when the formula is solved. If the literal is negative, it must
// end up false. Each clause ORs the literals together. All the clauses are
// ANDed together. Literals cannot be zero: a zero in the middle of a slice ends
// the clause, and causes AddClauses to skip reading the rest of the slice. Nil
// slices are ignored and skipped.
func (p *Pigosat) AddClauses(clauses [][]int32) {
	if p == nil || p.p == nil {
		return
	}
	p.lock.Lock()
	defer p.lock.Unlock()
	var had0 bool
	for _, clause := range clauses {
		if len(clause) == 0 {
			continue
		}
		had0 = false
		for _, lit := range clause {
			// int picosat_add (PicoSAT *, int lit);
			C.picosat_add(p.p, C.int(lit))
			if lit == 0 {
				had0 = true
				break
			}
		}
		if !had0 {
			C.picosat_add(p.p, 0)
		}
	}
}

// blocksol adds the inverse of the solution to the clauses.
// This private method does not acquire the lock or check if p is nil.
func (p *Pigosat) blocksol(sol []bool) {
	n := int(C.picosat_variables(p.p))
	for i := 1; i <= n; i++ {
		if sol[i] {
			C.picosat_add(p.p, C.int(-i))
		} else {
			C.picosat_add(p.p, C.int(i))
		}
	}
	C.picosat_add(p.p, 0)
}

// Solve the formula and return the status of the solution: one of the constants
// Unsatisfiable, Satisfiable, or Unknown. If satisfiable, return a slice
// indexed by the variables in the formula (so the first element is always
// false). Solve can be used like an iterator, yielding a new solution until
// there are no more feasible solutions:
//    for status, solution := p.Solve(); status == Satisfiable; status, solution = p.Solve {
//        // Do stuff with status, solution
//    }
func (p *Pigosat) Solve() (status int, solution []bool) {
	if p == nil || p.p == nil {
		return NotReady, nil
	}
	p.lock.Lock()
	defer p.lock.Unlock()
	// int picosat_sat (PicoSAT *, int decision_limit);
	status = int(C.picosat_sat(p.p, -1))
	if status == Unsatisfiable || status == Unknown {
		return
	} else if status != Satisfiable {
		panic(fmt.Errorf("Unknown sat status: %d", status))
	}
	n := int(C.picosat_variables(p.p)) // Calling Pigosat.Variables deadlocks
	solution = make([]bool, n+1)
	for i := 1; i <= n; i++ {
		// int picosat_deref (PicoSAT *, int lit);
		if val := C.picosat_deref(p.p, C.int(i)); val > 0 {
			solution[i] = true
		} else if val < 0 {
			solution[i] = false
		} else {
			panic(fmt.Errorf("Variable %d was assigned value 0", i))
		}
	}
	p.blocksol(solution)
	return
}

// BlockSolution adds a clause to the formula ruling out a given solution. It is
// a no-op if p is nil and returns an error if the solution is the wrong
// length. There is no need to call BlockSolution after calling Pigosat.Solve,
// which calls it automatically for every Satisfiable solution.
func (p *Pigosat) BlockSolution(solution []bool) error {
	if p == nil || p.p == nil {
		return nil
	}
	p.lock.Lock()
	defer p.lock.Unlock()
	if n := int(C.picosat_variables(p.p)); len(solution) != n+1 {
		return fmt.Errorf("solution length %d, but have %d variables",
			len(solution), n)
	}
	p.blocksol(solution)
	return nil
}
<commit_msg>Clarify Options doc comment<commit_after>// Copyright William Schwartz 2014. See the LICENSE file for more information.

// Package pigosat is a Go binding for the PicoSAT satisfiability solver.
//
// Designing your model is beyond the scope of this document, but Googling
// "satisfiability problem", "conjunctive normal form", and "DIMACS" are good
// places to start. Once you have your model, create a Pigosat instance p with
// pigosat.NewPigosat, add the model to the instance with p.AddClauses, and
// solve with p.Solve.
package pigosat

// picosat/libpicosat.a must exist to build this file. See README.md.

// #cgo CFLAGS: -I picosat
// #cgo LDFLAGS: -l picosat -L picosat
// #include "picosat.h"
import "C"
import (
	"fmt"
	"os"
	"runtime"
	"sync"
	"syscall"
	"time"
	"unsafe"
)

var Version = SemanticVersion{0, 3, 0, "", 0}

// PicosatVersion returns the version string from the underlying Picosat
// library.
func PicosatVersion() string {
	return C.GoString(C.picosat_version())
}

// Return values for Pigosat.Solve's status.
const (
	// NotReady is a solver status only used in PiGoSAT and it means the
	// underlying data structures of the solver have not been initialized or
	// their memory was previously freed.
	NotReady = -1
	// PicoSAT cannot determine the satisfiability of the formula.
	Unknown = 0
	// The formula is satisfiable.
	Satisfiable = 10
	// The formula cannot be satisfied.
	Unsatisfiable = 20
)

// Struct Pigosat must be created with NewPigosat and stores the state of the
// solver. Once initialized by NewPigosat, it is safe for concurrent use.
type Pigosat struct {
	// Pointer to the underlying C struct.
	p    *C.PicoSAT
	lock *sync.RWMutex
}

// Struct Options contains optional settings for the Pigosat constructor. Zero
// values for each field indicate default behavior.
type Options struct {
	// Set PropagationLimit to a positive value to limit how long the solver
	// tries to find a solution.
	PropagationLimit uint64

	// Default (nil value) output file stdout
	OutputFile *os.File

	/* Set verbosity level. A verbosity level of 1 and above prints more and
	* more detailed progress reports on the output file, set by
	* 'picosat_set_output'. Verbose messages are prefixed with the string set
	* by 'picosat_set_prefix'.
	 */
	Verbosity uint

	// Set the prefix used for printing verbose messages and statistics.
	// Default is "c ".
	Prefix string

	/* Measure all time spent in all calls in the solver.  By default only the
	 * time spent in 'picosat_sat' is measured.  Enabling this function may for
	 * instance triple the time needed to add large CNFs, since every call to
	 * 'picosat_add' will trigger a call to 'getrusage'.
	 */
	MeasureAllCalls bool
}

// cfdopen returns a C-level FILE*. mode should be as described in fdopen(3).
func cfdopen(file *os.File, mode string) (*C.FILE, error) {
	cmode := C.CString(mode)
	defer C.free(unsafe.Pointer(cmode))
	// FILE * fdopen(int fildes, const char *mode);
	cfile, err := C.fdopen(C.int(file.Fd()), cmode)
	if err != nil {
		return nil, err
	}
	if cfile == nil {
		return nil, syscall.EINVAL
	}
	return cfile, nil
}

// NewPigosat returns a new Pigosat instance, ready to have literals added to
// it. The error return value need only be checked if the OutputFile option is
// non-nil.
func NewPigosat(options *Options) (*Pigosat, error) {
	// PicoSAT * picosat_init (void);
	p := C.picosat_init()
	if options != nil {
		// void picosat_set_propagation_limit (PicoSAT *, unsigned long long limit);
		C.picosat_set_propagation_limit(p, C.ulonglong(options.PropagationLimit))
		if options.OutputFile != nil {
			cfile, err := cfdopen(options.OutputFile, "a")
			if err != nil {
				C.picosat_reset(p)
				return nil, &os.PathError{Op: "fdopen",
					Path: options.OutputFile.Name(), Err: err}
			}
			// void picosat_set_output (PicoSAT *, FILE *);
			C.picosat_set_output(p, cfile)
		}
		// void picosat_set_verbosity (PicoSAT *, int new_verbosity_level);
		C.picosat_set_verbosity(p, C.int(options.Verbosity))
		if options.Prefix != "" {
			// void picosat_set_prefix (PicoSAT *, const char *);
			prefix := C.CString(options.Prefix)
			defer C.free(unsafe.Pointer(prefix))
			C.picosat_set_prefix(p, prefix)
		}
		if options.MeasureAllCalls {
			// void picosat_measure_all_calls (PicoSAT *);
			C.picosat_measure_all_calls(p)
		}
	}
	pgo := &Pigosat{p: p, lock: new(sync.RWMutex)}
	runtime.SetFinalizer(pgo, (*Pigosat).Delete)
	return pgo, nil
}

// Delete may be called when you are done using a Pigosat instance, after which
// it cannot be used again. However, you only need to call this method if the
// instance's finalizer was reset using runtime.SetFinalizer (if you're not
// sure, it's always safe to call Delete again). Most users will not need this
// method.
func (p *Pigosat) Delete() {
	if p == nil || p.p == nil {
		return
	}
	p.lock.Lock()
	defer p.lock.Unlock()
	// void picosat_reset (PicoSAT *);
	C.picosat_reset(p.p)
	p.p = nil
	// No longer need a finalizer. See file.close (not File.close) in the os
	// package: http://golang.org/src/pkg/os/file_unix.go#L115 (sorry if the
	// line number ends up wrong).
	runtime.SetFinalizer(p, nil)
}

// Variables returns the number of variables in the formula: The m in the DIMACS
// header "p cnf <m> n".
func (p *Pigosat) Variables() int {
	if p == nil || p.p == nil {
		return 0
	}
	p.lock.RLock()
	defer p.lock.RUnlock()
	// int picosat_variables (PicoSAT *);
	return int(C.picosat_variables(p.p))
}

// AddedOriginalClauses returns the number of clauses in the formula: The n in
// the DIMACS header "p cnf m <n>".
func (p *Pigosat) AddedOriginalClauses() int {
	if p == nil || p.p == nil {
		return 0
	}
	p.lock.RLock()
	defer p.lock.RUnlock()
	// int picosat_added_original_clauses (PicoSAT *);
	return int(C.picosat_added_original_clauses(p.p))
}

// Seconds returns the time spent in the PicoSAT library.
func (p *Pigosat) Seconds() time.Duration {
	if p == nil || p.p == nil {
		return 0
	}
	p.lock.RLock()
	defer p.lock.RUnlock()
	// double picosat_seconds (PicoSAT *);
	return time.Duration(float64(C.picosat_seconds(p.p)) * float64(time.Second))
}

// AddClauses adds a list (as a slice) of clauses (the sub-slices). Each clause
// is a list of integers called literals. The absolute value of the literal i is
// the subscript for some variable x_i. If the literal is positive, x_i must end
// up being true when the formula is solved. If the literal is negative, it must
// end up false. Each clause ORs the literals together. All the clauses are
// ANDed together. Literals cannot be zero: a zero in the middle of a slice ends
// the clause, and causes AddClauses to skip reading the rest of the slice. Nil
// slices are ignored and skipped.
func (p *Pigosat) AddClauses(clauses [][]int32) {
	if p == nil || p.p == nil {
		return
	}
	p.lock.Lock()
	defer p.lock.Unlock()
	var had0 bool
	for _, clause := range clauses {
		if len(clause) == 0 {
			continue
		}
		had0 = false
		for _, lit := range clause {
			// int picosat_add (PicoSAT *, int lit);
			C.picosat_add(p.p, C.int(lit))
			if lit == 0 {
				had0 = true
				break
			}
		}
		if !had0 {
			C.picosat_add(p.p, 0)
		}
	}
}

// blocksol adds the inverse of the solution to the clauses.
// This private method does not acquire the lock or check if p is nil.
func (p *Pigosat) blocksol(sol []bool) {
	n := int(C.picosat_variables(p.p))
	for i := 1; i <= n; i++ {
		if sol[i] {
			C.picosat_add(p.p, C.int(-i))
		} else {
			C.picosat_add(p.p, C.int(i))
		}
	}
	C.picosat_add(p.p, 0)
}

// Solve the formula and return the status of the solution: one of the constants
// Unsatisfiable, Satisfiable, or Unknown. If satisfiable, return a slice
// indexed by the variables in the formula (so the first element is always
// false). Solve can be used like an iterator, yielding a new solution until
// there are no more feasible solutions:
//    for status, solution := p.Solve(); status == Satisfiable; status, solution = p.Solve {
//        // Do stuff with status, solution
//    }
func (p *Pigosat) Solve() (status int, solution []bool) {
	if p == nil || p.p == nil {
		return NotReady, nil
	}
	p.lock.Lock()
	defer p.lock.Unlock()
	// int picosat_sat (PicoSAT *, int decision_limit);
	status = int(C.picosat_sat(p.p, -1))
	if status == Unsatisfiable || status == Unknown {
		return
	} else if status != Satisfiable {
		panic(fmt.Errorf("Unknown sat status: %d", status))
	}
	n := int(C.picosat_variables(p.p)) // Calling Pigosat.Variables deadlocks
	solution = make([]bool, n+1)
	for i := 1; i <= n; i++ {
		// int picosat_deref (PicoSAT *, int lit);
		if val := C.picosat_deref(p.p, C.int(i)); val > 0 {
			solution[i] = true
		} else if val < 0 {
			solution[i] = false
		} else {
			panic(fmt.Errorf("Variable %d was assigned value 0", i))
		}
	}
	p.blocksol(solution)
	return
}

// BlockSolution adds a clause to the formula ruling out a given solution. It is
// a no-op if p is nil and returns an error if the solution is the wrong
// length. There is no need to call BlockSolution after calling Pigosat.Solve,
// which calls it automatically for every Satisfiable solution.
func (p *Pigosat) BlockSolution(solution []bool) error {
	if p == nil || p.p == nil {
		return nil
	}
	p.lock.Lock()
	defer p.lock.Unlock()
	if n := int(C.picosat_variables(p.p)); len(solution) != n+1 {
		return fmt.Errorf("solution length %d, but have %d variables",
			len(solution), n)
	}
	p.blocksol(solution)
	return nil
}
<|endoftext|>