Samuel Cui b5f28fa474 Release ACP v0.2.7
Add ordered content sources and targets to the shared copy pipeline. Observe
fs.File metadata before target creation, bind fs.FS sources, and expose
fatal-stop notification while preserving native copy behavior.
2026-10-15 18:18:39 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-15 18:18:39 +08:00
2026-10-05 16:29:55 +08:00
2026-03-11 00:55:46 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-07-11 20:45:07 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-05 16:29:55 +08:00
2022-09-07 17:00:21 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-05 16:29:55 +08:00
2026-10-02 17:14:11 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-05 16:29:55 +08:00
2026-10-15 18:18:39 +08:00
2026-10-05 16:29:55 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00
2026-10-15 18:18:39 +08:00

acp

ACP is a file copy tool and Go library with concurrent workers, ordered access for linear media, buffered or mapped reads, multiple targets from one content read, metadata preservation, progress events and JSON reports. An optional xattr cache stores SHA-256 content hashes.

Requirements

Build with Go 1.26.8 or newer, as declared in go.mod.

Operating model and stages

For the duration of a run, source contents and path relationships stay stable. The caller does not submit one item's output as another item's source in the same run, or edit files, directories, symlinks or hardlinks during copying. ACP owns its output changes; the rewrite command intentionally replaces its own source after reading it. ACP does not detect or recover from unsupported input changes. Files may change between runs.

Stage Responsibility
index Evaluate each needed Item method once, collect source facts (reusing available enumeration metadata), and resolve target paths and devices. Validate source/target aliases and overwrite rules before any target mutation. Pass the source facts unchanged to later stages.
prepare Open the source once when content or a cached hash is needed. Read the stored signature as required by the hash policy through that descriptor. Reserve a bounded file lifetime before dequeue; preparation does not read content ahead itself.
copy Read content once and, when enabled, compute one hash while fanning out to the targets. Complete ordinary-file metadata, cache work, synchronization and closure before final replacement; device nodes use direct output.
results Form one Result per accepted item and deliver it through one callback goroutine. Fatal pipeline failures are the exception to per-item completion; see delivery and lifecycle.

Indexed source facts are immutable; content-byte progress is tracked separately.

Content streams through shared immutable chunks. Hashing and writing consume independent references in parallel; each source is read once and each enabled digest is computed once. Random targets have independent bounded consumers of the same bytes. Linear source data reads remain ordered through EOF, then source Close runs independently of the next read. A linear target has one application Write lane: the next file may start after the previous file's last Write, while target Close, hash completion and metadata/publication settle asynchronously. This permits application overlap; the filesystem and device still decide physical scheduling.

The per-run pool allocates lazily, with at most 512 backing buffers of 1 MiB (512 MiB of content backing, not a total RSS limit). After all consumers and completion workers exit, backing returns to the shared reusable pool with no reference to the completed stream. Eight credits are reserved for the ordered foreground file. For linear targets, reads stay within the next 512 MiB from the writer's consumed frontier; this is an upper bound, not a guarantee that the window fills. Each tiny file still occupies a 1 MiB backing, and the 256 full file lifetimes also include pending completion and result delivery. Hash queues and each random-target queue hold four chunk references; file write queues hold references sized to the file prefix, at most 512. References share the global backing budget.

Configured source threads bound actual Read calls; configured target threads bound concurrent file data writers. Linear devices force their corresponding thread count to one. Preparation uses min(max(source threads, 8), 256) workers. Ordered reads reserve an opportunity for the earliest unfinished source; that reservation advances at source EOF independently of Write and Close, including with one source thread. Workers release Read permits before waiting on queues. Random-target files reserve their writer permit in dispatch order before reading, so future files cannot consume all backing while the active writers need it. File-lifetime admission is reserved before job dequeue, so later preparation cannot take the required head's slot. Open descriptors scale with admitted files and their target counts, rather than with Read permits alone. WithReadBuffer continues to count input items, independently of these content limits.

Target completion

