<commit_before>// Copyright 2016 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

// mixers is all the patterns that are constructions of other patterns.

package anim1d

// Gradient does a gradient between 2 patterns.
//
// A good example is using two colors but it can also be animations.
//
// TODO(maruel): Support N colors at M positions.
type Gradient struct {
	Left  SPattern
	Right SPattern
	Curve Curve
	buf   Frame
}

// Render implements Pattern.
func (g *Gradient) Render(pixels Frame, timeMS uint32) {
	l := len(pixels)
	if l == 0 {
		return
	}
	g.buf.reset(l)
	g.Left.Render(pixels, timeMS)
	g.Right.Render(g.buf, timeMS)
	if l == 1 {
		pixels.Mix(g.buf, g.Curve.Scale8(65535>>1))
	} else {
		max := l - 1
		for i := range pixels {
			intensity := uint16(i * 65535 / max)
			pixels[i].Mix(g.buf[i], g.Curve.Scale8(intensity))
		}
	}
}

// Split splits the strip in two.
//
// Unlike gradient, this create 2 logical independent subsets.
type Split struct {
	Left   SPattern
	Right  SPattern
	Offset SValue // Point to split between both sides.
}

// Render implements Pattern.
func (s *Split) Render(pixels Frame, timeMS uint32) {
	offset := MinMax(int(s.Offset.Eval(timeMS, len(pixels))), 0, len(pixels))
	if s.Left.Pattern != nil && offset != 0 {
		s.Left.Render(pixels[:offset], timeMS)
	}
	if s.Right.Pattern != nil && offset != len(pixels) {
		s.Right.Render(pixels[offset:], timeMS)
	}
}

// Transition changes from Before to After over time. It doesn't repeat.
//
// In gets timeMS that is subtracted by OffsetMS.
type Transition struct {
	Before       SPattern // Old pattern that is disappearing
	After        SPattern // New pattern to show
	OffsetMS     uint32   // Offset at which the transiton from Before->In starts
	TransitionMS uint32   // Duration of the transition while both are rendered
	Curve        Curve    // Type of transition, defaults to EaseOut if not set
	buf          Frame
}

// Render implements Pattern.
func (t *Transition) Render(pixels Frame, timeMS uint32) {
	if timeMS <= t.OffsetMS {
		// Before transition.
		t.Before.Render(pixels, timeMS)
		return
	}
	t.After.Render(pixels, timeMS-t.OffsetMS)
	if timeMS >= t.OffsetMS+t.TransitionMS {
		// After transition.
		t.buf = nil
		return
	}
	t.buf.reset(len(pixels))

	// TODO(maruel): Add lateral animation and others.
	t.Before.Render(t.buf, timeMS)
	intensity := uint16((timeMS - t.OffsetMS) * 65535 / (t.TransitionMS))
	pixels.Mix(t.buf, 255.-t.Curve.Scale8(intensity))
}

// Loop rotates between all the animations.
//
// Display starts with one ShowMS for Patterns[0], then starts looping.
// timeMS is not modified so it's like as all animations continued animating
// behind.
// TODO(maruel): Add lateral transition and others.
type Loop struct {
	Patterns     []SPattern
	ShowMS       uint32 // Duration for each pattern to be shown as pure
	TransitionMS uint32 // Duration of the transition between two patterns, can be 0
	Curve        Curve  // Type of transition, defaults to EaseOut if not set
	buf          Frame
}

// Render implements Pattern.
func (l *Loop) Render(pixels Frame, timeMS uint32) {
	lp := uint32(len(l.Patterns))
	if lp == 0 {
		return
	}
	cycleDuration := l.ShowMS + l.TransitionMS
	if cycleDuration == 0 {
		// Misconfigured. Lock to the first pattern.
		l.Patterns[0].Render(pixels, timeMS)
		return
	}

	base := timeMS / cycleDuration
	index := base % lp
	a := l.Patterns[index]
	a.Render(pixels, timeMS)
	offset := timeMS - (base * cycleDuration)
	if offset <= l.ShowMS {
		return
	}

	// Transition.
	l.buf.reset(len(pixels))
	b := l.Patterns[(index+1)%lp]
	b.Render(l.buf, timeMS)
	offset -= l.ShowMS
	intensity := uint16((l.TransitionMS - offset) * 65535 / l.TransitionMS)
	pixels.Mix(l.buf, l.Curve.Scale8(65535-intensity))
}

// Rotate rotates a pattern that can also cycle either way.
//
// Use negative to go left. Can be used for 'candy bar'.
//
// Similar to PingPong{} except that it doesn't bounce.
//
// Use 5x oversampling with Scale{} to create smoother animation.
type Rotate struct {
	Child       SPattern
	MovePerHour MovePerHour // Expressed in number of light jumps per hour.
	buf         Frame
}

// Render implements Pattern.
func (r *Rotate) Render(pixels Frame, timeMS uint32) {
	l := len(pixels)
	r.buf.reset(l)
	r.Child.Render(r.buf, timeMS)
	offset := r.MovePerHour.Eval(timeMS, len(pixels), l)
	if offset < 0 {
		// Reverse direction.
		offset = l + offset
	}
	copy(pixels[offset:], r.buf)
	copy(pixels[:offset], r.buf[l-offset:])
}

// Chronometer moves 3 lights to the right, each indicating second, minute, and
// hour passed since the start.
//
// Child has 4 pixels used in this order: [default, second, minute, hour].
type Chronometer struct {
	Child SPattern
	buf   Frame
}

// Render implements Pattern.
func (r *Chronometer) Render(pixels Frame, timeMS uint32) {
	l := uint32(len(pixels))
	if l == 0 {
		return
	}
	r.buf.reset(4)
	r.Child.Render(r.buf, timeMS)

	seconds := timeMS / 1000
	mins := seconds / 60
	hours := mins / 60

	secPos := (l*(seconds%60) + 30) / 60
	minPos := (l*(mins%60) + 30) / 60
	hourPos := hours % l

	for i := range pixels {
		switch uint32(i) {
		case secPos:
			pixels[i] = r.buf[1]
		case minPos:
			pixels[i] = r.buf[2]
		case hourPos:
			pixels[i] = r.buf[3]
		default:
			pixels[i] = r.buf[0]
		}
	}
}

// PingPong shows a 'ball' with a trail that bounces from one side to
// the other.
//
// Can be used for a ball, a water wave or K2000 (Knight Rider) style light.
// The trail can be a Frame or a dynamic pattern.
//
// To get smoothed movement, use Scale{} with a 5x factor or so.
// TODO(maruel): That's a bit inefficient, enable Interpolation here.
type PingPong struct {
	Child       SPattern    // [0] is the front pixel so the pixels are effectively drawn in reverse order
	MovePerHour MovePerHour // Expressed in number of light jumps per hour
	buf         Frame
}

// Render implements Pattern.
func (p *PingPong) Render(pixels Frame, timeMS uint32) {
	if len(pixels) == 0 {
		return
	}
	p.buf.reset(len(pixels)*2 - 1)
	p.Child.Render(p.buf, timeMS)
	// The last point of each extremity is only lit on one tick but every other
	// points are lit twice during a full cycle. This means the full cycle is
	// 2*(len(pixels)-1). For a 3 pixels line, the cycle is: x00, 0x0, 00x, 0x0.
	//
	// For Child being Frame "01234567":
	//   move == 0  -> "01234567"
	//   move == 2  -> "21056789"
	//   move == 5  -> "543210ab"
	//   move == 7  -> "76543210"
	//   move == 9  -> "98765012"
	//   move == 11 -> "ba901234"
	//   move == 13 -> "d0123456"
	//   move 14 -> move 0; "2*(8-1)"
	cycle := 2 * (len(pixels) - 1)
	// TODO(maruel): Smoothing with Curve, defaults to Step.
	pos := p.MovePerHour.Eval(timeMS, len(pixels), cycle)

	// Once it works the following code looks trivial but everytime it takes me
	// an absurd amount of time to rewrite it.
	if pos >= len(pixels)-1 {
		// Head runs left.
		// pos2 is the position from the right.
		pos2 := pos + 1 - len(pixels)
		// limit is the offset at which order change.
		limit := len(pixels) - pos2 - 1
		for i := range pixels {
			if i < limit {
				// Going right.
				pixels[i] = p.buf[len(pixels)-i+pos2-1]
			} else {
				// Going left.
				pixels[i] = p.buf[i-limit]
			}
		}
	} else {
		// Head runs right.
		for i := range pixels {
			if i <= pos {
				// Going right.
				pixels[i] = p.buf[pos-i]
			} else {
				// Going left.
				pixels[i] = p.buf[pos+i]
			}
		}
	}
}

// Crop skips the beginning and the end of the source.
type Crop struct {
	Child  SPattern
	Before SValue // Starting pixels to skip
	After  SValue // Ending pixels to skip
	buf    Frame
}

// Render implements Pattern.
func (c *Crop) Render(pixels Frame, timeMS uint32) {
	b := int(MinMax32(c.Before.Eval(timeMS, len(pixels)), 0, 1000))
	a := int(MinMax32(c.After.Eval(timeMS, len(pixels)), 0, 1000))
	// This is slightly wasteful as pixels are drawn just to be ditched.
	c.buf.reset(len(pixels) + b + a)
	c.Child.Render(c.buf, timeMS)
	copy(pixels, c.buf[b:])
}

// Subset skips the beginning and the end of the destination.
type Subset struct {
	Child  SPattern
	Offset SValue // Starting pixels to skip
	Length SValue // Length of the pixels to carry over
}

// Render implements Pattern.
func (s *Subset) Render(pixels Frame, timeMS uint32) {
	if s.Child.Pattern == nil {
		return
	}
	o := MinMax(int(s.Offset.Eval(timeMS, len(pixels))), 0, len(pixels)-1)
	l := MinMax(int(s.Length.Eval(timeMS, len(pixels))), 0, len(pixels)-1-o)
	s.Child.Render(pixels[o:o+l], timeMS)
}

// Dim is a filter that dim the intensity of a buffer.
type Dim struct {
	Child     SPattern //
	Intensity SValue   // 0 is transparent, 255 is fully opaque with original colors.
}

// Render implements Pattern.
func (d *Dim) Render(pixels Frame, timeMS uint32) {
	d.Child.Render(pixels, timeMS)
	i := MinMax32(d.Intensity.Eval(timeMS, len(pixels)), 0, 255)
	pixels.Dim(uint8(i))
}

// Add is a generic mixer that merges the output from multiple patterns with
// saturation.
type Add struct {
	Patterns []SPattern // It should be a list of Dim{} with their corresponding weight.
	buf      Frame      //
}

// Render implements Pattern.
func (a *Add) Render(pixels Frame, timeMS uint32) {
	a.buf.reset(len(pixels))
	// Draw and merge each pattern.
	for i := range a.Patterns {
		a.Patterns[i].Render(a.buf, timeMS)
		pixels.Add(a.buf)
	}
}

// Scale adapts a larger or smaller patterns to the Strip size
//
// This is useful to create smoother horizontal movement animation or to scale
// up/down images.
type Scale struct {
	Child SPattern
	// Defaults to Linear
	Interpolation Interpolation
	// A buffer of this len(buffer)*RatioMilli/1000 will be provided to Child and
	// will be scaled; 500 means smaller, 2000 is larger.
	//
	// Can be set to 0 when Child is a Frame. In this case it is stretched to the
	// strip size.
	RatioMilli SValue
	buf        Frame
}

// Render implements Pattern.
func (s *Scale) Render(pixels Frame, timeMS uint32) {
	if f, ok := s.Child.Pattern.(Frame); ok {
		if s.RatioMilli.Eval(timeMS, len(pixels)) == 0 {
			s.Interpolation.Scale(f, pixels)
			return
		}
	}
	v := MinMax32(s.RatioMilli.Eval(timeMS, len(pixels)), 1, 1000000)
	s.buf.reset((int(v)*len(pixels) + 500) / 1000)
	s.Child.Render(s.buf, timeMS)
	s.Interpolation.Scale(s.buf, pixels)
}
<commit_msg>anim1d: fix Add<commit_after>// Copyright 2016 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

// mixers is all the patterns that are constructions of other patterns.

package anim1d

// Gradient does a gradient between 2 patterns.
//
// A good example is using two colors but it can also be animations.
//
// TODO(maruel): Support N colors at M positions.
type Gradient struct {
	Left  SPattern
	Right SPattern
	Curve Curve
	buf   Frame
}

// Render implements Pattern.
func (g *Gradient) Render(pixels Frame, timeMS uint32) {
	l := len(pixels)
	if l == 0 {
		return
	}
	g.buf.reset(l)
	g.Left.Render(pixels, timeMS)
	g.Right.Render(g.buf, timeMS)
	if l == 1 {
		pixels.Mix(g.buf, g.Curve.Scale8(65535>>1))
	} else {
		max := l - 1
		for i := range pixels {
			intensity := uint16(i * 65535 / max)
			pixels[i].Mix(g.buf[i], g.Curve.Scale8(intensity))
		}
	}
}

// Split splits the strip in two.
//
// Unlike gradient, this create 2 logical independent subsets.
type Split struct {
	Left   SPattern
	Right  SPattern
	Offset SValue // Point to split between both sides.
}

// Render implements Pattern.
func (s *Split) Render(pixels Frame, timeMS uint32) {
	offset := MinMax(int(s.Offset.Eval(timeMS, len(pixels))), 0, len(pixels))
	if s.Left.Pattern != nil && offset != 0 {
		s.Left.Render(pixels[:offset], timeMS)
	}
	if s.Right.Pattern != nil && offset != len(pixels) {
		s.Right.Render(pixels[offset:], timeMS)
	}
}

// Transition changes from Before to After over time. It doesn't repeat.
//
// In gets timeMS that is subtracted by OffsetMS.
type Transition struct {
	Before       SPattern // Old pattern that is disappearing
	After        SPattern // New pattern to show
	OffsetMS     uint32   // Offset at which the transiton from Before->In starts
	TransitionMS uint32   // Duration of the transition while both are rendered
	Curve        Curve    // Type of transition, defaults to EaseOut if not set
	buf          Frame
}

// Render implements Pattern.
func (t *Transition) Render(pixels Frame, timeMS uint32) {
	if timeMS <= t.OffsetMS {
		// Before transition.
		t.Before.Render(pixels, timeMS)
		return
	}
	t.After.Render(pixels, timeMS-t.OffsetMS)
	if timeMS >= t.OffsetMS+t.TransitionMS {
		// After transition.
		t.buf = nil
		return
	}
	t.buf.reset(len(pixels))

	// TODO(maruel): Add lateral animation and others.
	t.Before.Render(t.buf, timeMS)
	intensity := uint16((timeMS - t.OffsetMS) * 65535 / (t.TransitionMS))
	pixels.Mix(t.buf, 255.-t.Curve.Scale8(intensity))
}

// Loop rotates between all the animations.
//
// Display starts with one ShowMS for Patterns[0], then starts looping.
// timeMS is not modified so it's like as all animations continued animating
// behind.
// TODO(maruel): Add lateral transition and others.
type Loop struct {
	Patterns     []SPattern
	ShowMS       uint32 // Duration for each pattern to be shown as pure
	TransitionMS uint32 // Duration of the transition between two patterns, can be 0
	Curve        Curve  // Type of transition, defaults to EaseOut if not set
	buf          Frame
}

// Render implements Pattern.
func (l *Loop) Render(pixels Frame, timeMS uint32) {
	lp := uint32(len(l.Patterns))
	if lp == 0 {
		return
	}
	cycleDuration := l.ShowMS + l.TransitionMS
	if cycleDuration == 0 {
		// Misconfigured. Lock to the first pattern.
		l.Patterns[0].Render(pixels, timeMS)
		return
	}

	base := timeMS / cycleDuration
	index := base % lp
	a := l.Patterns[index]
	a.Render(pixels, timeMS)
	offset := timeMS - (base * cycleDuration)
	if offset <= l.ShowMS {
		return
	}

	// Transition.
	l.buf.reset(len(pixels))
	b := l.Patterns[(index+1)%lp]
	b.Render(l.buf, timeMS)
	offset -= l.ShowMS
	intensity := uint16((l.TransitionMS - offset) * 65535 / l.TransitionMS)
	pixels.Mix(l.buf, l.Curve.Scale8(65535-intensity))
}

// Rotate rotates a pattern that can also cycle either way.
//
// Use negative to go left. Can be used for 'candy bar'.
//
// Similar to PingPong{} except that it doesn't bounce.
//
// Use 5x oversampling with Scale{} to create smoother animation.
type Rotate struct {
	Child       SPattern
	MovePerHour MovePerHour // Expressed in number of light jumps per hour.
	buf         Frame
}

// Render implements Pattern.
func (r *Rotate) Render(pixels Frame, timeMS uint32) {
	l := len(pixels)
	r.buf.reset(l)
	r.Child.Render(r.buf, timeMS)
	offset := r.MovePerHour.Eval(timeMS, len(pixels), l)
	if offset < 0 {
		// Reverse direction.
		offset = l + offset
	}
	copy(pixels[offset:], r.buf)
	copy(pixels[:offset], r.buf[l-offset:])
}

// Chronometer moves 3 lights to the right, each indicating second, minute, and
// hour passed since the start.
//
// Child has 4 pixels used in this order: [default, second, minute, hour].
type Chronometer struct {
	Child SPattern
	buf   Frame
}

// Render implements Pattern.
func (r *Chronometer) Render(pixels Frame, timeMS uint32) {
	l := uint32(len(pixels))
	if l == 0 {
		return
	}
	r.buf.reset(4)
	r.Child.Render(r.buf, timeMS)

	seconds := timeMS / 1000
	mins := seconds / 60
	hours := mins / 60

	secPos := (l*(seconds%60) + 30) / 60
	minPos := (l*(mins%60) + 30) / 60
	hourPos := hours % l

	for i := range pixels {
		switch uint32(i) {
		case secPos:
			pixels[i] = r.buf[1]
		case minPos:
			pixels[i] = r.buf[2]
		case hourPos:
			pixels[i] = r.buf[3]
		default:
			pixels[i] = r.buf[0]
		}
	}
}

// PingPong shows a 'ball' with a trail that bounces from one side to
// the other.
//
// Can be used for a ball, a water wave or K2000 (Knight Rider) style light.
// The trail can be a Frame or a dynamic pattern.
//
// To get smoothed movement, use Scale{} with a 5x factor or so.
// TODO(maruel): That's a bit inefficient, enable Interpolation here.
type PingPong struct {
	Child       SPattern    // [0] is the front pixel so the pixels are effectively drawn in reverse order
	MovePerHour MovePerHour // Expressed in number of light jumps per hour
	buf         Frame
}

// Render implements Pattern.
func (p *PingPong) Render(pixels Frame, timeMS uint32) {
	if len(pixels) == 0 {
		return
	}
	p.buf.reset(len(pixels)*2 - 1)
	p.Child.Render(p.buf, timeMS)
	// The last point of each extremity is only lit on one tick but every other
	// points are lit twice during a full cycle. This means the full cycle is
	// 2*(len(pixels)-1). For a 3 pixels line, the cycle is: x00, 0x0, 00x, 0x0.
	//
	// For Child being Frame "01234567":
	//   move == 0  -> "01234567"
	//   move == 2  -> "21056789"
	//   move == 5  -> "543210ab"
	//   move == 7  -> "76543210"
	//   move == 9  -> "98765012"
	//   move == 11 -> "ba901234"
	//   move == 13 -> "d0123456"
	//   move 14 -> move 0; "2*(8-1)"
	cycle := 2 * (len(pixels) - 1)
	// TODO(maruel): Smoothing with Curve, defaults to Step.
	pos := p.MovePerHour.Eval(timeMS, len(pixels), cycle)

	// Once it works the following code looks trivial but everytime it takes me
	// an absurd amount of time to rewrite it.
	if pos >= len(pixels)-1 {
		// Head runs left.
		// pos2 is the position from the right.
		pos2 := pos + 1 - len(pixels)
		// limit is the offset at which order change.
		limit := len(pixels) - pos2 - 1
		for i := range pixels {
			if i < limit {
				// Going right.
				pixels[i] = p.buf[len(pixels)-i+pos2-1]
			} else {
				// Going left.
				pixels[i] = p.buf[i-limit]
			}
		}
	} else {
		// Head runs right.
		for i := range pixels {
			if i <= pos {
				// Going right.
				pixels[i] = p.buf[pos-i]
			} else {
				// Going left.
				pixels[i] = p.buf[pos+i]
			}
		}
	}
}

// Crop skips the beginning and the end of the source.
type Crop struct {
	Child  SPattern
	Before SValue // Starting pixels to skip
	After  SValue // Ending pixels to skip
	buf    Frame
}

// Render implements Pattern.
func (c *Crop) Render(pixels Frame, timeMS uint32) {
	b := int(MinMax32(c.Before.Eval(timeMS, len(pixels)), 0, 1000))
	a := int(MinMax32(c.After.Eval(timeMS, len(pixels)), 0, 1000))
	// This is slightly wasteful as pixels are drawn just to be ditched.
	c.buf.reset(len(pixels) + b + a)
	c.Child.Render(c.buf, timeMS)
	copy(pixels, c.buf[b:])
}

// Subset skips the beginning and the end of the destination.
type Subset struct {
	Child  SPattern
	Offset SValue // Starting pixels to skip
	Length SValue // Length of the pixels to carry over
}

// Render implements Pattern.
func (s *Subset) Render(pixels Frame, timeMS uint32) {
	if s.Child.Pattern == nil {
		return
	}
	o := MinMax(int(s.Offset.Eval(timeMS, len(pixels))), 0, len(pixels)-1)
	l := MinMax(int(s.Length.Eval(timeMS, len(pixels))), 0, len(pixels)-1-o)
	s.Child.Render(pixels[o:o+l], timeMS)
}

// Dim is a filter that dim the intensity of a buffer.
type Dim struct {
	Child     SPattern //
	Intensity SValue   // 0 is transparent, 255 is fully opaque with original colors.
}

// Render implements Pattern.
func (d *Dim) Render(pixels Frame, timeMS uint32) {
	d.Child.Render(pixels, timeMS)
	i := MinMax32(d.Intensity.Eval(timeMS, len(pixels)), 0, 255)
	pixels.Dim(uint8(i))
}

// Add is a generic mixer that merges the output from multiple patterns with
// saturation.
type Add struct {
	Patterns []SPattern // It should be a list of Dim{} with their corresponding weight.
	buf      Frame      //
}

// Render implements Pattern.
func (a *Add) Render(pixels Frame, timeMS uint32) {
	// Draw and merge each pattern.
	for i := range a.Patterns {
		a.buf.reset(len(pixels))
		a.Patterns[i].Render(a.buf, timeMS)
		pixels.Add(a.buf)
	}
}

// Scale adapts a larger or smaller patterns to the Strip size
//
// This is useful to create smoother horizontal movement animation or to scale
// up/down images.
type Scale struct {
	Child SPattern
	// Defaults to Linear
	Interpolation Interpolation
	// A buffer of this len(buffer)*RatioMilli/1000 will be provided to Child and
	// will be scaled; 500 means smaller, 2000 is larger.
	//
	// Can be set to 0 when Child is a Frame. In this case it is stretched to the
	// strip size.
	RatioMilli SValue
	buf        Frame
}

// Render implements Pattern.
func (s *Scale) Render(pixels Frame, timeMS uint32) {
	if f, ok := s.Child.Pattern.(Frame); ok {
		if s.RatioMilli.Eval(timeMS, len(pixels)) == 0 {
			s.Interpolation.Scale(f, pixels)
			return
		}
	}
	v := MinMax32(s.RatioMilli.Eval(timeMS, len(pixels)), 1, 1000000)
	s.buf.reset((int(v)*len(pixels) + 500) / 1000)
	s.Child.Render(s.buf, timeMS)
	s.Interpolation.Scale(s.buf, pixels)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/libgit2/git2go"
)

func main() {
	repo, err := git.OpenRepository(".")
	if err != nil {
		fmt.Printf("not a repo: %s\n", err)
		os.Exit(5)
	}

	desc, err := repo.DescribeWorkdir(&git.DescribeOptions{ShowCommitOidAsFallback: true})
	if err != nil {
		fmt.Printf("an ineffable miracle beyond description: %s\n", err)
		os.Exit(6)
	}
	fmt.Printf("repo: %s\n", desc)
}
<commit_msg>taking notes on libgit and selecting api design principles.<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/libgit2/git2go"
)

func main() {
	repo, err := git.OpenRepository(".")
	if err != nil {
		fmt.Printf("not a repo: %s\n", err)
		os.Exit(5)
	}

	desc, err := repo.DescribeWorkdir(&git.DescribeOptions{ShowCommitOidAsFallback: true})
	if err != nil {
		fmt.Printf("an ineffable miracle beyond description: %s\n", err)
		os.Exit(6)
	}
	fmt.Printf("repo: %s\n", desc)
}

