<commit_before>package boardgame

import (
	"github.com/jkomoros/boardgame/enum"
)

//GameDelegate is the place that various parts of the game lifecycle can be
//modified to support this particular game. Typically you embed
//defaultGameDelegate in your won struct, and only override methods whose
//default behavior is incorrect for your game.
type GameDelegate interface {

	//Name is a string that defines the type of game this is. The name should
	//be unique and compact, and avoid any special characters other than "-"
	//or "_", since they will sometimes be used in a URL path. Good examples
	//are "tictactoe", "blackjack". Once configured, names should never change
	//over the lifetime of the gametype, since it will be persisted in
	//storage. Subclasses should override this.
	Name() string

	//DisplayName is a string that defines the type of game this is in a way
	//appropriate for humans. The name should be unique but human readable. It
	//is purely for human consumption, and may change over time with no
	//adverse effects. Good examples are "Tic Tac Toe", "Blackjack".
	//Subclasses should override this.
	DisplayName() string

	//Description is a string that describes the game type in a descriptive
	//sentence. A reasonable value for "tictactoe" is "A classic game where
	//players compete to get three in a row"
	Description() string

	//ConfigureMoves will be called during creation of a GameManager in
	//NewGameManager. This is the time to install moves onto the manager by
	//returning a list of moves to install. Typically you use moves.Combine
	//and friends to organize your list of moves to install. If the moves you
	//add are illegal for any reason, NewGameManager will fail with an error.
	//By the time this is called. delegate.SetManager will already have been
	//called, so you'll have access to the manager via Manager().
	ConfigureMoves() []MoveConfig

	//ConfigureAgents will be called when creating a new GameManager. Emit the
	//agents you want to install.
	ConfigureAgents() []Agent

	//ConfigureDecks will be called when the GameManager is being booted up.
	//Each entry in the return value will be configured on the ComponentChest
	//that is being created.
	ConfigureDecks() map[string]*Deck

	//ConfigureEnums is called during set up of a new GameManager. Return the
	//set of enums you want to be associated with this GameManagaer's Chest.
	ConfigureEnums() *enum.Set

	//ConfigureConstants is called during set-up of a new GameManager. Return
	//the map of constants you want to create, which will be configured onto
	//the newly created chest via AddConstant. If any of the AddConstant calls
	//errors, the GameManager will fail to be set up. Constants are primarily
	//useful in two cases: first, when you want to have access to a constant
	//value client-side, and second, when you want to be able to use a
	//constant value in a tag-based struct inflater.
	ConfigureConstants() PropertyCollection

	//GameStateConstructor and PlayerStateConstructor are called to get an
	//instantiation of the concrete game/player structs that your package
	//defines. This is used both to create the initial state, but also to
	//inflate states from the database. These methods should always return the
	//underlying same type of struct when called. This means that if different
	//players have very different roles in a game, there might be many
	//properties that are not in use for any given player. The simple
	//properties (ints, bools, strings) should all be their zero-value.
	//Importantly, all Stacks, Timers, and Enums should be non- nil, because
	//an initialized struct contains information about things like MaxSize,
	//Size, and a reference to the deck they are affiliated with. GameManger
	//will automatically create and use StructInflaters for these types of
	//objects, allowing you to use tag-based configuration to automatically
	//inflate these properties. See the documentation for StructInflater for
	//more. Since these two methods are always required and always specific to
	//each game type, defaultGameDelegate does not implement them, as an extra
	//reminder that you must implement them yourself.
	GameStateConstructor() ConfigurableSubState
	//PlayerStateConstructor is similar to GameStateConstructor, but
	//playerIndex is the value that this PlayerState must return when its
	//PlayerIndex() is called.
	PlayerStateConstructor(player PlayerIndex) ConfigurablePlayerState

	//DynamicComponentValuesConstructor returns an empty
	//DynamicComponentValues for the given deck. If nil is returned, then the
	//components in that deck don't have any dynamic component state. This
	//method must always return the same underlying type of struct for the
	//same deck. Like GameStateConstructor and PlayerStateConstructor, the
	//engine will automatically create StructInflaters for these objects,
	//allowing you to use tag-based inflation of properties. See StructInflate
	//for more. If the returned object also implements the ComponentValues
	//interface, then SetContainingComponent will be called on the
	//DynamicComponent whenever one is created, with a reference back to the
	//component it's associated with.
	DynamicComponentValuesConstructor(deck *Deck) ConfigurableSubState

	//DistributeComponentToStarterStack is called during set up to establish
	//the Deck/Stack invariant that every component in the chest is placed in
	//precisely one Stack. Game will call this on each component in the Chest
	//in order. This is where the logic goes to make sure each Component goes
	//into its correct starter stack. You must return a non-nil Stack for each
	//call, after which the given Component will be inserted into
	//NextSlotIndex of that stack. If that is not the ordering you desire, you
	//can fix it up in FinishSetUp by using SwapComponents. If any errors are
	//returned, any nil Stacks are returned, or any returned stacks don't have
	//space for another component, NewGame will fail and return an error.
	//State and Component are only provided for reference; do not modify them.
	DistributeComponentToStarterStack(state ImmutableState, c Component) (ImmutableStack, error)

	//BeginSetup is a chance to modify the initial state object *before* the
	//components are distributed to it. It is also where the variant
	//configuration for your gametype will be passed (it will already have
	//been checked for legality and had all configure defaults set), although
	//you can also retrieve that at any time via game.Variant(). This is a
	//good place to configure state that will be necessary for you to make the
	//right decisions in DistributeComponentToStarterStack, or to transcribe
	//config information you were passed into properties on your gameState as
	//appropriate. If error is non-nil, Game setup will be aborted, with the
	//reasoning including the error message provided.
	BeginSetUp(state State, variant Variant) error

	//FinishSetUp is called during NewGame, *after* components have been
	//distributed to their StarterStack. This is the last chance to modify the
	//state before the game's initial state is considered final. For example,
	//if you have a card game this is where you'd make sure the starter draw
	//stacks are shuffled. If your game has multiple rounds, or if you don't
	//want the game to start with it already set-up (e.g. you want to show
	//animations of starter cards being dealt) then it's probably best to do
	//most of the logic in a SetUp phase. See the README for more. If error is
	//non-nil, Game setup will be aborted, with the reasoning including the
	//error message provided.
	FinishSetUp(state State) error

	//CheckGameFinished should return true if the game is finished, and who
	//the winners are. Called after every move is applied.
	CheckGameFinished(state ImmutableState) (finished bool, winners []PlayerIndex)

	//ProposeFixUpMove is called after a move has been applied. It may return
	//a FixUp move, which will be applied before any other moves are applied.
	//If it returns nil, we may take the next move off of the queue. FixUp
	//moves are useful for things like shuffling a discard deck back into a
	//draw deck, or other moves that are necessary to get the GameState back
	//into reasonable shape.
	ProposeFixUpMove(state ImmutableState) Move

	//DefaultNumPlayers returns the number of users that this game defaults to.
	//For example, for tictactoe, it will be 2. If 0 is provided to
	//manager.NewGame(), we wil use this value instead.
	DefaultNumPlayers() int

	//Min/MaxNumPlayers should return the min and max number of players,
	//respectively. The engine doesn't use this directly, instead looking at
	//LegalNumPlayers. Typically your LegalNumPlayers will check the given
	//number of players is between these two extremes.
	MinNumPlayers() int
	MaxNumPlayers() int

	//LegalNumPlayers will be consulted when a new game is created. It should
	//return true if the given number of players is legal, and false
	//otherwise. If this returns false, the NewGame will fail with an error.
	//Game creation will automatically reject a numPlayers that does not
	//result in at least one player existing. Generally this is simply
	//checking to make sure the number of players is between Min and Max
	//(inclusive), although some games could only allow, for example, even
	//numbers of players.
	LegalNumPlayers(numPlayers int) bool

	//Variants returns a VariantConfig, which describes the different
	//categories of configuration values and the legal values they may take
	//on. Ultimately your LegalVariant is the final arbiter of which variants
	//are legal; this method is mainly used so that user interfaces know which
	//variants to show to the user. In general you shouldn't call this, but
	//instead call gameManager.Variants() which will ensure your VariantConfig
	//is initalized and memoize the return result.
	Variants() VariantConfig

	//CurrentPlayerIndex returns the index of the "current" player--a notion
	//that is game specific (and sometimes inapplicable). If CurrentPlayer
	//doesn't make sense (perhaps the game never has a notion of current
	//player, or the type of round that we're in has no current player), this
	//should return ObserverPlayerIndex. The result of this method is used to
	//power state.CurrentPlayer.
	CurrentPlayerIndex(state ImmutableState) PlayerIndex

	//CurrentPhase returns the phase that the game state is currently in.
	//Phase is a formalized convention used in moves.Base to make it easier to
	//write fix-up moves that only apply in certain phases, like SetUp. The
	//return result is primarily used in moves.Base to check whether it is one
	//of the phases in a give Move's LegalPhases. See moves.Base for more
	//information.
	CurrentPhase(state ImmutableState) int

	//PhaseEnum returns the enum for game phases (the return values of
	//CurrentPhase are expected to be valid enums within that enum). If this
	//returns a non-nil enums.TreeEnum, then the state will not be able to be
	//saved if CurrentPhase() returns a value that is not a leaf-node.
	PhaseEnum() enum.Enum

	//SanitizationPolicy is consulted when sanitizing states. It is called for
	//each prop in the state, including the set of groups that this player is
	//a mamber of. In practice the default behavior of defaultGameDelegate,
	//which uses struct tags to figure out the policy, is sufficient and you
	//do not need to override this. For more on how sanitization works, see
	//the package doc. The statePropetyRef passed will always have the Index
	//properties set to -1, signifying that the returned policy applies to all
	//items in the Stack/Board.
	SanitizationPolicy(prop StatePropertyRef, groupMembership map[int]bool) Policy

	//If you have computed properties that you want to be included in your
	//JSON (for example, for use clientside), export them here by creating a
	//dictionary with their values.
	ComputedGlobalProperties(state ImmutableState) PropertyCollection
	ComputedPlayerProperties(player ImmutablePlayerState) PropertyCollection

	//Diagram should return a basic debug rendering of state in multi-line
	//ascii art. Useful for debugging. State.Diagram() will reach out to this
	//method.
	Diagram(s ImmutableState) string

	//SetManager configures which manager this delegate is in use with. A
	//given delegate can only be used by a single manager at a time.
	SetManager(manager *GameManager)

	//Manager returns the Manager that was set on this delegate.
	Manager() *GameManager
}

//PropertyCollection is just an alias for map[string]interface{}
type PropertyCollection map[string]interface{}

//Copy returns a shallow copy of PropertyCollection
func (p PropertyCollection) Copy() PropertyCollection {
	result := make(PropertyCollection, len(p))
	for key, val := range result {
		result[key] = val
	}
	return result
}
<commit_msg>Update public docs that mentioned defaultGameDelegate. Part of #639.<commit_after>package boardgame