Random ordinary outputs complete metadata, Sync and Close before rename. Linear outputs omit per-file Sync and Close independently after data writing; metadata/cache work on their owned temporary paths follows closure. Linear sources, linear targets and single-writer targets preserve publication order for the same resolved path, including equivalent relative and absolute names. Their completion keys also group case-folded, canonically equivalent Unicode names conservatively, so case or normalization aliases keep the same winner. On a case-sensitive filesystem this may order otherwise distinct outputs; their actual names and contents remain distinct. This key is not a filesystem identity test for every vendor-specific naming rule. Other keys publish independently; the copy data flow does not wait for these ordinary-file completion dependencies. Results keep the caller's original target names, and the exclusive-rename fallback remains in force. A late linear Close or metadata error stops future admission once observed; it cannot retract already started writes. Results retain each started item's actual outcome, and completion waits for all source/target Close, hash, cache and publication dependencies.

Ordinary targets use an exclusively created .tmp_* file beside the resolved final path. The old target remains in place while ACP writes the new file; the rename happens only after that file's content, hash, metadata and required closure complete. Success is reported after replacement, using the final target path. A failed copy cleans up only its owned temporary file and retains cleanup errors alongside the primary error. ACP never deletes the original to work around a rename failure.

An existing valid destination symlink is resolved once during indexing: ACP replaces its referent and retains the symlink. A dangling destination symlink is refused. Directory symlinks are also resolved once per parent during the run, including when child directories do not yet exist. For missing directories, ACP finds the existing ancestor first and resolves its symlinks once. Replacement creates a new inode at the final path; other hardlinks to the old inode keep their old content. It requires write permission on the destination directory and space for the complete new file while the old file still exists. A source alias, including a hardlink or symlink alias, fails that target while independent targets continue.

Overwrite(false) and acp -n reject targets found during indexing. For an initially missing target, Linux and macOS use the filesystem's exclusive rename: another item's completed output cannot be replaced, and independent targets finish concurrently. Other platforms, and filesystems that do not support exclusive rename, serialize only the final existence check and rename. Copying, sync and close stay outside that fallback lock. This preserves ACP's own output without a per-file lock registry; external writers remain outside the supported operating model.

Device nodes are written directly, without rename or metadata/cache changes, and may contain partial output after failure. Linear writers retain request order and perform neither preallocation nor per-file sync. Ordinary non-linear files retain the allocation and sync policy described under platform behavior, with no free-space estimate. Linear targets check an advisory cached free-space estimate before output creation. Each mount gets an independent goroutine that samples immediately and refreshes every five seconds, with at most one query in flight. Writing never waits for a capacity query, including the first one: unknown capacity permits writing and relies on actual filesystem errors. Each positive-size admission debits its logical size; admissions overlapping a query are also deducted from the new observation. Empty targets skip the check, and equality permits the write. A completed observation failure fails subsequent positive-size targets until a successful refresh; the worker also logs that failure. Cleanup does not refund the estimate. This estimate reserves nothing and cannot account for ongoing writes, filesystem allocation, media buffering or overhead, so a passing estimate does not guarantee that the write will fit. Close/Wait stop and join the polling workers after copying drains. An in-flight filesystem capacity call cannot be cancelled and can delay shutdown, but never holds the cache lock. Actual no-space and read-only errors stop the affected device and retain ErrTargetNoSpace and ErrTargetDropToReadonly; ErrTargetIO remains an individual target failure.

Same-directory rename does not promise atomic replacement on every platform, including Windows, or durability through power failure. ACP's existing sync policy is not a filesystem transaction.

The internal fileio.Output owns the temporary descriptor and path. The rewrite command supplies its scanned source facts and preallocated output through internal RewriteItem, so source replacement reuses the same completion path without a second temporary allocation, open or source stat. Before replacement, rewrite closes its source descriptor and mapping, as required by Windows file sharing. Rewrite retains its own state, hardlink and report responsibilities.

Library API

The caller submits items to a push engine and consumes their results:

func NewStream(ctx context.Context, onResults func([]Result) error, opts ...Option) (*StreamCopyer, error)
func (c *StreamCopyer) Submit(items ...Item) error
func (c *StreamCopyer) Close() error
func (c *StreamCopyer) Wait() error
func (c *StreamCopyer) HardStopped() <-chan struct{}
stream, err := acp.NewStream(ctx, func(results []acp.Result) error {
	for _, result := range results {
		job := result.Job.(*acp.SimpleJob) // the exact instance that was submitted
		if result.Err != nil {
			recordFailure(job, result.Err)
			continue
		}
		recordFacts(job, result)
	}
	return nil
}, acp.WithHashPolicy(acp.HashRead))
if err != nil {
	return err // creation or validation error
}

