fix(store): 修正邻接查询、GC 竞态、备份权限与设置解析,新增 OpenData 纯数据打开
邻接查询 tie-break 与 pinned 排序键与列表序对齐,补表驱动测试(round2 P1-1); GC DELETE 带条件复查,防 SELECT 与 DELETE 间恢复/引用竞态(P2-6); 备份经 umask 收紧创建即 0600,消除 chmod 前暴露窗口(P2-7); page_size 改 strconv.Atoi 全文解析,拒部分解析脏值(P2-13); OpenData 不迁移、不做版本守卫(D6/P1-5)。
This commit is contained in:
+25
-4
@@ -3,6 +3,7 @@ package store
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -86,15 +87,29 @@ func (s *Store) GC(now time.Time, dryRun bool) (*GCReport, error) {
|
||||
return rep, nil
|
||||
}
|
||||
|
||||
// 条件复查删除(防 TOCTOU:SELECT 与 DELETE 之间管理员恢复笔记/图片被
|
||||
// 重新引用时,不删活数据;评审 round2 P2-6)
|
||||
for _, en := range rep.ExpiredNotes {
|
||||
if err := s.DeleteNoteForever(en.ID); err != nil {
|
||||
res, err := s.db.Exec(
|
||||
`DELETE FROM notes WHERE id=? AND deleted_at IS NOT NULL AND deleted_at < ?`,
|
||||
en.ID, noteCutoff)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("物理删除笔记 %d: %w", en.ID, err)
|
||||
}
|
||||
if rows, _ := res.RowsAffected(); rows == 0 {
|
||||
continue // 期间已被恢复,跳过
|
||||
}
|
||||
}
|
||||
for _, img := range rep.OrphanImages {
|
||||
if err := s.DeleteImage(img.ID); err != nil {
|
||||
res, err := s.db.Exec(
|
||||
`DELETE FROM images WHERE id=? AND NOT EXISTS (SELECT 1 FROM image_refs WHERE image_id=?)`,
|
||||
img.ID, img.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("删除孤儿图片 %d: %w", img.ID, err)
|
||||
}
|
||||
if rows, _ := res.RowsAffected(); rows == 0 {
|
||||
continue // 期间已被引用,跳过
|
||||
}
|
||||
}
|
||||
n, err := s.DeleteExpiredSessions(now.Unix())
|
||||
if err != nil {
|
||||
@@ -109,11 +124,17 @@ func (s *Store) Backup(destPath string) error {
|
||||
if _, err := os.Stat(destPath); err == nil {
|
||||
return fmt.Errorf("目标文件已存在: %s", destPath)
|
||||
}
|
||||
// 备份含全部私密内容:临时收紧 umask,确保 VACUUM INTO 创建即 0600
|
||||
// (否则以默认 umask(常 0644)创建、事后 chmod 存在暴露窗口,评审 round2 P2-7)。
|
||||
// backup 为 CLI 单线程进程,进程级 umask 无并发副作用。
|
||||
old := syscall.Umask(0o077)
|
||||
// VACUUM INTO 不接受参数绑定,路径经单引号转义(无参数化通道时的最小注入面)。
|
||||
if _, err := s.db.Exec("VACUUM INTO " + escapeSQLString(destPath)); err != nil {
|
||||
_, err := s.db.Exec("VACUUM INTO " + escapeSQLString(destPath))
|
||||
syscall.Umask(old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 备份含全部私钥内容:强制 0600(§10.3)
|
||||
// 双保险:显式收紧(幂等)
|
||||
if err := os.Chmod(destPath, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user