import (
	"github.com/jkomoros/boardgame/enum"
)

//GameDelegate is the place that various parts of the game lifecycle can be
//modified to support this particular game. Typically you embed
//base.GameDelegate in your won struct, and only override methods whose
//default behavior is incorrect for your game.
type GameDelegate interface {

	//Name is a string that defines the type of game this is. The name should
	//be unique and compact, and avoid any special characters other than "-"
	//or "_", since they will sometimes be used in a URL path. Good examples
	//are "tictactoe", "blackjack". Once configured, names should never change
	//over the lifetime of the gametype, since it will be persisted in
	//storage. Subclasses should override this.
	Name() string

	//DisplayName is a string that defines the type of game this is in a way
	//appropriate for humans. The name should be unique but human readable. It
	//is purely for human consumption, and may change over time with no
	//adverse effects. Good examples are "Tic Tac Toe", "Blackjack".
	//Subclasses should override this.
	DisplayName() string

	//Description is a string that describes the game type in a descriptive
	//sentence. A reasonable value for "tictactoe" is "A classic game where
	//players compete to get three in a row"
	Description() string

	//ConfigureMoves will be called during creation of a GameManager in
	//NewGameManager. This is the time to install moves onto the manager by
	//returning a list of moves to install. Typically you use moves.Combine
	//and friends to organize your list of moves to install. If the moves you
	//add are illegal for any reason, NewGameManager will fail with an error.
	//By the time this is called. delegate.SetManager will already have been
	//called, so you'll have access to the manager via Manager().
	ConfigureMoves() []MoveConfig

	//ConfigureAgents will be called when creating a new GameManager. Emit the
	//agents you want to install.
	ConfigureAgents() []Agent

	//ConfigureDecks will be called when the GameManager is being booted up.
	//Each entry in the return value will be configured on the ComponentChest
	//that is being created.
	ConfigureDecks() map[string]*Deck

	//ConfigureEnums is called during set up of a new GameManager. Return the
	//set of enums you want to be associated with this GameManagaer's Chest.
	ConfigureEnums() *enum.Set

	//ConfigureConstants is called during set-up of a new GameManager. Return
	//the map of constants you want to create, which will be configured onto
	//the newly created chest via AddConstant. If any of the AddConstant calls
	//errors, the GameManager will fail to be set up. Constants are primarily
	//useful in two cases: first, when you want to have access to a constant
	//value client-side, and second, when you want to be able to use a
	//constant value in a tag-based struct inflater.
	ConfigureConstants() PropertyCollection

	//GameStateConstructor and PlayerStateConstructor are called to get an
	//instantiation of the concrete game/player structs that your package
	//defines. This is used both to create the initial state, but also to
	//inflate states from the database. These methods should always return the
	//underlying same type of struct when called. This means that if different
	//players have very different roles in a game, there might be many
	//properties that are not in use for any given player. The simple
	//properties (ints, bools, strings) should all be their zero-value.
	//Importantly, all Stacks, Timers, and Enums should be non- nil, because
	//an initialized struct contains information about things like MaxSize,
	//Size, and a reference to the deck they are affiliated with. GameManger
	//will automatically create and use StructInflaters for these types of
	//objects, allowing you to use tag-based configuration to automatically
	//inflate these properties. See the documentation for StructInflater for
	//more. Since these two methods are always required and always specific to
	//each game type, defaultGameDelegate does not implement them, as an extra
	//reminder that you must implement them yourself.
	GameStateConstructor() ConfigurableSubState
	//PlayerStateConstructor is similar to GameStateConstructor, but
	//playerIndex is the value that this PlayerState must return when its
	//PlayerIndex() is called.
	PlayerStateConstructor(player PlayerIndex) ConfigurablePlayerState

	//DynamicComponentValuesConstructor returns an empty
	//DynamicComponentValues for the given deck. If nil is returned, then the
	//components in that deck don't have any dynamic component state. This
	//method must always return the same underlying type of struct for the
	//same deck. Like GameStateConstructor and PlayerStateConstructor, the
	//engine will automatically create StructInflaters for these objects,
	//allowing you to use tag-based inflation of properties. See StructInflate
	//for more. If the returned object also implements the ComponentValues
	//interface, then SetContainingComponent will be called on the
	//DynamicComponent whenever one is created, with a reference back to the
	//component it's associated with.
	DynamicComponentValuesConstructor(deck *Deck) ConfigurableSubState

	//DistributeComponentToStarterStack is called during set up to establish
	//the Deck/Stack invariant that every component in the chest is placed in
	//precisely one Stack. Game will call this on each component in the Chest
	//in order. This is where the logic goes to make sure each Component goes
	//into its correct starter stack. You must return a non-nil Stack for each
	//call, after which the given Component will be inserted into
	//NextSlotIndex of that stack. If that is not the ordering you desire, you
	//can fix it up in FinishSetUp by using SwapComponents. If any errors are
	//returned, any nil Stacks are returned, or any returned stacks don't have
	//space for another component, NewGame will fail and return an error.
	//State and Component are only provided for reference; do not modify them.
	DistributeComponentToStarterStack(state ImmutableState, c Component) (ImmutableStack, error)

	//BeginSetup is a chance to modify the initial state object *before* the
	//components are distributed to it. It is also where the variant
	//configuration for your gametype will be passed (it will already have
	//been checked for legality and had all configure defaults set), although
	//you can also retrieve that at any time via game.Variant(). This is a
	//good place to configure state that will be necessary for you to make the
	//right decisions in DistributeComponentToStarterStack, or to transcribe
	//config information you were passed into properties on your gameState as
	//appropriate. If error is non-nil, Game setup will be aborted, with the
	//reasoning including the error message provided.
	BeginSetUp(state State, variant Variant) error

	//FinishSetUp is called during NewGame, *after* components have been
	//distributed to their StarterStack. This is the last chance to modify the
	//state before the game's initial state is considered final. For example,
	//if you have a card game this is where you'd make sure the starter draw
	//stacks are shuffled. If your game has multiple rounds, or if you don't
	//want the game to start with it already set-up (e.g. you want to show
	//animations of starter cards being dealt) then it's probably best to do
	//most of the logic in a SetUp phase. See the README for more. If error is
	//non-nil, Game setup will be aborted, with the reasoning including the
	//error message provided.
	FinishSetUp(state State) error

	//CheckGameFinished should return true if the game is finished, and who
	//the winners are. Called after every move is applied.
	CheckGameFinished(state ImmutableState) (finished bool, winners []PlayerIndex)

	//ProposeFixUpMove is called after a move has been applied. It may return
	//a FixUp move, which will be applied before any other moves are applied.
	//If it returns nil, we may take the next move off of the queue. FixUp
	//moves are useful for things like shuffling a discard deck back into a
	//draw deck, or other moves that are necessary to get the GameState back
	//into reasonable shape.
	ProposeFixUpMove(state ImmutableState) Move

	//DefaultNumPlayers returns the number of users that this game defaults to.
	//For example, for tictactoe, it will be 2. If 0 is provided to
	//manager.NewGame(), we wil use this value instead.
	DefaultNumPlayers() int

	//Min/MaxNumPlayers should return the min and max number of players,
	//respectively. The engine doesn't use this directly, instead looking at
	//LegalNumPlayers. Typically your LegalNumPlayers will check the given
	//number of players is between these two extremes.
	MinNumPlayers() int
	MaxNumPlayers() int

	//LegalNumPlayers will be consulted when a new game is created. It should
	//return true if the given number of players is legal, and false
	//otherwise. If this returns false, the NewGame will fail with an error.
	//Game creation will automatically reject a numPlayers that does not
	//result in at least one player existing. Generally this is simply
	//checking to make sure the number of players is between Min and Max
	//(inclusive), although some games could only allow, for example, even
	//numbers of players.
	LegalNumPlayers(numPlayers int) bool

	//Variants returns a VariantConfig, which describes the different
	//categories of configuration values and the legal values they may take
	//on. Ultimately your LegalVariant is the final arbiter of which variants
	//are legal; this method is mainly used so that user interfaces know which
	//variants to show to the user. In general you shouldn't call this, but
	//instead call gameManager.Variants() which will ensure your VariantConfig
	//is initalized and memoize the return result.
	Variants() VariantConfig

	//CurrentPlayerIndex returns the index of the "current" player--a notion
	//that is game specific (and sometimes inapplicable). If CurrentPlayer
	//doesn't make sense (perhaps the game never has a notion of current
	//player, or the type of round that we're in has no current player), this
	//should return ObserverPlayerIndex. The result of this method is used to
	//power state.CurrentPlayer.
	CurrentPlayerIndex(state ImmutableState) PlayerIndex

	//CurrentPhase returns the phase that the game state is currently in.
	//Phase is a formalized convention used in moves.Base to make it easier to
	//write fix-up moves that only apply in certain phases, like SetUp. The
	//return result is primarily used in moves.Base to check whether it is one
	//of the phases in a give Move's LegalPhases. See moves.Base for more
	//information.
	CurrentPhase(state ImmutableState) int

	//PhaseEnum returns the enum for game phases (the return values of
	//CurrentPhase are expected to be valid enums within that enum). If this
	//returns a non-nil enums.TreeEnum, then the state will not be able to be
	//saved if CurrentPhase() returns a value that is not a leaf-node.
	PhaseEnum() enum.Enum

	//SanitizationPolicy is consulted when sanitizing states. It is called for
	//each prop in the state, including the set of groups that this player is
	//a mamber of. In practice the default behavior of defaultGameDelegate,
	//which uses struct tags to figure out the policy, is sufficient and you
	//do not need to override this. For more on how sanitization works, see
	//the package doc. The statePropetyRef passed will always have the Index
	//properties set to -1, signifying that the returned policy applies to all
	//items in the Stack/Board.
	SanitizationPolicy(prop StatePropertyRef, groupMembership map[int]bool) Policy

	//If you have computed properties that you want to be included in your
	//JSON (for example, for use clientside), export them here by creating a
	//dictionary with their values.
	ComputedGlobalProperties(state ImmutableState) PropertyCollection
	ComputedPlayerProperties(player ImmutablePlayerState) PropertyCollection

	//Diagram should return a basic debug rendering of state in multi-line
	//ascii art. Useful for debugging. State.Diagram() will reach out to this
	//method.
	Diagram(s ImmutableState) string

	//SetManager configures which manager this delegate is in use with. A
	//given delegate can only be used by a single manager at a time.
	SetManager(manager *GameManager)

	//Manager returns the Manager that was set on this delegate.
	Manager() *GameManager
}

//PropertyCollection is just an alias for map[string]interface{}
type PropertyCollection map[string]interface{}

//Copy returns a shallow copy of PropertyCollection
func (p PropertyCollection) Copy() PropertyCollection {
	result := make(PropertyCollection, len(p))
	for key, val := range result {
		result[key] = val
	}
	return result
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/ecopony/gamedayapi"
	"os"
	"strconv"
	"time"
)

var commands = map[string]func([]string) {}

func main() {
	initializeCommands()
	args := os.Args[1:]
	command := args[0]

	if function, ok := commands[command]; ok {
		function(args)
	} else {
		fmt.Println(fmt.Sprintf("%s is not a valid command. Valid commands:", command))
		printValidCommands()
		os.Exit(1)
	}
}

func initializeCommands() {
	commands["game"] = game
	commands["games-for-team-and-year"] = gameForTeamAndYears
	commands["games-for-year"] = gamesForYear
	commands["help"] = help
	commands["valid-teams-for-year"] = validTeamsForYear
}

func eagerLoadGame(game *gamedayapi.Game) {
	game.EagerLoad()
	fmt.Println("Game files saved to " + gamedayapi.BaseCachePath() + game.GameDataDirectory)
}

func printUsage() {
	fmt.Println("Usage: mlbgd <command> [<team code>] [<date|year(s)>]")
}

func printValidCommands() {
	printUsage()
	fmt.Println("Valid commands:")
	for validCommand, _ := range commands {
		fmt.Println(fmt.Sprintf("\t%s", validCommand))
	}
}

func validateArgLength(args []string, validLength int) {
	if len(args) <= validLength {
		printUsage()
		os.Exit(1)
	}
}

func game(args []string) {
	validateArgLength(args, 2)
	teamCode := args[1]
	date, err := time.Parse("2006-01-02", args[2])
	if err != nil {
		fmt.Println("Date must be in the format 2006-01-02")
		os.Exit(1)
	}
	game, err := gamedayapi.GameFor(teamCode, date)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	game.EagerLoad()
	fmt.Println("Game files saved to " + gamedayapi.BaseCachePath() + game.GameDataDirectory)
}

func gameForTeamAndYear(args []string) {
	validateArgLength(args, 1)
	yearArg := args[1]
	year, err := strconv.Atoi(yearArg)
	if err != nil {
		fmt.Println("Year is not valid")
	}
	teams := gamedayapi.TeamsForYear(year)
	for _, team := range teams {
		fmt.Println(team)
		gamedayapi.FetchByTeamAndYear(team, year, eagerLoadGame) // No goroutines here yet.
	}
}

func gameForTeamAndYears(args []string) {
	gamesFor(args)
}

func gamesForYear(args []string) {
	gamesFor(args)
}

func gamesFor(args []string) {
	validateArgLength(args, 2)
	teamCode := args[1]
	yearArgs := args[2:]
	var years []int
	for i := 0; i < len(yearArgs); i++ {
		year, err := strconv.Atoi(yearArgs[i])
		if err != nil {
			fmt.Println("Year is not valid")
		}
		years = append(years, year)
	}
	gamedayapi.FetchByTeamAndYears(teamCode, years, eagerLoadGame)
}

func help(args []string) {
	printValidCommands()
	os.Exit(0)
}

func validTeamsForYear(args []string) {
	validateArgLength(args, 1)
	yearArg := args[1]
	year, err := strconv.Atoi(yearArg)
	if err != nil {
		fmt.Println("Year is not valid")
	}
	teams := gamedayapi.TeamsForYear(year)
	fmt.Println(teams)
}
<commit_msg>Fixing up previous refactor.<commit_after>package main

import (
	"fmt"
	"github.com/ecopony/gamedayapi"
	"os"
	"strconv"
	"time"
)

var commands = map[string]func([]string) {}

func main() {
	initializeCommands()
	args := os.Args[1:]
	command := args[0]

	if function, ok := commands[command]; ok {
		function(args)
	} else {
		fmt.Println(fmt.Sprintf("%s is not a valid command. Valid commands:", command))
		printValidCommands()
		os.Exit(1)
	}
}

func initializeCommands() {
	commands["game"] = game
	commands["games-for-team-and-years"] = gameForTeamAndYears
	commands["games-for-year"] = gamesForYear
	commands["help"] = help
	commands["valid-teams-for-year"] = validTeamsForYear
}

func eagerLoadGame(game *gamedayapi.Game) {
	game.EagerLoad()
	fmt.Println("Game files saved to " + gamedayapi.BaseCachePath() + game.GameDataDirectory)
}

func printUsage() {
	fmt.Println("Usage: mlbgd <command> [<team code>] [<date|year(s)>]")
}

func printValidCommands() {
	printUsage()
	fmt.Println("Valid commands:")
	for validCommand, _ := range commands {
		fmt.Println(fmt.Sprintf("\t%s", validCommand))
	}
}

func validateArgLength(args []string, validLength int) {
	if len(args) <= validLength {
		printUsage()
		os.Exit(1)
	}
}

func game(args []string) {
	validateArgLength(args, 2)
	teamCode := args[1]
	date, err := time.Parse("2006-01-02", args[2])
	if err != nil {
		fmt.Println("Date must be in the format 2006-01-02")
		os.Exit(1)
	}
	game, err := gamedayapi.GameFor(teamCode, date)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	game.EagerLoad()
	fmt.Println("Game files saved to " + gamedayapi.BaseCachePath() + game.GameDataDirectory)
}

func gameForTeamAndYears(args []string) {
	gamesFor(args)
}

func gamesFor(args []string) {
	validateArgLength(args, 2)
	teamCode := args[1]
	yearArgs := args[2:]
	var years []int
	for i := 0; i < len(yearArgs); i++ {
		year, err := strconv.Atoi(yearArgs[i])
		if err != nil {
			fmt.Println("Year is not valid")
		}
		years = append(years, year)
	}
	gamedayapi.FetchByTeamAndYears(teamCode, years, eagerLoadGame)
}

func gamesForYear(args []string) {
	validateArgLength(args, 1)
	yearArg := args[1]
	year, err := strconv.Atoi(yearArg)
	if err != nil {
		fmt.Println("Year is not valid")
	}
	teams := gamedayapi.TeamsForYear(year)
	for _, team := range teams {
		fmt.Println(team)
		gamedayapi.FetchByTeamAndYear(team, year, eagerLoadGame) // No goroutines here yet.
	}
}

func help(args []string) {
	printValidCommands()
	os.Exit(0)
}

func validTeamsForYear(args []string) {
	validateArgLength(args, 1)
	yearArg := args[1]
	year, err := strconv.Atoi(yearArg)
	if err != nil {
		fmt.Println("Year is not valid")
	}
	teams := gamedayapi.TeamsForYear(year)
	fmt.Println(teams)
}
<|endoftext|>


<commit_before>package requests_test

import (
	"bytes"
	"io/ioutil"
	"net/http"
	"testing"

	"github.com/mschwager/riplink/src/requests"
)

type MockClient struct {
	Body string
	Code int
	Err  error
}

func (client MockClient) Do(request *http.Request) (response *http.Response, err error) {
	response = &http.Response{
		StatusCode: client.Code,
		Body:       ioutil.NopCloser(bytes.NewReader([]byte(client.Body))),
	}

	return response, client.Err
}

func TestDoBasic(t *testing.T) {
	body := ""
	code := 200

	client := MockClient{
		Body: body,
		Code: code,
	}

	result_body, result_code, result_err := requests.Request(client, "UNUSED", "UNUSED", nil)

	if result_body != body || result_code != code || result_err != nil {
		t.Error("Failed to parse request: ", client)
	}
}
<commit_msg>Added test for error state in requests library<commit_after>package requests_test

import (
	"bytes"
	"errors"
	"io/ioutil"
	"net/http"
	"testing"

	"github.com/mschwager/riplink/src/requests"
)

type MockClient struct {
	Body string
	Code int
	Err  error
}

func (client MockClient) Do(request *http.Request) (response *http.Response, err error) {
	response = &http.Response{
		StatusCode: client.Code,
		Body:       ioutil.NopCloser(bytes.NewReader([]byte(client.Body))),
	}

	return response, client.Err
}

func TestDoBasic(t *testing.T) {
	body := ""
	code := 200

	client := MockClient{
		Body: body,
		Code: code,
	}

	result_body, result_code, result_err := requests.Request(client, "UNUSED", "UNUSED", nil)

	if result_body != body || result_code != code || result_err != nil {
		t.Error("Failed to parse request: ", client)
	}
}

func TestDoError(t *testing.T) {
	body := ""
	code := 0
	err := errors.New("")

	client := MockClient{
		Body: body,
		Code: code,
		Err:  err,
	}

	result_body, result_code, result_err := requests.Request(client, "UNUSED", "UNUSED", nil)

	if result_body != body || result_code != code || result_err != err {
		t.Error("Failed to parse request: ", client)
	}
}
<|endoftext|>


<commit_before>package modbusone

import (
	"io"
	"runtime"
)

type rtuPacketReader struct {
	r          io.Reader //the underlining reader
	isClient   bool
	bufferSize int
}

//NewRTUPacketReader create a Reader that can read full packets.
//
//The input reader r must only return a read for only two siturations:
//reading stopped as per Modbus timing based packet terminater,
//or buffer is full.
//
//isClient set how RTU is interpeted.
//
//bufferSize is the max read size in bytes of the given reader. When a read reaches
//bufferSize, a formate aware RTU delimiter is used.
func NewRTUPacketReader(r io.Reader, isClient bool, bufferSize int) io.Reader {
	return &rtuPacketReader{r, isClient, bufferSize}
}

func (s *rtuPacketReader) Read(p []byte) (int, error) {
	expected := smallestRTUSize
	read := 0
	for read < expected {
		n, err := s.r.Read(p[read:])
		debugf("RTUPacketReader read (%v+%v)/%v %v, expcted %v, bufferSize %v", read, n, len(p), err, expected, s.bufferSize)
		read += n
		if n > s.bufferSize {
			debugf("recalibrating rtuPacketReader bufferSize to %v (sees larger packet)", n)
			s.bufferSize = n
		}
		if err != nil || read == len(p) {
			return read, err
		}
		if read > 0 && n < s.bufferSize {
			//some data is read, but buffer is not filled,
			//reader must have detected time based message termination.

			if runtime.GOOS == "windows" && (n == 1 || n == s.bufferSize-1) {
				//windows Overlapped IO short read work around
				//a full buffer read might be split into a 1 and a full-1
			} else {
				if read < expected {
					debugf("recalibrating rtuPacketReader bufferSize to %v (sees early termination)", n)
					s.bufferSize = n
				}
				return read, nil
			}
		}
		if read < expected {
			//lets read more
			continue
		}
		//lets see if there is more to read
		expected = GetRTUSizeFromHeader(p, s.isClient)
		debugf("RTUPacketReader new expected size %v", expected)
	}
	return read, nil
}