if err := stream.Submit(&acp.SimpleJob{Path: "/data/a.bin", Dsts: []string{"/tape/a.bin"}}); err != nil {
	// the run accepts no more work
}
if err := stream.Close(); err != nil {
	// the final flush failed
}
return stream.Wait() // the run's terminal error

An item is pure data. It has no callback, so the caller finds its own state by asserting Result.Job back to the instance it submitted:

type Item interface {
	Source() string
	Targets() []string // an empty slice writes nothing: it reads and hashes only when the
	                   // hash policy produces a hash, so the default HashOff records no hash
}

// Optional: override the source device mode for this item.
type ReadModeItem interface {
	ReadMode() ReadMode
}

type SimpleJob struct {
	Path string
	Dsts []string
}

A result reports the content facts of one finished item, one outcome per requested target in request order, and the item's own error:

type Result struct {
	Job  Item  // the exact instance that was submitted
	Err  error // nil = the item completed, even when every target failed

	Size      int64
	Mode      fs.FileMode
	ModTime   time.Time
	WriteTime time.Time

	SHA256            []byte // nil when the hash policy produces no hash
	SignatureCacheHit bool

	Targets []TargetResult
}

type TargetResult struct {
	Path      string
	Size      int64
	WriteTime time.Time
	Err       error // nil when this target was written
}

Content endpoints

An Item can optionally implement ContentItem to copy a member of an ordered stream:

type ContentMetadata struct {
	Info   fs.FileInfo
	Xattrs map[string][]byte
}
type ContentSource interface {
	Open(context.Context) (fs.File, error)
}
// Optional capability of the opened source file.
type XattrReader interface {
	Xattrs() (map[string][]byte, error)
}
func SourceFromFS(fsys fs.FS, name string) ContentSource

type ContentTarget interface {
	Open(context.Context, ContentMetadata) (ContentWriter, error)
}
type ContentWriter interface {
	io.Writer
	Finish(cause error) error
}
type ContentItem interface {
	Item
	ContentSource() ContentSource
	ContentTargets() []ContentTarget
}

A nil content source selects the native Source path. ContentTargets is nil or aligned with Targets; a nil entry selects that native target path. For an endpoint, the matching Item string is an exact display/result identifier: ACP never cleans, stats, caches or deletes it as a path. Each actual endpoint side requires LinearDevice(true); content sources require buffered reads. At most one non-nil content target is supported per item. Native sides retain their options, including mapped source reads, ordinary output staging, metadata, cache and no-overwrite behavior. These combinations are validated while indexing, before endpoint Open or native source observation.

Endpoint items run one complete lifecycle at a time through the existing bounded transfer engine. A content source opens lazily in that lane. ACP calls the opened file's Stat once, then optional Xattrs once, and takes its own portable metadata snapshot before any output is created. Stat must supply regular-file metadata with a nonnegative size. Expected metadata belongs to the caller and cannot substitute for that observation. Open, Stat or Xattrs failure creates no output. Any file returned alongside an Open error still receives one Close attempt; observation and cleanup errors retain their causes. A provider normalizes missing or unsupported xattr facilities to an empty map; other Xattrs errors fail the item.

Panics in custom source Open, Stat, Xattrs or file-info accessors retain their cause as an item failure before target creation, and any acquired file still receives one Close attempt. Panics in the Read or Close worker remain fatal pipeline failures. Empty observed members skip payload Read while retaining metadata observation, hashing when enabled, source Close and target Finish.

Source Close settles before the next source Open, before target Finish, and before any native target publication. A Close error discards the owned native temporary output and retains the existing final file. Endpoint failure stops future member admission; accepted unstarted items still receive the original stopping error. Source reads stop on cancellation without draining remaining payload. Context-aware endpoint implementations receive the run context and must make their blocking Open, Read and Write operations respond to cancellation. ACP also checks cancellation during source observation and closes any acquired file before reporting the outcome.

SourceFromFS binds an existing fs.FS and an fs.ValidPath name without opening it. It opens only in that same ordered lane and returns the filesystem's actual fs.File, retaining optional XattrReader. It never calls fs.Stat separately. Invalid names and a nil filesystem fail before target creation. The name is relative, UTF-8 and slash-delimited; Item.Source remains the caller's display identifier:

source := acp.SourceFromFS(fsys, "docs/readme.txt")
// Return source from the existing caller-owned Item's ContentSource method.

