Skip to content
quarry Artifact [72b2032157]
Login

Artifact [72b2032157]

Artifact 72b203215703c9bdc7a7c40ac125ce64e6895ed6051fe4229c3ba79822c8c3f8:


// mirroruv.go is RFC 0020 rung 6's half of the mirror row: whether a
// mirror imports its upstream's unversioned files, the credential the
// import round signs with, and what the last round did.
//
// The password is sealed the way a bucket secret is (secret.go): the
// round replays it into a login card on every pull, so a one-way hash
// cannot hold it, and a copy of oltp.db alone must not yield it.
// MirrorUVCredential is its one reader, and no op returns it.
package oltp

import (
	"database/sql"
	"errors"
	"fmt"
	"time"
)

// SetMirrorUV turns the import round on or off for repo's mirror.
func (d *DB) SetMirrorUV(repo string, on bool) error {
	res, err := d.w.Exec(`UPDATE mirror SET uv = ? WHERE repo = ?`, boolInt(on), repo)
	if err != nil {
		return err
	}
	if n, _ := res.RowsAffected(); n == 0 {
		return fmt.Errorf("oltp: %s is not a mirror", repo)
	}
	return nil
}

// SetMirrorUVCredential stores the login and password the import round
// signs with, sealing the password; an empty user clears both, and the
// round then reads as nobody. The upstream that the credential belongs
// to is the row's URL, and SetMirror drops it when the URL changes.
func (d *DB) SetMirrorUVCredential(repo, user, password string) error {
	sealed := ""
	if user != "" {
		var err error
		if sealed, err = d.seal.seal(password); err != nil {
			return err
		}
	}
	res, err := d.w.Exec(`UPDATE mirror SET uv_user = ?, uv_secret_enc = ? WHERE repo = ?`, user, sealed, repo)
	if err != nil {
		return err
	}
	if n, _ := res.RowsAffected(); n == 0 {
		return fmt.Errorf("oltp: %s is not a mirror", repo)
	}
	return nil
}

// MirrorUVCredential answers the import round's login with its password
// unsealed; ok is false when none is held.
func (d *DB) MirrorUVCredential(repo string) (user, password string, ok bool, err error) {
	var sealed string
	err = d.db.QueryRow(`SELECT uv_user, uv_secret_enc FROM mirror WHERE repo = ?`, repo).Scan(&user, &sealed)
	if errors.Is(err, sql.ErrNoRows) {
		return "", "", false, nil
	}
	if err != nil || user == "" || sealed == "" {
		return "", "", false, err
	}
	if password, err = d.seal.open(sealed); err != nil {
		return "", "", false, err
	}
	return user, password, true, nil
}

// RecordMirrorUV stamps an import round: on the mirror row as the last
// result, and on the pull it followed, the newest one recorded for repo.
// changed is how many files it applied; msg is what it could not do,
// empty when nothing.
func (d *DB) RecordMirrorUV(repo string, at time.Time, changed int, msg string) error {
	if _, err := d.w.Exec(`UPDATE mirror SET uv_at = ?, uv_changed = ?, uv_err = ? WHERE repo = ?`,
		at.UTC().Format(time.RFC3339), changed, msg, repo); err != nil {
		return err
	}
	_, err := d.w.Exec(`UPDATE mirror_pull SET uv_ran = 1, uv_changed = ?, uv_err = ?
		WHERE repo = ? AND at = (SELECT MAX(at) FROM mirror_pull WHERE repo = ?)`, changed, msg, repo, repo)
	return err
}