//GetPDUSizeFromHeader returns the expected sized of a pdu packet with the given
//PDU header, if not enough info is in the header, then it returns the shortest possible.
//isClient is true if a client/master is reading the packet.
func GetPDUSizeFromHeader(header []byte, isClient bool) int {
	if len(header) < 2 {
		return 2
	}
	ec, f := FunctionCode(header[0]).SeparateError()
	if ec || !f.Valid() {
		return 2
	}
	if isClient == f.IsWriteToServer() {
		//all packets without data: fc, address, and count
		return 5
	}
	if isClient {
		//all data replies: fc, data bytes, data
		return 2 + int(header[1])
	}
	if f.IsSingle() {
		//fc, address, one data
		return 5
	}
	//fc, adress, count, data bytes, data
	if len(header) < 6 {
		return 6
	}
	if OverSizeSupport {
		n := int(header[3])*256 + int(header[4])
		if f.IsUint16() {
			return 6 + n*2
		}
		return 6 + (n-1)/8 + 1
	}
	return 6 + int(header[5])
}

//GetRTUSizeFromHeader returns the expected sized of a rtu packet with the given
//RTU header, if not enough info is in the header, then it returns the shortest possible.
//isClient is true if a client/master is reading the packet.
func GetRTUSizeFromHeader(header []byte, isClient bool) int {
	if len(header) < 3 {
		return 3
	}
	return GetPDUSizeFromHeader(header[1:], isClient) + 3
}
<commit_msg>fix update expected size when testing read length<commit_after>package modbusone

import (
	"io"
	"runtime"
)

type rtuPacketReader struct {
	r          io.Reader //the underlining reader
	isClient   bool
	bufferSize int
}

//NewRTUPacketReader create a Reader that can read full packets.
//
//The input reader r must only return a read for only two siturations:
//reading stopped as per Modbus timing based packet terminater,
//or buffer is full.
//
//isClient set how RTU is interpeted.
//
//bufferSize is the max read size in bytes of the given reader. When a read reaches
//bufferSize, a formate aware RTU delimiter is used.
func NewRTUPacketReader(r io.Reader, isClient bool, bufferSize int) io.Reader {
	return &rtuPacketReader{r, isClient, bufferSize}
}

func (s *rtuPacketReader) Read(p []byte) (int, error) {
	expected := smallestRTUSize
	read := 0
	for read < expected {
		n, err := s.r.Read(p[read:])
		debugf("RTUPacketReader read (%v+%v)/%v %v, expcted %v, bufferSize %v", read, n, len(p), err, expected, s.bufferSize)
		read += n
		if n > s.bufferSize {
			debugf("recalibrating rtuPacketReader bufferSize to %v (sees larger packet)", n)
			s.bufferSize = n
		}
		if err != nil || read == len(p) {
			return read, err
		}
		if read > 0 && n < s.bufferSize {
			//some data is read, but buffer is not filled,
			//reader must have detected time based message termination.

			if runtime.GOOS == "windows" && (n == 1 || n == s.bufferSize-1) {
				//windows Overlapped IO short read work around
				//a full buffer read might be split into a 1 and a full-1
			} else {
				expected = GetRTUSizeFromHeader(p, s.isClient)
				if read < expected {
					debugf("recalibrating rtuPacketReader bufferSize to %v (sees early termination)", n)
					s.bufferSize = n
				}
				return read, nil
			}
		}
		if read < expected {
			//lets read more
			continue
		}
		//lets see if there is more to read
		expected = GetRTUSizeFromHeader(p, s.isClient)
		debugf("RTUPacketReader new expected size %v", expected)
	}
	return read, nil
}

//GetPDUSizeFromHeader returns the expected sized of a pdu packet with the given
//PDU header, if not enough info is in the header, then it returns the shortest possible.
//isClient is true if a client/master is reading the packet.
func GetPDUSizeFromHeader(header []byte, isClient bool) int {
	if len(header) < 2 {
		return 2
	}
	ec, f := FunctionCode(header[0]).SeparateError()
	if ec || !f.Valid() {
		return 2
	}
	if isClient == f.IsWriteToServer() {
		//all packets without data: fc, address, and count
		return 5
	}
	if isClient {
		//all data replies: fc, data bytes, data
		return 2 + int(header[1])
	}
	if f.IsSingle() {
		//fc, address, one data
		return 5
	}
	//fc, adress, count, data bytes, data
	if len(header) < 6 {
		return 6
	}
	if OverSizeSupport {
		n := int(header[3])*256 + int(header[4])
		if f.IsUint16() {
			return 6 + n*2
		}
		return 6 + (n-1)/8 + 1
	}
	return 6 + int(header[5])
}

//GetRTUSizeFromHeader returns the expected sized of a rtu packet with the given
//RTU header, if not enough info is in the header, then it returns the shortest possible.
//isClient is true if a client/master is reading the packet.
func GetRTUSizeFromHeader(header []byte, isClient bool) int {
	if len(header) < 3 {
		return 3
	}
	return GetPDUSizeFromHeader(header[1:], isClient) + 3
}
<|endoftext|>


<commit_before>package storage

import (
	"testing"
)

var (
	testProjectName = "andygrunwald/TrendingGithub"
)

func TestMemory_MarkRepositoryAsTweeted(t *testing.T) {
	storage := MemoryStorage{}
	pool := storage.NewPool("", "")
	conn := pool.Get()

	res, err := conn.MarkRepositoryAsTweeted(testProjectName, "1440946305")
	if err != nil {
		t.Fatalf("Error of marking repository: \"%s\"", err)
	}

	if res == false {
		t.Fatal("Marking repositoriy failed, got false, expected true")
	}
}

func TestMemory_IsRepositoryAlreadyTweeted(t *testing.T) {
	storage := MemoryStorage{}
	pool := storage.NewPool("", "")
	conn := pool.Get()

	res, err := conn.IsRepositoryAlreadyTweeted(testProjectName)
	if err != nil {
		t.Fatalf("First already tweeted check throws an error: \"%s\"", err)
	}
	if res == true {
		t.Fatal("Repository was already tweeted, got true, expected false")
	}

	res, err = conn.MarkRepositoryAsTweeted(testProjectName, "1440946884")
	if err != nil {
		t.Fatalf("Error of marking repository: \"%s\"", err)
	}

	if res == false {
		t.Fatal("Marking repositoriy failed, got false, expected true")
	}

	res, err = conn.IsRepositoryAlreadyTweeted(testProjectName)
	if err != nil {
		t.Fatalf("Second already tweeted check throws an error: \"%s\"", err)
	}
	if res == false {
		t.Fatal("Repository was not already tweeted, got false, expected true")
	}
}
<commit_msg>Added close calls to memory storage unit test<commit_after>package storage

import (
	"testing"
)

var (
	memoryTestProjectName = "andygrunwald/TrendingGithub"
)

func TestMemory_MarkRepositoryAsTweeted(t *testing.T) {
	storage := MemoryStorage{}
	pool := storage.NewPool("", "")
	defer pool.Close()
	conn := pool.Get()
	defer conn.Close()

	res, err := conn.MarkRepositoryAsTweeted(memoryTestProjectName, "1440946305")
	if err != nil {
		t.Fatalf("Error of marking repository: \"%s\"", err)
	}

	if res == false {
		t.Fatal("Marking repositoriy failed, got false, expected true")
	}
}

func TestMemory_IsRepositoryAlreadyTweeted(t *testing.T) {
	storage := MemoryStorage{}
	pool := storage.NewPool("", "")
	defer pool.Close()
	conn := pool.Get()
	defer conn.Close()

	res, err := conn.IsRepositoryAlreadyTweeted(memoryTestProjectName)
	if err != nil {
		t.Fatalf("First already tweeted check throws an error: \"%s\"", err)
	}
	if res == true {
		t.Fatal("Repository was already tweeted, got true, expected false")
	}

	res, err = conn.MarkRepositoryAsTweeted(memoryTestProjectName, "1440946884")
	if err != nil {
		t.Fatalf("Error of marking repository: \"%s\"", err)
	}

	if res == false {
		t.Fatal("Marking repositoriy failed, got false, expected true")
	}

	res, err = conn.IsRepositoryAlreadyTweeted(memoryTestProjectName)
	if err != nil {
		t.Fatalf("Second already tweeted check throws an error: \"%s\"", err)
	}
	if res == false {
		t.Fatal("Repository was not already tweeted, got false, expected true")
	}
}
<|endoftext|>


<commit_before>// Copyright 2013 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.

// Package redis implements the storage interface for a BitTorrent tracker.
//
// The client whitelist is represented as a set with the key name "whitelist"
// with an optional prefix. Torrents and users are JSON-formatted strings.
// Torrents' keys are named "torrent:<infohash>" with an optional prefix.
// Users' keys are named "user:<passkey>" with an optional prefix.
package redis

import (
	"encoding/json"
	"errors"
	"strings"
	"time"

	"github.com/garyburd/redigo/redis"

	"github.com/pushrax/chihaya/config"
	"github.com/pushrax/chihaya/storage"
)

type driver struct{}

func (d *driver) New(conf *config.Storage) storage.Pool {
	return &Pool{
		conf: conf,
		pool: redis.Pool{
			MaxIdle:      conf.MaxIdleConn,
			IdleTimeout:  conf.IdleTimeout.Duration,
			Dial:         makeDialFunc(conf),
			TestOnBorrow: testOnBorrow,
		},
	}
}

func makeDialFunc(conf *config.Storage) func() (redis.Conn, error) {
	return func() (redis.Conn, error) {
		var (
			conn redis.Conn
			err  error
		)

		if conf.ConnTimeout != nil {
			conn, err = redis.DialTimeout(
				conf.Network,
				conf.Addr,
				conf.ConnTimeout.Duration, // Connect Timeout
				conf.ConnTimeout.Duration, // Read Timeout
				conf.ConnTimeout.Duration, // Write Timeout
			)
		} else {
			conn, err = redis.Dial(conf.Network, conf.Addr)
		}
		if err != nil {
			return nil, err
		}
		return conn, nil
	}
}

func testOnBorrow(c redis.Conn, t time.Time) error {
	_, err := c.Do("PING")
	return err
}

type Pool struct {
	conf *config.Storage
	pool redis.Pool
}

func (p *Pool) Close() error {
	return p.pool.Close()
}

func (p *Pool) Get() (storage.Tx, error) {
	return &Tx{
		conf:  p.conf,
		done:  false,
		multi: false,
		Conn:  p.pool.Get(),
	}, nil
}

// Tx represents a transaction for Redis with one gotcha:
// all reads must be done prior to any writes. Writes will
// check if the MULTI command has been sent to redis and will
// send it if it hasn't.
//
// Internally a transaction looks like:
// WATCH keyA
// GET keyA
// WATCH keyB
// GET keyB
// MULTI
// SET keyA
// SET keyB
// EXEC
type Tx struct {
	conf  *config.Storage
	done  bool
	multi bool
	redis.Conn
}

func (tx *Tx) close() {
	if tx.done {
		panic("redis: transaction closed twice")
	}
	tx.done = true
	tx.Conn.Close()
}

func (tx *Tx) initiateWrite() error {
	if tx.done {
		return storage.ErrTxDone
	}
	if tx.multi != true {
		return tx.Send("MULTI")
	}
	return nil
}

func (tx *Tx) initiateRead() error {
	if tx.done {
		return storage.ErrTxDone
	}
	if tx.multi == true {
		return errors.New("Tried to read during MULTI")
	}
	return nil
}

func (tx *Tx) Commit() error {
	if tx.done {
		return storage.ErrTxDone
	}
	if tx.multi == true {
		_, err := tx.Do("EXEC")
		if err != nil {
			return err
		}
	}
	tx.close()
	return nil
}

func (tx *Tx) Rollback() error {
	if tx.done {
		return storage.ErrTxDone
	}
	// Redis doesn't need to do anything. Exec is atomic.
	tx.close()
	return nil
}

func (tx *Tx) FindUser(passkey string) (*storage.User, bool, error) {
	err := tx.initiateRead()
	if err != nil {
		return nil, false, err
	}

	key := tx.conf.Prefix + "user:" + passkey
	_, err = tx.Do("WATCH", key)
	if err != nil {
		return nil, false, err
	}
	reply, err := redis.String(tx.Do("GET", key))
	if err != nil {
		if err == redis.ErrNil {
			return nil, false, nil
		}
		return nil, false, err
	}

	user := &storage.User{}
	err = json.NewDecoder(strings.NewReader(reply)).Decode(user)
	if err != nil {
		return nil, true, err
	}
	return user, true, nil
}

func (tx *Tx) FindTorrent(infohash string) (*storage.Torrent, bool, error) {
	err := tx.initiateRead()
	if err != nil {
		return nil, false, err
	}

	key := tx.conf.Prefix + "torrent:" + infohash
	_, err = tx.Do("WATCH", key)
	if err != nil {
		return nil, false, err
	}
	reply, err := redis.String(tx.Do("GET", key))
	if err != nil {
		if err == redis.ErrNil {
			return nil, false, nil
		}
		return nil, false, err
	}

	torrent := &storage.Torrent{}
	err = json.NewDecoder(strings.NewReader(reply)).Decode(torrent)
	if err != nil {
		return nil, true, err
	}
	return torrent, true, nil
}

func (tx *Tx) ClientWhitelisted(peerID string) (exists bool, err error) {
	err = tx.initiateRead()
	if err != nil {
		return false, err
	}

	key := tx.conf.Prefix + "whitelist"
	_, err = tx.Do("WATCH", key)
	if err != nil {
		return
	}

	// TODO
	return
}

func (tx *Tx) RecordSnatch(user *storage.User, torrent *storage.Torrent) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) MarkActive(t *storage.Torrent) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) AddLeecher(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) SetLeecher(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) RemoveLeecher(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) AddSeeder(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) SetSeeder(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) RemoveSeeder(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) IncrementSlots(u *storage.User) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) DecrementSlots(u *storage.User) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func init() {
	storage.Register("redis", &driver{})
}
<commit_msg>panic on reads inside of a MULTI<commit_after>// Copyright 2013 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.

// Package redis implements the storage interface for a BitTorrent tracker.
//
// The client whitelist is represented as a set with the key name "whitelist"
// with an optional prefix. Torrents and users are JSON-formatted strings.
// Torrents' keys are named "torrent:<infohash>" with an optional prefix.
// Users' keys are named "user:<passkey>" with an optional prefix.
package redis

import (
	"encoding/json"
	"strings"
	"time"

	"github.com/garyburd/redigo/redis"

	"github.com/pushrax/chihaya/config"
	"github.com/pushrax/chihaya/storage"
)

type driver struct{}

func (d *driver) New(conf *config.Storage) storage.Pool {
	return &Pool{
		conf: conf,
		pool: redis.Pool{
			MaxIdle:      conf.MaxIdleConn,
			IdleTimeout:  conf.IdleTimeout.Duration,
			Dial:         makeDialFunc(conf),
			TestOnBorrow: testOnBorrow,
		},
	}
}

func makeDialFunc(conf *config.Storage) func() (redis.Conn, error) {
	return func() (redis.Conn, error) {
		var (
			conn redis.Conn
			err  error
		)

		if conf.ConnTimeout != nil {
			conn, err = redis.DialTimeout(
				conf.Network,
				conf.Addr,
				conf.ConnTimeout.Duration, // Connect Timeout
				conf.ConnTimeout.Duration, // Read Timeout
				conf.ConnTimeout.Duration, // Write Timeout
			)
		} else {
			conn, err = redis.Dial(conf.Network, conf.Addr)
		}
		if err != nil {
			return nil, err
		}
		return conn, nil
	}
}

func testOnBorrow(c redis.Conn, t time.Time) error {
	_, err := c.Do("PING")
	return err
}

type Pool struct {
	conf *config.Storage
	pool redis.Pool
}

func (p *Pool) Close() error {
	return p.pool.Close()
}

func (p *Pool) Get() (storage.Tx, error) {
	return &Tx{
		conf:  p.conf,
		done:  false,
		multi: false,
		Conn:  p.pool.Get(),
	}, nil
}

// Tx represents a transaction for Redis with one gotcha:
// all reads must be done prior to any writes. Writes will
// check if the MULTI command has been sent to redis and will
// send it if it hasn't.
//
// Internally a transaction looks like:
// WATCH keyA
// GET keyA
// WATCH keyB
// GET keyB
// MULTI
// SET keyA
// SET keyB
// EXEC
type Tx struct {
	conf  *config.Storage
	done  bool
	multi bool
	redis.Conn
}

func (tx *Tx) close() {
	if tx.done {
		panic("redis: transaction closed twice")
	}
	tx.done = true
	tx.Conn.Close()
}

func (tx *Tx) initiateWrite() error {
	if tx.done {
		return storage.ErrTxDone
	}
	if tx.multi != true {
		return tx.Send("MULTI")
	}
	return nil
}

func (tx *Tx) initiateRead() error {
	if tx.done {
		return storage.ErrTxDone
	}
	if tx.multi == true {
		panic("Tried to read during MULTI")
	}
	return nil
}

func (tx *Tx) Commit() error {
	if tx.done {
		return storage.ErrTxDone
	}
	if tx.multi == true {
		_, err := tx.Do("EXEC")
		if err != nil {
			return err
		}
	}
	tx.close()
	return nil
}

func (tx *Tx) Rollback() error {
	if tx.done {
		return storage.ErrTxDone
	}
	// Redis doesn't need to do anything. Exec is atomic.
	tx.close()
	return nil
}

func (tx *Tx) FindUser(passkey string) (*storage.User, bool, error) {
	err := tx.initiateRead()
	if err != nil {
		return nil, false, err
	}

	key := tx.conf.Prefix + "user:" + passkey
	_, err = tx.Do("WATCH", key)
	if err != nil {
		return nil, false, err
	}
	reply, err := redis.String(tx.Do("GET", key))
	if err != nil {
		if err == redis.ErrNil {
			return nil, false, nil
		}
		return nil, false, err
	}

	user := &storage.User{}
	err = json.NewDecoder(strings.NewReader(reply)).Decode(user)
	if err != nil {
		return nil, true, err
	}
	return user, true, nil
}

func (tx *Tx) FindTorrent(infohash string) (*storage.Torrent, bool, error) {
	err := tx.initiateRead()
	if err != nil {
		return nil, false, err
	}

	key := tx.conf.Prefix + "torrent:" + infohash
	_, err = tx.Do("WATCH", key)
	if err != nil {
		return nil, false, err
	}
	reply, err := redis.String(tx.Do("GET", key))
	if err != nil {
		if err == redis.ErrNil {
			return nil, false, nil
		}
		return nil, false, err
	}

	torrent := &storage.Torrent{}
	err = json.NewDecoder(strings.NewReader(reply)).Decode(torrent)
	if err != nil {
		return nil, true, err
	}
	return torrent, true, nil
}

func (tx *Tx) ClientWhitelisted(peerID string) (exists bool, err error) {
	err = tx.initiateRead()
	if err != nil {
		return false, err
	}

	key := tx.conf.Prefix + "whitelist"
	_, err = tx.Do("WATCH", key)
	if err != nil {
		return
	}

	// TODO
	return
}

func (tx *Tx) RecordSnatch(user *storage.User, torrent *storage.Torrent) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) MarkActive(t *storage.Torrent) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) AddLeecher(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) SetLeecher(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) RemoveLeecher(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) AddSeeder(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) SetSeeder(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) RemoveSeeder(t *storage.Torrent, p *storage.Peer) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) IncrementSlots(u *storage.User) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func (tx *Tx) DecrementSlots(u *storage.User) error {
	if err := tx.initiateWrite(); err != nil {
		return err
	}

	// TODO
	return nil
}

func init() {
	storage.Register("redis", &driver{})
}
<|endoftext|>


<commit_before>//go:build linux && cgo
// +build linux,cgo

package drivers

import (
	"fmt"
	"os"
	"unsafe"

	"github.com/pkg/errors"

	// Used by cgo
	_ "github.com/lxc/lxd/lxd/include"
)

