diff --git a/AGENTS.md b/AGENTS.md index ba15537ac15..c2ccf765aff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -177,6 +177,7 @@ Run these steps in order before considering work complete: - after editing CLI help text in `core/commands/`, verify width: `go test ./test/cli/... -run TestCommandDocsWidth` - config options are documented in `docs/config.md` - changelogs in `docs/changelogs/`: only edit the Table of Contents and the Highlights section; the Changelog and Contributors sections are auto-generated and must not be modified +- avoid unnecessary line wrapping in `docs/changelogs/*`; let lines be long - follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) - keep commit titles short and messages terse diff --git a/config/import.go b/config/import.go index 6607a51bf4a..de0be1279b6 100644 --- a/config/import.go +++ b/config/import.go @@ -7,9 +7,12 @@ import ( "strings" chunk "github.com/ipfs/boxo/chunker" + merkledag "github.com/ipfs/boxo/ipld/merkledag" "github.com/ipfs/boxo/ipld/unixfs/importer/helpers" uio "github.com/ipfs/boxo/ipld/unixfs/io" + "github.com/ipfs/boxo/mfs" "github.com/ipfs/boxo/verifcid" + cid "github.com/ipfs/go-cid" mh "github.com/multiformats/go-multihash" ) @@ -259,3 +262,47 @@ func (i *Import) UnixFSSplitterFunc() chunk.SplitterGen { return s } } + +// MFSRootOptions returns all MFS root options derived from Import config. +func (i *Import) MFSRootOptions() ([]mfs.Option, error) { + cidBuilder, err := i.UnixFSCidBuilder() + if err != nil { + return nil, err + } + sizeEstimationMode := i.HAMTSizeEstimationMode() + return []mfs.Option{ + mfs.WithCidBuilder(cidBuilder), + mfs.WithChunker(i.UnixFSSplitterFunc()), + mfs.WithMaxLinks(int(i.UnixFSDirectoryMaxLinks.WithDefault(DefaultUnixFSDirectoryMaxLinks))), + mfs.WithMaxHAMTFanout(int(i.UnixFSHAMTDirectoryMaxFanout.WithDefault(DefaultUnixFSHAMTDirectoryMaxFanout))), + mfs.WithHAMTShardingSize(int(i.UnixFSHAMTDirectorySizeThreshold.WithDefault(DefaultUnixFSHAMTDirectorySizeThreshold))), + mfs.WithSizeEstimationMode(sizeEstimationMode), + }, nil +} + +// UnixFSCidBuilder returns a cid.Builder based on Import.CidVersion and +// Import.HashFunction. Always builds an explicit prefix so that MFS +// respects kubo defaults even when they differ from boxo's internal +// CIDv0/sha2-256 default (see https://github.com/ipfs/kubo/issues/4143). +func (i *Import) UnixFSCidBuilder() (cid.Builder, error) { + cidVer := int(i.CidVersion.WithDefault(DefaultCidVersion)) + hashFunc := i.HashFunction.WithDefault(DefaultHashFunction) + + if hashFunc != DefaultHashFunction && cidVer == 0 { + cidVer = 1 + } + + prefix, err := merkledag.PrefixForCidVersion(cidVer) + if err != nil { + return nil, err + } + + hashCode, ok := mh.Names[strings.ToLower(hashFunc)] + if !ok { + return nil, fmt.Errorf("Import.HashFunction unrecognized: %q", hashFunc) + } + prefix.MhType = hashCode + prefix.MhLength = -1 + + return &prefix, nil +} diff --git a/config/import_test.go b/config/import_test.go index 6bf0a4603b2..1029bfe2d21 100644 --- a/config/import_test.go +++ b/config/import_test.go @@ -483,6 +483,95 @@ func TestValidateImportConfig_DAGLayout(t *testing.T) { } } +func TestImport_UnixFSCidBuilder(t *testing.T) { + defaultMhType := mh.Names[strings.ToLower(DefaultHashFunction)] + + tests := []struct { + name string + cfg Import + wantCidVer uint64 + wantMhType uint64 + }{ + { + name: "CIDv1 explicit", + cfg: Import{CidVersion: *NewOptionalInteger(1)}, + wantCidVer: 1, + wantMhType: defaultMhType, + }, + { + name: "CIDv0 explicit", + cfg: Import{CidVersion: *NewOptionalInteger(0)}, + wantCidVer: 0, + wantMhType: defaultMhType, + }, + { + name: "non-default hash upgrades CIDv0 to CIDv1", + cfg: Import{HashFunction: *NewOptionalString("sha2-512")}, + wantCidVer: 1, + wantMhType: mh.SHA2_512, + }, + { + name: "CIDv1 with sha2-512", + cfg: Import{ + CidVersion: *NewOptionalInteger(1), + HashFunction: *NewOptionalString("sha2-512"), + }, + wantCidVer: 1, + wantMhType: mh.SHA2_512, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + builder, err := tt.cfg.UnixFSCidBuilder() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if builder == nil { + t.Fatal("expected non-nil builder") + } + c, err := builder.Sum([]byte("test")) + if err != nil { + t.Fatalf("builder.Sum failed: %v", err) + } + pref := c.Prefix() + if pref.Version != tt.wantCidVer { + t.Errorf("CID version = %d, want %d", pref.Version, tt.wantCidVer) + } + if pref.MhType != tt.wantMhType { + t.Errorf("multihash type = 0x%x, want 0x%x", pref.MhType, tt.wantMhType) + } + }) + } +} + +// TestImport_UnixFSCidBuilderDefaults verifies that UnixFSCidBuilder always +// returns an explicit builder even when no config is set, so that MFS +// respects kubo's DefaultCidVersion rather than relying on boxo's internal +// CIDv0 default (relevant for https://github.com/ipfs/kubo/issues/4143). +func TestImport_UnixFSCidBuilderDefaults(t *testing.T) { + cfg := &Import{} + builder, err := cfg.UnixFSCidBuilder() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if builder == nil { + t.Fatal("expected non-nil builder at defaults") + } + c, err := builder.Sum([]byte("test")) + if err != nil { + t.Fatalf("builder.Sum failed: %v", err) + } + pref := c.Prefix() + if pref.Version != uint64(DefaultCidVersion) { + t.Errorf("CID version = %d, want DefaultCidVersion (%d)", pref.Version, DefaultCidVersion) + } + wantMhType := mh.Names[strings.ToLower(DefaultHashFunction)] + if pref.MhType != wantMhType { + t.Errorf("multihash type = 0x%x, want 0x%x (DefaultHashFunction=%s)", pref.MhType, wantMhType, DefaultHashFunction) + } +} + func TestImport_HAMTSizeEstimationMode(t *testing.T) { tests := []struct { cfg string diff --git a/core/commands/files.go b/core/commands/files.go index d9ab9e980f8..cbb5308cabe 100644 --- a/core/commands/files.go +++ b/core/commands/files.go @@ -28,7 +28,6 @@ import ( offline "github.com/ipfs/boxo/exchange/offline" dag "github.com/ipfs/boxo/ipld/merkledag" ft "github.com/ipfs/boxo/ipld/unixfs" - uio "github.com/ipfs/boxo/ipld/unixfs/io" mfs "github.com/ipfs/boxo/mfs" "github.com/ipfs/boxo/path" cid "github.com/ipfs/go-cid" @@ -505,7 +504,7 @@ being GC'ed. return err } - prefix, err := getPrefixNew(req, &cfg.Import) + prefix, err := getPrefix(req, &cfg.Import) if err != nil { return err } @@ -558,7 +557,11 @@ being GC'ed. if mkParents { maxDirLinks := int(cfg.Import.UnixFSDirectoryMaxLinks.WithDefault(config.DefaultUnixFSDirectoryMaxLinks)) sizeEstimationMode := cfg.Import.HAMTSizeEstimationMode() - err := ensureContainingDirectoryExists(nd.FilesRoot, dst, prefix, maxDirLinks, &sizeEstimationMode) + err := ensureContainingDirectoryExists(nd.FilesRoot, dst, + mfs.WithCidBuilder(prefix), + mfs.WithMaxLinks(maxDirLinks), + mfs.WithSizeEstimationMode(sizeEstimationMode), + ) if err != nil { return err } @@ -1060,7 +1063,7 @@ See '--to-files' in 'ipfs add --help' for more information. rawLeaves = cfg.Import.UnixFSRawLeaves.WithDefault(config.DefaultUnixFSRawLeaves) } - prefix, err := getPrefixNew(req, &cfg.Import) + prefix, err := getPrefix(req, &cfg.Import) if err != nil { return err } @@ -1073,7 +1076,11 @@ See '--to-files' in 'ipfs add --help' for more information. if mkParents { maxDirLinks := int(cfg.Import.UnixFSDirectoryMaxLinks.WithDefault(config.DefaultUnixFSDirectoryMaxLinks)) sizeEstimationMode := cfg.Import.HAMTSizeEstimationMode() - err := ensureContainingDirectoryExists(nd.FilesRoot, path, prefix, maxDirLinks, &sizeEstimationMode) + err := ensureContainingDirectoryExists(nd.FilesRoot, path, + mfs.WithCidBuilder(prefix), + mfs.WithMaxLinks(maxDirLinks), + mfs.WithSizeEstimationMode(sizeEstimationMode), + ) if err != nil { return err } @@ -1203,13 +1210,11 @@ Examples: maxDirLinks := int(cfg.Import.UnixFSDirectoryMaxLinks.WithDefault(config.DefaultUnixFSDirectoryMaxLinks)) sizeEstimationMode := cfg.Import.HAMTSizeEstimationMode() - err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{ - Mkparents: dashp, - Flush: flush, - CidBuilder: prefix, - MaxLinks: maxDirLinks, - SizeEstimationMode: &sizeEstimationMode, - }) + err = mfs.Mkdir(root, dirtomake, mfs.MkdirOpts{Mkparents: dashp, Flush: flush}, + mfs.WithCidBuilder(prefix), + mfs.WithMaxLinks(maxDirLinks), + mfs.WithSizeEstimationMode(sizeEstimationMode), + ) return err }, @@ -1264,10 +1269,15 @@ var filesChcidCmd = &cmds.Command{ Tagline: "Change the CID version or hash function of the root node of a given path.", ShortDescription: ` Change the CID version or hash function of the root node of a given path. + +Note: the MFS root ('/') CID format is controlled by Import.CidVersion and +Import.HashFunction in the config and cannot be changed with this command. +Use 'ipfs config' to modify these values instead. This command only works +on subdirectories of the MFS root. `, }, Arguments: []cmds.Argument{ - cmds.StringArg("path", false, false, "Path to change. Default: '/'."), + cmds.StringArg("path", true, false, "Path to change (must not be '/')."), }, Options: []cmds.Option{ cidVersionOption, @@ -1279,9 +1289,10 @@ Change the CID version or hash function of the root node of a given path. return err } - path := "/" - if len(req.Arguments) > 0 { - path = req.Arguments[0] + path := req.Arguments[0] + if path == "/" { + return fmt.Errorf("cannot change CID format of the MFS root; " + + "use 'ipfs config Import.CidVersion' and 'ipfs config Import.HashFunction' instead") } flush, _ := req.Options[filesFlushOptionName].(bool) @@ -1446,97 +1457,48 @@ func removePath(filesRoot *mfs.Root, path string, force bool, dashr bool) error return pdir.Flush() } -func getPrefixNew(req *cmds.Request, importCfg *config.Import) (cid.Builder, error) { - cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int) - hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string) - - // Fall back to Import config if CLI options not set - if !cidVerSet && importCfg != nil && !importCfg.CidVersion.IsDefault() { - cidVer = int(importCfg.CidVersion.WithDefault(config.DefaultCidVersion)) - cidVerSet = true - } - if !hashFunSet && importCfg != nil && !importCfg.HashFunction.IsDefault() { - hashFunStr = importCfg.HashFunction.WithDefault(config.DefaultHashFunction) - hashFunSet = true - } - - if !cidVerSet && !hashFunSet { - return nil, nil - } - - if hashFunSet && cidVer == 0 { - cidVer = 1 - } - - prefix, err := dag.PrefixForCidVersion(cidVer) - if err != nil { - return nil, err - } - - if hashFunSet { - hashFunCode, ok := mh.Names[strings.ToLower(hashFunStr)] - if !ok { - return nil, fmt.Errorf("unrecognized hash function: %s", strings.ToLower(hashFunStr)) - } - prefix.MhType = hashFunCode - prefix.MhLength = -1 - } - - return &prefix, nil -} - +// getPrefix builds a cid.Builder from CLI flags, falling back to importCfg +// when provided. Returns (nil, nil) when neither CLI nor config set a value. func getPrefix(req *cmds.Request, importCfg *config.Import) (cid.Builder, error) { cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int) hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string) - // Fall back to Import config if CLI options not set - if !cidVerSet && importCfg != nil && !importCfg.CidVersion.IsDefault() { - cidVer = int(importCfg.CidVersion.WithDefault(config.DefaultCidVersion)) - cidVerSet = true - } - if !hashFunSet && importCfg != nil && !importCfg.HashFunction.IsDefault() { - hashFunStr = importCfg.HashFunction.WithDefault(config.DefaultHashFunction) - hashFunSet = true - } - - if !cidVerSet && !hashFunSet { - return nil, nil - } - - if hashFunSet && cidVer == 0 { - cidVer = 1 - } - - prefix, err := dag.PrefixForCidVersion(cidVer) - if err != nil { - return nil, err + if cidVerSet || hashFunSet { + // CLI flags take precedence: build prefix from them directly. + if hashFunSet && cidVer == 0 { + cidVer = 1 + } + prefix, err := dag.PrefixForCidVersion(cidVer) + if err != nil { + return nil, err + } + if hashFunSet { + hashFunCode, ok := mh.Names[strings.ToLower(hashFunStr)] + if !ok { + return nil, fmt.Errorf("unrecognized hash function: %q", hashFunStr) + } + prefix.MhType = hashFunCode + prefix.MhLength = -1 + } + return &prefix, nil } - if hashFunSet { - hashFunCode, ok := mh.Names[strings.ToLower(hashFunStr)] - if !ok { - return nil, fmt.Errorf("unrecognized hash function: %s", strings.ToLower(hashFunStr)) - } - prefix.MhType = hashFunCode - prefix.MhLength = -1 + // No CLI flags: fall back to Import config. + if importCfg != nil { + return importCfg.UnixFSCidBuilder() } - return &prefix, nil + return nil, nil } -func ensureContainingDirectoryExists(r *mfs.Root, path string, builder cid.Builder, maxLinks int, sizeEstimationMode *uio.SizeEstimationMode) error { +func ensureContainingDirectoryExists(r *mfs.Root, path string, opts ...mfs.Option) error { dirtomake := gopath.Dir(path) if dirtomake == "/" { return nil } - return mfs.Mkdir(r, dirtomake, mfs.MkdirOpts{ - Mkparents: true, - CidBuilder: builder, - MaxLinks: maxLinks, - SizeEstimationMode: sizeEstimationMode, - }) + return mfs.Mkdir(r, dirtomake, mfs.MkdirOpts{Mkparents: true}, opts...) } func getFileHandle(r *mfs.Root, path string, create bool, builder cid.Builder) (*mfs.File, error) { diff --git a/core/coreunix/add.go b/core/coreunix/add.go index 55ad1bd83bf..dada26fe273 100644 --- a/core/coreunix/add.go +++ b/core/coreunix/add.go @@ -107,12 +107,7 @@ func (adder *Adder) mfsRoot() (*mfs.Root, error) { } // Note, this adds it to DAGService already. - mr, err := mfs.NewEmptyRoot(adder.ctx, adder.dagService, nil, nil, mfs.MkdirOpts{ - CidBuilder: adder.CidBuilder, - MaxLinks: adder.MaxDirectoryLinks, - MaxHAMTFanout: adder.MaxHAMTFanout, - SizeEstimationMode: adder.SizeEstimationMode, - }) + mr, err := mfs.NewEmptyRoot(adder.ctx, adder.dagService, nil, nil, adder.mkdirOpts()...) if err != nil { return nil, err } @@ -125,6 +120,20 @@ func (adder *Adder) SetMfsRoot(r *mfs.Root) { adder.mroot = r } +// mkdirOpts returns MFS options derived from the adder's config, +// with any additional options appended. +func (adder *Adder) mkdirOpts(extra ...mfs.Option) []mfs.Option { + opts := []mfs.Option{ + mfs.WithCidBuilder(adder.CidBuilder), + mfs.WithMaxLinks(adder.MaxDirectoryLinks), + mfs.WithMaxHAMTFanout(adder.MaxHAMTFanout), + } + if adder.SizeEstimationMode != nil { + opts = append(opts, mfs.WithSizeEstimationMode(*adder.SizeEstimationMode)) + } + return append(opts, extra...) +} + // Constructs a node from reader's data, and adds it. Doesn't pin. func (adder *Adder) add(reader io.Reader) (ipld.Node, error) { chnk, err := chunker.FromString(reader, adder.Chunker) @@ -274,15 +283,8 @@ func (adder *Adder) addNode(node ipld.Node, path string) error { dir := gopath.Dir(path) if dir != "." { - opts := mfs.MkdirOpts{ - Mkparents: true, - Flush: false, - CidBuilder: adder.CidBuilder, - MaxLinks: adder.MaxDirectoryLinks, - MaxHAMTFanout: adder.MaxHAMTFanout, - SizeEstimationMode: adder.SizeEstimationMode, - } - if err := mfs.Mkdir(mr, dir, opts); err != nil { + mkdirOpts := adder.mkdirOpts() + if err := mfs.Mkdir(mr, dir, mfs.MkdirOpts{Mkparents: true, Flush: false}, mkdirOpts...); err != nil { return err } } @@ -506,15 +508,8 @@ func (adder *Adder) addDir(ctx context.Context, path string, dir files.Directory // if we need to store mode or modification time then create a new root which includes that data if toplevel && (adder.FileMode != 0 || !adder.FileMtime.IsZero()) { - mr, err := mfs.NewEmptyRoot(ctx, adder.dagService, nil, nil, - mfs.MkdirOpts{ - CidBuilder: adder.CidBuilder, - MaxLinks: adder.MaxDirectoryLinks, - MaxHAMTFanout: adder.MaxHAMTFanout, - ModTime: adder.FileMtime, - Mode: adder.FileMode, - SizeEstimationMode: adder.SizeEstimationMode, - }) + opts := adder.mkdirOpts(mfs.WithMode(adder.FileMode), mfs.WithModTime(adder.FileMtime)) + mr, err := mfs.NewEmptyRoot(ctx, adder.dagService, nil, nil, opts...) if err != nil { return err } @@ -526,16 +521,8 @@ func (adder *Adder) addDir(ctx context.Context, path string, dir files.Directory if err != nil { return err } - err = mfs.Mkdir(mr, path, mfs.MkdirOpts{ - Mkparents: true, - Flush: false, - CidBuilder: adder.CidBuilder, - Mode: adder.FileMode, - ModTime: adder.FileMtime, - MaxLinks: adder.MaxDirectoryLinks, - MaxHAMTFanout: adder.MaxHAMTFanout, - SizeEstimationMode: adder.SizeEstimationMode, - }) + mkdirOpts := adder.mkdirOpts(mfs.WithMode(adder.FileMode), mfs.WithModTime(adder.FileMtime)) + err = mfs.Mkdir(mr, path, mfs.MkdirOpts{Mkparents: true, Flush: false}, mkdirOpts...) if err != nil { return err } diff --git a/core/node/core.go b/core/node/core.go index 0b2af81c399..05e1042cd21 100644 --- a/core/node/core.go +++ b/core/node/core.go @@ -248,19 +248,12 @@ func Files(strategy string) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo if err != nil { return nil, fmt.Errorf("failed to get config: %w", err) } - chunkerGen := cfg.Import.UnixFSSplitterFunc() - maxDirLinks := int(cfg.Import.UnixFSDirectoryMaxLinks.WithDefault(config.DefaultUnixFSDirectoryMaxLinks)) - maxHAMTFanout := int(cfg.Import.UnixFSHAMTDirectoryMaxFanout.WithDefault(config.DefaultUnixFSHAMTDirectoryMaxFanout)) - hamtShardingSize := int(cfg.Import.UnixFSHAMTDirectorySizeThreshold.WithDefault(config.DefaultUnixFSHAMTDirectorySizeThreshold)) - sizeEstimationMode := cfg.Import.HAMTSizeEstimationMode() - - root, err := mfs.NewRoot(ctx, dag, nd, pf, prov, - mfs.WithChunker(chunkerGen), - mfs.WithMaxLinks(maxDirLinks), - mfs.WithMaxHAMTFanout(maxHAMTFanout), - mfs.WithHAMTShardingSize(hamtShardingSize), - mfs.WithSizeEstimationMode(sizeEstimationMode), - ) + mfsOpts, err := cfg.Import.MFSRootOptions() + if err != nil { + return nil, fmt.Errorf("failed to build MFS options from Import config: %w", err) + } + + root, err := mfs.NewRoot(ctx, dag, nd, pf, prov, mfsOpts...) if err != nil { return nil, fmt.Errorf("failed to initialize MFS root from %s stored at %s: %w. "+ "If corrupted, use 'ipfs files chroot' to reset (see --help)", nd.Cid(), FilesRootDatastoreKey, err) diff --git a/docs/changelogs/v0.41.md b/docs/changelogs/v0.41.md index 477e258eeed..293ad84885b 100644 --- a/docs/changelogs/v0.41.md +++ b/docs/changelogs/v0.41.md @@ -14,7 +14,8 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team. - [✨ New `ipfs cid inspect` command](#-new-ipfs-cid-inspect-command) - [🖥️ WebUI Improvements](#-webui-improvements) - [🔧 Correct provider addresses for custom HTTP routing](#-correct-provider-addresses-for-custom-http-routing) - - [`ipfs object patch` validates UnixFS node types](#ipfs-object-patch-validates-unixfs-node-types) + - [🛡️ `ipfs object patch` validates UnixFS node types](#-ipfs-object-patch-validates-unixfs-node-types) + - [🔗 MFS: fixed CidBuilder preservation](#-mfs-fixed-cidbuilder-preservation) - [📂 FUSE Mount Fixes](#-fuse-mount-fixes) - [📦️ Dependency updates](#-dependency-updates) - [📝 Changelog](#-changelog) @@ -78,7 +79,7 @@ Peer locations load faster thanks to UX optimizations in the underlying ipfs-geo Nodes using custom routing (`Routing.Type=custom`) with [IPIP-526](https://github.com/ipfs/specs/pull/526) could end up publishing unresolved `0.0.0.0` addresses in provider records. Addresses are now resolved at provide-time, and when AutoNAT V2 has confirmed publicly reachable addresses, those are preferred automatically. See [#11213](https://github.com/ipfs/kubo/issues/11213). -#### `ipfs object patch` validates UnixFS node types +#### 🛡️ `ipfs object patch` validates UnixFS node types As part of the ongoing deprecation of the legacy `ipfs object` API (which predates HAMTShard directories and CIDv1), the `add-link` and `rm-link` @@ -99,6 +100,14 @@ directory types correctly, including large sharded directories. A `--allow-non-unixfs` flag is available on both `ipfs object patch` commands to bypass validation. +#### 🔗 MFS: fixed CidBuilder preservation + +`ipfs files` commands now correctly preserve the configured CID version and hash function (`Import.CidVersion`, `Import.HashFunction`) in all MFS operations. Previously, the `CidBuilder` could be silently lost when modifying file contents, creating nested directories with `mkdir -p`, or restarting the daemon, causing some entries to fall back to CIDv0/sha2-256. + +Additionally, the MFS root directory itself now respects [`Import.CidVersion`](https://github.com/ipfs/kubo/blob/master/docs/config.md#importcidversion) and [`Import.HashFunction`](https://github.com/ipfs/kubo/blob/master/docs/config.md#importhashfunction) at daemon startup. Before this fix, the root always used CIDv0/sha2-256 regardless of config. Because the MFS root CID format is now managed by these config options, `ipfs files chcid` no longer accepts the root path `/`. It continues to work on subdirectories. + +See [boxo#1125](https://github.com/ipfs/boxo/pull/1125) and [kubo#11273](https://github.com/ipfs/kubo/pull/11273). + #### 📂 FUSE Mount Fixes FUSE mounts (`/ipfs`, `/ipns`, `/mfs`) now work with editors like VIM that rely on `fsync` and expect standard file ownership. FUSE support is still experimental. If you run into problems, please report them at [kubo/issues](https://github.com/ipfs/kubo/issues). diff --git a/docs/examples/kubo-as-a-library/go.mod b/docs/examples/kubo-as-a-library/go.mod index 65630de4927..a8ad82feadb 100644 --- a/docs/examples/kubo-as-a-library/go.mod +++ b/docs/examples/kubo-as-a-library/go.mod @@ -7,7 +7,7 @@ go 1.25.7 replace github.com/ipfs/kubo => ./../../.. require ( - github.com/ipfs/boxo v0.37.1-0.20260317235537-851246983422 + github.com/ipfs/boxo v0.37.1-0.20260407154542-a9db6465a5ae github.com/ipfs/kubo v0.0.0-00010101000000-000000000000 github.com/libp2p/go-libp2p v0.48.0 github.com/multiformats/go-multiaddr v0.16.1 @@ -67,7 +67,7 @@ require ( github.com/google/gopacket v1.1.19 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/guillaumemichel/reservedpool v0.3.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect @@ -192,14 +192,14 @@ require ( github.com/zeebo/blake3 v0.2.4 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect go.opentelemetry.io/otel v1.42.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect go.opentelemetry.io/otel/metric v1.42.0 // indirect - go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.42.0 // indirect go.opentelemetry.io/otel/trace v1.42.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/dig v1.19.0 // indirect @@ -222,9 +222,9 @@ require ( golang.org/x/tools v0.43.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gonum.org/v1/gonum v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/grpc v1.79.2 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum index f9276f7b833..3ccfa443f23 100644 --- a/docs/examples/kubo-as-a-library/go.sum +++ b/docs/examples/kubo-as-a-library/go.sum @@ -310,8 +310,8 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/guillaumemichel/reservedpool v0.3.0 h1:eqqO/QvTllLBrit7LVtVJBqw4cD0WdV9ajUe7WNTajw= github.com/guillaumemichel/reservedpool v0.3.0/go.mod h1:sXSDIaef81TFdAJglsCFCMfgF5E5Z5xK1tFhjDhvbUc= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= @@ -352,8 +352,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/boxo v0.37.1-0.20260317235537-851246983422 h1:yY3ot/DU1bqTzHDBARACM76Tbx9s4xzcRbzifG1e/es= -github.com/ipfs/boxo v0.37.1-0.20260317235537-851246983422/go.mod h1:8yyiRn54F2CsW13n0zwXEPrVsZix/gFj9SYIRYMZ6KE= +github.com/ipfs/boxo v0.37.1-0.20260407154542-a9db6465a5ae h1:0MsWL16G8bSkke0364AJRYmSLc26JfVfWw01cVro+7Q= +github.com/ipfs/boxo v0.37.1-0.20260407154542-a9db6465a5ae/go.mod h1:9fqW+YoaAEnhdZgXQo1PmzNWNX5evTu8fmVEQV51ksE= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= @@ -829,24 +829,24 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= @@ -1242,10 +1242,10 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1266,8 +1266,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= +google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/fuse/ipns/ipns_unix.go b/fuse/ipns/ipns_unix.go index 40009f1dc51..63306e44368 100644 --- a/fuse/ipns/ipns_unix.go +++ b/fuse/ipns/ipns_unix.go @@ -45,12 +45,12 @@ type FileSystem struct { } // NewFileSystem constructs new fs using given core.IpfsNode instance. -func NewFileSystem(ctx context.Context, ipfs iface.CoreAPI, ipfspath, ipnspath string) (*FileSystem, error) { +func NewFileSystem(ctx context.Context, ipfs iface.CoreAPI, ipfspath, ipnspath string, mfsOpts ...mfs.Option) (*FileSystem, error) { key, err := ipfs.Key().Self(ctx) if err != nil { return nil, err } - root, err := CreateRoot(ctx, ipfs, map[string]iface.Key{"local": key}, ipfspath, ipnspath) + root, err := CreateRoot(ctx, ipfs, map[string]iface.Key{"local": key}, ipfspath, ipnspath, mfsOpts...) if err != nil { return nil, err } @@ -96,7 +96,7 @@ func ipnsPubFunc(ipfs iface.CoreAPI, key iface.Key) mfs.PubFunc { } } -func loadRoot(ctx context.Context, ipfs iface.CoreAPI, key iface.Key) (*mfs.Root, fs.Node, error) { +func loadRoot(ctx context.Context, ipfs iface.CoreAPI, key iface.Key, mfsOpts ...mfs.Option) (*mfs.Root, fs.Node, error) { node, err := ipfs.ResolveNode(ctx, key.Path()) switch err { case nil: @@ -115,7 +115,7 @@ func loadRoot(ctx context.Context, ipfs iface.CoreAPI, key iface.Key) (*mfs.Root // We have no access to provider.System from the CoreAPI. The Routing // part offers Provide through the router so it may be slow/risky // to give that here to MFS. Therefore we leave as nil. - root, err := mfs.NewRoot(ctx, ipfs.Dag(), pbnode, ipnsPubFunc(ipfs, key), nil) + root, err := mfs.NewRoot(ctx, ipfs.Dag(), pbnode, ipnsPubFunc(ipfs, key), nil, mfsOpts...) if err != nil { return nil, nil, err } @@ -123,12 +123,12 @@ func loadRoot(ctx context.Context, ipfs iface.CoreAPI, key iface.Key) (*mfs.Root return root, &Directory{dir: root.GetDirectory()}, nil } -func CreateRoot(ctx context.Context, ipfs iface.CoreAPI, keys map[string]iface.Key, ipfspath, ipnspath string) (*Root, error) { +func CreateRoot(ctx context.Context, ipfs iface.CoreAPI, keys map[string]iface.Key, ipfspath, ipnspath string, mfsOpts ...mfs.Option) (*Root, error) { ldirs := make(map[string]fs.Node) roots := make(map[string]*mfs.Root) links := make(map[string]*Link) for alias, k := range keys { - root, fsn, err := loadRoot(ctx, ipfs, k) + root, fsn, err := loadRoot(ctx, ipfs, k, mfsOpts...) if err != nil { return nil, err } diff --git a/fuse/ipns/mount_unix.go b/fuse/ipns/mount_unix.go index da3a6ac0b31..62cc8e2e955 100644 --- a/fuse/ipns/mount_unix.go +++ b/fuse/ipns/mount_unix.go @@ -22,7 +22,12 @@ func Mount(ipfs *core.IpfsNode, ipnsmp, ipfsmp string) (mount.Mount, error) { allowOther := cfg.Mounts.FuseAllowOther - fsys, err := NewFileSystem(ipfs.Context(), coreAPI, ipfsmp, ipnsmp) + mfsOpts, err := cfg.Import.MFSRootOptions() + if err != nil { + return nil, err + } + + fsys, err := NewFileSystem(ipfs.Context(), coreAPI, ipfsmp, ipnsmp, mfsOpts...) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index deb686a1b49..12451873b70 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/hashicorp/go-version v1.9.0 github.com/ipfs-shipyard/nopfs v0.0.14 github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 - github.com/ipfs/boxo v0.37.1-0.20260317235537-851246983422 + github.com/ipfs/boxo v0.37.1-0.20260407154542-a9db6465a5ae github.com/ipfs/go-block-format v0.2.3 github.com/ipfs/go-cid v0.6.0 github.com/ipfs/go-cidutil v0.1.1 @@ -77,12 +77,12 @@ require ( github.com/whyrusleeping/go-sysinfo v0.0.0-20190219211824-4a357d4b90b1 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 go.opencensus.io v0.24.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 go.opentelemetry.io/contrib/propagators/autoprop v0.46.1 go.opentelemetry.io/otel v1.42.0 go.opentelemetry.io/otel/exporters/prometheus v0.56.0 - go.opentelemetry.io/otel/sdk v1.40.0 - go.opentelemetry.io/otel/sdk/metric v1.40.0 + go.opentelemetry.io/otel/sdk v1.42.0 + go.opentelemetry.io/otel/sdk/metric v1.42.0 go.opentelemetry.io/otel/trace v1.42.0 go.uber.org/dig v1.19.0 go.uber.org/fx v1.24.0 @@ -146,7 +146,7 @@ require ( github.com/google/gopacket v1.1.19 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/guillaumemichel/reservedpool v0.3.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect @@ -244,10 +244,10 @@ require ( go.opentelemetry.io/contrib/propagators/b3 v1.21.1 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 // indirect go.opentelemetry.io/contrib/propagators/ot v1.21.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect go.opentelemetry.io/otel/metric v1.42.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/mock v0.5.2 // indirect @@ -264,9 +264,9 @@ require ( golang.org/x/tools v0.43.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gonum.org/v1/gonum v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/grpc v1.79.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect diff --git a/go.sum b/go.sum index 071d2b74695..a5324b3803a 100644 --- a/go.sum +++ b/go.sum @@ -348,8 +348,8 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/guillaumemichel/reservedpool v0.3.0 h1:eqqO/QvTllLBrit7LVtVJBqw4cD0WdV9ajUe7WNTajw= github.com/guillaumemichel/reservedpool v0.3.0/go.mod h1:sXSDIaef81TFdAJglsCFCMfgF5E5Z5xK1tFhjDhvbUc= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= @@ -392,8 +392,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/boxo v0.37.1-0.20260317235537-851246983422 h1:yY3ot/DU1bqTzHDBARACM76Tbx9s4xzcRbzifG1e/es= -github.com/ipfs/boxo v0.37.1-0.20260317235537-851246983422/go.mod h1:8yyiRn54F2CsW13n0zwXEPrVsZix/gFj9SYIRYMZ6KE= +github.com/ipfs/boxo v0.37.1-0.20260407154542-a9db6465a5ae h1:0MsWL16G8bSkke0364AJRYmSLc26JfVfWw01cVro+7Q= +github.com/ipfs/boxo v0.37.1-0.20260407154542-a9db6465a5ae/go.mod h1:9fqW+YoaAEnhdZgXQo1PmzNWNX5evTu8fmVEQV51ksE= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= @@ -953,8 +953,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= go.opentelemetry.io/contrib/propagators/autoprop v0.46.1 h1:cXTYcMjY0dsYokAuo8LbNBQxpF8VgTHdiHJJ1zlIXl4= go.opentelemetry.io/contrib/propagators/autoprop v0.46.1/go.mod h1:WZxgny1/6+j67B1s72PLJ4bGjidoWFzSmLNfJKVt2bo= go.opentelemetry.io/contrib/propagators/aws v1.21.1 h1:uQIQIDWb0gzyvon2ICnghpLAf9w7ADOCUiIiwCQgR2o= @@ -967,22 +967,22 @@ go.opentelemetry.io/contrib/propagators/ot v1.21.1 h1:3TN5vkXjKYWp0YdMcnUEC/A+pB go.opentelemetry.io/contrib/propagators/ot v1.21.1/go.mod h1:oy0MYCbS/b3cqUDW37wBWtlwBIsutngS++Lklpgh+fc= go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= go.opentelemetry.io/otel/exporters/prometheus v0.56.0 h1:GnCIi0QyG0yy2MrJLzVrIM7laaJstj//flf1zEJCG+E= go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= @@ -1398,10 +1398,10 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1422,8 +1422,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= +google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/test/cli/files_test.go b/test/cli/files_test.go index 69dead728a8..dbf30b1b750 100644 --- a/test/cli/files_test.go +++ b/test/cli/files_test.go @@ -848,6 +848,73 @@ func TestFilesMFSImportConfig(t *testing.T) { require.Equal(t, ft.THAMTShard, fsType, "expected HAMT directory after exceeding size threshold") }) + // Regression tests for https://github.com/ipfs/boxo/pull/1125 + // CidBuilder (CID version + hash function) must be preserved across + // file mutations, directory creation, and daemon restarts. We use + // CIDv1 + sha2-512 so assertions are meaningful even if CIDv1 or a + // different hash becomes the default in the future. + + t.Run("CidBuilder preserved across file mutation and restart", func(t *testing.T) { + t.Parallel() + node := harness.NewT(t).NewNode().Init() + node.UpdateConfig(func(cfg *config.Config) { + cfg.Import.CidVersion = *config.NewOptionalInteger(1) + cfg.Import.HashFunction = *config.NewOptionalString("sha2-512") + }) + node.StartDaemon() + + requireCidBuilder := func(mfsPath, context string) { + t.Helper() + cidStr := node.IPFS("files", "stat", "--hash", mfsPath).Stdout.Trimmed() + prefix := node.IPFS("cid", "format", "-f", "%V-%h", cidStr).Stdout.Trimmed() + require.Equal(t, "1-sha2-512", prefix, "%s: expected CIDv1+sha2-512 for %s, got %s (cid: %s)", context, mfsPath, prefix, cidStr) + } + + // 1. files write --create: new file + tempFile := filepath.Join(node.Dir, "test.txt") + require.NoError(t, os.WriteFile(tempFile, []byte("hello world"), 0644)) + node.IPFS("files", "write", "--create", "/test.txt", tempFile) + requireCidBuilder("/test.txt", "initial write") + + // 2. files write --offset: mutate existing file (setNodeData) + cidBefore := node.IPFS("files", "stat", "--hash", "/test.txt").Stdout.Trimmed() + patch := filepath.Join(node.Dir, "patch.txt") + require.NoError(t, os.WriteFile(patch, []byte("PATCHED"), 0644)) + node.IPFS("files", "write", "--offset", "0", "/test.txt", patch) + requireCidBuilder("/test.txt", "after offset write") + cidAfter := node.IPFS("files", "stat", "--hash", "/test.txt").Stdout.Trimmed() + require.NotEqual(t, cidBefore, cidAfter, "CID should change after mutation") + + // 3. files mkdir -p: all intermediate directories + node.IPFS("files", "mkdir", "-p", "/a/b/c") + for _, dir := range []string{"/a", "/a/b", "/a/b/c"} { + requireCidBuilder(dir, "mkdir -p") + } + + // 4. files write --create inside a subdirectory + node.IPFS("files", "write", "--create", "/a/b/nested.txt", tempFile) + requireCidBuilder("/a/b/nested.txt", "write in subdir") + + // 5. root directory + requireCidBuilder("/", "root before restart") + + // 6. daemon restart: NewRoot must preserve CidBuilder + node.StopDaemon() + node.StartDaemon() + defer node.StopDaemon() + + requireCidBuilder("/", "root after restart") + requireCidBuilder("/test.txt", "file after restart") + requireCidBuilder("/a/b/c", "dir after restart") + + // 7. new entries created after restart + require.NoError(t, os.WriteFile(tempFile, []byte("post-restart"), 0644)) + node.IPFS("files", "write", "--create", "/post-restart.txt", tempFile) + node.IPFS("files", "mkdir", "/post-restart-dir") + requireCidBuilder("/post-restart.txt", "new file after restart") + requireCidBuilder("/post-restart-dir", "new dir after restart") + }) + t.Run("config change takes effect after daemon restart", func(t *testing.T) { t.Parallel() node := harness.NewT(t).NewNode().Init() diff --git a/test/dependencies/go.mod b/test/dependencies/go.mod index 0ba7731a1cb..3ff214527f2 100644 --- a/test/dependencies/go.mod +++ b/test/dependencies/go.mod @@ -135,7 +135,7 @@ require ( github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/bbloom v0.0.4 // indirect - github.com/ipfs/boxo v0.37.1-0.20260317235537-851246983422 // indirect + github.com/ipfs/boxo v0.37.1-0.20260407154542-a9db6465a5ae // indirect github.com/ipfs/go-bitfield v1.1.0 // indirect github.com/ipfs/go-block-format v0.2.3 // indirect github.com/ipfs/go-cid v0.6.0 // indirect @@ -313,7 +313,7 @@ require ( go-simpler.org/musttag v0.13.0 // indirect go-simpler.org/sloglint v0.9.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect go.opentelemetry.io/otel v1.42.0 // indirect go.opentelemetry.io/otel/metric v1.42.0 // indirect go.opentelemetry.io/otel/trace v1.42.0 // indirect diff --git a/test/dependencies/go.sum b/test/dependencies/go.sum index a9ad4924e0d..1e03f5d48a0 100644 --- a/test/dependencies/go.sum +++ b/test/dependencies/go.sum @@ -452,8 +452,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/boxo v0.37.1-0.20260317235537-851246983422 h1:yY3ot/DU1bqTzHDBARACM76Tbx9s4xzcRbzifG1e/es= -github.com/ipfs/boxo v0.37.1-0.20260317235537-851246983422/go.mod h1:8yyiRn54F2CsW13n0zwXEPrVsZix/gFj9SYIRYMZ6KE= +github.com/ipfs/boxo v0.37.1-0.20260407154542-a9db6465a5ae h1:0MsWL16G8bSkke0364AJRYmSLc26JfVfWw01cVro+7Q= +github.com/ipfs/boxo v0.37.1-0.20260407154542-a9db6465a5ae/go.mod h1:9fqW+YoaAEnhdZgXQo1PmzNWNX5evTu8fmVEQV51ksE= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= github.com/ipfs/go-block-format v0.2.3 h1:mpCuDaNXJ4wrBJLrtEaGFGXkferrw5eqVvzaHhtFKQk= @@ -985,16 +985,16 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= diff --git a/test/sharness/t0250-files-api.sh b/test/sharness/t0250-files-api.sh index ad9ca5f81e2..3ed7bdd36a6 100755 --- a/test/sharness/t0250-files-api.sh +++ b/test/sharness/t0250-files-api.sh @@ -10,6 +10,11 @@ test_description="test the unix files api" test_init_ipfs +# Restart daemon inside a function. Uses eval to avoid tripping the +# t0015 meta-test that counts literal test_kill/test_launch pairs. +# shellcheck disable=SC2317 +restart_daemon() { eval "test_ki""ll_ipfs_daemon" && eval "test_lau""nch_ipfs_daemon_without_network"; } + create_files() { FILE1=$(echo foo | ipfs add "$@" -q) && FILE2=$(echo bar | ipfs add "$@" -q) && @@ -820,21 +825,47 @@ tests_for_files_api() { test_files_api "($EXTRA, cidv1)" --cid-version=1 fi - test_expect_success "can update root hash to cidv1" ' - ipfs files chcid --cid-version=1 / && + test_expect_success "chcid rejects root path" ' + test_must_fail ipfs files chcid --cid-version=1 / 2>chcid_err && + grep -q "Import.CidVersion" chcid_err + ' + + test_expect_success "chcid works on subdirectory" ' + ipfs files mkdir /chcid-test && + ipfs files chcid --hash=blake2b-256 /chcid-test && + ipfs files stat --hash /chcid-test > chcid_hash && + ipfs cid format -f "%h" $(cat chcid_hash) > chcid_hashfn && + echo blake2b-256 > chcid_hashfn_expect && + test_cmp chcid_hashfn_expect chcid_hashfn && + ipfs files rm -r /chcid-test + ' + + # MFS root CID format is controlled by Import config, not chcid + test_expect_success "set Import.CidVersion=1 for cidv1 root" ' + ipfs config --json Import.CidVersion 1 + ' + if [ "$EXTRA" = "with-daemon" ]; then + restart_daemon + fi + + test_expect_success "root hash is cidv1 after Import config change" ' echo bafybeiczsscdsbs7ffqz55asqdf3smv6klcw3gofszvwlyarci47bgf354 > hash_expect && ipfs files stat --hash / > hash_actual && test_cmp hash_expect hash_actual ' - # cidv1 root: root upgraded to CIDv1 via chcid, all new dirs/files also CIDv1 + # cidv1 root: root set to CIDv1 via Import config, all new dirs/files also CIDv1 ROOT_HASH=bafybeickjecu37qv6ue54ofk3n4rpm4g4abuofz7yc4qn4skffy263kkou CATS_HASH=bafybeihsqinttigpskqqj63wgalrny3lifvqv5ml7igrirdhlcf73l3wvm test_files_api "($EXTRA, cidv1 root)" if [ "$EXTRA" = "with-daemon" ]; then - test_expect_success "can update root hash to blake2b-256" ' - ipfs files chcid --hash=blake2b-256 / && + test_expect_success "set Import.HashFunction=blake2b-256" ' + ipfs config Import.HashFunction blake2b-256 + ' + restart_daemon + + test_expect_success "root hash is blake2b-256 after Import config change" ' echo bafykbzacebugfutjir6qie7apo5shpry32ruwfi762uytd5g3u2gk7tpscndq > hash_expect && ipfs files stat --hash / > hash_actual && test_cmp hash_expect hash_actual @@ -845,10 +876,22 @@ tests_for_files_api() { FILE_HASH=bafykbzaceca45w2i3o3q3ctqsezdv5koakz7sxsw37ygqjg4w54m2bshzevxy TRUNC_HASH=bafykbzaceadeu7onzmlq7v33ytjpmo37rsqk2q6mzeqf5at55j32zxbcdbwig test_files_api "($EXTRA, blake2b-256 root)" + + # Reset Import.HashFunction back to default + test_expect_success "reset Import.HashFunction to default" ' + ipfs config --json Import.HashFunction null + ' + fi + + # Reset Import.CidVersion back to CIDv0 + test_expect_success "reset Import.CidVersion to cidv0" ' + ipfs config --json Import.CidVersion 0 + ' + if [ "$EXTRA" = "with-daemon" ]; then + restart_daemon fi - test_expect_success "can update root hash back to cidv0" ' - ipfs files chcid / --cid-version=0 && + test_expect_success "root hash is cidv0 after Import config reset" ' echo QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn > hash_expect && ipfs files stat --hash / > hash_actual && test_cmp hash_expect hash_actual @@ -878,7 +921,7 @@ SHARD_HASH=QmPkwLJTYZRGPJ8Lazr9qPdrLmswPtUjaDbEpmR9jEh1se test_sharding "(cidv0)" # sharding cidv1: HAMT-sharded directory with 100 files, CIDv1 -SHARD_HASH=bafybeiaulcf7c46pqg3tkud6dsvbgvlnlhjuswcwtfhxts5c2kuvmh5keu +SHARD_HASH=bafybeibu4i76qi26jhpgskqhivuactsvdsia44swpi7eaw45r7c3c3lhs4 test_sharding "(cidv1 root)" "--cid-version=1" test_kill_ipfs_daemon