/*
	Hokay, so.

	Is it implementation detail or showing truth to have the api take a batch of operations on a repo in one bulk message.

	- If the `OpenRepository` call makes a lock on the index, it is truth.
	- If it doesn't, then batching things should very well be transparent.
	- If the error paths may change based on whether or not your request required a fresh open, that would also indicate explicit batching is a necessary truth.

	Observations:

	- still haven't detected boolean answer to that last
	- there are definitely operations that don't operate on any repo at all, so keep that in mind
	- mind the iterables, and the progress bars.  this will definitely not be an rpc api that's 1:1 on ask/answer messages.
*/

/*
	Some attitudes we have:

	Repo data dirs and checked out working trees are *always* treated separately.
	We will not autodetect on these things.  If you want the git data dir at '$WORKTREE/.git/', *say so*.
	We believe this it the right thing to do because many, many git operations desired in scripting we've performed
	become radically simpler, more predictable, and less error-prone if you simply do them in a clean working
	tree (while also understanding that you *can do this* without giving up the shared cache of git data).
	Pivoting your understanding of git to regard the data dir as the center of the universe and worktrees
	as an incidental, optional, no-blessed-default thing will improve your designs significantly.

	It should always be extremely clear whether you're talking about a local filesystem repo or a remote repo.
	If your application doesn't know whether it's talking about remote resources or local resources, it's poorly
	designed: it's going to suffer from the non-transparent performance implications of that; and any local-only
	code paths will suffer from complex error handling for cases that can't actually happen.
	Therefore, our API always emphasizes functions that operate on local-only paths; functions that may accept
	remote repo paths are always the exception, and have different names to clear mark them as such.
	Functions that operation on remote paths may also accept local paths, but never vice versa.
*/
<|endoftext|>


<commit_before>package imgscale

import (
)

/*
	Configure returns Handler which implement http.Handler
	Filename is the configuration file in json, content looks something like this

		{
			"Path": "./data",
			"Prefix": "img",
			"Formats": [
				{"Prefix": "100x100", "Height": 100, "Ratio": 1.0, "Thumbnail": true},
				{"Prefix": "66x100", "Height": 100, "Ratio": 0.67, "Thumbnail": true},
				{"Prefix": "100x75", "Height": 75, "Ratio": 1.335, "Thumbnail": true},
				{"Prefix": "100x0", "Height": 100, "Ratio": 0.0, "Thumbnail": true},
				{"Prefix": "originalx1", "Height": 0, "Ratio": 1.0, "Thumbnail": false},
				{"Prefix": "original", "Height": 0, "Ratio": 0.0, "Thumbnail": false}
			],
			"Exts": ["jpg", "png"],
			"Comment": "Copyright"
		}

	The return handler could use as middleware handler

	Negroni middleware:

		n := negroni.New()
		n.UseHandler(imgscale.Configure("./config/formats.json"))
		http.ListenAndServe(fmt.Sprintf("%s:%d", "127.0.0.1", 8081), n)

	Martini middleware:

		app := martini.Classic()
		app.Use(imgscale.Configure("./config/formats.json").ServeHTTP)
		http.ListenAndServe(fmt.Sprintf("%s:%d", "127.0.0.1", 8080), app)

	http.Handle:

		http.Handle("/", imgscale.Configure("./config/formats.json"))
		http.ListenAndServe(fmt.Sprintf("%s:%d", "", 8082), nil)

*/
func Configure(filename string) Handler {
	config := LoadConfig(filename)
	return configure(config)
}
<commit_msg>Spelling<commit_after>package imgscale

import (
)

/*
	Configure returns Handler which implement http.Handler
	Filename is the configuration file in json, content looks something like this

		{
			"Path": "./data",
			"Prefix": "img",
			"Formats": [
				{"Prefix": "100x100", "Height": 100, "Ratio": 1.0, "Thumbnail": true},
				{"Prefix": "66x100", "Height": 100, "Ratio": 0.67, "Thumbnail": true},
				{"Prefix": "100x75", "Height": 75, "Ratio": 1.335, "Thumbnail": true},
				{"Prefix": "100x0", "Height": 100, "Ratio": 0.0, "Thumbnail": true},
				{"Prefix": "originalx1", "Height": 0, "Ratio": 1.0, "Thumbnail": false},
				{"Prefix": "original", "Height": 0, "Ratio": 0.0, "Thumbnail": false}
			],
			"Exts": ["jpg", "png"],
			"Comment": "Copyright"
		}

	The returned handler could use as middleware handler

	Negroni middleware:

		n := negroni.New()
		n.UseHandler(imgscale.Configure("./config/formats.json"))
		http.ListenAndServe(fmt.Sprintf("%s:%d", "127.0.0.1", 8081), n)

	Martini middleware:

		app := martini.Classic()
		app.Use(imgscale.Configure("./config/formats.json").ServeHTTP)
		http.ListenAndServe(fmt.Sprintf("%s:%d", "127.0.0.1", 8080), app)

	http.Handle:

		http.Handle("/", imgscale.Configure("./config/formats.json"))
		http.ListenAndServe(fmt.Sprintf("%s:%d", "", 8082), nil)

*/
func Configure(filename string) Handler {
	config := LoadConfig(filename)
	return configure(config)
}
<|endoftext|>


<commit_before>package main

import (
	"regexp"

	"github.com/sapphirecat/devproxy"
)

// Interception configuration
func ConfigureRules(r RouteArgs) devproxy.Ruleset {
	// Full ruleset constructed here; capacity=1 because there will be 1 rule.
	rules := devproxy.NewRuleset(1)
	rules.Add(devproxy.Rule{
		// Matcher: may be matched against a hostname only (http + default port)
		// or may include a ":port" section (http + other port, https + any port)
		regexp.MustCompile("^(?:.+\\.)?example\\.(com|net|org)(?::\\d+)?$"),

		// Action: a func(host,mode)string that returns either "" (meaning
		// declined, try the next matcher) or a "hostname[:port]" to connect.
		// :port MUST be added for TLS!
		//
		// We also offer a set of functions that build Actions: SendHttpTo,
		// SendTlsTo, and SendAllTo all take a host, and send traffic to ports 80
		// and/or 443 as appropriate.  Each of those has a Send*ToPort variant
		// that takes a host and port, and sends traffic to the specified port.
		//
		// Yes, SendAllToPort sends both HTTP and TLS traffic to the _same_ port.
		devproxy.SendAllTo(r.Target)})

	return rules
}

// vim:ft=go
<commit_msg>Add/reword comments in config.go for clarity.<commit_after>package main

import (
	"regexp"

	"github.com/sapphirecat/devproxy"
)

// Interception configuration
func ConfigureRules(r RouteArgs) devproxy.Ruleset {
	// Full ruleset constructed here; capacity=1 is an optimization hint.
	// Try to match it to the number of rules, but it's not critical.  The
	// Add() function will allocate internally if needed.
	rules := devproxy.NewRuleset(1)

	// Add a rule to the ruleset.
	rules.Add(devproxy.Rule{
		// Matcher: may be matched against a hostname only (http + default port)
		// or may include a ":port" section (http + other port, https + any port)
		regexp.MustCompile("^(?:.+\\.)?example\\.(com|net|org)(?::\\d+)?$"),

		// Action: a destination to forward to.  Represented as a function that
		// returns either "" (meaning declined, try the next matcher) or a
		// "hostname[:port]" to connect.  :port MUST be added for TLS!
		//
		// There are some pre-defined functions to build Actions: SendHttpTo,
		// SendTlsTo, and SendAllTo all take a host, and send traffic to ports 80
		// and/or 443 as appropriate.  Each of those has a Send*ToPort variant
		// that takes a host and port, and sends traffic to the specified port.
		// SendAllToPort sends both HTTP and TLS traffic to the _same_ port; it's
		// largely defined for completeness.
		devproxy.SendAllTo(r.Target)})

	// More rules can be added here, if needed.

	// Return the completed ruleset.
	return rules
}

// vim:ft=go
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"
	"text/tabwriter"
	"time"

	"github.com/influxdb/influxdb/cmd/influx_tsm/b1"
	"github.com/influxdb/influxdb/cmd/influx_tsm/bz1"
	"github.com/influxdb/influxdb/cmd/influx_tsm/tsdb"
)

type ShardReader interface {
	KeyIterator
	Open() error
	Close() error
}

const (
	backupExt = "bak"
	tsmExt    = "tsm"
)

var description = fmt.Sprintf(`
Convert a database from b1 or bz1 format to tsm1 format.

This tool will backup any directory before conversion. It is up to the
end-user to delete the backup on the disk. Backups are named by suffixing
the database name with '.%s'. The backups will be ignored by the system
since they are not registered with the cluster.

To restore a backup, delete the tsm version, rename the backup and
restart the node.`, backupExt)

var dataPath string
var ds string
var tsmSz uint64
var parallel bool
var disBack bool

const maxTSMSz = 1 * 1024 * 1024 * 1024

func init() {
	flag.StringVar(&ds, "dbs", "", "Comma-delimited list of databases to convert. Default is to convert all")
	flag.Uint64Var(&tsmSz, "sz", maxTSMSz, "Maximum size of individual TSM files.")
	flag.BoolVar(&parallel, "parallel", false, "Perform parallel conversion.")
	flag.BoolVar(&disBack, "nobackup", false, "Disable database backups. Not recommended.")
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [options] <data-path> \n", os.Args[0])
		fmt.Fprintf(os.Stderr, "%s\n\n", description)
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr, "\n")
	}
}

func main() {
	pg := NewParallelGroup(1)

	flag.Parse()
	if len(flag.Args()) < 1 {
		fmt.Fprintf(os.Stderr, "No data directory specified\n")
		os.Exit(1)
	}
	dataPath = flag.Args()[0]

	if tsmSz > maxTSMSz {
		fmt.Fprintf(os.Stderr, "Maximum TSM file size is %d\n", maxTSMSz)
		os.Exit(1)
	}

	// Check if specific directories were requested.
	reqDs := strings.Split(ds, ",")
	if len(reqDs) == 1 && reqDs[0] == "" {
		reqDs = nil
	}

	// Determine the list of databases
	dbs, err := ioutil.ReadDir(dataPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to access data directory at %s: %s\n", dataPath, err.Error())
		os.Exit(1)
	}
	fmt.Println() // Cleanly separate output from start of program.

	// Dump summary of what is about to happen.
	fmt.Println("b1 and bz1 shard conversion.")
	fmt.Println("-----------------------------------")
	fmt.Println("Data directory is:       ", dataPath)
	fmt.Println("Databases specified:     ", allDBs(reqDs))
	fmt.Println("Database backups enabled:", yesno(!disBack))
	fmt.Println("Parallel mode enabled:   ", yesno(parallel))
	fmt.Println()

	// Get the list of shards for conversion.
	var shards []*tsdb.ShardInfo
	for _, db := range dbs {
		if strings.HasSuffix(db.Name(), backupExt) {
			fmt.Printf("Skipping %s as it looks like a backup.\n", db.Name())
			continue
		}

		d := tsdb.NewDatabase(filepath.Join(dataPath, db.Name()))
		shs, err := d.Shards()
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to access shards for database %s: %s\n", d.Name(), err.Error())
			os.Exit(1)
		}
		shards = append(shards, shs...)
	}
	sort.Sort(tsdb.ShardInfos(shards))
	usl := len(shards)
	shards = tsdb.ShardInfos(shards).FilterFormat(tsdb.TSM1).ExclusiveDatabases(reqDs)
	sl := len(shards)

	// Anything to convert?
	fmt.Printf("\n%d shard(s) detected, %d non-TSM shards detected.\n", usl, sl)
	if len(shards) == 0 {
		fmt.Printf("Nothing to do.\n")
		os.Exit(0)
	}

	// Display list of convertible shards.
	fmt.Println()
	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 1, '\t', 0)
	fmt.Fprintln(w, "Database\tRetention\tPath\tEngine\tSize")
	for _, si := range shards {
		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n", si.Database, si.RetentionPolicy, si.FullPath(dataPath), si.FormatAsString(), si.Size)
	}
	w.Flush()

	// Get confirmation from user.
	fmt.Printf("\nThese shards will be converted. Proceed? y/N: ")
	liner := bufio.NewReader(os.Stdin)
	yn, err := liner.ReadString('\n')
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to read response: %s", err.Error())
		os.Exit(1)
	}
	yn = strings.TrimRight(strings.ToLower(yn), "\n")
	if yn != "y" {
		fmt.Println("Conversion aborted.")
		os.Exit(1)
	}
	fmt.Println("Conversion starting....")

	// Backup each directory.
	if !disBack {
		databases := tsdb.ShardInfos(shards).Databases()
		if parallel {
			pg = NewParallelGroup(len(databases))
		}
		for _, db := range databases {
			pg.Request()
			go func(db string) {
				defer pg.Release()

				start := time.Now()
				err := backupDatabase(filepath.Join(dataPath, db))
				if err != nil {
					fmt.Fprintf(os.Stderr, "Backup of database %s failed: %s\n", db, err.Error())
					os.Exit(1)
				}
				fmt.Printf("Database %s backed up (%v)\n", db, time.Now().Sub(start))
			}(db)
		}
		pg.Wait()
	} else {
		fmt.Println("Database backup disabled.")
	}

	// Convert each shard.
	if parallel {
		pg = NewParallelGroup(len(shards))
	}
	for _, si := range shards {
		pg.Request()
		go func(si *tsdb.ShardInfo) {
			defer pg.Release()

			start := time.Now()
			if err := convertShard(si); err != nil {
				fmt.Fprintf(os.Stderr, "Failed to convert %s: %s\n", si.FullPath(dataPath), err.Error())
				os.Exit(1)
			}
			fmt.Printf("Conversion of %s successful (%s)\n", si.FullPath(dataPath), time.Now().Sub(start))
		}(si)
	}
	pg.Wait()
}

