fix: unix_ls parse_ls_la_line 兼容 long-iso 8字段格式
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# 2026-06-02 文件管理器目录列表全空 Bug 修复(unix_ls long-iso 解析)
|
||||
|
||||
## 日期
|
||||
2026-06-02
|
||||
|
||||
## 变更摘要
|
||||
修复 `server/utils/unix_ls.py` 中 `parse_ls_la_line` 函数对 GNU `ls -la --time-style=long-iso`
|
||||
输出格式的解析错误,导致所有普通文件和目录(非软链接)被静默丢弃。
|
||||
|
||||
## 根本原因
|
||||
|
||||
`ls -la --time-style=long-iso` 输出每行 **8 个字段**(日期和时间各占 1 个字段):
|
||||
```
|
||||
drwxr-xr-x 2 root root 4096 2024-05-22 10:30 dirname
|
||||
```
|
||||
而原始代码要求 `len(parts) >= 9`,导致所有普通文件/目录行被 `return None` 丢弃。
|
||||
|
||||
软链接能幸存,是因为它们的文件名包含 ` -> target`,在 **标准格式**(3 个日期字段)下
|
||||
使字段数达到 9,但在 long-iso 格式下仍只有 8 个字段,实际上也应该被修复。
|
||||
|
||||
**表现**:
|
||||
- root 用户(最高权限)浏览任意目录,返回条目数为 0,且 `error` 为空
|
||||
- 只有软链接偶尔出现(取决于目标系统的 ls 实现)
|
||||
- 浏览 `/` 只显示 `bin, lib, lib64, sbin` 这 4 个软链接,而非全部目录
|
||||
|
||||
## 修复方案
|
||||
|
||||
在 `parse_ls_la_line` 中检测字段 `parts[5]` 是否匹配 `YYYY-MM-DD`:
|
||||
- **long-iso** → 文件名在 `parts[7]`,修改时间为 `parts[5] + parts[6]`
|
||||
- **standard** → 文件名在 `parts[8]`(同原逻辑)
|
||||
|
||||
两种格式均正确提取文件名、软链接目标、修改时间。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `server/utils/unix_ls.py` | 修复 `parse_ls_la_line`,兼容 long-iso 格式(8 字段) |
|
||||
|
||||
## 重要说明
|
||||
|
||||
- 此 Bug 与 SSH 用户权限(root / 非 root)**无关**
|
||||
- 使用 GNU `ls`(支持 `--time-style`)的服务器均受影响
|
||||
- 使用 BusyBox `ls`(不支持 `--time-style`,会 fallback 到标准格式)的服务器不受影响
|
||||
- **修复后无需重新配置 sudoers**,纯后端解析逻辑修复
|
||||
|
||||
## 需要迁移/重启
|
||||
- 后端重启生效(`supervisorctl restart nexus`)
|
||||
|
||||
## 验证方式
|
||||
1. 运行 `python scripts/_test_unix_ls.py` — 全部 OK
|
||||
2. 部署后访问 `/files?server_id=8`,应能看到 `/` 下的完整目录列表
|
||||
3. 对比修复前后:`/etc`, `/usr`, `/var`, `/tmp` 等目录应正常显示
|
||||
+32
-4
@@ -20,18 +20,46 @@ def modified_from_ls_parts(parts: list[str]) -> str:
|
||||
|
||||
|
||||
def parse_ls_la_line(line: str) -> dict | None:
|
||||
"""Parse one ``ls -la`` line; returns None for total/skip lines."""
|
||||
"""Parse one ``ls -la`` line; returns None for total/skip lines.
|
||||
|
||||
Handles two timestamp formats produced by different ``ls`` implementations:
|
||||
- Standard (3-token date): ``perms nlinks owner group size Mon DD HH:MM name`` → 9+ fields
|
||||
- GNU long-iso (2-token): ``perms nlinks owner group size YYYY-MM-DD HH:MM name`` → 8+ fields
|
||||
|
||||
With ``--time-style=long-iso`` the date collapses from 3 tokens to 2, so
|
||||
the total token count drops from 9 to 8. Directories and regular files were
|
||||
silently dropped (only symlinks survived because their names contain
|
||||
``" -> target"`` which pushed the count back to 9).
|
||||
"""
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith("total "):
|
||||
return None
|
||||
|
||||
# Split into at most 9 pieces so filenames with spaces are kept intact.
|
||||
parts = stripped.split(None, 8)
|
||||
if len(parts) < 9:
|
||||
if len(parts) < 8:
|
||||
return None
|
||||
|
||||
perms = parts[0]
|
||||
is_dir = perms.startswith("d")
|
||||
is_link = perms.startswith("l")
|
||||
name = parts[8]
|
||||
|
||||
# Detect format by checking whether field[5] is an ISO date (YYYY-MM-DD).
|
||||
# long-iso: [perms nlinks owner group size DATE TIME name] → name at index 7
|
||||
# standard: [perms nlinks owner group size MON DAY TIME name] → name at index 8
|
||||
if _ISO_DATE.match(parts[5]):
|
||||
# long-iso format — name is at index 7 (or rest of string)
|
||||
if len(parts) < 8:
|
||||
return None
|
||||
name = parts[7]
|
||||
modified = f"{parts[5]} {parts[6].split('.', 1)[0]}"
|
||||
else:
|
||||
# standard format — need at least 9 tokens for the name at index 8
|
||||
if len(parts) < 9:
|
||||
return None
|
||||
name = parts[8]
|
||||
modified = modified_from_ls_parts(parts)
|
||||
|
||||
link_target = None
|
||||
if is_link and " -> " in name:
|
||||
name, link_target = name.split(" -> ", 1)
|
||||
@@ -47,7 +75,7 @@ def parse_ls_la_line(line: str) -> dict | None:
|
||||
"perms": perms,
|
||||
"owner": parts[2],
|
||||
"group": parts[3] if len(parts) > 3 else "",
|
||||
"modified": modified_from_ls_parts(parts),
|
||||
"modified": modified,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user