The adapter checks context before and after Open, retaining any acquired file and joining an Open error with cancellation for ACP's common cleanup. Standard filesystem methods that ignore context are not made interruptible. An injected os.DirFS stays an opaque filesystem: ACP does not infer a native root, mmap, ownership or cache access. The caller keeps its storage disjoint from native outputs under the stable-input operating model. Native-path entrypoints retain their native name, symlink, alias, mapped-read and rewrite behavior.

ACP calls each returned writer's Finish exactly once after writes stop, passing any read, write, hash or source Close error as its cause. A Finish error also fails that target and stops the lane. Finish must settle a failed member without acknowledging missing bytes as successful. Completion proves one member's transfer only; finalization of the enclosing stream belongs to the caller after Close/Wait settle handles and result callbacks. Endpoint operations perform no native mmap, cache, capacity query, rename or per-file sync. Content-requiring hash policies hash actual bytes; HashCachedOnly on a targetless content source opens nothing and reports no hash or cache hit.

Metadata is copied into a portable immutable snapshot of size, mode, mtime and ordinary xattrs; endpoint Info.Sys never supplies native ownership. Native sources reuse their already-captured platform-selected attrs. Native outputs restore ordinary attrs before restrictive permissions, retain ACP's existing best-effort destination xattr error policy, and regenerate managed cache entries through the selected hash policy. Both managed keys (acp.signature and user.acp.signature) are excluded from endpoint snapshots. Empty and binary attribute values are preserved. Attribute names and values remain opaque to generic snapshots; each codec owns its representation checks, including PAX key grammar. ACP applies these exported envelope limits:

Limit Value
ContentMaxXattrCount 256 ordinary attributes
ContentMaxXattrNameBytes 1 KiB per name
ContentMaxXattrValueBytes 1 MiB per value
ContentMaxMetadataBytes 4 MiB aggregate xattr names/values, including 64 bytes framing allowance per attribute

Native endpoint capture validates the name-list and each name/value/count/aggregate limit before the corresponding buffer allocation or snapshot growth. Managed keys are excluded before ordinary count/value/aggregate checks. ACP bounds its own snapshot allocations; a custom XattrReader must bound any earlier decoding itself. Ordinary native-to-native capture retains its current behavior. A codec must also bound its own other metadata and encoded header chain. Optional XattrReader does not opt a custom source into native signature-cache reuse or refresh.

Terminology

Term Meaning
Item One caller-owned unit submitted to the push engine through Item.
Job The compatibility Job report row; SimpleJob is the existing convenience implementation of Item, and internal job types carry pipeline state. These roles retain their names.
Target One path requested by Item.Targets or the shell's target options; “destination” describes the same path.
Result One item's terminal facts, error and target outcomes. Result.Job retains the submitted item.
Result buffer The bounded queue of results waiting for delivery.
Result batch The slice passed to one invocation of the results callback.
Content hash / cached signature SHA-256 identifies content; a cached signature stores that hash together with size and mtime.
Stream / compatibility shell The stream accepts items; the shell enumerates job options and produces report rows over that stream.

Copyer, StreamCopyer, AccurateJob, WildcardJob, Result.Job and the report's existing field names are public API names. Their spelling and type roles are preserved for compatibility. A rewrite entry describes persisted rewrite work; it is separate from the stream's submitted item and the report row.

Delivery and lifecycle

Use one submitter, then call Close and Wait. Submit blocks when the read buffer is full and rejects nil items. Close ends the feed and drains accepted work; it returns wrap-up or flush errors. NewStream returns creation and option-validation errors; Wait returns the terminal run error.

Result.Err describes an item ACP could not process, including a source description/open failure, a failed targetless read or an item abandoned by a graceful stop. TargetResult.Err describes one failed target. A nil Result.Err can accompany failed targets, so callers must inspect both. Any result carrying an error is delivered immediately as its own batch. Successful results wait for WithResultBatch, WithResultFlushInterval, or the final Close flush. WithResultBuffer bounds the queue; slow callbacks apply backpressure. ACP owns batching; persistence belongs to the caller. Copy a callback's result slice if retaining it beyond that call.

Result order is unspecified; target outcomes within a result retain request order. Never call Submit, Close or Wait from inside onResults, since delivery must return before the run can finish.

Cancellation of the caller's context, a callback error or an exhausted linear target stops new submissions. Accepted items that cannot start receive the stopping error; work already past the read stage finishes and is reported. A callback error does not retract the submission already in progress. Closing the feed alone is not a stopping error. A fatal pipeline failure ends the run without a per-item completion guarantee.

