<commit_before>package vault

import (
	"log"
	"sync/atomic"
	"time"

	"github.com/hashicorp/vault/logical"
)

// RollbackManager is responsible for performing rollbacks of partial
// secrets within logical backends.
//
// During normal operations, it is possible for logical backends to
// error partially through an operation. These are called "partial secrets":
// they are never sent back to a user, but they do need to be cleaned up.
// This manager handles that by periodically (on a timer) requesting that the
// backends clean up.
//
// The RollbackManager periodically (according to the Period option)
// initiates a logical.RollbackOperation on every mounted logical backend.
// It ensures that only one rollback operation is in-flight at any given
// time within a single seal/unseal phase.
type RollbackManager struct {
	Logger *log.Logger
	Mounts *MountTable
	Router *Router

	Period time.Duration // time between rollback calls

	running uint32
}

// Start starts the rollback manager. This will block until Stop is called
// so it should be executed within a goroutine.
func (m *RollbackManager) Start() {
	// If we're already running, then don't start again
	if !atomic.CompareAndSwapUint32(&m.running, 0, 1) {
		return
	}

	m.Logger.Printf("[INFO] rollback: starting rollback manager")

	var mounts map[string]*uint32
	tick := time.NewTicker(m.Period)
	defer tick.Stop()
	for {
		// Wait for the tick
		<-tick.C

		// If we're quitting, then stop
		if atomic.LoadUint32(&m.running) != 1 {
			m.Logger.Printf("[INFO] rollback: stopping rollback manager")
			return
		}

		// Get the list of paths that we should rollback and setup our
		// mounts mapping. Mounts that have since been unmounted will
		// just "fall off" naturally: they aren't in our new mount mapping
		// and when their goroutine ends they'll naturally lose the reference.
		newMounts := make(map[string]*uint32)
		m.Mounts.RLock()
		for _, e := range m.Mounts.Entries {
			if s, ok := mounts[e.Path]; ok {
				newMounts[e.Path] = s
			} else {
				newMounts[e.Path] = new(uint32)
			}
		}
		m.Mounts.RUnlock()
		mounts = newMounts

		// Go through the mounts and start the rollback if we can
		for path, status := range mounts {
			// If we can change the status from 0 to 1, we can start it
			if !atomic.CompareAndSwapUint32(status, 0, 1) {
				continue
			}

			go m.rollback(path, status)
		}
	}
}

// Stop stops the running manager. This will not halt any in-flight
// rollbacks.
func (m *RollbackManager) Stop() {
	atomic.StoreUint32(&m.running, 0)
}

func (m *RollbackManager) rollback(path string, state *uint32) {
	defer atomic.StoreUint32(state, 0)

	m.Logger.Printf(
		"[DEBUG] rollback: starting rollback for %s",
		path)
	req := &logical.Request{
		Operation: logical.RollbackOperation,
		Path:      path,
	}
	if _, err := m.Router.Route(req); err != nil {
		m.Logger.Printf(
			"[ERR] rollback: error rolling back %s: %s",
			path, err)
	}
}

// The methods below are the hooks from core that are called pre/post seal.

func (c *Core) startRollback() error {
	// Ensure if we had a rollback it was stopped. This should never
	// be the case but it doesn't hurt to check.
	if c.rollback != nil {
		c.rollback.Stop()
	}

	c.rollback = &RollbackManager{
		Logger: c.logger,
		Router: c.router,
		Mounts: c.mounts,
		Period: 1 * time.Minute,
	}
	go c.rollback.Start()

	return nil
}

func (c *Core) stopRollback() error {
	if c.rollback != nil {
		c.rollback.Stop()
		c.rollback = nil
	}

	return nil
}
<commit_msg>vault: ignore backends that don't support rollback<commit_after>package vault

import (
	"log"
	"sync/atomic"
	"time"

	"github.com/hashicorp/vault/logical"
)

