Skip to content

Commit 58651ce

Browse files
author
r.inyakin
committed
start: insufficient permissions to directories
Fixed a bug where an error did not appear when access rights to the var directive were insufficient. Closes #1238
1 parent b7accee commit 58651ce

4 files changed

Lines changed: 135 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1313

1414
### Fixed
1515

16+
- `tt start`: fixed a bug where an error did not appear when access rights
17+
to the var directive were insufficient.
18+
1619
## [2.12.0] - 2026-03-30
1720

1821
This maintenance release marks the end of active development on the v2

cli/cmd/start.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"sync"
1111
"syscall"
1212

13+
"github.com/apex/log"
1314
"github.com/spf13/cobra"
1415
"github.com/tarantool/tt/cli/cmd/internal"
1516
"github.com/tarantool/tt/cli/cmdcontext"
@@ -92,9 +93,12 @@ func startInstancesInteractive(cmdCtx *cmdcontext.CmdCtx, instances []running.In
9293
prefix := running.GetAppInstanceName(instCtx) + " "
9394
wg.Add(1)
9495
go func(inst running.InstanceCtx) {
95-
running.RunInstance(ctx, cmdCtx, inst,
96+
err := running.RunInstance(ctx, cmdCtx, inst,
9697
running.NewColorizedPrefixWriter(os.Stdout, clr, prefix),
9798
running.NewColorizedPrefixWriter(os.Stderr, clr, prefix))
99+
if err != nil {
100+
log.Error(err.Error())
101+
}
98102
wg.Done()
99103
}(instCtx)
100104
}

cli/running/running.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/tarantool/tt/cli/util/regexputil"
2727
libcluster "github.com/tarantool/tt/lib/cluster"
2828
"github.com/tarantool/tt/lib/integrity"
29+
"golang.org/x/sys/unix"
2930
)
3031