// backupDatabase backs up the database at src.
func backupDatabase(src string) error {
	dest := filepath.Join(src + "." + backupExt)
	if _, err := os.Stat(dest); !os.IsNotExist(err) {
		return fmt.Errorf("backup of %s already exists", src)
	}
	return copyDir(dest, src)
}

// copyDir copies the directory at src to dest. If dest does not exist it
// will be created. It is up to the caller to ensure the paths don't overlap.
func copyDir(dest, src string) error {
	copyFile := func(path string, info os.FileInfo, err error) error {
		// Strip the src from the path and replace with dest.
		toPath := strings.Replace(path, src, dest, 1)

		// Copy it.
		if info.IsDir() {
			if err := os.MkdirAll(toPath, info.Mode()); err != nil {
				return err
			}
		} else {
			err := func() error {
				in, err := os.Open(path)
				if err != nil {
					return err
				}
				defer in.Close()

				out, err := os.OpenFile(toPath, os.O_CREATE|os.O_WRONLY, info.Mode())
				if err != nil {
					return err
				}
				defer out.Close()

				_, err = io.Copy(out, in)
				return err
			}()
			if err != nil {
				return err
			}
		}
		return nil
	}

	return filepath.Walk(src, copyFile)
}

// convertShard converts the shard in-place.
func convertShard(si *tsdb.ShardInfo) error {
	src := si.FullPath(dataPath)
	dst := fmt.Sprintf("%s.%s", src, tsmExt)

	var reader ShardReader
	switch si.Format {
	case tsdb.BZ1:
		reader = bz1.NewReader(src)
	case tsdb.B1:
		reader = b1.NewReader(src)
	default:
		return fmt.Errorf("Unsupported shard format: %s", si.FormatAsString())
	}
	defer reader.Close()

	// Open the shard, and create a converter.
	if err := reader.Open(); err != nil {
		return fmt.Errorf("Failed to open %s for conversion: %s", src, err.Error())
	}
	converter := NewConverter(dst, uint32(tsmSz))

	// Perform the conversion.
	if err := converter.Process(reader); err != nil {
		return fmt.Errorf("Conversion of %s failed: %s", src, err.Error())
	}

	// Delete source shard, and rename new tsm1 shard.
	if err := reader.Close(); err != nil {
		return fmt.Errorf("Conversion of %s failed due to close: %s", src, err.Error())
	}

	if err := os.RemoveAll(si.FullPath(dataPath)); err != nil {
		return fmt.Errorf("Deletion of %s failed: %s", src, err.Error())
	}
	if err := os.Rename(dst, src); err != nil {
		return fmt.Errorf("Rename of %s to %s failed: %s", dst, src, err.Error())
	}

	return nil
}

// ParallelGroup allows the maximum parrallelism of a set of operations to be controlled.
type ParallelGroup struct {
	c  chan struct{}
	wg sync.WaitGroup
}

// NewParallelGroup returns a group which allows n operations to run in parallel. A value of 0
// means no operations will ever run.
func NewParallelGroup(n int) *ParallelGroup {
	return &ParallelGroup{
		c: make(chan struct{}, n),
	}
}

// Request requests permission to start an operation. It will block unless and until
// the parallel requirements would not be violated.
func (p *ParallelGroup) Request() {
	p.wg.Add(1)
	p.c <- struct{}{}
}

// Release informs the group that a previoulsy requested operation has completed.
func (p *ParallelGroup) Release() {
	<-p.c
	p.wg.Done()
}

// Wait blocks until the ParallelGroup has no unreleased operations.
func (p *ParallelGroup) Wait() {
	p.wg.Wait()
}

// yesno returns "yes" for true, "no" for false.
func yesno(b bool) string {
	if b {
		return "yes"
	}
	return "no"
}

// allDBs returns "all" if all databases are requested for conversion.
func allDBs(dbs []string) string {
	if dbs == nil {
		return "all"
	}
	return fmt.Sprintf("%v", dbs)
}
<commit_msg>Tweak influx_tsm help output<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"
	"text/tabwriter"
	"time"

	"github.com/influxdb/influxdb/cmd/influx_tsm/b1"
	"github.com/influxdb/influxdb/cmd/influx_tsm/bz1"
	"github.com/influxdb/influxdb/cmd/influx_tsm/tsdb"
)

type ShardReader interface {
	KeyIterator
	Open() error
	Close() error
}

const (
	backupExt = "bak"
	tsmExt    = "tsm"
)

var description = fmt.Sprintf(`
Convert a database from b1 or bz1 format to tsm1 format.

This tool will backup any directory before conversion. It is up to the
end-user to delete the backup on the disk, once the end-user is happy
with the converted data. Backups are named by suffixing the database
name with '.%s'. The backups will be ignored by the system since they
are not registered with the cluster.

To restore a backup, delete the tsm1 version, rename the backup directory
restart the node.`, backupExt)

var dataPath string
var ds string
var tsmSz uint64
var parallel bool
var disBack bool

const maxTSMSz = 1 * 1024 * 1024 * 1024

func init() {
	flag.StringVar(&ds, "dbs", "", "Comma-delimited list of databases to convert. Default is to convert all databases.")
	flag.Uint64Var(&tsmSz, "sz", maxTSMSz, "Maximum size of individual TSM files.")
	flag.BoolVar(&parallel, "parallel", false, "Perform parallel conversion.")
	flag.BoolVar(&disBack, "nobackup", false, "Disable database backups. Not recommended.")
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [options] <data-path> \n", os.Args[0])
		fmt.Fprintf(os.Stderr, "%s\n\n", description)
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr, "\n")
	}
}

func main() {
	pg := NewParallelGroup(1)

	flag.Parse()
	if len(flag.Args()) < 1 {
		fmt.Fprintf(os.Stderr, "No data directory specified\n")
		os.Exit(1)
	}
	dataPath = flag.Args()[0]

	if tsmSz > maxTSMSz {
		fmt.Fprintf(os.Stderr, "Maximum TSM file size is %d\n", maxTSMSz)
		os.Exit(1)
	}

	// Check if specific directories were requested.
	reqDs := strings.Split(ds, ",")
	if len(reqDs) == 1 && reqDs[0] == "" {
		reqDs = nil
	}

	// Determine the list of databases
	dbs, err := ioutil.ReadDir(dataPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to access data directory at %s: %s\n", dataPath, err.Error())
		os.Exit(1)
	}
	fmt.Println() // Cleanly separate output from start of program.

	// Dump summary of what is about to happen.
	fmt.Println("b1 and bz1 shard conversion.")
	fmt.Println("-----------------------------------")
	fmt.Println("Data directory is:       ", dataPath)
	fmt.Println("Databases specified:     ", allDBs(reqDs))
	fmt.Println("Database backups enabled:", yesno(!disBack))
	fmt.Println("Parallel mode enabled:   ", yesno(parallel))
	fmt.Println()

	// Get the list of shards for conversion.
	var shards []*tsdb.ShardInfo
	for _, db := range dbs {
		if strings.HasSuffix(db.Name(), backupExt) {
			fmt.Printf("Skipping %s as it looks like a backup.\n", db.Name())
			continue
		}

		d := tsdb.NewDatabase(filepath.Join(dataPath, db.Name()))
		shs, err := d.Shards()
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to access shards for database %s: %s\n", d.Name(), err.Error())
			os.Exit(1)
		}
		shards = append(shards, shs...)
	}
	sort.Sort(tsdb.ShardInfos(shards))
	usl := len(shards)
	shards = tsdb.ShardInfos(shards).FilterFormat(tsdb.TSM1).ExclusiveDatabases(reqDs)
	sl := len(shards)

	// Anything to convert?
	fmt.Printf("\n%d shard(s) detected, %d non-TSM shards detected.\n", usl, sl)
	if len(shards) == 0 {
		fmt.Printf("Nothing to do.\n")
		os.Exit(0)
	}

	// Display list of convertible shards.
	fmt.Println()
	w := new(tabwriter.Writer)
	w.Init(os.Stdout, 0, 8, 1, '\t', 0)
	fmt.Fprintln(w, "Database\tRetention\tPath\tEngine\tSize")
	for _, si := range shards {
		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n", si.Database, si.RetentionPolicy, si.FullPath(dataPath), si.FormatAsString(), si.Size)
	}
	w.Flush()

	// Get confirmation from user.
	fmt.Printf("\nThese shards will be converted. Proceed? y/N: ")
	liner := bufio.NewReader(os.Stdin)
	yn, err := liner.ReadString('\n')
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to read response: %s", err.Error())
		os.Exit(1)
	}
	yn = strings.TrimRight(strings.ToLower(yn), "\n")
	if yn != "y" {
		fmt.Println("Conversion aborted.")
		os.Exit(1)
	}
	fmt.Println("Conversion starting....")

	// Backup each directory.
	if !disBack {
		databases := tsdb.ShardInfos(shards).Databases()
		if parallel {
			pg = NewParallelGroup(len(databases))
		}
		for _, db := range databases {
			pg.Request()
			go func(db string) {
				defer pg.Release()

				start := time.Now()
				err := backupDatabase(filepath.Join(dataPath, db))
				if err != nil {
					fmt.Fprintf(os.Stderr, "Backup of database %s failed: %s\n", db, err.Error())
					os.Exit(1)
				}
				fmt.Printf("Database %s backed up (%v)\n", db, time.Now().Sub(start))
			}(db)
		}
		pg.Wait()
	} else {
		fmt.Println("Database backup disabled.")
	}

	// Convert each shard.
	if parallel {
		pg = NewParallelGroup(len(shards))
	}
	for _, si := range shards {
		pg.Request()
		go func(si *tsdb.ShardInfo) {
			defer pg.Release()

			start := time.Now()
			if err := convertShard(si); err != nil {
				fmt.Fprintf(os.Stderr, "Failed to convert %s: %s\n", si.FullPath(dataPath), err.Error())
				os.Exit(1)
			}
			fmt.Printf("Conversion of %s successful (%s)\n", si.FullPath(dataPath), time.Now().Sub(start))
		}(si)
	}
	pg.Wait()
}

// backupDatabase backs up the database at src.
func backupDatabase(src string) error {
	dest := filepath.Join(src + "." + backupExt)
	if _, err := os.Stat(dest); !os.IsNotExist(err) {
		return fmt.Errorf("backup of %s already exists", src)
	}
	return copyDir(dest, src)
}

// copyDir copies the directory at src to dest. If dest does not exist it
// will be created. It is up to the caller to ensure the paths don't overlap.
func copyDir(dest, src string) error {
	copyFile := func(path string, info os.FileInfo, err error) error {
		// Strip the src from the path and replace with dest.
		toPath := strings.Replace(path, src, dest, 1)

		// Copy it.
		if info.IsDir() {
			if err := os.MkdirAll(toPath, info.Mode()); err != nil {
				return err
			}
		} else {
			err := func() error {
				in, err := os.Open(path)
				if err != nil {
					return err
				}
				defer in.Close()

				out, err := os.OpenFile(toPath, os.O_CREATE|os.O_WRONLY, info.Mode())
				if err != nil {
					return err
				}
				defer out.Close()

				_, err = io.Copy(out, in)
				return err
			}()
			if err != nil {
				return err
			}
		}
		return nil
	}

	return filepath.Walk(src, copyFile)
}