HardStopped closes when that fatal boundary is crossed, so a caller waiting for per-item source completion can escape without first closing the feed. It stays open for normal completion and graceful stops. The signal does not prove that sources or device commands have settled; Close and Wait still settle resources, and Wait supplies the authoritative terminal error.

Caller methods, the results callback and event handlers run under panic protection. A panic in an item method fails that item; a callback or event-handler panic becomes a run error. A panic escaping pipeline code is a fatal run error, preserving the panic value.

Options

Option Scope Meaning
SetFromDevice(...) run Source device options
SetToDevice(...) run Target device options
LinearDevice(bool) device Enable linear device behavior
DeviceThreads(int) device Worker count, default 8, forced to 1 for a linear device
WithReadMode(ReadBuffered|ReadMapped) source device Read sources buffered or through a memory mapping
Overwrite(bool) run Replace an existing target instead of refusing it; applies to every target
WithReadBuffer(items) run Backpressure limit of the feed, default 4096 items
WithResultBuffer(items) run Depth of the result queue, default 256 items
WithResultBatch(items) run Results one delivery carries, default 256 items
WithResultFlushInterval(d) run How long a successful result may wait for its batch, default 1s
WithHashPolicy(HashPolicy) run Content hash and stored hash policy, default HashOff
WithLogger(*logrus.Logger) run Logger for pipeline diagnostics
WithEventHandler(EventHandler) run Progress, count, and error events
WithProgressBar() run Terminal progress bar handler

Validation happens in NewStream/New, which reject: a negative DeviceThreads; any ReadMode other than ReadBuffered or ReadMapped; WithReadMode applied to the target device; an unknown HashPolicy; WithReadBuffer(0) or less; WithResultBuffer(0) or less; WithResultBatch(0) or less; and a WithResultFlushInterval below 100ms. A job option (AccurateJob, WildcardJob) is valid only for New, which is the shell that enumerates it; NewStream rejects it instead of running an empty stream.

Repeated value options are last-wins, WithEventHandler included: the handler registered last is the only one that receives events, and WithEventHandler(nil) clears the registration. WithProgressBar() installs its handler through the same option, so a later WithEventHandler replaces the bar. The job options and SetFromDevice/SetToDevice accumulate by design: a second WildcardJob adds another walk, and a second device option adjusts the same device description.

WithReadMode applies to the source device only. An item implementing ReadModeItem overrides that choice for its own source; an invalid mode or panic fails that item without stopping the run. Buffered reads are the default; platform differences are listed below.

Events

WithEventHandler receives Event values serially on one goroutine. A command needing both a progress bar and another consumer composes them into that handler. EventFinished is sent once, last; Close and Wait wait for the handler, so a handler that never returns holds the run open.

Event What it reports
EventUpdateCount Accepted totals (Bytes, Files), with Finished on the last update
EventUpdateProgress Completed totals (Bytes, Files), with Finished on the last update
EventUpdateJob One terminal Job report row, emitted by the compatibility shell
EventReportError A pipeline error (Error: source, target, cause)
EventSignatureCacheSummary Aggregate cache activity when a policy manages the cache
EventFinished The run ended

Content hash policy

HashPolicy selects both how an item's content hash is produced and how the stored hash is used, because refreshing the cache requires a computed hash. A targetless item records content facts; a transfer reads its source to write it somewhere:

Policy Targetless item Transfer
HashOff no hash or cache use no hash or cache use
HashCachedOnly stored hash reused; a miss leaves the item without one rejected: a copy always reads its source
HashCachedOrRead stored hash reused; a miss reads and hashes rejected: a copy always reads its source
HashCachedOrReadRefresh stored hash reused; a miss reads, hashes and refreshes always reads, hashes and refreshes
HashRead always reads and hashes; no cache use always reads and hashes; no cache use
HashReadRefresh always reads and hashes, and refreshes the cache always reads, hashes and refreshes the cache

Transfers read source content and compute a hash only when the policy enables hashing. The two reuse-only policies fail the item with Result.Err when it requests targets; they do not fail stream creation. HashCachedOrReadRefresh transfers as HashReadRefresh.

Content signature cache