3132
const defaultDirPerms = 0o770
@@ -751,10 +752,15 @@ func FillCtx(cliOpts *config.CliOpts, cmdCtx *cmdcontext.CmdCtx,
751752
func RunInstance(ctx context.Context, cmdCtx *cmdcontext.CmdCtx, inst InstanceCtx,
752753
stdOut, stdErr io.Writer,
753754
) error {
754-
for _, dataDir := range [...]string{inst.WalDir, inst.VinylDir, inst.MemtxDir, inst.RunDir} {
755-
if err := util.CreateDirectory(dataDir, defaultDirPerms); err != nil {
756-
return err
757-
}
755+
err := util.CreateDirectory(inst.LogDir, defaultDirPerms)
756+
if err != nil {
757+
return fmt.Errorf("failed to create log directory for instance %q: %s",
758+
GetAppInstanceName(inst), err)
759+
}
760+
761+
if err := syscall.Access(inst.LogDir, unix.W_OK); err != nil {
762+
return fmt.Errorf("instance %q has non-writable log directory %q: %s",
763+
inst.InstName, inst.RunDir, err)
758764
}
759765

760766
logger := ttlog.NewCustomLogger(stdOut, "", 0)
@@ -789,15 +795,19 @@ func RunInstance(ctx context.Context, cmdCtx *cmdcontext.CmdCtx, inst InstanceCt
789795

790796
// Start an Instance.
791797
func Start(cmdCtx *cmdcontext.CmdCtx, inst *InstanceCtx) error {
792-
if err := createInstanceDataDirectories(*inst); err != nil {
793-
return fmt.Errorf("failed to create a directory: %s", err)
794-
}
795798
logger, err := createLogger(inst)
796799
if err != nil {
797800
return fmt.Errorf("cannot create a logger: %s", err)
798801
}
799802
logger.Println("[INFO] Start") // Create a log file before any other actions.
800803

804+
if err := createInstanceDataDirectories(*inst); err != nil {
805+
logger.Fatalf("[ERROR] Failed to create data directories for instance %q: %s",
806+
inst.InstName,
807+
err)
808+
return fmt.Errorf("failed to create a directory: %s", err)
809+
}
810+
801811
provider := providerImpl{cmdCtx: cmdCtx, instanceCtx: inst}
802812
preStartAction := func() error {
803813
if err := process_utils.CreatePIDFile(inst.PIDFile, os.Getpid()); err != nil {
@@ -966,6 +976,17 @@ Cluster config path: %q`, tntVersion.Str, inst.ClusterConfigPath)
966976
func StartWatchdog(cmdCtx *cmdcontext.CmdCtx, ttExecutable string, instance InstanceCtx,
967977
args []string,
968978
) error {
979+
err := util.CreateDirectory(instance.LogDir, defaultDirPerms)
980+
if err != nil {
981+
return fmt.Errorf("failed to create log directory for instance %q: %s",
982+
GetAppInstanceName(instance), err)
983+
}
984+
985+
if err := syscall.Access(instance.LogDir, unix.W_OK); err != nil {
986+
return fmt.Errorf("instance %q has non-writable log directory %q: %s",
987+
instance.InstName, instance.RunDir, err)
988+
}
989+
969990
appName := GetAppInstanceName(instance)
970991
// If an instance is already running don't try to start it again.
971992
// To restart an instance use tt restart command.

test/integration/start/test_start.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
13
import pytest
24
import tt_helper
35

@@ -109,3 +111,100 @@ def test_start_multi_inst(tt, tt_app, target):
109111
)
110112
def test_start_cluster(tt, tt_app, target):
111113
check_start(tt, tt_app, target)
114+
115+
116+
################################################################
117+
# Permission denied
118+
119+
120+
@pytest.mark.skipif(skip_cluster_cond, reason=skip_cluster_reason)
121+
@pytest.mark.skipif(
122+
os.getuid() == 0,
123+
reason="Skipping the test, it shouldn't run as root",
124+
)
125+
@pytest.mark.tt_app(**tt_cluster_app)
126+
@pytest.mark.parametrize(
127+
"tt_running_targets",
128+
[
129+
pytest.param([], id="running:none"),
130+
],
131+
)
132+
@pytest.mark.parametrize(
133+
"denied_dir, expect_console_error",
134+
[
135+
pytest.param(["var"], True, id="var_denied"),
136+
pytest.param(["var", "log"], True, id="var_log_denied"),
137+
pytest.param(["var", "lib"], False, id="var_lib_denied"),
138+
],
139+
)
140+
def test_start_permission_denied(tt, tt_app, denied_dir, expect_console_error):
141+
target_dir = tt_app.path(*denied_dir)
142+
os.makedirs(target_dir, exist_ok=True)
143+
os.chmod(target_dir, 0o444)
144+
145+
try:
146+
rc, out = tt.exec("start")
147+
148+
if expect_console_error:
149+
assert rc != 0
150+
assert "permission denied" in out.lower()
151+
else:
152+
assert rc == 0
153+
154+
logs = list(tt_helper.log_files(tt_app, tt_app.instances))
155+
assert utils.wait_files(5, logs)
156+
157+
error_found = False
158+
for log_file in logs:
159+
with open(log_file, "r") as f:
160+
if "permission denied" in f.read().lower():
161+
error_found = True
162+
break
163+
164+
assert error_found, "Permission denied error was not found in logs."
165+
finally:
166+
os.chmod(target_dir, 0o755)
167+
168+
169+
@pytest.mark.skipif(skip_cluster_cond, reason=skip_cluster_reason)
170+
@pytest.mark.skipif(
171+
os.getuid() == 0,
172+
reason="Skipping the test, it shouldn't run as root",
173+
)
174+
@pytest.mark.tt_app(**tt_cluster_app)
175+
@pytest.mark.parametrize(
176+
"tt_running_targets",
177+
[
178+
pytest.param([], id="running:none"),
179+
],
180+
)
181+
def test_start_permission_denied_permissions_denied_files(tt, tt_app):
182+
rc, _ = tt.exec("start")
183+
assert rc == 0
184+
assert utils.wait_files(5, tt_helper.pid_files(tt_app, tt_app.instances))
185+
186+
rc, _ = tt.exec("stop", "-y")
187+
assert rc == 0
188+
189+
log_dir = tt_app.path("var", "log")
190+
lib_dir = tt_app.path("var", "lib")
191+
run_dir = tt_app.path("var", "run")
192+
193+
dirs_to_lock = []
194+
for data_dir in [log_dir, lib_dir, run_dir]:
195+
for root, dirs, files in os.walk(data_dir, topdown=False):
196+
dirs_to_lock.extend(os.path.join(root, f) for f in files)
197+
dirs_to_lock.extend(os.path.join(root, d) for d in dirs)
198+
dirs_to_lock.append(data_dir)
199+
200+
for d in dirs_to_lock:
201+
os.chmod(d, 0o444)
202+
203+
try:
204+
rc, out = tt.exec("start")
205+
assert rc != 0
206+
assert "permission denied" in out.lower()
207+
finally:
208+
for d in dirs_to_lock:
209+
if os.path.exists(d):
210+
os.chmod(d, 0o755)

0 commit comments

Comments
 (0)