// convertShard converts the shard in-place.
func convertShard(si *tsdb.ShardInfo) error {
	src := si.FullPath(dataPath)
	dst := fmt.Sprintf("%s.%s", src, tsmExt)

	var reader ShardReader
	switch si.Format {
	case tsdb.BZ1:
		reader = bz1.NewReader(src)
	case tsdb.B1:
		reader = b1.NewReader(src)
	default:
		return fmt.Errorf("Unsupported shard format: %s", si.FormatAsString())
	}
	defer reader.Close()

	// Open the shard, and create a converter.
	if err := reader.Open(); err != nil {
		return fmt.Errorf("Failed to open %s for conversion: %s", src, err.Error())
	}
	converter := NewConverter(dst, uint32(tsmSz))

	// Perform the conversion.
	if err := converter.Process(reader); err != nil {
		return fmt.Errorf("Conversion of %s failed: %s", src, err.Error())
	}

	// Delete source shard, and rename new tsm1 shard.
	if err := reader.Close(); err != nil {
		return fmt.Errorf("Conversion of %s failed due to close: %s", src, err.Error())
	}

	if err := os.RemoveAll(si.FullPath(dataPath)); err != nil {
		return fmt.Errorf("Deletion of %s failed: %s", src, err.Error())
	}
	if err := os.Rename(dst, src); err != nil {
		return fmt.Errorf("Rename of %s to %s failed: %s", dst, src, err.Error())
	}

	return nil
}

// ParallelGroup allows the maximum parrallelism of a set of operations to be controlled.
type ParallelGroup struct {
	c  chan struct{}
	wg sync.WaitGroup
}

// NewParallelGroup returns a group which allows n operations to run in parallel. A value of 0
// means no operations will ever run.
func NewParallelGroup(n int) *ParallelGroup {
	return &ParallelGroup{
		c: make(chan struct{}, n),
	}
}

// Request requests permission to start an operation. It will block unless and until
// the parallel requirements would not be violated.
func (p *ParallelGroup) Request() {
	p.wg.Add(1)
	p.c <- struct{}{}
}

// Release informs the group that a previoulsy requested operation has completed.
func (p *ParallelGroup) Release() {
	<-p.c
	p.wg.Done()
}

// Wait blocks until the ParallelGroup has no unreleased operations.
func (p *ParallelGroup) Wait() {
	p.wg.Wait()
}

// yesno returns "yes" for true, "no" for false.
func yesno(b bool) string {
	if b {
		return "yes"
	}
	return "no"
}