/*
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#define _FILE_OFFSET_BITS 64
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/loop.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "../../include/macro.h"
#include "../../include/memory_utils.h"

#define LXD_MAXPATH 4096
#define LXD_NUMSTRLEN64 21
#define LXD_MAX_LOOP_PATHLEN (2 * sizeof("loop/")) + LXD_NUMSTRLEN64 + sizeof("backing_file") + 1

// If a loop file is already associated with a loop device, find it.
// This looks at "/sys/block" to avoid having to parse all of "/dev". Also, this
// allows to retrieve the full name of the backing file even if
// strlen(backing file) > LO_NAME_SIZE.
static int find_associated_loop_device(const char *loop_file,
				       char *loop_dev_name)
{
	__do_closedir DIR *dir = NULL;
	char looppath[LXD_MAX_LOOP_PATHLEN];
	char buf[LXD_MAXPATH];
	struct dirent *dp;

	dir = opendir("/sys/block");
	if (!dir)
		return -1;

	while ((dp = readdir(dir))) {
		__do_close int loop_path_fd = -EBADF;
		int ret;
		size_t totlen;
		struct stat fstatbuf;
		int dfd = -1;

		if (!dp)
			break;

		if (strncmp(dp->d_name, "loop", 4))
			continue;

		dfd = dirfd(dir);
		if (dfd < 0)
			continue;

		ret = snprintf(looppath, sizeof(looppath), "%s/loop/backing_file", dp->d_name);
		if (ret < 0 || (size_t)ret >= sizeof(looppath))
			continue;

		ret = fstatat(dfd, looppath, &fstatbuf, 0);
		if (ret < 0)
			continue;

		loop_path_fd = openat(dfd, looppath, O_RDONLY | O_CLOEXEC, 0);
		if (ret < 0)
			continue;

		// Clear buffer.
		memset(buf, 0, sizeof(buf));
		ret = read(loop_path_fd, buf, sizeof(buf));
		if (ret < 0)
			continue;

		totlen = strlen(buf);

		// Trim newlines.
		while ((totlen > 0) && (buf[totlen - 1] == '\n'))
			buf[--totlen] = '\0';

		if (strcmp(buf, loop_file))
			continue;

		// Create path to loop device.
		ret = snprintf(loop_dev_name, LO_NAME_SIZE, "/dev/%s",
			       dp->d_name);
		if (ret < 0 || ret >= LO_NAME_SIZE)
			continue;

		// Open fd to loop device.
		return open(loop_dev_name, O_RDWR | O_CLOEXEC);
	}

	return -1;
}

static int get_unused_loop_dev_legacy(char *loop_name)
{
	__do_closedir DIR *dir = NULL;
	struct dirent *dp;
	struct loop_info64 lo64;

	dir = opendir("/dev");
	if (!dir)
		return -1;

	while ((dp = readdir(dir))) {
		__do_close int dfd = -EBADF, fd = -EBADF;
		int ret;

		if (!dp)
			break;

		if (strncmp(dp->d_name, "loop", 4) != 0)
			continue;

		dfd = dirfd(dir);
		if (dfd < 0)
			continue;

		fd = openat(dfd, dp->d_name, O_RDWR);
		if (fd < 0)
			continue;

		ret = ioctl(fd, LOOP_GET_STATUS64, &lo64);
		if (ret < 0)
			if (ioctl(fd, LOOP_GET_STATUS64, &lo64) == 0 || errno != ENXIO)
				continue;

		ret = snprintf(loop_name, LO_NAME_SIZE, "/dev/%s", dp->d_name);
		if (ret < 0 || ret >= LO_NAME_SIZE)
			continue;

		return move_fd(fd);
	}

	return -1;
}

static int get_unused_loop_dev(char *name_loop)
{
	__do_close int fd_ctl = -EBADF;
	int loop_nr, ret;

	fd_ctl = open("/dev/loop-control", O_RDWR | O_CLOEXEC);
	if (fd_ctl < 0)
		return -ENODEV;

	loop_nr = ioctl(fd_ctl, LOOP_CTL_GET_FREE);
	if (loop_nr < 0)
		return -1;

	ret = snprintf(name_loop, LO_NAME_SIZE, "/dev/loop%d", loop_nr);
	if (ret < 0 || ret >= LO_NAME_SIZE)
		return -1;

	return open(name_loop, O_RDWR | O_CLOEXEC);
}

static int prepare_loop_dev(const char *source, char *loop_dev, int flags)
{
	__do_close int fd_img = -EBADF, fd_loop = -EBADF;
	int ret;
	struct loop_info64 lo64;

	fd_loop = get_unused_loop_dev(loop_dev);
	if (fd_loop < 0) {
		if (fd_loop == -ENODEV)
			fd_loop = get_unused_loop_dev_legacy(loop_dev);
		else
			return -1;
	}

	fd_img = open(source, O_RDWR | O_CLOEXEC);
	if (fd_img < 0)
		return -1;

	ret = ioctl(fd_loop, LOOP_SET_FD, fd_img);
	if (ret < 0)
		return -1;

	memset(&lo64, 0, sizeof(lo64));
	lo64.lo_flags = flags;

	ret = ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
	if (ret < 0)
		return -1;

	return move_fd(fd_loop);
}

static inline int prepare_loop_dev_retry(const char *source, char *loop_dev, int flags)
{
	int ret;
	unsigned int idx = 0;

	do {
		ret = prepare_loop_dev(source, loop_dev, flags);
		idx++;
	} while (ret < 0 && errno == EBUSY && idx < 30);

	return ret;
}

// Note that this does not guarantee to clear the loop device in time so that
// find_associated_loop_device() will not report that there still is a
// configured device (udev and so on...). So don't call
// find_associated_loop_device() after having called
// set_autoclear_loop_device().
int set_autoclear_loop_device(int fd_loop)
{
	struct loop_info64 lo64;

	memset(&lo64, 0, sizeof(lo64));
	lo64.lo_flags = LO_FLAGS_AUTOCLEAR;
	errno = 0;
	return ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
}

// Directly release the loop device
int free_loop_device(int fd_loop)
{
	return ioctl(fd_loop, LOOP_CLR_FD);
}

// Unset the LO_FLAGS_AUTOCLEAR flag on the given loop device file descriptor.
int unset_autoclear_loop_device(int fd_loop)
{
	int ret;
	struct loop_info64 lo64;

	errno = 0;
	ret = ioctl(fd_loop, LOOP_GET_STATUS64, &lo64);
	if (ret < 0)
		return -1;

	if ((lo64.lo_flags & LO_FLAGS_AUTOCLEAR) == 0)
		return 0;

	lo64.lo_flags &= ~LO_FLAGS_AUTOCLEAR;
	errno = 0;
	return ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
}
*/
import "C"

// LoFlagsAutoclear determines whether the loop device will autodestruct on last
// close.
const LoFlagsAutoclear int = C.LO_FLAGS_AUTOCLEAR

// LoFlagsDirectIO determines whether the loop device will use Direct IO with the
// backing file.
const LoFlagsDirectIO int = C.LO_FLAGS_DIRECT_IO

// PrepareLoopDev detects and sets up a loop device for source. It returns an
// open file descriptor to the free loop device and the path of the free loop
// device. It's the callers responsibility to close the open file descriptor.
func PrepareLoopDev(source string, flags int) (*os.File, error) {
	cLoopDev := C.malloc(C.size_t(C.LO_NAME_SIZE))
	if cLoopDev == nil {
		return nil, fmt.Errorf("Failed to allocate memory in C")
	}
	defer C.free(cLoopDev)

	cSource := C.CString(source)
	defer C.free(unsafe.Pointer(cSource))
	loopFd, _ := C.find_associated_loop_device(cSource, (*C.char)(cLoopDev))
	if loopFd >= 0 {
		return os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev))), nil
	}

	loopFd, err := C.prepare_loop_dev_retry(cSource, (*C.char)(cLoopDev), C.int(flags))
	if loopFd < 0 {
		if err != nil {
			return nil, errors.Wrapf(err, "Failed to prepare loop device for %q", source)
		}

		return nil, fmt.Errorf("Failed to prepare loop device for %q", source)
	}

	return os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev))), nil
}

// releaseLoopDev releases the loop dev assigned to the provided file.
func releaseLoopDev(source string) error {
	cLoopDev := C.malloc(C.size_t(C.LO_NAME_SIZE))
	if cLoopDev == nil {
		return fmt.Errorf("Failed to allocate memory in C")
	}
	defer C.free(cLoopDev)

	cSource := C.CString(source)
	loopFd, err := C.find_associated_loop_device(cSource, (*C.char)(cLoopDev))
	if err != nil {
		return err
	}

	// Prepare a Go file and defer close on the loop device.
	fd := os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev)))
	defer fd.Close()

	if loopFd >= 0 {
		_, err := C.free_loop_device(C.int(loopFd))
		if err != nil {
			return err
		}
	}

	return nil
}

// SetAutoclearOnLoopDev enables autodestruction of the provided loopback device.
func SetAutoclearOnLoopDev(loopFd int) error {
	ret, err := C.set_autoclear_loop_device(C.int(loopFd))
	if ret < 0 {
		if err != nil {
			return err
		}
		return fmt.Errorf("Failed to set LO_FLAGS_AUTOCLEAR")
	}

	return nil
}

// UnsetAutoclearOnLoopDev disables autodestruction of the provided loopback device.
func UnsetAutoclearOnLoopDev(loopFd int) error {
	ret, err := C.unset_autoclear_loop_device(C.int(loopFd))
	if ret < 0 {
		if err != nil {
			return err
		}
		return fmt.Errorf("Failed to unset LO_FLAGS_AUTOCLEAR")
	}

	return nil
}
<commit_msg>lxd/storage: Check for kernel support and O_DIRECT flag before setting direct IO<commit_after>//go:build linux && cgo
// +build linux,cgo

package drivers

import (
	"fmt"
	"os"
	"unsafe"

	"github.com/pkg/errors"

	// Used by cgo
	_ "github.com/lxc/lxd/lxd/include"
)

