Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cmds): files: add new-root command to change the MFS root #8648

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 120 additions & 10 deletions core/commands/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ 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"
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"
Expand Down Expand Up @@ -70,16 +74,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,
Copy link
Member

@lidel lidel Jan 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 loose thoughts

  • since we already use unix terms like mkdir mv cp rm, perhaps this one should be named chroot ?
  • another idea: perhaps we could add a second (optional) argument to already existing chcid, which would allow in-place swapping of CIDs for any path (not just root)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we already use unix terms like mkdir mv cp rm, perhaps this one should be named chroot ?

The current name came from an informal Slack thread, but I'm fine with whatever is decided. My 2 cents on chroot is that it might imply some temporary change related to the running daemon/process when in fact we're doing a long-term modification of the single filesystem available (since we don't have any jail/sandbox abstractions).

another idea: perhaps we could add a second (optional) argument to already existing chcid, which would allow in-place swapping of CIDs for any path (not just root)

This sounds like it is beyond the scope of this issue which is fixing a corrupted filesystem preventing daemon startup (corruption in sub-paths wouldn't have the same effect). The idea is valid on itself though, maybe we should discuss it in another issue as a feature request.

},
}

Expand Down Expand Up @@ -1134,6 +1139,111 @@ Remove files or directories.
},
}

var filesReplaceRoot = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Replace the filesystem root.",
ShortDescription: `
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

# 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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

NoRemote: true,
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
if len(req.Arguments) < 1 {
return 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.
Comment on lines +1254 to +1255
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO


cfgRoot, err := cmdenv.GetConfigRoot(env)
if err != nil {
return err
}

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()
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.
Comment on lines +1289 to +1290
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

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)
Expand Down
13 changes: 9 additions & 4 deletions core/node/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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:
Expand Down