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

add / update fstab entry with --update-fstab #2462

Merged
merged 6 commits into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ libjfs.h
.DS_Store
docs/node_modules
cmd/cmd
*.dump
*.out
2 changes: 1 addition & 1 deletion cmd/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
)

func TestBench(t *testing.T) {
mountTemp(t, nil, false)
mountTemp(t, nil, []string{"--trash-days=0"}, nil)
defer umountTemp(t)

os.Setenv("SKIP_DROP_CACHES", "true")
Expand Down
2 changes: 1 addition & 1 deletion cmd/fsck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
)

func TestFsck(t *testing.T) {
mountTemp(t, nil, true)
mountTemp(t, nil, nil, nil)
defer umountTemp(t)

for i := 0; i < 10; i++ {
Expand Down
2 changes: 1 addition & 1 deletion cmd/gc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func getFileCount(dir string) int {

func TestGc(t *testing.T) {
var bucket string
mountTemp(t, &bucket, false)
mountTemp(t, &bucket, []string{"--trash-days=0"}, nil)
defer umountTemp(t)

if err := writeSmallBlocks(testMountPoint); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestInfo(t *testing.T) {
patches := gomonkey.ApplyGlobalVar(os.Stdout, *tmpFile)
defer patches.Reset()

mountTemp(t, nil, true)
mountTemp(t, nil, nil, nil)
defer umountTemp(t)

if err = os.MkdirAll(fmt.Sprintf("%s/dir1", testMountPoint), 0777); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func startWebdav(t *testing.T) {
}

func TestIntegration(t *testing.T) {
mountTemp(t, nil, true)
mountTemp(t, nil, nil, nil)
defer umountTemp(t)
startGateway(t)
startWebdav(t)
Expand Down
9 changes: 6 additions & 3 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ func Main(args []string) error {
},
}

// Called via mount or fstab.
if strings.HasSuffix(args[0], "/mount.juicefs") {
if calledViaMount(args) {
args = handleSysMountArgs(args)
if len(args) < 1 {
args = []string{"--help"}
Expand All @@ -86,6 +85,10 @@ func Main(args []string) error {
return app.Run(reorderOptions(app, args))
}

func calledViaMount(args []string) bool {
return strings.HasSuffix(args[0], "/mount.juicefs")
}

func handleSysMountArgs(args []string) []string {
optionToCmdFlag := map[string]string{
"attrcacheto": "attr-cache",
Expand Down Expand Up @@ -119,7 +122,7 @@ func handleSysMountArgs(args []string) []string {
opts := strings.Split(option, ",")
for _, opt := range opts {
opt = strings.TrimSpace(opt)
if opt == "" || utils.StringContains(sysOptions, opt) {
if opt == "" || opt == "background" || utils.StringContains(sysOptions, opt) {
continue
}
// Lower case option name is preferred, but if it's the same as flag name, we also accept it
Expand Down
144 changes: 135 additions & 9 deletions cmd/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package cmd

import (
"bufio"
"fmt"
"net"
"net/http"
Expand All @@ -25,6 +26,7 @@ import (
"os/signal"
"path/filepath"
"runtime"
"sort"
"strings"
"syscall"
"time"
Expand Down Expand Up @@ -212,26 +214,33 @@ func daemonRun(c *cli.Context, addr string, vfsConf *vfs.Config, m meta.Meta) {
}
}
}
_ = expandPathForEmbedded(addr)
// The default log to syslog is only in daemon mode.
utils.InitLoggers(!c.Bool("no-syslog"))
err := makeDaemon(c, vfsConf.Format.Name, vfsConf.Meta.MountPoint, m)
if err != nil {
logger.Fatalf("Failed to make daemon: %s", err)
}
}

func expandPathForEmbedded(addr string) string {
embeddedSchemes := []string{"sqlite3://", "badger://"}
for _, es := range embeddedSchemes {
if strings.HasPrefix(addr, es) {
path := addr[len(es):]
path2, err := filepath.Abs(path)
if err == nil && path2 != path {
absPath, err := filepath.Abs(path)
if err == nil && absPath != path {
for i, a := range os.Args {
if a == addr {
os.Args[i] = es + path2
expanded := es + absPath
os.Args[i] = expanded
return expanded
}
}
}
}
}
// The default log to syslog is only in daemon mode.
utils.InitLoggers(!c.Bool("no-syslog"))
err := makeDaemon(c, vfsConf.Format.Name, vfsConf.Meta.MountPoint, m)
if err != nil {
logger.Fatalf("Failed to make daemon: %s", err)
}
return addr
}

func getVfsConf(c *cli.Context, metaConf *meta.Config, format *meta.Format, chunkConf *chunk.Config) *vfs.Config {
Expand Down Expand Up @@ -392,6 +401,110 @@ func NewReloadableStorage(format *meta.Format, reload func() (*meta.Format, erro
return holder, nil
}

func tellFstabOptions(c *cli.Context) string {
opts := []string{"_netdev"}
for _, s := range os.Args[2:] {
timfeirg marked this conversation as resolved.
Show resolved Hide resolved
if !strings.HasPrefix(s, "-") {
continue
}
s = strings.TrimLeft(s, "-")
s = strings.Split(s, "=")[0]
if !c.IsSet(s) || s == "update-fstab" || s == "background" || s == "d" {
continue
}
if s == "o" {
opts = append(opts, c.String(s))
} else if v := c.Bool(s); v {
opts = append(opts, s)
} else {
opts = append(opts, fmt.Sprintf("%s=%s", s, c.Generic(s)))
}
}
sort.Strings(opts)
return strings.Join(opts, ",")
}

func tryToInstallMountExec() error {
if _, err := os.Stat("/sbin/mount.juicefs"); err == nil {
return nil
}
src, err := filepath.Abs(os.Args[0])
if err != nil {
return err
}
return os.Symlink(src, "/sbin/mount.juicefs")
}

func insideContainer() bool {
if _, err := os.Stat("/.dockerenv"); err == nil {
timfeirg marked this conversation as resolved.
Show resolved Hide resolved
return true
}
mountinfo, err := os.Open("/proc/1/mountinfo")
if os.IsNotExist(err) {
return false
}
scanner := bufio.NewScanner(mountinfo)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) > 8 && fields[4] == "/" {
fstype := fields[8]
return strings.Contains(fstype, "overlay") || strings.Contains(fstype, "aufs")
}
}
if err = scanner.Err(); err != nil {
logger.Warnf("scan /proc/1/mountinfo: %s", err)
}
return false
}

func updateFstab(c *cli.Context) error {
addr := expandPathForEmbedded(c.Args().Get(0))
mp := c.Args().Get(1)
var fstab = "/etc/fstab"
timfeirg marked this conversation as resolved.
Show resolved Hide resolved

f, err := os.Open(fstab)
if err != nil {
return err
}
defer f.Close()
entryIndex := -1
var lines []string
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) >= 6 && fields[2] == "juicefs" && fields[0] == addr && fields[1] == mp {
entryIndex = len(lines)
}
lines = append(lines, line)
}
if err = scanner.Err(); err != nil {
return err
}
opts := tellFstabOptions(c)
entry := fmt.Sprintf("%s %s juicefs %s 0 0", addr, mp, opts)
if entryIndex >= 0 {
if entry == lines[entryIndex] {
return nil
}
lines[entryIndex] = entry
} else {
lines = append(lines, entry)
}
tempFstab := fstab + ".tmp"
tmpf, err := os.OpenFile(tempFstab, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer tmpf.Close()
if _, err := tmpf.WriteString(strings.Join(lines, "\n") + "\n"); err != nil {
_ = os.Remove(tempFstab)
return err
}
return os.Rename(tempFstab, fstab)
}

func mount(c *cli.Context) error {
setup(c, 2)
addr := c.Args().Get(0)
Expand Down Expand Up @@ -421,6 +534,19 @@ func mount(c *cli.Context) error {
}
logger.Infof("Data use %s", blob)

if c.Bool("update-fstab") && runtime.GOOS == "linux" && !calledViaMount(os.Args) && !insideContainer() {
if os.Getuid() != 0 {
logger.Warnf("--update-fstab should be used with root")
} else {
if err := tryToInstallMountExec(); err != nil {
logger.Warnf("failed to create /sbin/mount.juicefs: %s", err)
}
if err := updateFstab(c); err != nil {
logger.Warnf("failed to update fstab: %s", err)
}
}
}

chunkConf := getChunkConf(c, format)
store := chunk.NewCachedStore(blob, *chunkConf, registerer)
registerMetaMsg(metaCli, store, chunkConf)
Expand Down
53 changes: 45 additions & 8 deletions cmd/mount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"net/url"
"os"
"reflect"
"runtime"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -92,25 +94,29 @@ func resetTestMeta() *redis.Client { // using Redis
return rdb
}

func mountTemp(t *testing.T, bucket *string, trash bool) {
func mountTemp(t *testing.T, bucket *string, extraFormatOpts []string, extraMountOpts []string) {
_ = resetTestMeta()
testDir := t.TempDir()
if bucket != nil {
*bucket = testDir
}
args := []string{"", "format", "--bucket", testDir, testMeta, testVolume}
if !trash {
args = append(args, "--trash-days=0")
formatArgs := []string{"", "format", "--bucket", testDir, testMeta, testVolume}
if extraFormatOpts != nil {
formatArgs = append(formatArgs, extraFormatOpts...)
}
if err := Main(args); err != nil {
if err := Main(formatArgs); err != nil {
t.Fatalf("format failed: %s", err)
}

// must do reset, otherwise will panic
ResetHttp()

mountArgs := []string{"", "mount", "--enable-xattr", testMeta, testMountPoint, "--no-usage-report"}
if extraMountOpts != nil {
mountArgs = append(mountArgs, extraMountOpts...)
}
go func() {
if err := Main([]string{"", "mount", "--enable-xattr", testMeta, testMountPoint, "--no-usage-report"}); err != nil {
if err := Main(mountArgs); err != nil {
t.Errorf("mount failed: %s", err)
}
}()
Expand All @@ -124,16 +130,47 @@ func umountTemp(t *testing.T) {
}

func TestMount(t *testing.T) {
mountTemp(t, nil, true)
mountTemp(t, nil, nil, nil)
defer umountTemp(t)

if err := os.WriteFile(fmt.Sprintf("%s/f1.txt", testMountPoint), []byte("test"), 0644); err != nil {
t.Fatalf("write file failed: %s", err)
}
}

func TestUpdateFstab(t *testing.T) {
if runtime.GOOS != "linux" {
t.SkipNow()
}
mockFstab, err := os.CreateTemp("/tmp", "fstab")
if err != nil {
t.Fatalf("cannot make temp file: %s", err)
}
defer os.Remove(mockFstab.Name())

patches := gomonkey.ApplyFunc(os.Rename, func(src, dest string) error {
content, err := os.ReadFile(mockFstab.Name())
if err != nil {
t.Fatalf("error reading mocked fstab: %s", err)
}
rv := "redis://127.0.0.1:6379/11 /tmp/jfs-unit-test juicefs _netdev,enable-xattr,entry-cache=2,max-uploads=3,max_read=99,no-usage-report,writeback 0 0"
lv := strings.TrimSpace(string(content))
if lv != rv {
t.Fatalf("incorrect fstab entry: %s", content)
}
return os.Rename(src, dest)
})
defer patches.Reset()
mountArgs := []string{"juicefs", "mount", "--enable-xattr", testMeta, testMountPoint, "--no-usage-report"}
mountOpts := []string{"--update-fstab", "--writeback", "--entry-cache=2", "--max-uploads", "3", "-o", "max_read=99"}
patches = gomonkey.ApplyGlobalVar(&os.Args, append(mountArgs, mountOpts...))
defer patches.Reset()
mountTemp(t, nil, nil, mountOpts)
defer umountTemp(t)
}

func TestUmount(t *testing.T) {
mountTemp(t, nil, true)
mountTemp(t, nil, nil, nil)
umountTemp(t)

inode, err := utils.GetFileInode(testMountPoint)
Expand Down
4 changes: 4 additions & 0 deletions cmd/mount_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ func mount_flags() []cli.Flag {
Name: "enable-xattr",
Usage: "enable extended attributes (xattr)",
},
&cli.BoolFlag{
Name: "update-fstab",
Usage: "add / update entry in /etc/fstab, will create a symlink at /sbin/mount.juicefs if not existing",
},
}
return append(selfFlags, cacheFlags(1.0)...)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/rmr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
)

func TestRmr(t *testing.T) {
mountTemp(t, nil, true)
mountTemp(t, nil, nil, nil)
defer umountTemp(t)

paths := []string{"/dir1", "/dir2", "/dir3/dir2"}
Expand Down
2 changes: 1 addition & 1 deletion cmd/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestStatus(t *testing.T) {
patches := gomonkey.ApplyGlobalVar(os.Stdout, *tmpFile)
defer patches.Reset()

mountTemp(t, nil, true)
mountTemp(t, nil, nil, nil)
defer umountTemp(t)

if err = Main([]string{"", "status", testMeta}); err != nil {
Expand Down
Loading