/*
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#define _FILE_OFFSET_BITS 64
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/loop.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "../../include/macro.h"
#include "../../include/memory_utils.h"

#define LXD_MAXPATH 4096
#define LXD_NUMSTRLEN64 21
#define LXD_MAX_LOOP_PATHLEN (2 * sizeof("loop/")) + LXD_NUMSTRLEN64 + sizeof("backing_file") + 1

// If a loop file is already associated with a loop device, find it.
// This looks at "/sys/block" to avoid having to parse all of "/dev". Also, this
// allows to retrieve the full name of the backing file even if
// strlen(backing file) > LO_NAME_SIZE.
static int find_associated_loop_device(const char *loop_file,
				       char *loop_dev_name)
{
	__do_closedir DIR *dir = NULL;
	char looppath[LXD_MAX_LOOP_PATHLEN];
	char buf[LXD_MAXPATH];
	struct dirent *dp;

	dir = opendir("/sys/block");
	if (!dir)
		return -1;

	while ((dp = readdir(dir))) {
		__do_close int loop_path_fd = -EBADF;
		int ret;
		size_t totlen;
		struct stat fstatbuf;
		int dfd = -1;

		if (!dp)
			break;

		if (strncmp(dp->d_name, "loop", 4))
			continue;

		dfd = dirfd(dir);
		if (dfd < 0)
			continue;

		ret = snprintf(looppath, sizeof(looppath), "%s/loop/backing_file", dp->d_name);
		if (ret < 0 || (size_t)ret >= sizeof(looppath))
			continue;

		ret = fstatat(dfd, looppath, &fstatbuf, 0);
		if (ret < 0)
			continue;

		loop_path_fd = openat(dfd, looppath, O_RDONLY | O_CLOEXEC, 0);
		if (ret < 0)
			continue;

		// Clear buffer.
		memset(buf, 0, sizeof(buf));
		ret = read(loop_path_fd, buf, sizeof(buf));
		if (ret < 0)
			continue;

		totlen = strlen(buf);

		// Trim newlines.
		while ((totlen > 0) && (buf[totlen - 1] == '\n'))
			buf[--totlen] = '\0';

		if (strcmp(buf, loop_file))
			continue;

		// Create path to loop device.
		ret = snprintf(loop_dev_name, LO_NAME_SIZE, "/dev/%s",
			       dp->d_name);
		if (ret < 0 || ret >= LO_NAME_SIZE)
			continue;

		// Open fd to loop device.
		return open(loop_dev_name, O_RDWR | O_CLOEXEC);
	}

	return -1;
}

static int get_unused_loop_dev_legacy(char *loop_name)
{
	__do_closedir DIR *dir = NULL;
	struct dirent *dp;
	struct loop_info64 lo64;

	dir = opendir("/dev");
	if (!dir)
		return -1;

	while ((dp = readdir(dir))) {
		__do_close int dfd = -EBADF, fd = -EBADF;
		int ret;

		if (!dp)
			break;

		if (strncmp(dp->d_name, "loop", 4) != 0)
			continue;

		dfd = dirfd(dir);
		if (dfd < 0)
			continue;

		fd = openat(dfd, dp->d_name, O_RDWR);
		if (fd < 0)
			continue;

		ret = ioctl(fd, LOOP_GET_STATUS64, &lo64);
		if (ret < 0)
			if (ioctl(fd, LOOP_GET_STATUS64, &lo64) == 0 || errno != ENXIO)
				continue;

		ret = snprintf(loop_name, LO_NAME_SIZE, "/dev/%s", dp->d_name);
		if (ret < 0 || ret >= LO_NAME_SIZE)
			continue;

		return move_fd(fd);
	}

	return -1;
}

static int get_unused_loop_dev(char *name_loop)
{
	__do_close int fd_ctl = -EBADF;
	int loop_nr, ret;

	fd_ctl = open("/dev/loop-control", O_RDWR | O_CLOEXEC);
	if (fd_ctl < 0)
		return -ENODEV;

	loop_nr = ioctl(fd_ctl, LOOP_CTL_GET_FREE);
	if (loop_nr < 0)
		return -1;

	ret = snprintf(name_loop, LO_NAME_SIZE, "/dev/loop%d", loop_nr);
	if (ret < 0 || ret >= LO_NAME_SIZE)
		return -1;

	// try to open loop file with O_DIRECT flag, try again without if it fails
	ret = open(name_loop, O_RDWR | O_CLOEXEC | O_DIRECT);
	if (ret < 0)
		ret = open(name_loop, O_RDWR | O_CLOEXEC);
	return ret;
}

int set_direct_io_loop_device(int fd_loop) {
	unsigned long direct_io_flag = 1;
	return ioctl(fd_loop, LOOP_SET_DIRECT_IO, direct_io_flag);
}

static int prepare_loop_dev(const char *source, char *loop_dev, int flags)
{
	__do_close int fd_img = -EBADF, fd_loop = -EBADF;
	int ret, f_flags;
	struct loop_info64 lo64;

	fd_loop = get_unused_loop_dev(loop_dev);
	if (fd_loop < 0) {
		if (fd_loop == -ENODEV)
			fd_loop = get_unused_loop_dev_legacy(loop_dev);
		else
			return -1;
	}

	fd_img = open(source, O_RDWR | O_CLOEXEC);
	if (fd_img < 0)
		return -1;

	ret = ioctl(fd_loop, LOOP_SET_FD, fd_img);
	if (ret < 0)
		return -1;

	f_flags = fcntl(fd_loop, F_GETFL);
	if (f_flags < 0)
		return -1;

	if (flags & LO_FLAGS_DIRECT_IO) {
		if (f_flags & O_DIRECT) {
			ret = set_direct_io_loop_device(fd_loop);
			if (ret < 0)
				flags &= ~LO_FLAGS_DIRECT_IO;
		} else {
			flags &= ~LO_FLAGS_DIRECT_IO;
		}
	}

	memset(&lo64, 0, sizeof(lo64));
	lo64.lo_flags = flags;

	ret = ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
	if (ret < 0)
		return -1;

	return move_fd(fd_loop);
}

static inline int prepare_loop_dev_retry(const char *source, char *loop_dev, int flags)
{
	int ret;
	unsigned int idx = 0;

	do {
		ret = prepare_loop_dev(source, loop_dev, flags);
		idx++;
	} while (ret < 0 && errno == EBUSY && idx < 30);

	return ret;
}

// Note that this does not guarantee to clear the loop device in time so that
// find_associated_loop_device() will not report that there still is a
// configured device (udev and so on...). So don't call
// find_associated_loop_device() after having called
// set_autoclear_loop_device().
int set_autoclear_loop_device(int fd_loop)
{
	struct loop_info64 lo64;

	memset(&lo64, 0, sizeof(lo64));
	lo64.lo_flags = LO_FLAGS_AUTOCLEAR;
	errno = 0;
	return ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
}

// Directly release the loop device
int free_loop_device(int fd_loop)
{
	return ioctl(fd_loop, LOOP_CLR_FD);
}

// Unset the LO_FLAGS_AUTOCLEAR flag on the given loop device file descriptor.
int unset_autoclear_loop_device(int fd_loop)
{
	int ret;
	struct loop_info64 lo64;

	errno = 0;
	ret = ioctl(fd_loop, LOOP_GET_STATUS64, &lo64);
	if (ret < 0)
		return -1;

	if ((lo64.lo_flags & LO_FLAGS_AUTOCLEAR) == 0)
		return 0;

	lo64.lo_flags &= ~LO_FLAGS_AUTOCLEAR;
	errno = 0;
	return ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
}
*/
import "C"

// LoFlagsAutoclear determines whether the loop device will autodestruct on last
// close.
const LoFlagsAutoclear int = C.LO_FLAGS_AUTOCLEAR

// LoFlagsDirectIO determines whether the loop device will use Direct IO with the
// backing file.
const LoFlagsDirectIO int = C.LO_FLAGS_DIRECT_IO

// PrepareLoopDev detects and sets up a loop device for source. It returns an
// open file descriptor to the free loop device and the path of the free loop
// device. It's the callers responsibility to close the open file descriptor.
func PrepareLoopDev(source string, flags int) (*os.File, error) {
	cLoopDev := C.malloc(C.size_t(C.LO_NAME_SIZE))
	if cLoopDev == nil {
		return nil, fmt.Errorf("Failed to allocate memory in C")
	}
	defer C.free(cLoopDev)

	cSource := C.CString(source)
	defer C.free(unsafe.Pointer(cSource))
	loopFd, _ := C.find_associated_loop_device(cSource, (*C.char)(cLoopDev))
	if loopFd >= 0 {
		return os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev))), nil
	}

	loopFd, err := C.prepare_loop_dev_retry(cSource, (*C.char)(cLoopDev), C.int(flags))
	if loopFd < 0 {
		if err != nil {
			return nil, errors.Wrapf(err, "Failed to prepare loop device for %q", source)
		}

		return nil, fmt.Errorf("Failed to prepare loop device for %q", source)
	}

	return os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev))), nil
}

// releaseLoopDev releases the loop dev assigned to the provided file.
func releaseLoopDev(source string) error {
	cLoopDev := C.malloc(C.size_t(C.LO_NAME_SIZE))
	if cLoopDev == nil {
		return fmt.Errorf("Failed to allocate memory in C")
	}
	defer C.free(cLoopDev)

	cSource := C.CString(source)
	loopFd, err := C.find_associated_loop_device(cSource, (*C.char)(cLoopDev))
	if err != nil {
		return err
	}

	// Prepare a Go file and defer close on the loop device.
	fd := os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev)))
	defer fd.Close()

	if loopFd >= 0 {
		_, err := C.free_loop_device(C.int(loopFd))
		if err != nil {
			return err
		}
	}

	return nil
}

// SetAutoclearOnLoopDev enables autodestruction of the provided loopback device.
func SetAutoclearOnLoopDev(loopFd int) error {
	ret, err := C.set_autoclear_loop_device(C.int(loopFd))
	if ret < 0 {
		if err != nil {
			return err
		}
		return fmt.Errorf("Failed to set LO_FLAGS_AUTOCLEAR")
	}

	return nil
}

// UnsetAutoclearOnLoopDev disables autodestruction of the provided loopback device.
func UnsetAutoclearOnLoopDev(loopFd int) error {
	ret, err := C.unset_autoclear_loop_device(C.int(loopFd))
	if ret < 0 {
		if err != nil {
			return err
		}
		return fmt.Errorf("Failed to unset LO_FLAGS_AUTOCLEAR")
	}

	return nil
}
<|endoftext|>


<commit_before>package server
import (
	"net/http"
	"regexp"
	"os/user"
	"os/exec"
	"servant/conf"
	//"io/ioutil"
	"time"
	"syscall"
	"strconv"
	"io"
	"io/ioutil"
)

var argRe, _ = regexp.Compile(`("[^"]*"|'[^']*'|[^\s"']+)`)

type CommandServer struct {
	*Session
}

func NewCommandServer(sess *Session) Handler {
	return CommandServer{
		Session:sess,
	}
}

func (self CommandServer) findCommandConfig() *conf.Command {
	cmdsConf, ok := self.config.Commands[self.group]
	if !ok {
		return nil
	}
	cmdConf, ok := cmdsConf.Commands[self.item]
	if !ok {
		return nil
	}
	return cmdConf
}

func getCmdBashArgs(code string, query ParamFunc) (string, []string) {
	return "bash", []string{"-c", code}
}

// TODO: return error when arg miss
func replaceCmdParams(arg string, query ParamFunc) string {
	return paramRe.ReplaceAllStringFunc(arg, func(s string) string {
		if query == nil {
			return ""
		}
		ret, _ := query(s[2:len(s) - 1])
		return ret
	})
}
func getCmdExecArgs(code string, query ParamFunc) (string, []string) {
	argsMatches := argRe.FindAllStringSubmatch(code, -1)
	args := make([]string, 0, 4)
	for i := 0; i < len(argsMatches); i++ {
		arg := argsMatches[i][1]
		if arg[0] == '\'' || arg[0] == '"' {
			arg = arg[1 : len(arg)-1]
		}
		arg = replaceCmdParams(arg, query)
		args = append(args, arg)
	}
	return args[0], args[1:]
}

func (self CommandServer) serve() {
	urlPath := self.req.URL.Path
	method := self.req.Method

	if method != "GET" && method != "POST" {
		self.ErrorEnd(http.StatusMethodNotAllowed, "not allow method: %s", method)
		return
	}
	cmdConf := self.findCommandConfig()
	if cmdConf == nil {
		self.ErrorEnd(http.StatusNotFound, "command %s not found", urlPath)
		self.resp.WriteHeader(http.StatusNotFound)
		return
	}
	if cmdConf.Lock.Name == "" {
		self.serveCommand(cmdConf)
	} else {
		if cmdConf.Lock.Wait {
			GetLock(cmdConf.Lock.Name).TimeoutWith(time.Duration(cmdConf.Lock.Timeout) * time.Second, func() {
				self.serveCommand(cmdConf)
			})
		} else {
			GetLock(cmdConf.Lock.Name).TryWith(func(){
				self.serveCommand(cmdConf)
			})
		}
	}
}

