tools

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: BSD-2-Clause Imports: 11 Imported by: 0

Documentation

Overview

Package tools provides high-level meta-only rewrites of Tarantool journal files. The headline operation is RewriteMeta, which parses the meta header, hands a clone to a caller-supplied transform, writes the new header, and then byte-copies the rest of the source (tx blocks + EOF marker) verbatim to the destination. CRCs are preserved because the on-disk bytes are unchanged.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrUUIDLineNotFound is returned when the header has no Instance/Server
	// line to overwrite.
	ErrUUIDLineNotFound = errors.New("tools.ReplaceInstanceUUIDInPlace: no Instance/Server line in header")

	// ErrUUIDWidthMismatch is returned when the on-disk UUID value is not the
	// same byte width as the canonical replacement, so an in-place overwrite
	// would shift following bytes. Use RewriteMetaFields (on-copy) instead.
	ErrUUIDWidthMismatch = errors.New("tools.ReplaceInstanceUUIDInPlace: on-disk UUID width differs from replacement; use RewriteMetaFields")

	// ErrCorruptUUIDBackup is returned by RecoverInstanceUUIDInPlace when the
	// undo-log sidecar exists but is too short or self-inconsistent to apply.
	ErrCorruptUUIDBackup = errors.New("tools.RecoverInstanceUUIDInPlace: corrupt undo-log sidecar")
)

Sentinel errors returned by ReplaceInstanceUUIDInPlace.

Functions

func RecoverInstanceUUIDInPlace

func RecoverInstanceUUIDInPlace(path string) (bool, error)

RecoverInstanceUUIDInPlace completes the crash-recovery half of ReplaceInstanceUUIDInPlace. If path has an undo-log sidecar (path+".uuidbak"), a prior in-place overwrite was interrupted: this restores the original bytes recorded in the sidecar to their offset in path (so a torn UUID becomes the consistent pre-write value again) and then removes the sidecar. It reports whether a recovery was performed.

With no sidecar present it is a no-op returning (false, nil), so it is safe to call unconditionally before opening a file. A sidecar that is too short or describes an out-of-range span is rejected with ErrCorruptUUIDBackup and left in place for inspection.

func RemapVClock

func RemapVClock(remap map[uint32]uint32) func(*format.Meta) *format.Meta

RemapVClock returns a transform fn that renumbers replica ids in both VClock and PrevVClock according to remap: a key oldID present in remap becomes remap[oldID]; a key not present in remap is left at its current id. Per-row ReplicaID values inside the tx blocks are NOT rewritten by this transform — RewriteMeta copies tx bytes verbatim. Callers who need a consistent rewrite (so Tarantool's filename-signature check stays valid) must also remap row ReplicaIDs by other means; otherwise the resulting file's vclock signature will not match the sum of the rows' LSNs that load into Tarantool.

func ReplaceInstanceUUID

func ReplaceInstanceUUID(newID uuid.UUID) func(*format.Meta) *format.Meta

ReplaceInstanceUUID returns a transform fn that sets m.InstanceUUID to newID and leaves every other field untouched. Suitable as the fn argument to RewriteMeta.

func ReplaceInstanceUUIDInPlace

func ReplaceInstanceUUIDInPlace(path string, newID uuid.UUID) error

ReplaceInstanceUUIDInPlace overwrites the instance UUID in path's meta header directly, without copying the file. This is sound only because a canonical UUID is always exactly 36 bytes, so the replacement occupies the same span and no following byte moves — tx blocks, their CRCs, and the EOF marker are left untouched. Use it to re-stamp the instance UUID of a large file without rewriting every byte; for any other header change, or when the on-disk UUID is not 36 bytes, use RewriteMetaFields.

Crash safety. The 36-byte WriteAt is not itself atomic: a power loss or kernel crash while the span straddles a sector boundary can leave a torn, unparseable UUID. To make this recoverable without giving up the no-copy property, the original bytes and their offset are first written to an undo-log sidecar (path+".uuidbak"), fsync'd, then the in-place write is performed and fsync'd, and only then is the sidecar removed. If the process dies in that window the sidecar survives; the next call to ReplaceInstanceUUIDInPlace heals it automatically (it runs recovery on entry), or the caller can run RecoverInstanceUUIDInPlace explicitly. The net guarantee is old-or-new, never a torn header.

The file is opened read-write. It returns ErrUUIDLineNotFound if the header has no Instance/Server line, ErrUUIDWidthMismatch if the on-disk value is not 36 bytes, and leaves the file unmodified in both cases. When the on-disk UUID already equals newID the call is a no-op. Legacy 0.12 headers are accepted (the version is not touched).

Example

ExampleReplaceInstanceUUIDInPlace re-stamps the instance UUID directly in a file, without copying it — sound because a canonical UUID is always 36 bytes, so nothing downstream shifts.

package main

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"

	"github.com/google/uuid"
	"github.com/tarantool/go-iproto"
	"github.com/vmihailenco/msgpack/v5"

	"github.com/tarantool/go-xlog/format"
	"github.com/tarantool/go-xlog/reader"
	"github.com/tarantool/go-xlog/tools"
	"github.com/tarantool/go-xlog/writer"
)

