diff --git a/src/control/common/file_utils.go b/src/control/common/file_utils.go index d0c159dc551..be423cf7485 100644 --- a/src/control/common/file_utils.go +++ b/src/control/common/file_utils.go @@ -1,12 +1,18 @@ // // (C) Copyright 2019-2024 Intel Corporation. -// (C) Copyright 2025 Hewlett Packard Enterprise Development LP +// (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP // // SPDX-License-Identifier: BSD-2-Clause-Patent // package common +/* +#cgo CFLAGS: -I${SRCDIR}/../../include/ +#include "daos_srv/control.h" +*/ +import "C" + import ( "fmt" "io" @@ -20,9 +26,13 @@ import ( yaml "gopkg.in/yaml.v2" ) -// UtilLogDepth signifies stack depth, set calldepth on calls to logger so -// log message context refers to caller not callee. -const UtilLogDepth = 4 +const ( + // UtilLogDepth signifies stack depth, set calldepth on calls to logger so + // log message context refers to caller not callee. + UtilLogDepth = 4 + DefaultFilePerm = C.DEFAULT_FILE_PERM + DefaultDirPerm = C.DEFAULT_DIR_PERM +) // GetFilenames returns names of files in a directory. func GetFilenames(dir string) ([]string, error) { @@ -139,6 +149,13 @@ func writeFile(path string, data []byte, perm os.FileMode) (err error) { } }() + // The requested permissions may have been reduced by the umask. + // Using chmod ensures the requested permissions are applied. + err = os.Chmod(path, perm) + if err != nil { + return + } + n, err := f.Write(data) if err != nil { return @@ -370,3 +387,14 @@ func HasPrefixPath(base, sub string) (bool, error) { return true, nil } + +// Mkdir2 creates a new directory with the specified name and permission bits (umask ignored). +func Mkdir2(path string, perm os.FileMode) error { + err := os.Mkdir(path, perm) + if err != nil { + return err + } + // The requested permissions may have been reduced by the umask. + // Using Chmod ensures the requested permissions are applied. + return os.Chmod(path, perm) +} diff --git a/src/control/common/file_utils_test.go b/src/control/common/file_utils_test.go index 7ed90e12717..a3b58968cb7 100644 --- a/src/control/common/file_utils_test.go +++ b/src/control/common/file_utils_test.go @@ -1,5 +1,6 @@ // // (C) Copyright 2019-2022 Intel Corporation. +// (C) Copyright 2026 Hewlett Packard Enterprise Development LP // // SPDX-License-Identifier: BSD-2-Clause-Patent // @@ -176,5 +177,27 @@ func TestUtils_HasPrefixPath(t *testing.T) { if hp { t.Fatalf("%q is not a prefix of %q", testDir, testPath) } +} + +func TestUtils_Mkdir2(t *testing.T) { + exp_perm := 0777 + + testDir, clean := CreateTestDir(t) + defer clean() + testPath := path.Join(testDir, "foo") + err := Mkdir2(testPath, os.FileMode(exp_perm)) + if err != nil { + t.Fatalf("Unexpected error: %q", err) + } + + info, err := os.Stat(testPath) + if err != nil { + t.Fatalf("Unexpected error: %q", err) + } + + perm := int(info.Mode().Perm()) + if perm != exp_perm { + t.Fatalf("Created directory has unexpected permissions: %04o instead of %04o", perm, exp_perm) + } } diff --git a/src/control/provider/system/mocks.go b/src/control/provider/system/mocks.go index 52384054f8a..76523cfbf52 100644 --- a/src/control/provider/system/mocks.go +++ b/src/control/provider/system/mocks.go @@ -1,6 +1,6 @@ // // (C) Copyright 2022-2024 Intel Corporation. -// (C) Copyright 2025 Hewlett Packard Enterprise Development LP +// (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP // // SPDX-License-Identifier: BSD-2-Clause-Patent // @@ -12,6 +12,7 @@ import ( "strings" "sync" + "github.com/daos-stack/daos/src/control/common" "github.com/daos-stack/daos/src/control/logging" ) @@ -247,6 +248,14 @@ func (msp *MockSysProvider) Mkdir(path string, flags os.FileMode) error { return msp.cfg.MkdirErr } +// Mkdir2 creates a new directory with the specified name and permission bits (umask ignored). +func (msp *MockSysProvider) Mkdir2(path string, flags os.FileMode) error { + if msp.cfg.RealMkdir { + return common.Mkdir2(path, flags) + } + return msp.cfg.MkdirErr +} + // RemoveAll removes path and any children it contains. func (msp *MockSysProvider) RemoveAll(path string) error { if msp.cfg.RealRemoveAll { diff --git a/src/control/provider/system/system_linux.go b/src/control/provider/system/system_linux.go index 846e2f4ca50..ecabf86b1e4 100644 --- a/src/control/provider/system/system_linux.go +++ b/src/control/provider/system/system_linux.go @@ -17,6 +17,7 @@ import ( "strings" "syscall" + "github.com/daos-stack/daos/src/control/common" "github.com/pkg/errors" "golang.org/x/sys/unix" ) @@ -397,6 +398,11 @@ func (s LinuxProvider) Mkdir(name string, perm os.FileMode) error { return os.Mkdir(name, perm) } +// Mkdir2 creates a new directory with the specified name and permission bits (umask ignored). +func (s LinuxProvider) Mkdir2(path string, perm os.FileMode) error { + return common.Mkdir2(path, perm) +} + // RemoveAll removes path and any children it contains. func (s LinuxProvider) RemoveAll(path string) error { return os.RemoveAll(path) diff --git a/src/control/server/instance_superblock.go b/src/control/server/instance_superblock.go index 4c62f7ccb5c..8bd249d8387 100644 --- a/src/control/server/instance_superblock.go +++ b/src/control/server/instance_superblock.go @@ -1,6 +1,6 @@ // // (C) Copyright 2019-2024 Intel Corporation. -// (C) Copyright 2025 Hewlett Packard Enterprise Development LP +// (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP // // SPDX-License-Identifier: BSD-2-Clause-Patent // @@ -205,6 +205,6 @@ func WriteSuperblock(sbPath string, sb *Superblock) error { return err } - return errors.Wrapf(common.WriteFileAtomic(sbPath, data, 0600), + return errors.Wrapf(common.WriteFileAtomic(sbPath, data, common.DefaultFilePerm), "Failed to write Superblock to %s", sbPath) } diff --git a/src/control/server/storage/bdev/backend_class.go b/src/control/server/storage/bdev/backend_class.go index f0d101e5d31..5977bec6eac 100644 --- a/src/control/server/storage/bdev/backend_class.go +++ b/src/control/server/storage/bdev/backend_class.go @@ -1,5 +1,6 @@ // // (C) Copyright 2021-2024 Intel Corporation. +// (C) Copyright 2026 Hewlett Packard Enterprise Development LP // (C) Copyright 2025 Google LLC // // SPDX-License-Identifier: BSD-2-Clause-Patent @@ -24,8 +25,8 @@ import ( ) const ( - aioBlockSize = humanize.KiByte * 4 // device block size hardcoded to 4096 bytes - defaultAioFileMode = 0600 // AIO file permissions set to owner +rw + aioBlockSize = humanize.KiByte * 4 // device block size hardcoded to 4096 bytes + defaultAioFileMode = common.DefaultFilePerm // AIO file permissions ) func createEmptyFile(log logging.Logger, path string, size uint64) error { @@ -102,6 +103,14 @@ func writeConfigFile(log logging.Logger, buf *bytes.Buffer, req *storage.BdevWri return errors.Wrap(err, "create") } + // os.Create() above creates a file with 0666 permissions (before umask). + // Typical umask is 022 so the effective permissions of the created file is 0644. + // The os.Chmod ensures the final permissions for both the user and their group are the same. + err = os.Chmod(req.ConfigOutputPath, 0664) + if err != nil { + return errors.Wrap(err, "chmod") + } + defer func() { if err := f.Close(); err != nil { log.Errorf("closing %q: %s", req.ConfigOutputPath, err) diff --git a/src/control/server/storage/metadata/provider.go b/src/control/server/storage/metadata/provider.go index 07a25157817..e7af555093f 100644 --- a/src/control/server/storage/metadata/provider.go +++ b/src/control/server/storage/metadata/provider.go @@ -29,7 +29,7 @@ type ( Getfs(device string) (string, error) GetfsType(path string) (*system.FsType, error) GetDeviceLabel(string) (string, error) - Mkdir(string, os.FileMode) error + Mkdir2(string, os.FileMode) error Mkfs(req system.MkfsReq) error RemoveAll(string) error Stat(string) (os.FileInfo, error) @@ -196,11 +196,13 @@ func (p *Provider) isUsableFS(fs *system.FsType, path string) bool { } func (p *Provider) setupDataDir(req storage.MetadataFormatRequest) error { + perms := os.FileMode(0775) + if err := p.sys.RemoveAll(req.DataPath); err != nil { return errors.Wrap(err, "removing old control metadata subdirectory") } - if err := p.sys.Mkdir(req.DataPath, 0755); err != nil { + if err := p.sys.Mkdir2(req.DataPath, perms); err != nil { return errors.Wrap(err, "creating control metadata subdirectory") } @@ -210,7 +212,7 @@ func (p *Provider) setupDataDir(req storage.MetadataFormatRequest) error { for _, idx := range req.EngineIdxs { engPath := storage.ControlMetadataEngineDir(req.DataPath, idx) - if err := p.sys.Mkdir(engPath, 0755); err != nil { + if err := p.sys.Mkdir2(engPath, perms); err != nil { return errors.Wrapf(err, "creating control metadata engine %d subdirectory", idx) } diff --git a/src/control/server/storage/mount/provider.go b/src/control/server/storage/mount/provider.go index c14c7485ff2..c72eca54068 100644 --- a/src/control/server/storage/mount/provider.go +++ b/src/control/server/storage/mount/provider.go @@ -1,5 +1,6 @@ // // (C) Copyright 2022-2024 Intel Corporation. +// (C) Copyright 2026 Hewlett Packard Enterprise Development LP // // SPDX-License-Identifier: BSD-2-Clause-Patent // @@ -19,7 +20,7 @@ import ( ) const ( - defaultMountPointPerms = 0755 + defaultMountPointPerms = 0775 defaultUnmountFlags = 0 ) @@ -33,7 +34,7 @@ type ( Chown(string, int, int) error Getegid() int Geteuid() int - Mkdir(string, os.FileMode) error + Mkdir2(string, os.FileMode) error RemoveAll(string) error Stat(string) (os.FileInfo, error) } @@ -173,7 +174,7 @@ func (p *Provider) MakeMountPath(path string, tgtUID, tgtGID int) error { switch { case os.IsNotExist(err): // subdir missing, attempt to create and chown - if err := p.sys.Mkdir(ps, defaultMountPointPerms); err != nil { + if err := p.sys.Mkdir2(ps, defaultMountPointPerms); err != nil { return errors.Wrapf(err, "failed to create directory %q", ps) } if err := p.sys.Chown(ps, tgtUID, tgtGID); err != nil { diff --git a/src/control/system/raft/raft.go b/src/control/system/raft/raft.go index 35a6535af6e..17a57322957 100644 --- a/src/control/system/raft/raft.go +++ b/src/control/system/raft/raft.go @@ -1,6 +1,6 @@ // // (C) Copyright 2020-2024 Intel Corporation. -// (C) Copyright 2025 Hewlett Packard Enterprise Development LP +// (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP // // SPDX-License-Identifier: BSD-2-Clause-Patent // @@ -267,6 +267,13 @@ func ConfigureComponents(log logging.Logger, dbCfg *DatabaseConfig) (*RaftCompon return nil, errors.Wrapf(err, "failed to init boltdb at %s", dbCfg.DBFilePath()) } + // Boltdb file permissions on create are set to 0600. + // The os.Chmod ensures the final permissions for both the user and their group are the same. + err = os.Chmod(dbCfg.DBFilePath(), common.DefaultFilePerm) + if err != nil { + return nil, errors.Wrapf(err, "failed to set permissions for boltdb at %s", dbCfg.DBFilePath()) + } + return &RaftComponents{ Logger: log, Config: raftCfg, diff --git a/src/control/system/raft/raft_recovery.go b/src/control/system/raft/raft_recovery.go index 0647f43c077..9741150eff1 100644 --- a/src/control/system/raft/raft_recovery.go +++ b/src/control/system/raft/raft_recovery.go @@ -1,5 +1,6 @@ // // (C) Copyright 2022-2024 Intel Corporation. +// (C) Copyright 2026 Hewlett Packard Enterprise Development LP // // SPDX-License-Identifier: BSD-2-Clause-Patent // @@ -21,6 +22,7 @@ import ( "github.com/pkg/errors" "go.etcd.io/bbolt" + "github.com/daos-stack/daos/src/control/common" "github.com/daos-stack/daos/src/control/lib/ranklist" "github.com/daos-stack/daos/src/control/logging" "github.com/daos-stack/daos/src/control/system" @@ -86,7 +88,7 @@ func RecoverLocalReplica(log logging.Logger, cfg *DatabaseConfig) error { } func createRaftDir(dbPath string) error { - if err := os.Mkdir(dbPath, 0700); err != nil && !os.IsExist(err) { + if err := common.Mkdir2(dbPath, common.DefaultDirPerm); err != nil && !os.IsExist(err) { return errors.Wrap(err, "failed to create raft directory") } return nil diff --git a/src/engine/init.c b/src/engine/init.c index 0072b87be33..b29fac7c55b 100644 --- a/src/engine/init.c +++ b/src/engine/init.c @@ -670,6 +670,13 @@ server_init(int argc, char *argv[]) int rc; struct engine_metrics *metrics; + /** + * The typical umask is 022. The group portion is cleared, which allows the group + * permissions to be set freely. This setting is intended to remain in effect for the entire + * lifetime of the process. + */ + (void)umask(002); + /* * Begin the HLC recovery as early as possible. Do not read the HLC * before the hlc_recovery_end call below. diff --git a/src/include/daos_srv/control.h b/src/include/daos_srv/control.h index b977fb69f7f..8c043052f08 100644 --- a/src/include/daos_srv/control.h +++ b/src/include/daos_srv/control.h @@ -16,8 +16,12 @@ #include #include #include +#include #include +#define DEFAULT_FILE_PERM (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) +#define DEFAULT_DIR_PERM (S_IRWXU | S_IRWXG) + #define NVME_PCI_DEV_TYPE_VMD "vmd" #define NVME_DETAIL_BUFLEN 1024 /** diff --git a/src/mgmt/mgmt_common.c b/src/mgmt/mgmt_common.c index 70ffcc46776..df7764f1b4f 100644 --- a/src/mgmt/mgmt_common.c +++ b/src/mgmt/mgmt_common.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #include @@ -124,7 +123,7 @@ ds_mgmt_tgt_recreate(uuid_t pool_uuid, daos_size_t scm_size, int tgt_nr, int *tg DP_RC(rc)); goto out; } - rc = mkdir(pool_newborns_path, 0700); + rc = mkdir(pool_newborns_path, DEFAULT_DIR_PERM); if (rc < 0 && errno != EEXIST) { rc = daos_errno2der(errno); D_ERROR("failed to created pool directory: " DF_RC "\n", DP_RC(rc)); diff --git a/src/mgmt/srv_target.c b/src/mgmt/srv_target.c index 08db055e5ac..e82d32d5713 100644 --- a/src/mgmt/srv_target.c +++ b/src/mgmt/srv_target.c @@ -449,7 +449,7 @@ ds_mgmt_tgt_setup(void) stored_mode = umask(0); /** create NEWBORNS directory if it does not exist already */ - rc = mkdir(newborns_path, S_IRWXU); + rc = mkdir(newborns_path, DEFAULT_DIR_PERM); if (rc < 0 && errno != EEXIST) { D_ERROR("failed to create NEWBORNS dir: %d\n", errno); umask(stored_mode); @@ -457,7 +457,7 @@ ds_mgmt_tgt_setup(void) } /** create ZOMBIES directory if it does not exist already */ - rc = mkdir(zombies_path, S_IRWXU); + rc = mkdir(zombies_path, DEFAULT_DIR_PERM); if (rc < 0 && errno != EEXIST) { D_ERROR("failed to create ZOMBIES dir: %d\n", errno); umask(stored_mode); @@ -648,7 +648,7 @@ struct tgt_create_args { static void * tgt_create_preallocate(void *arg) { - struct tgt_create_args *tca = arg; + struct tgt_create_args *tca = arg; int rc; (void)dss_xstream_set_affinity(tca->tca_dx); @@ -677,7 +677,7 @@ tgt_create_preallocate(void *arg) if (rc) goto out; - rc = mkdir(tca->tca_newborn, 0700); + rc = mkdir(tca->tca_newborn, DEFAULT_DIR_PERM); if (rc < 0 && errno != EEXIST) { rc = daos_errno2der(errno); D_ERROR("failed to created pool directory: "DF_RC"\n", @@ -712,6 +712,7 @@ tgt_create_preallocate(void *arg) } out: tca->tca_rc = rc; + return NULL; } diff --git a/src/utils/ddb/ddb_mgmt.c b/src/utils/ddb/ddb_mgmt.c index 3941168eb48..668fa2d30e4 100644 --- a/src/utils/ddb/ddb_mgmt.c +++ b/src/utils/ddb/ddb_mgmt.c @@ -229,11 +229,11 @@ ddb_dirs_prepare(const char *path) if (unlikely(rc >= sizeof(zombies_path))) return -DER_EXCEEDS_PATH_LEN; - rc = ddb_mkdir(newborns_path, S_IRWXU); + rc = ddb_mkdir(newborns_path, DEFAULT_DIR_PERM); if (rc) return rc; - rc = ddb_mkdir(zombies_path, S_IRWXU); + rc = ddb_mkdir(zombies_path, DEFAULT_DIR_PERM); if (rc) return rc;