func setCmdUser(cmd *exec.Cmd, username string) error {
	sysUser, err := user.Lookup(username)
	if err != nil {
		return err
	}
	uid , err := strconv.Atoi(sysUser.Uid)
	if err != nil {
		return err
	}
	gid, err := strconv.Atoi(sysUser.Gid)
	if err != nil {
		return err
	}
	cred := syscall.Credential{ Uid: uint32(uid), Gid: uint32(gid) }

	cmd.SysProcAttr.Credential = &cred
	return nil
}

func (self CommandServer) serveCommand(cmdConf *conf.Command) {
	outBuf, err := self.execCommand(cmdConf)
	if err != nil {
		self.ErrorEnd(err.(ServantError).HttpCode, err.(ServantError).Message)
		return
	}
	_, err = self.resp.Write(outBuf) // may log errors
	if err != nil {
		self.BadEnd("io error: %s", err)
	} else {
		self.GoodEnd("execution done")
	}
}

func cmdFromConf(cmdConf *conf.Command, params ParamFunc, input io.ReadCloser) (cmd *exec.Cmd, out io.ReadCloser, err error) {
	var name string
	var args []string
	if !ValidateParams(cmdConf.Validators, params) {
		return nil, nil, NewServantError(http.StatusBadRequest, "validate params failed")
	}
	switch cmdConf.Lang {
	case "exec":
		name, args = getCmdExecArgs(cmdConf.Code, params)
	case "bash", "":
		name, args = getCmdBashArgs(cmdConf.Code, params)
	default:
		err = NewServantError(http.StatusInternalServerError, "unknown language")
		return
	}
	cmd = exec.Command(name, args...)
	cmd.SysProcAttr = &syscall.SysProcAttr{}
	cmd.Dir = "/"
	if cmdConf.User != "" {
		err = setCmdUser(cmd, cmdConf.User)
		if err != nil {
			err = NewServantError(http.StatusInternalServerError, "set user failed: %s", err.Error())
			return
		}
	}
	cmd.Stdin = input
	cmd.Stderr = nil
	if cmdConf.Background {
		//cmd.SysProcAttr.Setpgid = true
		//cmd.SysProcAttr.Pgid = 0
		cmd.SysProcAttr.Setsid = true
		cmd.SysProcAttr.Foreground = false
		cmd.Stdout = nil
		cmd.Stdin = nil
	} else {
		out, err = cmd.StdoutPipe()
		if err != nil {
			err = NewServantError(http.StatusInternalServerError, "pipe stdout failed: %s", err.Error())
			return
		}
	}
	return cmd, out, nil
}

func (self CommandServer) execCommand(cmdConf *conf.Command) (outBuf []byte, err error) {
	var input io.ReadCloser = nil
	if self.req.Method == "POST" {
		input = self.req.Body
	}
	cmd, out, err := cmdFromConf(cmdConf, requestParams(self.req), input)
	if err != nil {
		return
	}
	self.info("command: %v", cmd.Args)
	if out != nil {
		defer out.Close()
	}
	err = cmd.Start()
	if err != nil {
		err = NewServantError(http.StatusBadGateway, "execution error: %s", err)
		return
	}
	self.info("process started. pid: %d", cmd.Process.Pid)
	if cmdConf.Background {
		go func() {
			err = cmd.Wait()
			if err != nil {
				self.warn("background process %d ended with error: %s", cmd.Process.Pid, err.Error())
			} else {
				self.info("background process %d ended", cmd.Process.Pid)
			}
		}()
	} else {
		ch := make(chan error, 1)
		go func() {
			if out != nil {
				outBuf, err = ioutil.ReadAll(out)
				if err != nil {
					ch <- err
					return
				}
			}
			err = cmd.Wait()
			ch <- err
		}()
		timeout := time.Duration(cmdConf.Timeout)
		select {
		case err = <-ch:
			if err != nil {
				err = NewServantError(http.StatusBadGateway, "execution error: %s", err)
			}
		case <-time.After(timeout * time.Second):
			cmd.Process.Kill()
			err = NewServantError(http.StatusGatewayTimeout, "command execution timeout: %d", timeout)
		}
	}
	return
}
<commit_msg>protect when command code is empty<commit_after>package server
import (
	"net/http"
	"regexp"
	"os/user"
	"os/exec"
	"servant/conf"
	//"io/ioutil"
	"time"
	"syscall"
	"strconv"
	"io"
	"io/ioutil"
	"strings"
)

var argRe, _ = regexp.Compile(`("[^"]*"|'[^']*'|[^\s"']+)`)

type CommandServer struct {
	*Session
}

func NewCommandServer(sess *Session) Handler {
	return CommandServer{
		Session:sess,
	}
}

func (self CommandServer) findCommandConfig() *conf.Command {
	cmdsConf, ok := self.config.Commands[self.group]
	if !ok {
		return nil
	}
	cmdConf, ok := cmdsConf.Commands[self.item]
	if !ok {
		return nil
	}
	return cmdConf
}

func getCmdBashArgs(code string, query ParamFunc) (string, []string) {
	return "bash", []string{"-c", code}
}

// TODO: return error when arg miss
func replaceCmdParams(arg string, query ParamFunc) string {
	return paramRe.ReplaceAllStringFunc(arg, func(s string) string {
		if query == nil {
			return ""
		}
		ret, _ := query(s[2:len(s) - 1])
		return ret
	})
}
func getCmdExecArgs(code string, query ParamFunc) (string, []string) {
	argsMatches := argRe.FindAllStringSubmatch(code, -1)
	args := make([]string, 0, 4)
	for i := 0; i < len(argsMatches); i++ {
		arg := argsMatches[i][1]
		if arg[0] == '\'' || arg[0] == '"' {
			arg = arg[1 : len(arg)-1]
		}
		arg = replaceCmdParams(arg, query)
		args = append(args, arg)
	}
	return args[0], args[1:]
}

func (self CommandServer) serve() {
	urlPath := self.req.URL.Path
	method := self.req.Method

	if method != "GET" && method != "POST" {
		self.ErrorEnd(http.StatusMethodNotAllowed, "not allow method: %s", method)
		return
	}
	cmdConf := self.findCommandConfig()
	if cmdConf == nil {
		self.ErrorEnd(http.StatusNotFound, "command %s not found", urlPath)
		self.resp.WriteHeader(http.StatusNotFound)
		return
	}
	if cmdConf.Lock.Name == "" {
		self.serveCommand(cmdConf)
	} else {
		if cmdConf.Lock.Wait {
			GetLock(cmdConf.Lock.Name).TimeoutWith(time.Duration(cmdConf.Lock.Timeout) * time.Second, func() {
				self.serveCommand(cmdConf)
			})
		} else {
			GetLock(cmdConf.Lock.Name).TryWith(func(){
				self.serveCommand(cmdConf)
			})
		}
	}
}

func setCmdUser(cmd *exec.Cmd, username string) error {
	sysUser, err := user.Lookup(username)
	if err != nil {
		return err
	}
	uid , err := strconv.Atoi(sysUser.Uid)
	if err != nil {
		return err
	}
	gid, err := strconv.Atoi(sysUser.Gid)
	if err != nil {
		return err
	}
	cred := syscall.Credential{ Uid: uint32(uid), Gid: uint32(gid) }

	cmd.SysProcAttr.Credential = &cred
	return nil
}

func (self CommandServer) serveCommand(cmdConf *conf.Command) {
	outBuf, err := self.execCommand(cmdConf)
	if err != nil {
		self.ErrorEnd(err.(ServantError).HttpCode, err.(ServantError).Message)
		return
	}
	_, err = self.resp.Write(outBuf) // may log errors
	if err != nil {
		self.BadEnd("io error: %s", err)
	} else {
		self.GoodEnd("execution done")
	}
}

func cmdFromConf(cmdConf *conf.Command, params ParamFunc, input io.ReadCloser) (cmd *exec.Cmd, out io.ReadCloser, err error) {
	var name string
	var args []string
	if !ValidateParams(cmdConf.Validators, params) {
		return nil, nil, NewServantError(http.StatusBadRequest, "validate params failed")
	}
	code := strings.TrimSpace(cmdConf.Code)
	if code == "" {
		return nil, nil, NewServantError(http.StatusInternalServerError, "command code is empty")
	}
	switch cmdConf.Lang {
	case "exec":
		name, args = getCmdExecArgs(code, params)
	case "bash", "":
		name, args = getCmdBashArgs(code, params)
	default:
		err = NewServantError(http.StatusInternalServerError, "unknown language")
		return
	}
	cmd = exec.Command(name, args...)
	cmd.SysProcAttr = &syscall.SysProcAttr{}
	cmd.Dir = "/"
	if cmdConf.User != "" {
		err = setCmdUser(cmd, cmdConf.User)
		if err != nil {
			err = NewServantError(http.StatusInternalServerError, "set user failed: %s", err.Error())
			return
		}
	}
	cmd.Stdin = input
	cmd.Stderr = nil
	if cmdConf.Background {
		//cmd.SysProcAttr.Setpgid = true
		//cmd.SysProcAttr.Pgid = 0
		cmd.SysProcAttr.Setsid = true
		cmd.SysProcAttr.Foreground = false
		cmd.Stdout = nil
		cmd.Stdin = nil
	} else {
		out, err = cmd.StdoutPipe()
		if err != nil {
			err = NewServantError(http.StatusInternalServerError, "pipe stdout failed: %s", err.Error())
			return
		}
	}
	return cmd, out, nil
}

func (self CommandServer) execCommand(cmdConf *conf.Command) (outBuf []byte, err error) {
	var input io.ReadCloser = nil
	if self.req.Method == "POST" {
		input = self.req.Body
	}
	cmd, out, err := cmdFromConf(cmdConf, requestParams(self.req), input)
	if err != nil {
		return
	}
	self.info("command: %v", cmd.Args)
	if out != nil {
		defer out.Close()
	}
	err = cmd.Start()
	if err != nil {
		err = NewServantError(http.StatusBadGateway, "execution error: %s", err)
		return
	}
	self.info("process started. pid: %d", cmd.Process.Pid)
	if cmdConf.Background {
		go func() {
			err = cmd.Wait()
			if err != nil {
				self.warn("background process %d ended with error: %s", cmd.Process.Pid, err.Error())
			} else {
				self.info("background process %d ended", cmd.Process.Pid)
			}
		}()
	} else {
		ch := make(chan error, 1)
		go func() {
			if out != nil {
				outBuf, err = ioutil.ReadAll(out)
				if err != nil {
					ch <- err
					return
				}
			}
			err = cmd.Wait()
			ch <- err
		}()
		timeout := time.Duration(cmdConf.Timeout)
		select {
		case err = <-ch:
			if err != nil {
				err = NewServantError(http.StatusBadGateway, "execution error: %s", err)
			}
		case <-time.After(timeout * time.Second):
			cmd.Process.Kill()
			err = NewServantError(http.StatusGatewayTimeout, "command execution timeout: %d", timeout)
		}
	}
	return
}
<|endoftext|>