// writeSampleXlog writes a minimal one-row xlog to path, for the on-disk
// tools examples.
func writeSampleXlog(path string) {
	meta := &format.Meta{
		Filetype:     format.FiletypeXLOG,
		Version:      "example",
		InstanceUUID: uuid.MustParse("11111111-2222-3333-4444-555555555555"),
	}

	var buf bytes.Buffer

	w, err := writer.NewWriter(&buf, meta)
	if err != nil {
		panic(err)
	}

	body, err := msgpack.Marshal([]int{1})
	if err != nil {
		panic(err)
	}

	if err := w.WriteTx([]format.XRow{{Type: iproto.IPROTO_INSERT, LSN: 1, BodyRaw: body}}); err != nil {
		panic(err)
	}

	if err := w.Close(); err != nil {
		panic(err)
	}

	if err := os.WriteFile(path, buf.Bytes(), 0o600); err != nil {
		panic(err)
	}
}

func main() {
	dir, err := os.MkdirTemp("", "go-xlog-example")
	if err != nil {
		panic(err)
	}

	defer func() { _ = os.RemoveAll(dir) }()

	path := filepath.Join(dir, "x.xlog")
	writeSampleXlog(path)

	newID := uuid.MustParse("99999999-aaaa-bbbb-cccc-dddddddddddd")
	if err := tools.ReplaceInstanceUUIDInPlace(path, newID); err != nil {
		panic(err)
	}

	m, err := reader.ReadHeader(path)
	if err != nil {
		panic(err)
	}

	fmt.Printf("uuid=%s\n", m.InstanceUUID)

}
Output:
uuid=99999999-aaaa-bbbb-cccc-dddddddddddd

func RewriteMeta

func RewriteMeta(srcPath, dstPath string, fn func(*format.Meta) *format.Meta) error

RewriteMeta reads srcPath, parses its meta header, hands a deep clone of that meta to fn (so the caller's transform cannot mutate state shared with the on-disk parse), encodes the returned meta into a fresh `<dstPath>.inprogress` file, byte-copies every byte of srcPath from the position right after the source's meta blank-line terminator through to the end of the file (including the 4-byte EOF marker), syncs, and atomically renames `.inprogress` → dstPath.

Tx blocks are NOT re-encoded — their CRCs and zstd-compressed payloads are preserved exactly. The caller-supplied transform is therefore expected to limit its changes to header fields; any rewrite that would invalidate the rows (e.g. a VClock signature that disagrees with the per-row LSN sums) will be rejected by Tarantool at load time.

Failure modes:

  • srcPath cannot be opened or its meta is malformed: error returned; no `.inprogress` file is created.
  • The destination `.inprogress` already exists (O_EXCL): error returned; nothing is overwritten.
  • Any failure after the destination is created: the partial `.inprogress` file is removed before returning.

On success, dstPath exists and any pre-existing file at dstPath is replaced atomically via os.Rename.

func RewriteMetaFields

func RewriteMetaFields(srcPath, dstPath string, opts ...Option) error

RewriteMetaFields copies srcPath to dstPath, overwriting the header fields named by opts and preserving every tx block and the EOF marker verbatim. It is a thin convenience over RewriteMeta: listed fields change, everything else (PrevVClock, Extras, and any field without an Option) is kept. With no opts it is a meta-preserving copy.

See RewriteMeta for the failure modes and the verbatim-tx-bytes contract.

Example

ExampleRewriteMetaFields copies an xlog while overwriting selected header fields (here the instance UUID and version). The tx blocks are copied verbatim — only the header changes.

package main

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"

	"github.com/google/uuid"
	"github.com/tarantool/go-iproto"
	"github.com/vmihailenco/msgpack/v5"

	"github.com/tarantool/go-xlog/format"
	"github.com/tarantool/go-xlog/reader"
	"github.com/tarantool/go-xlog/tools"
	"github.com/tarantool/go-xlog/writer"
)

// writeSampleXlog writes a minimal one-row xlog to path, for the on-disk
// tools examples.
func writeSampleXlog(path string) {
	meta := &format.Meta{
		Filetype:     format.FiletypeXLOG,
		Version:      "example",
		InstanceUUID: uuid.MustParse("11111111-2222-3333-4444-555555555555"),
	}

	var buf bytes.Buffer

	w, err := writer.NewWriter(&buf, meta)
	if err != nil {
		panic(err)
	}

	body, err := msgpack.Marshal([]int{1})
	if err != nil {
		panic(err)
	}

	if err := w.WriteTx([]format.XRow{{Type: iproto.IPROTO_INSERT, LSN: 1, BodyRaw: body}}); err != nil {
		panic(err)
	}

	if err := w.Close(); err != nil {
		panic(err)
	}

	if err := os.WriteFile(path, buf.Bytes(), 0o600); err != nil {
		panic(err)
	}
}

func main() {
	dir, err := os.MkdirTemp("", "go-xlog-example")
	if err != nil {
		panic(err)
	}

	defer func() { _ = os.RemoveAll(dir) }()

	src := filepath.Join(dir, "src.xlog")
	dst := filepath.Join(dir, "dst.xlog")

	writeSampleXlog(src)

	newID := uuid.MustParse("99999999-aaaa-bbbb-cccc-dddddddddddd")
	if err := tools.RewriteMetaFields(src, dst,
		tools.WithInstanceUUID(newID),
		tools.WithVersion("3.8.0"),
	); err != nil {
		panic(err)
	}

	m, err := reader.ReadHeader(dst)
	if err != nil {
		panic(err)
	}

	fmt.Printf("uuid=%s version=%s\n", m.InstanceUUID, m.Version)

}
Output:
uuid=99999999-aaaa-bbbb-cccc-dddddddddddd version=3.8.0

Types

type Option

type Option func(*format.Meta)

Option overrides a single meta header field in an on-copy rewrite performed by RewriteMetaFields. Options follow the functional-options pattern (cf. Writer.Option): each mutates the cloned meta in place, and any field without a corresponding Option is left exactly as the source had it.

func WithFormatVer

func WithFormatVer(v string) Option

WithFormatVer sets the format-version line. It must be "0.13" or the legacy "0.12" — EncodeMeta rejects anything else. Changing the version does NOT re-encode tx rows (they are copied verbatim), so only retarget the version when the existing row encoding is already compatible.

func WithInstanceUUID

func WithInstanceUUID(id uuid.UUID) Option

WithInstanceUUID sets the instance UUID.

func WithVClock

func WithVClock(vc format.VClock) Option

WithVClock sets the VClock line. A copy of vc is stored, so later mutation of vc by the caller does not affect the rewrite. As with RemapVClock this touches only the header: a vclock that disagrees with the per-row LSN sums will fail Tarantool's signature check at load time.

func WithVersion

func WithVersion(v string) Option

WithVersion sets the free-form Version string (Tarantool's PACKAGE_VERSION).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL