From 247816a58fefdadf890eb6948bb09de3cea1b104 Mon Sep 17 00:00:00 2001 From: Lucas Molas Date: Mon, 10 Jan 2022 11:56:04 -0300 Subject: [PATCH 1/3] feat(cmds): files: add new-root command to change the MFS root --- core/commands/files.go | 119 +++++++++++++++++++++++++++++++++++++---- core/node/core.go | 13 +++-- 2 files changed, 118 insertions(+), 14 deletions(-) diff --git a/core/commands/files.go b/core/commands/files.go index 3f7e3e6b9a9..d3637396bed 100644 --- a/core/commands/files.go +++ b/core/commands/files.go @@ -13,10 +13,13 @@ import ( humanize "github.com/dustin/go-humanize" "github.com/ipfs/go-ipfs/core" "github.com/ipfs/go-ipfs/core/commands/cmdenv" + "github.com/ipfs/go-ipfs/core/node" + "github.com/ipfs/go-ipfs/repo/fsrepo" bservice "github.com/ipfs/go-blockservice" cid "github.com/ipfs/go-cid" cidenc "github.com/ipfs/go-cidutil/cidenc" + "github.com/ipfs/go-datastore" cmds "github.com/ipfs/go-ipfs-cmds" offline "github.com/ipfs/go-ipfs-exchange-offline" ipld "github.com/ipfs/go-ipld-format" @@ -70,16 +73,17 @@ operations. cmds.BoolOption(filesFlushOptionName, "f", "Flush target and ancestors after write.").WithDefault(true), }, Subcommands: map[string]*cmds.Command{ - "read": filesReadCmd, - "write": filesWriteCmd, - "mv": filesMvCmd, - "cp": filesCpCmd, - "ls": filesLsCmd, - "mkdir": filesMkdirCmd, - "stat": filesStatCmd, - "rm": filesRmCmd, - "flush": filesFlushCmd, - "chcid": filesChcidCmd, + "read": filesReadCmd, + "write": filesWriteCmd, + "mv": filesMvCmd, + "cp": filesCpCmd, + "ls": filesLsCmd, + "mkdir": filesMkdirCmd, + "stat": filesStatCmd, + "rm": filesRmCmd, + "flush": filesFlushCmd, + "chcid": filesChcidCmd, + "replace-root": filesReplaceRoot, }, } @@ -1134,6 +1138,101 @@ Remove files or directories. }, } +var filesReplaceRoot = &cmds.Command{ + Helptext: cmds.HelpText{ + // FIXME(BLOCKING): Somewhere around we should flag that this is an advanced command + // that you normally wouldn't need except in case of filesystem corruption. Where? + Tagline: "Replace the filesystem root.", + ShortDescription: ` + Replace the filesystem root with another CID. + + $ ipfs init + [...] + ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme # <- init dir + [...] + $ ipfs files ls / + [nothing; empty dir] + $ ipfs files stat / --hash + QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn + + # FIXME(BLOCKING): Need the following to somehow "start" the root dir, otherwise + # the replace-root will fail to find the '/local/filesroot' entry in the repo + $ ipfs files cp /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme /file + + $ GOLOG_LOG_LEVEL="info" ipfs files replace-root QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc # init dir from before + [...] replaced MFS files root QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc [...] + [here we have the CID of the old root to "undo" in case of error] + $ ipfs files ls / + [contents from init dir now set as the root of the filesystem] + about + contact + help + ping + quick-start + readme + security-notes + $ ipfs files replace-root QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn # empty dir from init + $ ipfs files ls / + [nothing; empty dir] +`, + }, + Arguments: []cmds.Argument{ + cmds.StringArg("new-root", true, false, "New root to use."), + }, + // FIXME(BLOCKING): Can/should we do this with the repo running? + NoRemote: true, + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { + if len(req.Arguments) < 1 { + fmt.Errorf("new root not provided") + } + newFilesRootCid, err := cid.Parse(req.Arguments[0]) + if err != nil { + return fmt.Errorf("files root argument provided %s is not a valid CID: %w", req.Arguments[0], err) + + } + // FIXME(BLOCKING): Check (a) that this CID exists *locally* and (b) + // that it's a dir. + + cfgRoot, err := cmdenv.GetConfigRoot(env) + if err != nil { + return err + } + + repo, err := fsrepo.Open(cfgRoot) + if err != nil { + return err + } + localDS := repo.Datastore() + defer repo.Close() + + filesRootBytes, err := localDS.Get(req.Context, node.FilesRootDatastoreKey) + if err == datastore.ErrNotFound { + return fmt.Errorf("MFS files root %s not found in repo", node.FilesRootDatastoreKey) + } else if err != nil { + return fmt.Errorf("looking for MFS files root: %w", err) + } + filesRootCid, err := cid.Cast(filesRootBytes) + if err != nil { + return fmt.Errorf("casting MFS files root %s as CID: %w", filesRootBytes, err) + } + + err = localDS.Put(req.Context, node.FilesRootDatastoreKey, newFilesRootCid.Bytes()) + if err != nil { + return fmt.Errorf("storing new files root: %w", err) + } + // FIXME(BLOCKING): Do we need this if we're closing the repo at the end + // of the command? Likely not. + err = localDS.Sync(req.Context, node.FilesRootDatastoreKey) + if err != nil { + return fmt.Errorf("syncing new files root: %w", err) + } + + log.Infof("replaced MFS files root %s with %s", filesRootCid, newFilesRootCid) + + return nil + }, +} + func getPrefixNew(req *cmds.Request) (cid.Builder, error) { cidVer, cidVerSet := req.Options[filesCidVersionOptionName].(int) hashFunStr, hashFunSet := req.Options[filesHashOptionName].(string) diff --git a/core/node/core.go b/core/node/core.go index c8305bb610a..410a869871b 100644 --- a/core/node/core.go +++ b/core/node/core.go @@ -29,6 +29,12 @@ import ( "github.com/ipfs/go-ipfs/repo" ) +var FilesRootDatastoreKey datastore.Key + +func init() { + FilesRootDatastoreKey = datastore.NewKey("/local/filesroot") +} + // BlockService creates new blockservice which provides an interface to fetch content-addressable blocks func BlockService(lc fx.Lifecycle, bs blockstore.Blockstore, rem exchange.Interface) blockservice.BlockService { bsvc := blockservice.New(bs, rem) @@ -110,7 +116,6 @@ func Dag(bs blockservice.BlockService) format.DAGService { // Files loads persisted MFS root func Files(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo repo.Repo, dag format.DAGService) (*mfs.Root, error) { - dsk := datastore.NewKey("/local/filesroot") pf := func(ctx context.Context, c cid.Cid) error { rootDS := repo.Datastore() if err := rootDS.Sync(ctx, blockstore.BlockPrefix); err != nil { @@ -120,15 +125,15 @@ func Files(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo repo.Repo, dag format. return err } - if err := rootDS.Put(ctx, dsk, c.Bytes()); err != nil { + if err := rootDS.Put(ctx, FilesRootDatastoreKey, c.Bytes()); err != nil { return err } - return rootDS.Sync(ctx, dsk) + return rootDS.Sync(ctx, FilesRootDatastoreKey) } var nd *merkledag.ProtoNode ctx := helpers.LifecycleCtx(mctx, lc) - val, err := repo.Datastore().Get(ctx, dsk) + val, err := repo.Datastore().Get(ctx, FilesRootDatastoreKey) switch { case err == datastore.ErrNotFound || val == nil: From cb1212f799b24636da1436d8db772dc7f2325b58 Mon Sep 17 00:00:00 2001 From: Lucas Molas Date: Tue, 11 Jan 2022 11:45:55 -0300 Subject: [PATCH 2/3] fix error return --- core/commands/files.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/commands/files.go b/core/commands/files.go index d3637396bed..b93ea1becd1 100644 --- a/core/commands/files.go +++ b/core/commands/files.go @@ -1183,7 +1183,7 @@ var filesReplaceRoot = &cmds.Command{ NoRemote: true, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { if len(req.Arguments) < 1 { - fmt.Errorf("new root not provided") + return fmt.Errorf("new root not provided") } newFilesRootCid, err := cid.Parse(req.Arguments[0]) if err != nil { From a0f16cc886cc0010dc925d20f6a04b0c6bd5e48a Mon Sep 17 00:00:00 2001 From: Lucas Molas Date: Tue, 11 Jan 2022 12:10:15 -0300 Subject: [PATCH 3/3] better doc and error around running daemon --- core/commands/files.go | 75 ++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/core/commands/files.go b/core/commands/files.go index b93ea1becd1..fbc08f7e5b7 100644 --- a/core/commands/files.go +++ b/core/commands/files.go @@ -20,6 +20,7 @@ import ( cid "github.com/ipfs/go-cid" cidenc "github.com/ipfs/go-cidutil/cidenc" "github.com/ipfs/go-datastore" + fslock "github.com/ipfs/go-fs-lock" cmds "github.com/ipfs/go-ipfs-cmds" offline "github.com/ipfs/go-ipfs-exchange-offline" ipld "github.com/ipfs/go-ipld-format" @@ -1140,40 +1141,45 @@ Remove files or directories. var filesReplaceRoot = &cmds.Command{ Helptext: cmds.HelpText{ - // FIXME(BLOCKING): Somewhere around we should flag that this is an advanced command - // that you normally wouldn't need except in case of filesystem corruption. Where? Tagline: "Replace the filesystem root.", ShortDescription: ` - Replace the filesystem root with another CID. - - $ ipfs init - [...] - ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme # <- init dir - [...] - $ ipfs files ls / - [nothing; empty dir] - $ ipfs files stat / --hash - QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn - - # FIXME(BLOCKING): Need the following to somehow "start" the root dir, otherwise - # the replace-root will fail to find the '/local/filesroot' entry in the repo - $ ipfs files cp /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme /file - - $ GOLOG_LOG_LEVEL="info" ipfs files replace-root QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc # init dir from before - [...] replaced MFS files root QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc [...] - [here we have the CID of the old root to "undo" in case of error] - $ ipfs files ls / - [contents from init dir now set as the root of the filesystem] - about - contact - help - ping - quick-start - readme - security-notes - $ ipfs files replace-root QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn # empty dir from init - $ ipfs files ls / - [nothing; empty dir] +Replace the filesystem root with another CID when the filesystem has been corrupted. +`, + LongDescription: ` +Replace the filesystem root with another CID. +This command is meant to run in standalone mode when the daemon isn't running. +It is an advanced command that you normally do *not* want to run except when +the filesystem has been corrupted and the daemon refuses to run. + +FIXME: Add an example of the daemon not running once https://github.com/ipfs/go-ipfs/issues/7183 +is resolved. + +$ ipfs init +[...] +ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme # <- init dir +[...] +$ ipfs files ls / +[nothing; empty dir] +$ ipfs files stat / --hash +QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn +# FIXME(BLOCKING): Need the following to somehow "start" the root dir, otherwise +# the replace-root will fail to find the '/local/filesroot' entry in the repo +$ ipfs files cp /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme /file +$ GOLOG_LOG_LEVEL="info" ipfs files replace-root QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc # init dir from before +[...] replaced MFS files root QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc [...] +[here we have the CID of the old root to "undo" in case of error] +$ ipfs files ls / +[contents from init dir now set as the root of the filesystem] +about +contact +help +ping +quick-start +readme +security-notes +$ ipfs files replace-root QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn # empty dir from init +$ ipfs files ls / +[nothing; empty dir] `, }, Arguments: []cmds.Argument{ @@ -1200,6 +1206,11 @@ var filesReplaceRoot = &cmds.Command{ repo, err := fsrepo.Open(cfgRoot) if err != nil { + if pathError, ok := err.(*os.PathError); ok { + if _, isLockError := pathError.Unwrap().(fslock.LockedError); isLockError { + return fmt.Errorf("aquiring the repo lock (make sure the daemon isn't running): %w", err) + } + } return err } localDS := repo.Datastore()