fix(weixin): probe sync dir writability before choosing home
This commit is contained in:
parent
12d0bc2514
commit
e0265088b7
2 changed files with 69 additions and 7 deletions
|
|
@ -40,6 +40,31 @@ type contextTokensFile struct {
|
||||||
Tokens map[string]string `json:"tokens"`
|
Tokens map[string]string `json:"tokens"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func syncDirForHome(home string) string {
|
||||||
|
return filepath.Join(home, "channels", "weixin", "sync")
|
||||||
|
}
|
||||||
|
|
||||||
|
func probeSyncDirWritable(home string) error {
|
||||||
|
syncDir := syncDirForHome(home)
|
||||||
|
if err := os.MkdirAll(syncDir, 0o700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
probeFile, err := os.CreateTemp(syncDir, ".write-probe-*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
probePath := probeFile.Name()
|
||||||
|
if err := probeFile.Close(); err != nil {
|
||||||
|
_ = os.Remove(probePath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Remove(probePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func picoclawHomeDir() string {
|
func picoclawHomeDir() string {
|
||||||
if home := os.Getenv(config.EnvHome); home != "" {
|
if home := os.Getenv(config.EnvHome); home != "" {
|
||||||
return home
|
return home
|
||||||
|
|
@ -47,15 +72,14 @@ func picoclawHomeDir() string {
|
||||||
|
|
||||||
if userHome, err := os.UserHomeDir(); err == nil && userHome != "" {
|
if userHome, err := os.UserHomeDir(); err == nil && userHome != "" {
|
||||||
home := filepath.Join(userHome, ".picoclaw")
|
home := filepath.Join(userHome, ".picoclaw")
|
||||||
syncDir := filepath.Join(home, "channels", "weixin", "sync")
|
if probeErr := probeSyncDirWritable(home); probeErr == nil {
|
||||||
mkErr := os.MkdirAll(syncDir, 0o700)
|
|
||||||
if mkErr == nil {
|
|
||||||
return home
|
return home
|
||||||
|
} else {
|
||||||
|
logger.WarnCF("weixin", "Default picoclaw home is not writable; using temp directory for sync cursor", map[string]any{
|
||||||
|
"path": home,
|
||||||
|
"error": probeErr.Error(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
logger.WarnCF("weixin", "Default picoclaw home is not writable; using temp directory for sync cursor", map[string]any{
|
|
||||||
"path": home,
|
|
||||||
"error": mkErr.Error(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Join(os.TempDir(), "picoclaw")
|
return filepath.Join(os.TempDir(), "picoclaw")
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,44 @@ func TestBuildWeixinSyncBufPathFallsBackWhenHomeIsUnusable(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildWeixinSyncBufPathFallsBackWhenSyncDirUnwritable(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("permission mode behavior differs on windows")
|
||||||
|
}
|
||||||
|
if os.Geteuid() == 0 {
|
||||||
|
t.Skip("root can usually bypass directory write permissions")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Setenv(config.EnvHome, "")
|
||||||
|
home := t.TempDir()
|
||||||
|
t.Setenv("HOME", home)
|
||||||
|
|
||||||
|
syncDir := filepath.Join(home, ".picoclaw", "channels", "weixin", "sync")
|
||||||
|
if err := os.MkdirAll(syncDir, 0o700); err != nil {
|
||||||
|
t.Fatalf("MkdirAll(syncDir) error = %v", err)
|
||||||
|
}
|
||||||
|
if err := os.Chmod(syncDir, 0o500); err != nil {
|
||||||
|
t.Fatalf("Chmod(syncDir, 0500) error = %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { _ = os.Chmod(syncDir, 0o700) })
|
||||||
|
|
||||||
|
if probeFile, err := os.CreateTemp(syncDir, ".probe-*"); err == nil {
|
||||||
|
_ = probeFile.Close()
|
||||||
|
_ = os.Remove(probeFile.Name())
|
||||||
|
t.Skip("environment does not enforce non-writable directory permissions")
|
||||||
|
}
|
||||||
|
|
||||||
|
wxCfg := config.WeixinConfig{
|
||||||
|
BaseURL: "https://ilinkai.weixin.qq.com/",
|
||||||
|
}
|
||||||
|
wxCfg.SetToken("token-123")
|
||||||
|
got := buildWeixinSyncBufPath(wxCfg)
|
||||||
|
wantDir := filepath.Join(os.TempDir(), "picoclaw", "channels", "weixin", "sync")
|
||||||
|
if filepath.Dir(got) != wantDir {
|
||||||
|
t.Fatalf("sync path dir = %q, want %q", filepath.Dir(got), wantDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSessionPauseGuard(t *testing.T) {
|
func TestSessionPauseGuard(t *testing.T) {
|
||||||
ch := &WeixinChannel{
|
ch := &WeixinChannel{
|
||||||
typingCache: make(map[string]typingTicketCacheEntry),
|
typingCache: make(map[string]typingTicketCacheEntry),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue