diff --git a/config.go b/config.go index 12cbb81..8d36e35 100644 --- a/config.go +++ b/config.go @@ -404,6 +404,20 @@ func currentAuthToken() string { return config.AuthToken } +// 检查配置里的文件夹字段,返回错误信息,空字符串表示通过。 +// 启动读配置、web UI 改设置、转码排队都用这一份 —— 以前各自实现, +// 启动那份直接 os.Stat + checkErr,目录不存在时会 panic 崩掉整个进程。 +func checkDirField(path, name string) string { + if path == "" { + return "" + } + info, err := os.Stat(path) + if err != nil || !info.IsDir() { + return name + " 必须是存在的文件夹:" + path + } + return "" +} + // 检查 config.json 里的配置 func checkConfig() { for _, u := range config.UIDs { @@ -416,21 +430,15 @@ func checkConfig() { lPrintErr(configFile + "里的 webPort 必须为 0(不启动 web UI)或者大于 1023 且少于 65536") os.Exit(1) } - if config.Directory != "" { - info, err := os.Stat(config.Directory) - checkErr(err) - if !info.IsDir() { - lPrintErrf("%s里的directory必须是存在的文件夹:%s", configFile, config.Directory) - os.Exit(1) - } + // 目录不在(盘没插、路径拼错)要给人话提示然后退出,不能直接 panic: + // 崩溃时文件系统风险未知,而且服务被托管时会被反复拉起来 + if msg := checkDirField(config.Directory, configFile+"里的directory"); msg != "" { + lPrintErr(msg) + os.Exit(1) } - if config.MergeOutputDir != "" { - info, err := os.Stat(config.MergeOutputDir) - checkErr(err) - if !info.IsDir() { - lPrintErrf("%s里的mergeOutputDir必须是存在的文件夹:%s", configFile, config.MergeOutputDir) - os.Exit(1) - } + if msg := checkDirField(config.MergeOutputDir, configFile+"里的mergeOutputDir"); msg != "" { + lPrintErr(msg) + os.Exit(1) } if config.MergeBitrate < 0 { lPrintErrf("%s里的mergeBitrate不能小于 0", configFile) diff --git a/config_test.go b/config_test.go index b125adc..6d824a3 100644 --- a/config_test.go +++ b/config_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "os" "path/filepath" + "strings" "testing" ) @@ -145,3 +146,48 @@ func TestReloadConfigKeepsMergeTempFiles(t *testing.T) { } } } + +// 保存目录填错(盘没插、路径拼错)时启动必须给一句人话再退出, +// 不能 panic 崩掉整个进程 —— 服务被托管时崩了还会被反复拉起来 +func TestCheckDirFieldReportsMissingDir(t *testing.T) { + root := t.TempDir() + if msg := checkDirField("", "directory"); msg != "" { + t.Errorf("留空表示不指定,不该报错:%s", msg) + } + if msg := checkDirField(root, "directory"); msg != "" { + t.Errorf("存在的文件夹不该报错:%s", msg) + } + if msg := checkDirField(filepath.Join(root, "没有这个目录"), "directory"); !strings.Contains(msg, "必须是存在的文件夹") { + t.Errorf("目录不存在时要给出提示,实际返回 %q", msg) + } + file := filepath.Join(root, "afile") + if err := os.WriteFile(file, []byte("x"), 0644); err != nil { + t.Fatal(err) + } + if msg := checkDirField(file, "directory"); msg == "" { + t.Error("指向文件而不是文件夹时应当报错") + } +} + +// checkConfig 里曾经直接 os.Stat + checkErr:目录不在时会 panic 崩掉进程。 +// 这条路径没法在测试里真跑一遍(失败时是 os.Exit(1),会把整个测试进程带走), +// 所以退一步把「只能给提示再退出」钉在源码上。 +func TestCheckConfigNeverPanicsOnMissingDir(t *testing.T) { + src, err := os.ReadFile("config.go") + if err != nil { + t.Skip("读不到 config.go:" + err.Error()) + } + text := string(src) + start := strings.Index(text, "func checkConfig()") + if start < 0 { + t.Fatal("config.go 里找不到 checkConfig") + } + rest := text[start:] + end := strings.Index(rest, "\n}\n") + if end < 0 { + t.Fatal("checkConfig 的函数体没有正常收尾") + } + if strings.Contains(rest[:end], "checkErr(") { + t.Error("checkConfig 里不能用 checkErr:出错会变成 panic 崩进程,改成 lPrintErr + os.Exit(1)") + } +} diff --git a/transcode.go b/transcode.go index 677e852..2e90635 100644 --- a/transcode.go +++ b/transcode.go @@ -956,7 +956,7 @@ func transcodeHandler(w http.ResponseWriter, r *http.Request) { if outDir == "" { outDir = mergeSaveDir() } else { - if msg := checkWebDir(outDir, "输出目录"); msg != "" { + if msg := checkDirField(outDir, "输出目录"); msg != "" { http.Error(w, msg, http.StatusBadRequest) return } diff --git a/webui.go b/webui.go index 2caa097..79de53e 100644 --- a/webui.go +++ b/webui.go @@ -2431,18 +2431,6 @@ func statusHandler(w http.ResponseWriter, r *http.Request) { checkErr(err) } -// 检查配置里的文件夹字段,返回错误信息,空字符串表示通过 -func checkWebDir(path, name string) string { - if path == "" { - return "" - } - info, err := os.Stat(path) - if err != nil || !info.IsDir() { - return name + " 必须是存在的文件夹:" + path - } - return "" -} - // 修改设置(保存目录和自动合并相关) func webConfigHandler(w http.ResponseWriter, r *http.Request) { var req struct { @@ -2481,11 +2469,11 @@ func webConfigHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "请求不是合法的 json:"+err.Error(), http.StatusBadRequest) return } - if msg := checkWebDir(req.Directory, "directory"); msg != "" { + if msg := checkDirField(req.Directory, "directory"); msg != "" { http.Error(w, msg, http.StatusBadRequest) return } - if msg := checkWebDir(req.MergeOutputDir, "mergeOutputDir"); msg != "" { + if msg := checkDirField(req.MergeOutputDir, "mergeOutputDir"); msg != "" { http.Error(w, msg, http.StatusBadRequest) return }