The managed xattr holds a fixed binary SHA-256, size and nanosecond mtime. A lookup compares the stored metadata with the indexed facts to detect a cache made stale between runs. The source entry is read at most once when needed; refresh compares that saved value with the computed entry and skips an unchanged write. A fresh staged target receives a new entry directly only when refresh is enabled, without reading or invalidating an old one. The target entry is written before restoring restrictive permissions; all metadata is complete before replacement.

Source cache lookup uses its content descriptor. Source refresh runs after hash and source Close through the stable source path; a closed linear output uses its owned temporary path. Random output cache work uses its still-open descriptor before Sync and Close. Platforms may reopen these paths for metadata operations without rereading content. Rewrite waits for source cache and closure before replacing its source. The managed key is excluded from ordinary xattr copying. Cache diagnostics are aggregated in EventSignatureCacheSummary; they never become copy or Wait errors. A filesystem without the managed attribute namespace stores nothing: reads miss and writes are silent no-ops.

Platform behavior

Platform Buffered source access time Managed signature attribute Non-linear file allocation Metadata restore Mapped read
Linux O_NOATIME, falling back when refused user.acp.signature fallocate xattrs, mode, owner as root, times syscall.Mmap; sequential/prefetch advice for files up to 16 MiB
Darwin not suppressed acp.signature Truncate xattrs, mode, owner as root, times syscall.Mmap
FreeBSD not suppressed acp.signature in the user namespace Truncate mode and times descriptor-backed ReadAt
Windows not suppressed none Truncate mode and times CreateFileMapping and MapViewOfFile
other not suppressed none Truncate mode and times descriptor-backed ReadAt

Zero-length files need no preallocation. Non-linear writers sync content before closing. Mapped reads do not suppress access time. See platform verification limits for checks requiring a native runtime.

af05f05c compatibility shell

The af05f05c surface delegates to the push engine and retains its exported names, field types and report format. v0.1.0 and v0.2.0 are withdrawn and are not compatibility baselines.

c, err := acp.New(ctx, acp.WildcardJob(acp.Source("example"), acp.Target("target")), acp.WithHash(true))
if err != nil {
	return err
}
c.Wait()
  • New(ctx, opts...) (*Copyer, error), (*Copyer).Wait(), (*Copyer).WaitErr() error. Wait/WaitErr close the stream and report how the run ended.
  • AccurateJob(src, dsts) copies one exact source to exact target paths; WildcardJob(Source(...), Target(...)), AccurateSource(base, paths...), WildcardJobOption walk sources, keep regular files, map each source-relative path onto every target directory, and sort them in the platform's path order.
  • A repeated relative path inside one WildcardJob is an enumeration error: WaitErr returns it and nothing is copied.
  • Overwrite(bool) Option, SetFromDevice, SetToDevice, LinearDevice, DeviceThreads, WithReadMode, WithReadBuffer, WithResultBuffer, WithResultBatch, WithResultFlushInterval, WithHashPolicy, WithLogger, WithEventHandler, WithProgressBar, WithHash(bool), Event*, EventHandler, Error, Job, Report, ReportGetter, NewReportGetter, NewProgressBar, Cache[K,V], DeviceOption, WildcardJobOption, Option, ReadCachedSignature, CachedSignature, DecodeCachedSignature, ErrTargetNoSpace, ErrTargetDropToReadonly, ErrTargetIO, CopyAttrs, UnexpectFileMode.
  • WithHash(true) maps to HashReadRefresh; WithHash(false) maps to HashOff.
  • Pipeline errors are returned by WaitErr and cause the command to fail. The shell produces reports from final results, including failed items.
  • Cache[K,V] initializes a key once while allowing different keys to initialize concurrently.

Memory reader

The mmap package includes code derived from golang.org/x/exp/mmap; its upstream notice and license are retained in mmap/LICENSE.

The mmap package retains its source descriptor. Explicit Close releases a mapping before closing the descriptor and is idempotent on every platform. An open empty file is readable as empty, not closed; a failed mapping setup releases its resources. Slice bounds are checked before allocation. Read returns io.EOF at the end; ReadAt rejects an offset past the end. File returns the owned descriptor, so keep the reader reachable while using it. The finalizer is a fallback, not a substitute for Close.

Install

go install github.com/samuelncui/acp/cmd/acp@latest
go install github.com/samuelncui/acp/cmd/acp-rewrite@latest

Usage

