From 73576ca07ac9e9c76c1ccf8fadc2394587841435 Mon Sep 17 00:00:00 2001 From: Qingyuan Wang <162230229@nuaa.edu.cn> Date: Wed, 28 Oct 2026 02:23:59 +0000 Subject: [PATCH] fix(dir): report an error instead of panicking when the path is missing DirSize ignores the error its filepath.Walk callback is handed. When the root cannot be stat-ed, filepath.Walk calls the callback with a nil FileInfo, so info.IsDir() dereferences nil and the process panics. This is reachable from the admin dashboard: calculateStorage walks serviceConfig.UploadPath and explicitly logs and swallows the error it expects back, so a missing or unmounted upload directory turns the request into a panic instead of the intended "size unknown" path. Handle the callback error before touching info. --- pkg/dir/dir.go | 7 ++++- pkg/dir/dir_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 pkg/dir/dir_test.go diff --git a/pkg/dir/dir.go b/pkg/dir/dir.go index 09591f6ca..bfd1b1101 100644 --- a/pkg/dir/dir.go +++ b/pkg/dir/dir.go @@ -42,10 +42,15 @@ func CheckFileExist(path string) bool { func DirSize(path string) (int64, error) { var size int64 err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error { + // filepath.Walk passes a nil info when it cannot stat the root, so the + // error has to be handled before info is dereferenced. + if err != nil { + return err + } if !info.IsDir() { size += info.Size() } - return err + return nil }) return size, err } diff --git a/pkg/dir/dir_test.go b/pkg/dir/dir_test.go new file mode 100644 index 000000000..80241427e --- /dev/null +++ b/pkg/dir/dir_test.go @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package dir + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDirSize(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "a.txt"), []byte("12345"), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "b.txt"), []byte("1234567890"), 0o600)) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "sub"), 0o700)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "sub", "c.txt"), []byte("1234567"), 0o600)) + + size, err := DirSize(dir) + require.NoError(t, err) + assert.Equal(t, int64(22), size) +} + +// A path that does not exist must be reported as an error. Before the fix +// filepath.Walk hands the callback a nil FileInfo for the root, and +// `info.IsDir()` panicked with a nil pointer dereference instead. +func TestDirSizeMissingPath(t *testing.T) { + missing := filepath.Join(t.TempDir(), "no-such-dir") + + size, err := DirSize(missing) + require.Error(t, err) + assert.Equal(t, int64(0), size) +} + +// Same for a path whose parent is missing, and for a relative path that +// cannot be resolved from the working directory. +func TestDirSizeMissingPathsDoNotPanic(t *testing.T) { + for _, missing := range []string{"no/such/relative/dir", "/no/such/absolute/dir"} { + t.Run(missing, func(t *testing.T) { + size, err := DirSize(missing) + require.Error(t, err) + assert.Equal(t, int64(0), size) + }) + } +}