// RollbackManager is responsible for performing rollbacks of partial
// secrets within logical backends.
//
// During normal operations, it is possible for logical backends to
// error partially through an operation. These are called "partial secrets":
// they are never sent back to a user, but they do need to be cleaned up.
// This manager handles that by periodically (on a timer) requesting that the
// backends clean up.
//
// The RollbackManager periodically (according to the Period option)
// initiates a logical.RollbackOperation on every mounted logical backend.
// It ensures that only one rollback operation is in-flight at any given
// time within a single seal/unseal phase.
type RollbackManager struct {
	Logger *log.Logger
	Mounts *MountTable
	Router *Router

	Period time.Duration // time between rollback calls

	running uint32
}

// Start starts the rollback manager. This will block until Stop is called
// so it should be executed within a goroutine.
func (m *RollbackManager) Start() {
	// If we're already running, then don't start again
	if !atomic.CompareAndSwapUint32(&m.running, 0, 1) {
		return
	}

	m.Logger.Printf("[INFO] rollback: starting rollback manager")

	var mounts map[string]*uint32
	tick := time.NewTicker(m.Period)
	defer tick.Stop()
	for {
		// Wait for the tick
		<-tick.C

		// If we're quitting, then stop
		if atomic.LoadUint32(&m.running) != 1 {
			m.Logger.Printf("[INFO] rollback: stopping rollback manager")
			return
		}

		// Get the list of paths that we should rollback and setup our
		// mounts mapping. Mounts that have since been unmounted will
		// just "fall off" naturally: they aren't in our new mount mapping
		// and when their goroutine ends they'll naturally lose the reference.
		newMounts := make(map[string]*uint32)
		m.Mounts.RLock()
		for _, e := range m.Mounts.Entries {
			if s, ok := mounts[e.Path]; ok {
				newMounts[e.Path] = s
			} else {
				newMounts[e.Path] = new(uint32)
			}
		}
		m.Mounts.RUnlock()
		mounts = newMounts

		// Go through the mounts and start the rollback if we can
		for path, status := range mounts {
			// If we can change the status from 0 to 1, we can start it
			if !atomic.CompareAndSwapUint32(status, 0, 1) {
				continue
			}

			go m.rollback(path, status)
		}
	}
}

// Stop stops the running manager. This will not halt any in-flight
// rollbacks.
func (m *RollbackManager) Stop() {
	atomic.StoreUint32(&m.running, 0)
}

func (m *RollbackManager) rollback(path string, state *uint32) {
	defer atomic.StoreUint32(state, 0)

	m.Logger.Printf(
		"[DEBUG] rollback: starting rollback for %s",
		path)
	req := &logical.Request{
		Operation: logical.RollbackOperation,
		Path:      path,
	}
	if _, err := m.Router.Route(req); err != nil {
		// If the error is an unsupported operation, then it doesn't
		// matter, the backend doesn't support it.
		if err == logical.ErrUnsupportedOperation {
			return
		}

		m.Logger.Printf(
			"[ERR] rollback: error rolling back %s: %s",
			path, err)
	}
}

// The methods below are the hooks from core that are called pre/post seal.

func (c *Core) startRollback() error {
	// Ensure if we had a rollback it was stopped. This should never
	// be the case but it doesn't hurt to check.
	if c.rollback != nil {
		c.rollback.Stop()
	}

	c.rollback = &RollbackManager{
		Logger: c.logger,
		Router: c.router,
		Mounts: c.mounts,
		Period: 1 * time.Minute,
	}
	go c.rollback.Start()

	return nil
}

func (c *Core) stopRollback() error {
	if c.rollback != nil {
		c.rollback.Stop()
		c.rollback = nil
	}

	return nil
}

{'fraction_non_alphanumeric': 0.10683642402435649, 'fraction_numerical': 0.008303349017437033, 'mean_word_length': 3.3275449101796406, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5586109', 'n_tokens_mistral': 2542, 'n_tokens_neox': 2313, 'n_words': 862}