// allDBs returns "all" if all databases are requested for conversion.
func allDBs(dbs []string) string {
	if dbs == nil {
		return "all"
	}
	return fmt.Sprintf("%v", dbs)
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

// The kubelet binary is responsible for maintaining a set of containers on a particular host VM.
// It syncs data from both configuration file(s) as well as from a quorum of etcd servers.
// It then queries Docker to see what is currently running.  It synchronizes the configuration data,
// with the running set of containers by starting or stopping Docker containers.
package main

import (
	"math/rand"
	"os"
	"time"

	"k8s.io/component-base/logs"
	_ "k8s.io/component-base/metrics/prometheus/restclient"
	_ "k8s.io/component-base/metrics/prometheus/version" // for version metric registration
	"k8s.io/kubernetes/cmd/kubelet/app"
)

func main() {
	rand.Seed(time.Now().UnixNano())

	command := app.NewKubeletCommand()
	logs.InitLogs()
	defer logs.FlushLogs()

	if err := command.Execute(); err != nil {
		os.Exit(1)
	}
}
<commit_msg>kubelet: Imporve cmd comment for how kubelet works<commit_after>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

// The kubelet binary is responsible for maintaining a set of containers on a particular host VM.
// It syncs data from both configuration file(s) as well as from a quorum of etcd servers.
// It then communicates with the container runtime (or a CRI shim for the runtime) to see what is
// currently running.  It synchronizes the configuration data, with the running set of containers
// by starting or stopping containers.
package main

import (
	"math/rand"
	"os"
	"time"

	"k8s.io/component-base/logs"
	_ "k8s.io/component-base/metrics/prometheus/restclient"
	_ "k8s.io/component-base/metrics/prometheus/version" // for version metric registration
	"k8s.io/kubernetes/cmd/kubelet/app"
)

func main() {
	rand.Seed(time.Now().UnixNano())

	command := app.NewKubeletCommand()
	logs.InitLogs()
	defer logs.FlushLogs()

	if err := command.Execute(); err != nil {
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>// Copyright (C) 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Command llvm-build generates bazel build rules for the LLVM dependency.
package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

type entry struct {
	name string
	mode string
	path string
	deps []string
}

func main() {
	base := os.Args[1]
	libs := []entry{}
	filepath.Walk(base, func(path string, info os.FileInfo, err error) error {
		if info.Name() != "LLVMBuild.txt" {
			return nil
		}
		dir := filepath.Dir(path)
		rel, err := filepath.Rel(base, dir)
		if err != nil {
			return nil
		}
		file, err := os.Open(path)
		if err != nil {
			return nil
		}
		scanner := bufio.NewScanner(file)
		lib := entry{path: rel}
		key := ""
		for scanner.Scan() {
			line := strings.TrimSpace(scanner.Text())
			if len(line) == 0 {
				continue
			}
			if line[0] == ';' {
				continue
			}
			if line[0] == '[' {
				libs = append(libs, lib)
				lib = entry{path: rel}
				continue
			}
			if words := strings.SplitN(line, "=", 2); len(words) == 2 {
				key = strings.TrimSpace(words[0])
				line = strings.TrimSpace(words[1])
			}
			if line == "" {
				continue
			}
			switch key {
			case "name":
				lib.name += line
			case "type":
				lib.mode += line
			case "required_libraries":
				lib.deps = append(lib.deps, strings.Split(line, " ")...)
			}
		}
		libs = append(libs, lib)
		return nil
	})
	fmt.Printf(`# AUTOGENERATED FILE
# This file is automatically generated from the LLVMBuild.txt files
# Do not change this file by hand.
# See cmd/llvm-build/main.go for details.
# To update this file run
# bazel run //cmd/llvm-build $(bazel info output_base)/external/llvm > $(bazel info workspace)/tools/build/third_party/llvm/libs.bzl

load("@gapid//tools/build/third_party:llvm/rules.bzl", "llvmLibrary")

def llvm_auto_libs(**kwargs):
`)
	for _, lib := range libs {
		if lib.name == "" || lib.mode != "Library" {
			continue
		}
		fmt.Printf(`	llvm%v(
		name="%v",
		path="%v",
		deps=[`, lib.mode, lib.name, lib.path)
		for i, dep := range lib.deps {
			if i > 0 {
				fmt.Printf(`, `)
			}
			fmt.Printf(`":%v"`, dep)
		}
		fmt.Printf(`],
		**kwargs
	)
`)
	}
}
<commit_msg>Small fixes to the llvm-build generator script.<commit_after>// Copyright (C) 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Command llvm-build generates bazel build rules for the LLVM dependency.
package main

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

type entry struct {
	name string
	mode string
	path string
	deps []string
}

func main() {
	base := os.Args[1]
	libs := []entry{}
	err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error {
		if info.Name() != "LLVMBuild.txt" {
			return nil
		}
		dir := filepath.Dir(path)
		rel, err := filepath.Rel(base, dir)
		if err != nil {
			return err
		}
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		scanner := bufio.NewScanner(file)
		lib := entry{path: rel}
		key := ""
		for scanner.Scan() {
			line := strings.TrimSpace(scanner.Text())
			if len(line) == 0 {
				continue
			}
			if line[0] == ';' {
				continue
			}
			if line[0] == '[' {
				libs = append(libs, lib)
				lib = entry{path: rel}
				continue
			}
			if words := strings.SplitN(line, "=", 2); len(words) == 2 {
				key = strings.TrimSpace(words[0])
				line = strings.TrimSpace(words[1])
			}
			if line == "" {
				continue
			}
			switch key {
			case "name":
				lib.name += line
			case "type":
				lib.mode += line
			case "required_libraries":
				lib.deps = append(lib.deps, strings.Split(line, " ")...)
			}
			key = ""
		}
		libs = append(libs, lib)
		return nil
	})
	if err != nil {
		panic(err)
	}
	fmt.Printf(`# AUTOGENERATED FILE
# This file is automatically generated from the LLVMBuild.txt files
# Do not change this file by hand.
# See cmd/llvm-build/main.go for details.
# To update this file run
# bazel run //cmd/llvm-build $(bazel info output_base)/external/llvm > $(bazel info workspace)/tools/build/third_party/llvm/libs.bzl

load("@gapid//tools/build/third_party:llvm/rules.bzl", "llvmLibrary")

def llvm_auto_libs(**kwargs):
`)
	for _, lib := range libs {
		if lib.name == "" || lib.mode != "Library" {
			continue
		}
		fmt.Printf("  llvm%v(\n    name=\"%v\",\n    path=\"%v\",\n    deps=[", lib.mode, lib.name, lib.path)
		sort.Strings(lib.deps)
		for i, dep := range lib.deps {
			if i > 0 {
				fmt.Printf(`, `)
			}
			fmt.Printf(`":%v"`, dep)
		}
		fmt.Printf("],\n    **kwargs\n  )\n")
	}
}
<|endoftext|>


<commit_before>package test_helpers

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
	"testing"
	"time"

	"github.com/rfjakob/gocryptfs/internal/ctlsock"
	"github.com/rfjakob/gocryptfs/internal/nametransform"
)

// TmpDir will be created inside this directory
const testParentDir = "/tmp/gocryptfs-test-parent"

// GocryptfsBinary is the assumed path to the gocryptfs build.
const GocryptfsBinary = "../../gocryptfs"

// UnmountScript is the fusermount/umount compatability wrapper script
const UnmountScript = "../fuse-unmount.bash"

// TmpDir is a unique temporary directory. "go test" runs package tests in parallel. We create a
// unique TmpDir in init() so the tests do not interfere.
var TmpDir string

// DefaultPlainDir is TmpDir + "/default-plain"
var DefaultPlainDir string

// DefaultCipherDir is TmpDir + "/default-cipher"
var DefaultCipherDir string

func init() {
	os.MkdirAll(testParentDir, 0700)
	var err error
	TmpDir, err = ioutil.TempDir(testParentDir, "")
	if err != nil {
		panic(err)
	}
	DefaultPlainDir = TmpDir + "/default-plain"
	DefaultCipherDir = TmpDir + "/default-cipher"
}

// ResetTmpDir deletes TmpDir, create new dir tree:
//
// TmpDir
// |-- DefaultPlainDir
// *-- DefaultCipherDir
//     *-- gocryptfs.diriv
func ResetTmpDir(createDirIV bool) {
	// Try to unmount and delete everything
	entries, err := ioutil.ReadDir(TmpDir)
	if err == nil {
		for _, e := range entries {
			d := filepath.Join(TmpDir, e.Name())
			err = os.Remove(d)
			if err != nil {
				pe := err.(*os.PathError)
				if pe.Err == syscall.EBUSY {
					if testing.Verbose() {
						fmt.Printf("Remove failed: %v. Maybe still mounted?\n", pe)
					}
					err = UnmountErr(d)
					if err != nil {
						panic(err)
					}
				} else if pe.Err != syscall.ENOTEMPTY {
					panic("Unhandled error: " + pe.Err.Error())
				}
				err = os.RemoveAll(d)
				if err != nil {
					panic(err)
				}
			}
		}
	}
	err = os.Mkdir(DefaultPlainDir, 0700)
	if err != nil {
		panic(err)
	}
	err = os.Mkdir(DefaultCipherDir, 0700)
	if err != nil {
		panic(err)
	}
	if createDirIV {
		err = nametransform.WriteDirIV(DefaultCipherDir)
		if err != nil {
			panic(err)
		}
	}
}

// InitFS calls "gocryptfs -init" on a new directory in TmpDir, passing
// "extraArgs" in addition to useful defaults.
//
// The returned cipherdir has NO trailing slash.
func InitFS(t *testing.T, extraArgs ...string) string {
	dir, err := ioutil.TempDir(TmpDir, "")
	if err != nil {
		t.Fatal(err)
	}
	args := []string{"-q", "-init", "-extpass", "echo test", "-scryptn=10"}
	args = append(args, extraArgs...)
	args = append(args, dir)

	cmd := exec.Command(GocryptfsBinary, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err = cmd.Run()
	if err != nil {
		t.Fatalf("InitFS with args %v failed: %v", args, err)
	}

	return dir
}

// Mount CIPHERDIR "c" on PLAINDIR "p"
// Creates "p" if it does not exist.
func Mount(c string, p string, showOutput bool, extraArgs ...string) error {
	var args []string
	args = append(args, "-q", "-wpanic", "-nosyslog")
	args = append(args, extraArgs...)
	//args = append(args, "-fusedebug")
	//args = append(args, "-d")
	args = append(args, c)
	args = append(args, p)

	if _, err := os.Stat(p); err != nil {
		err = os.Mkdir(p, 0777)
		if err != nil {
			return err
		}
	}

	cmd := exec.Command(GocryptfsBinary, args...)
	if showOutput {
		cmd.Stderr = os.Stderr
		cmd.Stdout = os.Stdout
	}

	return cmd.Run()
}

// MountOrExit calls Mount() and exits on failure.
func MountOrExit(c string, p string, extraArgs ...string) {
	err := Mount(c, p, true, extraArgs...)
	if err != nil {
		fmt.Printf("mount failed: %v", err)
		os.Exit(1)
	}
}

// MountOrFatal calls Mount() and calls t.Fatal() on failure.
func MountOrFatal(t *testing.T, c string, p string, extraArgs ...string) {
	err := Mount(c, p, true, extraArgs...)
	if err != nil {
		t.Fatal(fmt.Errorf("mount failed: %v", err))
	}
}

// UnmountPanic tries to umount "dir" and panics on error.
func UnmountPanic(dir string) {
	err := UnmountErr(dir)
	if err != nil {
		fmt.Println(err)
		panic(err)
	}
}

// UnmountErr tries to unmount "dir" and returns the resulting error.
func UnmountErr(dir string) error {
	cmd := exec.Command(UnmountScript, "-u", "-z", dir)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

// Md5fn returns an md5 string for file "filename"
func Md5fn(filename string) string {
	buf, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Printf("ReadFile: %v\n", err)
		return ""
	}
	return Md5hex(buf)
}

// Md5hex returns an md5 string for "buf"
func Md5hex(buf []byte) string {
	rawHash := md5.Sum(buf)
	hash := hex.EncodeToString(rawHash[:])
	return hash
}

// VerifySize checks that the file size equals "want". This checks:
// 1) Size reported by Stat()
// 2) Number of bytes returned when reading the whole file
func VerifySize(t *testing.T, path string, want int) {
	buf, err := ioutil.ReadFile(path)
	if err != nil {
		t.Errorf("ReadFile failed: %v", err)
	} else if len(buf) != want {
		t.Errorf("wrong read size: got=%d want=%d", len(buf), want)
	}

	fi, err := os.Stat(path)
	if err != nil {
		t.Errorf("Stat failed: %v", err)
	} else if fi.Size() != int64(want) {
		t.Errorf("wrong stat file size, got=%d want=%d", fi.Size(), want)
	}
}

// TestMkdirRmdir creates and deletes a directory
func TestMkdirRmdir(t *testing.T, plainDir string) {
	dir := plainDir + "/dir1"
	err := os.Mkdir(dir, 0777)
	if err != nil {
		t.Error(err)
		return
	}
	err = syscall.Rmdir(dir)
	if err != nil {
		t.Error(err)
		return
	}
	// Removing a non-empty dir should fail with ENOTEMPTY
	if os.Mkdir(dir, 0777) != nil {
		t.Error(err)
		return
	}
	f, err := os.Create(dir + "/file")
	if err != nil {
		t.Error(err)
		return
	}
	f.Close()
	err = syscall.Rmdir(dir)
	errno := err.(syscall.Errno)
	if errno != syscall.ENOTEMPTY {
		t.Errorf("Should have gotten ENOTEMPTY, go %v", errno)
	}
	if syscall.Unlink(dir+"/file") != nil {
		t.Error(err)
		return
	}
	if syscall.Rmdir(dir) != nil {
		t.Error(err)
		return
	}
	// We should also be able to remove a directory we do not have permissions to
	// read or write
	err = os.Mkdir(dir, 0000)
	if err != nil {
		t.Error(err)
		return
	}
	err = syscall.Rmdir(dir)
	if err != nil {
		// Make sure the directory can cleaned up by the next test run
		os.Chmod(dir, 0700)
		t.Error(err)
		return
	}
}

// TestRename creates and renames a file
func TestRename(t *testing.T, plainDir string) {
	file1 := plainDir + "/rename1"
	file2 := plainDir + "/rename2"
	err := ioutil.WriteFile(file1, []byte("content"), 0777)
	if err != nil {
		t.Error(err)
		return
	}
	err = syscall.Rename(file1, file2)
	if err != nil {
		t.Error(err)
		return
	}
	syscall.Unlink(file2)
}

// VerifyExistence checks in 3 ways that "path" exists:
// stat, open, readdir
func VerifyExistence(path string) bool {
	// Check that file can be stated
	_, err := os.Stat(path)
	if err != nil {
		//t.Log(err)
		return false
	}
	// Check that file can be opened
	fd, err := os.Open(path)
	if err != nil {
		//t.Log(err)
		return false
	}
	fd.Close()
	// Check that file shows up in directory listing
	dir := filepath.Dir(path)
	name := filepath.Base(path)
	fi, err := ioutil.ReadDir(dir)
	if err != nil {
		//t.Log(err)
		return false
	}
	for _, i := range fi {
		if i.Name() == name {
			return true
		}
	}
	return false
}

// Du returns the disk usage of the file "fd" points to, in bytes.
// Same as "du --block-size=1".
func Du(t *testing.T, fd int) (nBytes int64) {
	var st syscall.Stat_t
	err := syscall.Fstat(fd, &st)
	if err != nil {
		t.Fatal(err)
	}
	// st.Blocks = number of 512-byte blocks
	return st.Blocks * 512
}

// QueryCtlSock sends a request to the control socket at "socketPath" and
// returns the response.
func QueryCtlSock(t *testing.T, socketPath string, req ctlsock.RequestStruct) (response ctlsock.ResponseStruct) {
	conn, err := net.DialTimeout("unix", socketPath, 1*time.Second)
	if err != nil {
		t.Fatal(err)
	}
	defer conn.Close()
	conn.SetDeadline(time.Now().Add(time.Second))
	msg, err := json.Marshal(req)
	if err != nil {
		t.Fatal(err)
	}
	_, err = conn.Write(msg)
	if err != nil {
		t.Fatal(err)
	}
	buf := make([]byte, 2*syscall.PathMax)
	n, err := conn.Read(buf)
	if err != nil {
		t.Fatal(err)
	}
	buf = buf[:n]
	json.Unmarshal(buf, &response)
	return response
}
<commit_msg>tests: get rid of syscall.PathMax<commit_after>package test_helpers

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
	"testing"
	"time"

	"github.com/rfjakob/gocryptfs/internal/ctlsock"
	"github.com/rfjakob/gocryptfs/internal/nametransform"
)

// TmpDir will be created inside this directory
const testParentDir = "/tmp/gocryptfs-test-parent"

// GocryptfsBinary is the assumed path to the gocryptfs build.
const GocryptfsBinary = "../../gocryptfs"

// UnmountScript is the fusermount/umount compatability wrapper script
const UnmountScript = "../fuse-unmount.bash"

// TmpDir is a unique temporary directory. "go test" runs package tests in parallel. We create a
// unique TmpDir in init() so the tests do not interfere.
var TmpDir string

// DefaultPlainDir is TmpDir + "/default-plain"
var DefaultPlainDir string

// DefaultCipherDir is TmpDir + "/default-cipher"
var DefaultCipherDir string

func init() {
	os.MkdirAll(testParentDir, 0700)
	var err error
	TmpDir, err = ioutil.TempDir(testParentDir, "")
	if err != nil {
		panic(err)
	}
	DefaultPlainDir = TmpDir + "/default-plain"
	DefaultCipherDir = TmpDir + "/default-cipher"
}

// ResetTmpDir deletes TmpDir, create new dir tree:
//
// TmpDir
// |-- DefaultPlainDir
// *-- DefaultCipherDir
//     *-- gocryptfs.diriv
func ResetTmpDir(createDirIV bool) {
	// Try to unmount and delete everything
	entries, err := ioutil.ReadDir(TmpDir)
	if err == nil {
		for _, e := range entries {
			d := filepath.Join(TmpDir, e.Name())
			err = os.Remove(d)
			if err != nil {
				pe := err.(*os.PathError)
				if pe.Err == syscall.EBUSY {
					if testing.Verbose() {
						fmt.Printf("Remove failed: %v. Maybe still mounted?\n", pe)
					}
					err = UnmountErr(d)
					if err != nil {
						panic(err)
					}
				} else if pe.Err != syscall.ENOTEMPTY {
					panic("Unhandled error: " + pe.Err.Error())
				}
				err = os.RemoveAll(d)
				if err != nil {
					panic(err)
				}
			}
		}
	}
	err = os.Mkdir(DefaultPlainDir, 0700)
	if err != nil {
		panic(err)
	}
	err = os.Mkdir(DefaultCipherDir, 0700)
	if err != nil {
		panic(err)
	}
	if createDirIV {
		err = nametransform.WriteDirIV(DefaultCipherDir)
		if err != nil {
			panic(err)
		}
	}
}

// InitFS calls "gocryptfs -init" on a new directory in TmpDir, passing
// "extraArgs" in addition to useful defaults.
//
// The returned cipherdir has NO trailing slash.
func InitFS(t *testing.T, extraArgs ...string) string {
	dir, err := ioutil.TempDir(TmpDir, "")
	if err != nil {
		t.Fatal(err)
	}
	args := []string{"-q", "-init", "-extpass", "echo test", "-scryptn=10"}
	args = append(args, extraArgs...)
	args = append(args, dir)

	cmd := exec.Command(GocryptfsBinary, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err = cmd.Run()
	if err != nil {
		t.Fatalf("InitFS with args %v failed: %v", args, err)
	}

	return dir
}

// Mount CIPHERDIR "c" on PLAINDIR "p"
// Creates "p" if it does not exist.
func Mount(c string, p string, showOutput bool, extraArgs ...string) error {
	var args []string
	args = append(args, "-q", "-wpanic", "-nosyslog")
	args = append(args, extraArgs...)
	//args = append(args, "-fusedebug")
	//args = append(args, "-d")
	args = append(args, c)
	args = append(args, p)

	if _, err := os.Stat(p); err != nil {
		err = os.Mkdir(p, 0777)
		if err != nil {
			return err
		}
	}

	cmd := exec.Command(GocryptfsBinary, args...)
	if showOutput {
		cmd.Stderr = os.Stderr
		cmd.Stdout = os.Stdout
	}

	return cmd.Run()
}

// MountOrExit calls Mount() and exits on failure.
func MountOrExit(c string, p string, extraArgs ...string) {
	err := Mount(c, p, true, extraArgs...)
	if err != nil {
		fmt.Printf("mount failed: %v", err)
		os.Exit(1)
	}
}

// MountOrFatal calls Mount() and calls t.Fatal() on failure.
func MountOrFatal(t *testing.T, c string, p string, extraArgs ...string) {
	err := Mount(c, p, true, extraArgs...)
	if err != nil {
		t.Fatal(fmt.Errorf("mount failed: %v", err))
	}
}

// UnmountPanic tries to umount "dir" and panics on error.
func UnmountPanic(dir string) {
	err := UnmountErr(dir)
	if err != nil {
		fmt.Println(err)
		panic(err)
	}
}

// UnmountErr tries to unmount "dir" and returns the resulting error.
func UnmountErr(dir string) error {
	cmd := exec.Command(UnmountScript, "-u", "-z", dir)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

// Md5fn returns an md5 string for file "filename"
func Md5fn(filename string) string {
	buf, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Printf("ReadFile: %v\n", err)
		return ""
	}
	return Md5hex(buf)
}

// Md5hex returns an md5 string for "buf"
func Md5hex(buf []byte) string {
	rawHash := md5.Sum(buf)
	hash := hex.EncodeToString(rawHash[:])
	return hash
}

// VerifySize checks that the file size equals "want". This checks:
// 1) Size reported by Stat()
// 2) Number of bytes returned when reading the whole file
func VerifySize(t *testing.T, path string, want int) {
	buf, err := ioutil.ReadFile(path)
	if err != nil {
		t.Errorf("ReadFile failed: %v", err)
	} else if len(buf) != want {
		t.Errorf("wrong read size: got=%d want=%d", len(buf), want)
	}

	fi, err := os.Stat(path)
	if err != nil {
		t.Errorf("Stat failed: %v", err)
	} else if fi.Size() != int64(want) {
		t.Errorf("wrong stat file size, got=%d want=%d", fi.Size(), want)
	}
}

// TestMkdirRmdir creates and deletes a directory
func TestMkdirRmdir(t *testing.T, plainDir string) {
	dir := plainDir + "/dir1"
	err := os.Mkdir(dir, 0777)
	if err != nil {
		t.Error(err)
		return
	}
	err = syscall.Rmdir(dir)
	if err != nil {
		t.Error(err)
		return
	}
	// Removing a non-empty dir should fail with ENOTEMPTY
	if os.Mkdir(dir, 0777) != nil {
		t.Error(err)
		return
	}
	f, err := os.Create(dir + "/file")
	if err != nil {
		t.Error(err)
		return
	}
	f.Close()
	err = syscall.Rmdir(dir)
	errno := err.(syscall.Errno)
	if errno != syscall.ENOTEMPTY {
		t.Errorf("Should have gotten ENOTEMPTY, go %v", errno)
	}
	if syscall.Unlink(dir+"/file") != nil {
		t.Error(err)
		return
	}
	if syscall.Rmdir(dir) != nil {
		t.Error(err)
		return
	}
	// We should also be able to remove a directory we do not have permissions to
	// read or write
	err = os.Mkdir(dir, 0000)
	if err != nil {
		t.Error(err)
		return
	}
	err = syscall.Rmdir(dir)
	if err != nil {
		// Make sure the directory can cleaned up by the next test run
		os.Chmod(dir, 0700)
		t.Error(err)
		return
	}
}

// TestRename creates and renames a file
func TestRename(t *testing.T, plainDir string) {
	file1 := plainDir + "/rename1"
	file2 := plainDir + "/rename2"
	err := ioutil.WriteFile(file1, []byte("content"), 0777)
	if err != nil {
		t.Error(err)
		return
	}
	err = syscall.Rename(file1, file2)
	if err != nil {
		t.Error(err)
		return
	}
	syscall.Unlink(file2)
}

// VerifyExistence checks in 3 ways that "path" exists:
// stat, open, readdir
func VerifyExistence(path string) bool {
	// Check that file can be stated
	_, err := os.Stat(path)
	if err != nil {
		//t.Log(err)
		return false
	}
	// Check that file can be opened
	fd, err := os.Open(path)
	if err != nil {
		//t.Log(err)
		return false
	}
	fd.Close()
	// Check that file shows up in directory listing
	dir := filepath.Dir(path)
	name := filepath.Base(path)
	fi, err := ioutil.ReadDir(dir)
	if err != nil {
		//t.Log(err)
		return false
	}
	for _, i := range fi {
		if i.Name() == name {
			return true
		}
	}
	return false
}

// Du returns the disk usage of the file "fd" points to, in bytes.
// Same as "du --block-size=1".
func Du(t *testing.T, fd int) (nBytes int64) {
	var st syscall.Stat_t
	err := syscall.Fstat(fd, &st)
	if err != nil {
		t.Fatal(err)
	}
	// st.Blocks = number of 512-byte blocks
	return st.Blocks * 512
}

// QueryCtlSock sends a request to the control socket at "socketPath" and
// returns the response.
func QueryCtlSock(t *testing.T, socketPath string, req ctlsock.RequestStruct) (response ctlsock.ResponseStruct) {
	conn, err := net.DialTimeout("unix", socketPath, 1*time.Second)
	if err != nil {
		t.Fatal(err)
	}
	defer conn.Close()
	conn.SetDeadline(time.Now().Add(time.Second))
	msg, err := json.Marshal(req)
	if err != nil {
		t.Fatal(err)
	}
	_, err = conn.Write(msg)
	if err != nil {
		t.Fatal(err)
	}
	buf := make([]byte, ctlsock.ReadBufSize)
	n, err := conn.Read(buf)
	if err != nil {
		t.Fatal(err)
	}
	buf = buf[:n]
	json.Unmarshal(buf, &response)
	return response
}
<|endoftext|>


<commit_before>/*
 * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"fmt"
	"math/rand"
	"net/rpc"
	"path"
	"sync"
	"time"

	router "github.com/gorilla/mux"
	"github.com/minio/dsync"
)

const (
	// Lock rpc server endpoint.
	lockServicePath = "/lock"

	// Lock rpc service name.
	lockServiceName = "Dsync"

	// Lock maintenance interval.
	lockMaintenanceInterval = 1 * time.Minute // 1 minute.

	// Lock validity check interval.
	lockValidityCheckInterval = 2 * time.Minute // 2 minutes.
)

// lockRequesterInfo stores various info from the client for each lock that is requested.
type lockRequesterInfo struct {
	writer          bool      // Bool whether write or read lock.
	node            string    // Network address of client claiming lock.
	serviceEndpoint string    // RPC path of client claiming lock.
	uid             string    // UID to uniquely identify request of client.
	timestamp       time.Time // Timestamp set at the time of initialization.
	timeLastCheck   time.Time // Timestamp for last check of validity of lock.
}

// isWriteLock returns whether the lock is a write or read lock.
func isWriteLock(lri []lockRequesterInfo) bool {
	return len(lri) == 1 && lri[0].writer
}

// lockServer is type for RPC handlers
type lockServer struct {
	AuthRPCServer
	ll localLocker
}

// Start lock maintenance from all lock servers.
func startLockMaintainence(lockServers []*lockServer) {
	for _, locker := range lockServers {
		// Start loop for stale lock maintenance
		go func(lk *lockServer) {
			// Initialize a new ticker with a minute between each ticks.
			ticker := time.NewTicker(lockMaintenanceInterval)

			// Start with random sleep time, so as to avoid "synchronous checks" between servers
			time.Sleep(time.Duration(rand.Float64() * float64(lockMaintenanceInterval)))
			for {
				// Verifies every minute for locks held more than 2minutes.
				select {
				case <-ticker.C:
					lk.lockMaintenance(lockValidityCheckInterval)
				case <-globalServiceDoneCh:
					// Stop the timer.
					ticker.Stop()
				}
			}
		}(locker)
	}
}

// Register distributed NS lock handlers.
func registerDistNSLockRouter(mux *router.Router, endpoints EndpointList) error {
	// Start lock maintenance from all lock servers.
	startLockMaintainence(globalLockServers)

	// Register initialized lock servers to their respective rpc endpoints.
	return registerStorageLockers(mux, globalLockServers)
}

// registerStorageLockers - register locker rpc handlers for net/rpc library clients
func registerStorageLockers(mux *router.Router, lockServers []*lockServer) error {
	for _, lockServer := range lockServers {
		lockRPCServer := rpc.NewServer()
		if err := lockRPCServer.RegisterName(lockServiceName, lockServer); err != nil {
			return traceError(err)
		}
		lockRouter := mux.PathPrefix(minioReservedBucketPath).Subrouter()
		lockRouter.Path(path.Join(lockServicePath, lockServer.ll.serviceEndpoint)).Handler(lockRPCServer)
	}
	return nil
}

// localLocker implements Dsync.NetLocker
type localLocker struct {
	mutex           sync.Mutex
	serviceEndpoint string
	serverAddr      string
	lockMap         map[string][]lockRequesterInfo
}

func (l *localLocker) ServerAddr() string {
	return l.serverAddr
}

func (l *localLocker) ServiceEndpoint() string {
	return l.serviceEndpoint
}

func (l *localLocker) Lock(args dsync.LockArgs) (reply bool, err error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	_, isLockTaken := l.lockMap[args.Resource]
	if !isLockTaken { // No locks held on the given name, so claim write lock
		l.lockMap[args.Resource] = []lockRequesterInfo{
			{
				writer:          true,
				node:            args.ServerAddr,
				serviceEndpoint: args.ServiceEndpoint,
				uid:             args.UID,
				timestamp:       UTCNow(),
				timeLastCheck:   UTCNow(),
			},
		}
	}
	// return reply=true if lock was granted.
	return !isLockTaken, nil
}

func (l *localLocker) Unlock(args dsync.LockArgs) (reply bool, err error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	var lri []lockRequesterInfo
	if lri, reply = l.lockMap[args.Resource]; !reply {
		// No lock is held on the given name
		return reply, fmt.Errorf("Unlock attempted on an unlocked entity: %s", args.Resource)
	}
	if reply = isWriteLock(lri); !reply {
		// Unless it is a write lock
		return reply, fmt.Errorf("Unlock attempted on a read locked entity: %s (%d read locks active)", args.Resource, len(lri))
	}
	if !l.removeEntry(args.Resource, args.UID, &lri) {
		return false, fmt.Errorf("Unlock unable to find corresponding lock for uid: %s", args.UID)
	}
	return true, nil

}

func (l *localLocker) RLock(args dsync.LockArgs) (reply bool, err error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	lrInfo := lockRequesterInfo{
		writer:          false,
		node:            args.ServerAddr,
		serviceEndpoint: args.ServiceEndpoint,
		uid:             args.UID,
		timestamp:       UTCNow(),
		timeLastCheck:   UTCNow(),
	}
	if lri, ok := l.lockMap[args.Resource]; ok {
		if reply = !isWriteLock(lri); reply {
			// Unless there is a write lock
			l.lockMap[args.Resource] = append(l.lockMap[args.Resource], lrInfo)
		}
	} else {
		// No locks held on the given name, so claim (first) read lock
		l.lockMap[args.Resource] = []lockRequesterInfo{lrInfo}
		reply = true
	}
	return reply, nil
}

func (l *localLocker) RUnlock(args dsync.LockArgs) (reply bool, err error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	var lri []lockRequesterInfo
	if lri, reply = l.lockMap[args.Resource]; !reply {
		// No lock is held on the given name
		return reply, fmt.Errorf("RUnlock attempted on an unlocked entity: %s", args.Resource)
	}
	if reply = !isWriteLock(lri); !reply {
		// A write-lock is held, cannot release a read lock
		return reply, fmt.Errorf("RUnlock attempted on a write locked entity: %s", args.Resource)
	}
	if !l.removeEntry(args.Resource, args.UID, &lri) {
		return false, fmt.Errorf("RUnlock unable to find corresponding read lock for uid: %s", args.UID)
	}
	return reply, nil
}

func (l *localLocker) ForceUnlock(args dsync.LockArgs) (reply bool, err error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	if len(args.UID) != 0 {
		return false, fmt.Errorf("ForceUnlock called with non-empty UID: %s", args.UID)
	}
	if _, ok := l.lockMap[args.Resource]; ok {
		// Only clear lock when it is taken
		// Remove the lock (irrespective of write or read lock)
		delete(l.lockMap, args.Resource)
	}
	return true, nil
}

///  Distributed lock handlers

// Lock - rpc handler for (single) write lock operation.
func (l *lockServer) Lock(args *LockArgs, reply *bool) (err error) {
	if err = args.IsAuthenticated(); err != nil {
		return err
	}
	*reply, err = l.ll.Lock(args.LockArgs)
	return err
}

// Unlock - rpc handler for (single) write unlock operation.
func (l *lockServer) Unlock(args *LockArgs, reply *bool) (err error) {
	if err = args.IsAuthenticated(); err != nil {
		return err
	}
	*reply, err = l.ll.Unlock(args.LockArgs)
	return err
}

// RLock - rpc handler for read lock operation.
func (l *lockServer) RLock(args *LockArgs, reply *bool) (err error) {
	if err = args.IsAuthenticated(); err != nil {
		return err
	}
	*reply, err = l.ll.RLock(args.LockArgs)
	return err
}

// RUnlock - rpc handler for read unlock operation.
func (l *lockServer) RUnlock(args *LockArgs, reply *bool) (err error) {
	if err = args.IsAuthenticated(); err != nil {
		return err
	}
	*reply, err = l.ll.RUnlock(args.LockArgs)
	return err
}

// ForceUnlock - rpc handler for force unlock operation.
func (l *lockServer) ForceUnlock(args *LockArgs, reply *bool) (err error) {
	if err = args.IsAuthenticated(); err != nil {
		return err
	}
	*reply, err = l.ll.ForceUnlock(args.LockArgs)
	return err
}

// Expired - rpc handler for expired lock status.
func (l *lockServer) Expired(args *LockArgs, reply *bool) error {
	if err := args.IsAuthenticated(); err != nil {
		return err
	}
	l.ll.mutex.Lock()
	defer l.ll.mutex.Unlock()
	// Lock found, proceed to verify if belongs to given uid.
	if lri, ok := l.ll.lockMap[args.LockArgs.Resource]; ok {
		// Check whether uid is still active
		for _, entry := range lri {
			if entry.uid == args.LockArgs.UID {
				*reply = false // When uid found, lock is still active so return not expired.
				return nil     // When uid found *reply is set to true.
			}
		}
	}
	// When we get here lock is no longer active due to either args.LockArgs.Resource
	// being absent from map or uid not found for given args.LockArgs.Resource
	*reply = true
	return nil
}

// nameLockRequesterInfoPair is a helper type for lock maintenance
type nameLockRequesterInfoPair struct {
	name string
	lri  lockRequesterInfo
}

// lockMaintenance loops over locks that have been active for some time and checks back
// with the original server whether it is still alive or not
//
// Following logic inside ignores the errors generated for Dsync.Active operation.
// - server at client down
// - some network error (and server is up normally)
//
// We will ignore the error, and we will retry later to get a resolve on this lock
func (l *lockServer) lockMaintenance(interval time.Duration) {
	l.ll.mutex.Lock()
	// Get list of long lived locks to check for staleness.
	nlripLongLived := getLongLivedLocks(l.ll.lockMap, interval)
	l.ll.mutex.Unlock()

	serverCred := serverConfig.GetCredential()
	// Validate if long lived locks are indeed clean.
	for _, nlrip := range nlripLongLived {
		// Initialize client based on the long live locks.
		c := newLockRPCClient(authConfig{
			accessKey:       serverCred.AccessKey,
			secretKey:       serverCred.SecretKey,
			serverAddr:      nlrip.lri.node,
			secureConn:      globalIsSSL,
			serviceEndpoint: nlrip.lri.serviceEndpoint,
			serviceName:     lockServiceName,
		})

		// Call back to original server verify whether the lock is still active (based on name & uid)
		expired, _ := c.Expired(dsync.LockArgs{
			UID:      nlrip.lri.uid,
			Resource: nlrip.name,
		})

		// Close the connection regardless of the call response.
		c.rpcClient.Close()

		// For successful response, verify if lock is indeed active or stale.
		if expired {
			// The lock is no longer active at server that originated the lock
			// So remove the lock from the map.
			l.ll.mutex.Lock()
			l.ll.removeEntryIfExists(nlrip) // Purge the stale entry if it exists.
			l.ll.mutex.Unlock()
		}
	}
}
<commit_msg>Fix spelling of function name to `startLockMaintenance` (#4561)<commit_after>/*
 * Minio Cloud Storage, (C) 2016, 2017 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"fmt"
	"math/rand"
	"net/rpc"
	"path"
	"sync"
	"time"

	router "github.com/gorilla/mux"
	"github.com/minio/dsync"
)

const (
	// Lock rpc server endpoint.
	lockServicePath = "/lock"

	// Lock rpc service name.
	lockServiceName = "Dsync"

	// Lock maintenance interval.
	lockMaintenanceInterval = 1 * time.Minute // 1 minute.

	// Lock validity check interval.
	lockValidityCheckInterval = 2 * time.Minute // 2 minutes.
)

// lockRequesterInfo stores various info from the client for each lock that is requested.
type lockRequesterInfo struct {
	writer          bool      // Bool whether write or read lock.
	node            string    // Network address of client claiming lock.
	serviceEndpoint string    // RPC path of client claiming lock.
	uid             string    // UID to uniquely identify request of client.
	timestamp       time.Time // Timestamp set at the time of initialization.
	timeLastCheck   time.Time // Timestamp for last check of validity of lock.
}

// isWriteLock returns whether the lock is a write or read lock.
func isWriteLock(lri []lockRequesterInfo) bool {
	return len(lri) == 1 && lri[0].writer
}

// lockServer is type for RPC handlers
type lockServer struct {
	AuthRPCServer
	ll localLocker
}

// Start lock maintenance from all lock servers.
func startLockMaintenance(lockServers []*lockServer) {
	for _, locker := range lockServers {
		// Start loop for stale lock maintenance
		go func(lk *lockServer) {
			// Initialize a new ticker with a minute between each ticks.
			ticker := time.NewTicker(lockMaintenanceInterval)

			// Start with random sleep time, so as to avoid "synchronous checks" between servers
			time.Sleep(time.Duration(rand.Float64() * float64(lockMaintenanceInterval)))
			for {
				// Verifies every minute for locks held more than 2minutes.
				select {
				case <-ticker.C:
					lk.lockMaintenance(lockValidityCheckInterval)
				case <-globalServiceDoneCh:
					// Stop the timer.
					ticker.Stop()
				}
			}
		}(locker)
	}
}

// Register distributed NS lock handlers.
func registerDistNSLockRouter(mux *router.Router, endpoints EndpointList) error {
	// Start lock maintenance from all lock servers.
	startLockMaintenance(globalLockServers)

	// Register initialized lock servers to their respective rpc endpoints.
	return registerStorageLockers(mux, globalLockServers)
}

// registerStorageLockers - register locker rpc handlers for net/rpc library clients
func registerStorageLockers(mux *router.Router, lockServers []*lockServer) error {
	for _, lockServer := range lockServers {
		lockRPCServer := rpc.NewServer()
		if err := lockRPCServer.RegisterName(lockServiceName, lockServer); err != nil {
			return traceError(err)
		}
		lockRouter := mux.PathPrefix(minioReservedBucketPath).Subrouter()
		lockRouter.Path(path.Join(lockServicePath, lockServer.ll.serviceEndpoint)).Handler(lockRPCServer)
	}
	return nil
}

// localLocker implements Dsync.NetLocker
type localLocker struct {
	mutex           sync.Mutex
	serviceEndpoint string
	serverAddr      string
	lockMap         map[string][]lockRequesterInfo
}

func (l *localLocker) ServerAddr() string {
	return l.serverAddr
}

func (l *localLocker) ServiceEndpoint() string {
	return l.serviceEndpoint
}

func (l *localLocker) Lock(args dsync.LockArgs) (reply bool, err error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	_, isLockTaken := l.lockMap[args.Resource]
	if !isLockTaken { // No locks held on the given name, so claim write lock
		l.lockMap[args.Resource] = []lockRequesterInfo{
			{
				writer:          true,
				node:            args.ServerAddr,
				serviceEndpoint: args.ServiceEndpoint,
				uid:             args.UID,
				timestamp:       UTCNow(),
				timeLastCheck:   UTCNow(),
			},
		}
	}
	// return reply=true if lock was granted.
	return !isLockTaken, nil
}

func (l *localLocker) Unlock(args dsync.LockArgs) (reply bool, err error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	var lri []lockRequesterInfo
	if lri, reply = l.lockMap[args.Resource]; !reply {
		// No lock is held on the given name
		return reply, fmt.Errorf("Unlock attempted on an unlocked entity: %s", args.Resource)
	}
	if reply = isWriteLock(lri); !reply {
		// Unless it is a write lock
		return reply, fmt.Errorf("Unlock attempted on a read locked entity: %s (%d read locks active)", args.Resource, len(lri))
	}
	if !l.removeEntry(args.Resource, args.UID, &lri) {
		return false, fmt.Errorf("Unlock unable to find corresponding lock for uid: %s", args.UID)
	}
	return true, nil

}

func (l *localLocker) RLock(args dsync.LockArgs) (reply bool, err error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	lrInfo := lockRequesterInfo{
		writer:          false,
		node:            args.ServerAddr,
		serviceEndpoint: args.ServiceEndpoint,
		uid:             args.UID,
		timestamp:       UTCNow(),
		timeLastCheck:   UTCNow(),
	}
	if lri, ok := l.lockMap[args.Resource]; ok {
		if reply = !isWriteLock(lri); reply {
			// Unless there is a write lock
			l.lockMap[args.Resource] = append(l.lockMap[args.Resource], lrInfo)
		}
	} else {
		// No locks held on the given name, so claim (first) read lock
		l.lockMap[args.Resource] = []lockRequesterInfo{lrInfo}
		reply = true
	}
	return reply, nil
}

func (l *localLocker) RUnlock(args dsync.LockArgs) (reply bool, err error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	var lri []lockRequesterInfo
	if lri, reply = l.lockMap[args.Resource]; !reply {
		// No lock is held on the given name
		return reply, fmt.Errorf("RUnlock attempted on an unlocked entity: %s", args.Resource)
	}
	if reply = !isWriteLock(lri); !reply {
		// A write-lock is held, cannot release a read lock
		return reply, fmt.Errorf("RUnlock attempted on a write locked entity: %s", args.Resource)
	}
	if !l.removeEntry(args.Resource, args.UID, &lri) {
		return false, fmt.Errorf("RUnlock unable to find corresponding read lock for uid: %s", args.UID)
	}
	return reply, nil
}

func (l *localLocker) ForceUnlock(args dsync.LockArgs) (reply bool, err error) {
	l.mutex.Lock()
	defer l.mutex.Unlock()
	if len(args.UID) != 0 {
		return false, fmt.Errorf("ForceUnlock called with non-empty UID: %s", args.UID)
	}
	if _, ok := l.lockMap[args.Resource]; ok {
		// Only clear lock when it is taken
		// Remove the lock (irrespective of write or read lock)
		delete(l.lockMap, args.Resource)
	}
	return true, nil
}

///  Distributed lock handlers

// Lock - rpc handler for (single) write lock operation.
func (l *lockServer) Lock(args *LockArgs, reply *bool) (err error) {
	if err = args.IsAuthenticated(); err != nil {
		return err
	}
	*reply, err = l.ll.Lock(args.LockArgs)
	return err
}

// Unlock - rpc handler for (single) write unlock operation.
func (l *lockServer) Unlock(args *LockArgs, reply *bool) (err error) {
	if err = args.IsAuthenticated(); err != nil {
		return err
	}
	*reply, err = l.ll.Unlock(args.LockArgs)
	return err
}

// RLock - rpc handler for read lock operation.
func (l *lockServer) RLock(args *LockArgs, reply *bool) (err error) {
	if err = args.IsAuthenticated(); err != nil {
		return err
	}
	*reply, err = l.ll.RLock(args.LockArgs)
	return err
}

// RUnlock - rpc handler for read unlock operation.
func (l *lockServer) RUnlock(args *LockArgs, reply *bool) (err error) {
	if err = args.IsAuthenticated(); err != nil {
		return err
	}
	*reply, err = l.ll.RUnlock(args.LockArgs)
	return err
}

// ForceUnlock - rpc handler for force unlock operation.
func (l *lockServer) ForceUnlock(args *LockArgs, reply *bool) (err error) {
	if err = args.IsAuthenticated(); err != nil {
		return err
	}
	*reply, err = l.ll.ForceUnlock(args.LockArgs)
	return err
}

// Expired - rpc handler for expired lock status.
func (l *lockServer) Expired(args *LockArgs, reply *bool) error {
	if err := args.IsAuthenticated(); err != nil {
		return err
	}
	l.ll.mutex.Lock()
	defer l.ll.mutex.Unlock()
	// Lock found, proceed to verify if belongs to given uid.
	if lri, ok := l.ll.lockMap[args.LockArgs.Resource]; ok {
		// Check whether uid is still active
		for _, entry := range lri {
			if entry.uid == args.LockArgs.UID {
				*reply = false // When uid found, lock is still active so return not expired.
				return nil     // When uid found *reply is set to true.
			}
		}
	}
	// When we get here lock is no longer active due to either args.LockArgs.Resource
	// being absent from map or uid not found for given args.LockArgs.Resource
	*reply = true
	return nil
}

// nameLockRequesterInfoPair is a helper type for lock maintenance
type nameLockRequesterInfoPair struct {
	name string
	lri  lockRequesterInfo
}

// lockMaintenance loops over locks that have been active for some time and checks back
// with the original server whether it is still alive or not
//
// Following logic inside ignores the errors generated for Dsync.Active operation.
// - server at client down
// - some network error (and server is up normally)
//
// We will ignore the error, and we will retry later to get a resolve on this lock
func (l *lockServer) lockMaintenance(interval time.Duration) {
	l.ll.mutex.Lock()
	// Get list of long lived locks to check for staleness.
	nlripLongLived := getLongLivedLocks(l.ll.lockMap, interval)
	l.ll.mutex.Unlock()

	serverCred := serverConfig.GetCredential()
	// Validate if long lived locks are indeed clean.
	for _, nlrip := range nlripLongLived {
		// Initialize client based on the long live locks.
		c := newLockRPCClient(authConfig{
			accessKey:       serverCred.AccessKey,
			secretKey:       serverCred.SecretKey,
			serverAddr:      nlrip.lri.node,
			secureConn:      globalIsSSL,
			serviceEndpoint: nlrip.lri.serviceEndpoint,
			serviceName:     lockServiceName,
		})

		// Call back to original server verify whether the lock is still active (based on name & uid)
		expired, _ := c.Expired(dsync.LockArgs{
			UID:      nlrip.lri.uid,
			Resource: nlrip.name,
		})

		// Close the connection regardless of the call response.
		c.rpcClient.Close()

		// For successful response, verify if lock is indeed active or stale.
		if expired {
			// The lock is no longer active at server that originated the lock
			// So remove the lock from the map.
			l.ll.mutex.Lock()
			l.ll.removeEntryIfExists(nlrip) // Purge the stale entry if it exists.
			l.ll.mutex.Unlock()
		}
	}
}
<|endoftext|>