From bcc6963db69b7368cc6267821fa5d583ef4aa915 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Tue, 3 Nov 2020 12:21:13 -0700 Subject: [PATCH] Use hidden directory based on mount path --- cmd/litestream/main.go | 61 +++++++++++++++++++++++++++++++++--------- file_system.go | 4 +-- node.go | 4 +-- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/cmd/litestream/main.go b/cmd/litestream/main.go index ba00d69..3824c58 100644 --- a/cmd/litestream/main.go +++ b/cmd/litestream/main.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "log" "os" + "path/filepath" "bazil.org/fuse" "bazil.org/fuse/fs" @@ -27,8 +28,8 @@ func main() { type Main struct { logger *log.Logger - SourcePath string - MountPath string + TargetPath string + Path string } func NewMain() *Main { @@ -39,19 +40,36 @@ func NewMain() *Main { func (m *Main) Run(args []string) (err error) { flagSet := flag.NewFlagSet("litestream", flag.ContinueOnError) + flagSet.StringVar(&m.TargetPath, "target", "", "target directory") verbose := flagSet.Bool("v", false, "verbose") flagSet.Usage = m.usage if err := flagSet.Parse(args); err != nil { return err } - // Ensure src & mount paths are specified. - if m.SourcePath = flagSet.Arg(0); m.SourcePath == "" { - return errors.New("source path required") - } else if m.MountPath = flagSet.Arg(1); m.MountPath == "" { + // Ensure mount path is specified. + if flagSet.NArg() > 1 { + return errors.New("too many arguments, only specify mount path") + } else if m.Path = flagSet.Arg(0); m.Path == "" { return errors.New("mount path required") } + // Ensure mount path exists & is a directory. + if fi, err := os.Stat(m.Path); err != nil { + return err + } else if !fi.IsDir() { + return fmt.Errorf("mount path must be a directory") + } + + // If no target is specified, default to a hidden directory based on the mount path. + if m.TargetPath == "" { + m.TargetPath = filepath.Join(filepath.Dir(m.Path), "."+filepath.Base(m.Path)) + + if err := m.ensureTargetPath(); err != nil { + return err + } + } + // Setup logging, if verbose specified. var config fs.Config if *verbose { @@ -60,29 +78,48 @@ func (m *Main) Run(args []string) (err error) { } // Mount FUSE filesystem. - conn, err := fuse.Mount(m.MountPath, fuse.FSName("litestream"), fuse.Subtype("litestreamfs")) + conn, err := fuse.Mount(m.Path, fuse.FSName("litestream"), fuse.Subtype("litestreamfs")) if err != nil { return err } - defer fuse.Unmount(m.MountPath) + defer fuse.Unmount(m.Path) defer conn.Close() - m.logger.Printf("mounted") + m.logger.Printf("mounted %s; target=%s", m.Path, m.TargetPath) s := fs.New(conn, &config) - return s.Serve(&litestream.FileSystem{SourcePath: m.SourcePath}) + return s.Serve(&litestream.FileSystem{TargetPath: m.TargetPath}) +} + +func (m *Main) ensureTargetPath() error { + // Check if target path exists, exit if it does. + if _, err := os.Stat(m.TargetPath); err == nil { + return nil + } else if err != nil && !os.IsNotExist(err) { + return err + } + + // Create target path with the same permissions as the mount path. + fi, err := os.Stat(m.Path) + if err != nil { + return err + } + return os.Mkdir(m.TargetPath, fi.Mode()) } func (m *Main) usage() { fmt.Println(` -Litestream is a FUSE file system that automatically replicates SQLite databases. +Litestream is a FUSE file system that replicates SQLite databases. Usage: - litestream [arguments] source_dir mount_dir + litestream [arguments] PATH Arguments: + -target PATH + Specifies the directory to store data. + Defaults to a hidden directory next to PATH. -v Enable verbose logging. diff --git a/file_system.go b/file_system.go index 8f41f8e..8dee7d9 100644 --- a/file_system.go +++ b/file_system.go @@ -24,7 +24,7 @@ type FileSystem struct { config Config // configuration file // Filepath to the root of the source directory. - SourcePath string + TargetPath string } func NewFileSystem() *FileSystem { @@ -35,7 +35,7 @@ func NewFileSystem() *FileSystem { // ConfigPath returns the path to the file system config file. func (f *FileSystem) ConfigPath() string { - return filepath.Join(f.SourcePath, ConfigName) + return filepath.Join(f.TargetPath, ConfigName) } // Open initializes the file system and finds all managed database files. diff --git a/node.go b/node.go index 1c03a52..75a39aa 100644 --- a/node.go +++ b/node.go @@ -42,7 +42,7 @@ func NewNode(fs *FileSystem, path string) *Node { } func (n *Node) srcpath() string { - return filepath.Join(n.fs.SourcePath, n.path) + return filepath.Join(n.fs.TargetPath, n.path) } func (n *Node) Attr(ctx context.Context, a *fuse.Attr) (err error) { @@ -82,7 +82,7 @@ func (n *Node) Attr(ctx context.Context, a *fuse.Attr) (err error) { // Lookup need not to handle the names "." and "..". func (n *Node) Lookup(ctx context.Context, name string) (_ fs.Node, err error) { path := filepath.Join(n.path, name) - srcpath := filepath.Join(n.fs.SourcePath, path) + srcpath := filepath.Join(n.fs.TargetPath, path) if _, err := os.Stat(srcpath); os.IsNotExist(err) { return nil, syscall.ENOENT }