diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index 91907abbefab..f07f8bcd07f1 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -93,6 +93,7 @@ type dbConfigValidation struct { runtimeValue string key []byte defaultValue string + isPath bool } // Check if the configuration of the database is compatible with the @@ -111,42 +112,49 @@ func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error { runtime.GOOS, osKey, runtime.GOOS, + false, }, { "libpod root directory (staticdir)", filepath.Clean(rt.config.Engine.StaticDir), staticDirKey, "", + true, }, { "libpod temporary files directory (tmpdir)", filepath.Clean(rt.config.Engine.TmpDir), tmpDirKey, "", + true, }, { "storage temporary directory (runroot)", filepath.Clean(rt.StorageConfig().RunRoot), runRootKey, storeOpts.RunRoot, + true, }, { "storage graph root directory (graphroot)", filepath.Clean(rt.StorageConfig().GraphRoot), graphRootKey, storeOpts.GraphRoot, + true, }, { "storage graph driver", rt.StorageConfig().GraphDriverName, graphDriverKey, storeOpts.GraphDriverName, + false, }, { "volume path", rt.config.Engine.VolumePath, volPathKey, "", + true, }, } @@ -221,22 +229,45 @@ func readOnlyValidateConfig(bucket *bolt.Bucket, toCheck dbConfigValidation) (bo } dbValue := string(keyBytes) + ourValue := toCheck.runtimeValue + + // Tolerate symlinks when possible - most relevant for OStree systems + // and rootless containers, where we want to put containers in /home, + // which is symlinked to /var/home. + if toCheck.isPath { + if dbValue != "" { + // Ignore ENOENT on both, on a fresh system some paths + // may not exist this early in Libpod init. + dbVal, err := filepath.EvalSymlinks(dbValue) + if err != nil && !os.IsNotExist(err) { + return false, fmt.Errorf("evaluating symlinks on DB %s path %q: %w", toCheck.name, dbValue, err) + } + dbValue = dbVal + } + if ourValue != "" { + ourVal, err := filepath.EvalSymlinks(ourValue) + if err != nil && !os.IsNotExist(err) { + return false, fmt.Errorf("evaluating symlinks on configured %s path %q: %w", toCheck.name, ourValue, err) + } + ourValue = ourVal + } + } - if toCheck.runtimeValue != dbValue { + if ourValue != dbValue { // If the runtime value is the empty string and default is not, // check against default. - if toCheck.runtimeValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue { + if ourValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue { return true, nil } // If the DB value is the empty string, check that the runtime // value is the default. - if dbValue == "" && toCheck.defaultValue != "" && toCheck.runtimeValue == toCheck.defaultValue { + if dbValue == "" && toCheck.defaultValue != "" && ourValue == toCheck.defaultValue { return true, nil } return true, fmt.Errorf("database %s %q does not match our %s %q: %w", - toCheck.name, dbValue, toCheck.name, toCheck.runtimeValue, define.ErrDBBadConfig) + toCheck.name, dbValue, toCheck.name, ourValue, define.ErrDBBadConfig) } return true, nil diff --git a/libpod/sqlite_state.go b/libpod/sqlite_state.go index 2dc1bb384fe3..4979f71ef226 100644 --- a/libpod/sqlite_state.go +++ b/libpod/sqlite_state.go @@ -315,7 +315,7 @@ func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) { );` var ( - os, staticDir, tmpDir, graphRoot, runRoot, graphDriver, volumePath string + dbOS, staticDir, tmpDir, graphRoot, runRoot, graphDriver, volumePath string runtimeOS = goruntime.GOOS runtimeStaticDir = filepath.Clean(s.runtime.config.Engine.StaticDir) runtimeTmpDir = filepath.Clean(s.runtime.config.Engine.TmpDir) @@ -359,7 +359,7 @@ func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) { row := tx.QueryRow("SELECT Os, StaticDir, TmpDir, GraphRoot, RunRoot, GraphDriver, VolumeDir FROM DBConfig;") - if err := row.Scan(&os, &staticDir, &tmpDir, &graphRoot, &runRoot, &graphDriver, &volumePath); err != nil { + if err := row.Scan(&dbOS, &staticDir, &tmpDir, &graphRoot, &runRoot, &graphDriver, &volumePath); err != nil { if errors.Is(err, sql.ErrNoRows) { if _, err := tx.Exec(createRow, 1, schemaVersion, runtimeOS, runtimeStaticDir, runtimeTmpDir, runtimeGraphRoot, @@ -377,7 +377,21 @@ func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) { return fmt.Errorf("retrieving DB config: %w", err) } - checkField := func(fieldName, dbVal, ourVal string) error { + checkField := func(fieldName, dbVal, ourVal string, isPath bool) error { + if isPath { + // Evaluate symlinks. Ignore ENOENT. + dbValClean, err := filepath.EvalSymlinks(dbVal) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("cannot evaluate symlinks on DB %s path %q: %w", fieldName, dbVal, err) + } + dbVal = dbValClean + ourValClean, err := filepath.EvalSymlinks(dbVal) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("cannot evaluate symlinks on our %s path %q: %w", fieldName, ourVal, err) + } + ourVal = ourValClean + } + if dbVal != ourVal { return fmt.Errorf("database %s %q does not match our %s %q: %w", fieldName, dbVal, fieldName, ourVal, define.ErrDBBadConfig) } @@ -385,25 +399,25 @@ func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) { return nil } - if err := checkField("os", os, runtimeOS); err != nil { + if err := checkField("os", dbOS, runtimeOS, false); err != nil { return err } - if err := checkField("static dir", staticDir, runtimeStaticDir); err != nil { + if err := checkField("static dir", staticDir, runtimeStaticDir, true); err != nil { return err } - if err := checkField("tmp dir", tmpDir, runtimeTmpDir); err != nil { + if err := checkField("tmp dir", tmpDir, runtimeTmpDir, true); err != nil { return err } - if err := checkField("graph root", graphRoot, runtimeGraphRoot); err != nil { + if err := checkField("graph root", graphRoot, runtimeGraphRoot, true); err != nil { return err } - if err := checkField("run root", runRoot, runtimeRunRoot); err != nil { + if err := checkField("run root", runRoot, runtimeRunRoot, true); err != nil { return err } - if err := checkField("graph driver", graphDriver, runtimeGraphDriver); err != nil { + if err := checkField("graph driver", graphDriver, runtimeGraphDriver, false); err != nil { return err } - if err := checkField("volume path", volumePath, runtimeVolumePath); err != nil { + if err := checkField("volume path", volumePath, runtimeVolumePath, true); err != nil { return err } diff --git a/test/system/005-info.bats b/test/system/005-info.bats index 1f99c3d55527..b4bbc0547753 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -187,6 +187,25 @@ host.slirp4netns.executable | $expr_path fi } +@test "rootless podman with symlinked $HOME" { + if ! is_remote; then + # This is only needed as rootless, but we don't have a skip_if_root + # And it will not hurt to run as root. + ln -s /home /tmp/home + + old_home=$HOME + + export HOME=/tmp/home + + # Just need the command to run cleanly + run_podman info + + export HOME=$old_home + + rm /tmp/home + fi +} + @test "podman --root PATH --volumepath info - basic output" { volumePath=${PODMAN_TMPDIR}/volumesGoHere if ! is_remote; then