Usage of acp:
  -cpu.arm
    	allow ARM features to be detected; can potentially crash
  -cpu.disable string
    	disable cpu features; comma separated list
  -cpu.features
    	lists cpu features and exits
  -from-linear
    	copy from linear device, such like tape drive
  -n	not overwrite exist file
  -notarget
    	do not have target, use as dir index tool
  -p	display progress bar (default true)
  -report string
    	json report storage path
  -report-indent
    	json report with indent
  -target value
    	use target flag to give multi target path
  -to-linear
    	copy to linear device, such like tape drive

The command exits 0 only when every selected item was copied to every requested target. Any item failure, any target that was not written, cancellation, and any pipeline failure make it exit 1, and the requested report is attempted before exit. Failure to write that report also exits 1; invalid flags or missing positional arguments exit 2. Independent items and targets continue after individual failures. -notarget selects an index operation with no destinations, including when -target flags were supplied.

Examples

Place flags before positional source and target paths; Go flag parsing stops at the first positional argument.

# copy `example` dir to `target` dir
acp example target/

# copy `example` dir to `target` dir, and output a report to `report.json`
acp -report report.json example target/

# the same report, indented with two spaces
acp -report report.json -report-indent example target/

# copy one file to one exact path
acp example/a.bin target-b.bin

# copy `example` dir to `target1` and `target2` dir
acp -target target1 -target target2 example

# do not copy, just get a dir index, write to `report.json`
acp -notarget -report report.json example

# copy from a tape-like source to a tape-like target
acp -from-linear -to-linear example target/

Report

The compatibility shell owns report rows; the push engine returns Result values. Each terminal item, including a failed item, produces one Job row. Base is the source base directory and Path is its relative segment slice, encoded as a JSON array. Their joined path is the additive FullPath (full_path); SignatureCacheHit (signature_cache_hit) is also additive. NewReportGetter keys and sorts rows by the joined relative path. Overlapping job options that produce the same relative key share a row.

-report writes these rows under files. Per-target failures use the final target path in fail_target; item-level failures use the empty key. Pipeline errors appear only in the top-level errors array. Empty files and errors arrays are omitted, so an empty report is {}. Nil errors remain absent. -report-indent and Report.ToJSONString(true) use two-space indentation. Standard encoding/json handles reports without a custom decoder.

JSON report paths require valid UTF-8. Invalid filename bytes cause an encoding error instead of a lossy path; native copies and in-memory results retain host filename semantics. Individual rows and errors use *Job and *Error because their JSON methods have pointer receivers. json.Marshal and json.Encoder return encoding errors; Report.ToJSONString returns an empty string on encoding failure. Commands preserve the previous report and exit 1 if the requested report cannot be saved.

Rewrite command

acp-rewrite -state state.json -report report.json ROOT replaces regular files under the target completion contract, preserves each scanned hardlink group, and accumulates report rows across runs. It reports final paths and duplicate content by size and SHA-256. -dryrun saves the selected work without rewriting it; repeated -ignore PATH excludes a file or subtree. Busy entries remain in the state's busy queue; missing and failed entries remain pending for an explicit later invocation. Missing paths are also recorded in the state's missing list. As with acp, exit codes are 0 for success, 1 for execution or partial failure, and 2 for usage errors.

The persisted pending queue includes the current entry, its hardlinks and the unprocessed tail. An entry leaves pending only after its copy, replacement, relinks, report and progress writes succeed. An interrupted current entry may be rewritten again. Individual failures retain that entry and allow independent entries to continue; a state or report persistence failure stops new entries. State and report files must both decode before any source or saved temporary file is changed.

Rewrite requires valid UTF-8 paths for its root, selected regular files and hardlinks, and the directories holding its state/report documents and their temporary files. The document basenames themselves retain host filename semantics. Scanning rejects an unsupported selected filename before any file is rewritten; no lossy queue is saved. -ignore can exclude an unsupported file or subtree. Whitespace, control characters, backslashes and the literal Unicode replacement character are preserved wherever the host filesystem supports them.

State and report writes use the shared temporary-file allocation and JSON writer: encode, sync, close, then rename. Temporary hardlinks use exclusive allocation with collision retry. Cleanup removes only allocated paths or ownership recorded in state; a cleanup failure keeps that ownership for a later run and is reported alongside the primary failure.

Testing

See TESTING.md for unit, race, end-to-end, and cross-platform test instructions and the release SOP.

S
Description
No description provided
Readme BSD-2-Clause
1.2 MiB
0 Stars 1 Watchers 0 Forks
Languages
Go 89.8%
Python 10%
Makefile 0.2%