summaryrefslogtreecommitdiff
path: root/cmd/btrfs-rec
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-07-11 00:43:59 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2022-07-11 00:48:10 -0600
commit0f853e1ead10347be8c5715d6bb797dd5e643e2f (patch)
tree688e9a9b2e57058906f3501a3d9bdc894b83c784 /cmd/btrfs-rec
parentad9ac6d07ce1819260c2b7f090fd4fe742c80d9f (diff)
wip btrfs-rec
Diffstat (limited to 'cmd/btrfs-rec')
-rw-r--r--cmd/btrfs-rec/main.go145
1 files changed, 145 insertions, 0 deletions
diff --git a/cmd/btrfs-rec/main.go b/cmd/btrfs-rec/main.go
new file mode 100644
index 0000000..9b285b1
--- /dev/null
+++ b/cmd/btrfs-rec/main.go
@@ -0,0 +1,145 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/datawire/dlib/dgroup"
+ "github.com/datawire/dlib/dlog"
+ "github.com/datawire/ocibuild/pkg/cliutil"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsutil"
+)
+
+type logLevelFlag struct {
+ logrus.Level
+}
+
+func (lvl *logLevelFlag) Type() string { return "loglevel" }
+func (lvl *logLevelFlag) Set(str string) error {
+ var err error
+ lvl.Level, err = logrus.ParseLevel(str)
+ return err
+}
+
+var _ pflag.Value = (*logLevelFlag)(nil)
+
+type subcommand struct {
+ cobra.Command
+ RunE func(*btrfs.FS, *cobra.Command, []string) error
+}
+
+var inspectors, repairers []subcommand
+
+func main() {
+ logLevelFlag := logLevelFlag{
+ Level: logrus.InfoLevel,
+ }
+ var pvsFlag []string
+
+ argparser := &cobra.Command{
+ Use: "btrfs-rec {[flags]|SUBCOMMAND}",
+ Short: "Recover (data from) a broken btrfs filesystem",
+
+ Args: cliutil.WrapPositionalArgs(cliutil.OnlySubcommands),
+ RunE: cliutil.RunSubcommands,
+
+ SilenceErrors: true, // main() will handle this after .ExecuteContext() returns
+ SilenceUsage: true, // our FlagErrorFunc will handle it
+
+ CompletionOptions: cobra.CompletionOptions{ //nolint:exhaustivestruct
+ DisableDefaultCmd: true,
+ },
+ }
+ argparser.SetFlagErrorFunc(cliutil.FlagErrorFunc)
+ argparser.SetHelpTemplate(cliutil.HelpTemplate)
+ argparser.PersistentFlags().Var(&logLevelFlag, "verbosity", "set the verbosity")
+ argparser.PersistentFlags().StringArrayVar(&pvsFlag, "pv", nil, "open the file `physical_volume` as part of the filesystem")
+ if err := argparser.MarkPersistentFlagFilename("pv"); err != nil {
+ panic(err)
+ }
+ if err := argparser.MarkPersistentFlagRequired("pv"); err != nil {
+ panic(err)
+ }
+
+ var openFlag int = os.O_RDONLY
+
+ argparserInspect := &cobra.Command{
+ Use: "inpsect {[flags]|SUBCOMMAND}",
+ Short: "Inspect (but don't modify) a broken btrfs filesystem",
+
+ Args: cliutil.WrapPositionalArgs(cliutil.OnlySubcommands),
+ RunE: cliutil.RunSubcommands,
+ }
+ argparser.AddCommand(argparserInspect)
+
+ argparserRepair := &cobra.Command{
+ Use: "repair {[flags]|SUBCOMMAND}",
+ Short: "Repair a broken btrfs filesystem",
+
+ Args: cliutil.WrapPositionalArgs(cliutil.OnlySubcommands),
+ RunE: cliutil.RunSubcommands,
+
+ PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
+ openFlag = os.O_RDWR
+ return nil
+ },
+ }
+ argparser.AddCommand(argparserRepair)
+
+ for _, cmdgrp := range []struct {
+ parent *cobra.Command
+ children []subcommand
+ }{
+ {argparserInspect, inspectors},
+ {argparserRepair, repairers},
+ } {
+ for _, child := range cmdgrp.children {
+ cmd := child.Command
+ runE := child.RunE
+ cmd.RunE = func(cmd *cobra.Command, args []string) error {
+ ctx := cmd.Context()
+ logger := logrus.New()
+ logger.SetLevel(logLevelFlag.Level)
+ ctx = dlog.WithLogger(ctx, dlog.WrapLogrus(logger))
+
+ grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{
+ EnableSignalHandling: true,
+ })
+ grp.Go("main", func(ctx context.Context) (err error) {
+ maybeSetErr := func(_err error) {
+ if _err != nil && err == nil {
+ err = _err
+ }
+ }
+ fs, err := btrfsutil.Open(openFlag, pvsFlag...)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ maybeSetErr(fs.Close())
+ }()
+
+ cmd.SetContext(ctx)
+ return runE(fs, cmd, args)
+ })
+ return grp.Wait()
+ }
+ cmdgrp.parent.AddCommand(&cmd)
+ }
+ }
+
+ if err := argparser.ExecuteContext(context.Background()); err != nil {
+ fmt.Fprintf(os.Stderr, "%v: error: %v\n", argparser.CommandPath(), err)
+ os.Exit(1)
+ }
+}