siyuan-note/siyuan · critical

unzip [ ] failed

Error message

unzip [%s] failed: %s

What it means

The mobile gomobile binding Unzip extracts a zip archive via gulu.Zip.Unzip and, on any error, logs it and panics. The panic message reports the source archive path and the underlying unzip error. On mobile, a panic aborts the calling native bridge call.

Solutions

  1. Verify the zip file exists and is complete (re-download/re-transfer the archive) before calling Unzip
  2. Ensure the destination directory exists and the app has write permission to it
  3. Check the underlying error text in the log line "unzip [...] failed: ..." for the specific cause (e.g. CRC mismatch vs. open failure)
  4. Catch/recover the panic on the native side and surface a user-facing failure instead of crashing
  5. Free up device storage if the error indicates write failure

Example fix

// before
Unzip("/sdcard/backup.zip", "/data/data/app/workspace") // panics if file truncated
// after
if fileExistsAndValid("/sdcard/backup.zip") && dirWritable("/data/data/app/workspace") {
    Unzip("/sdcard/backup.zip", "/data/data/app/workspace")
} else { showError() }
Defensive patterns

Strategy: try-catch

Validate before calling

func safeUnzip(zipPath, dest string) (err error) {
    if _, err = os.Stat(zipPath); err != nil { return fmt.Errorf("zip missing: %w", err) }
    if err = os.MkdirAll(dest, 0755); err != nil { return fmt.Errorf("dest unwritable: %w", err) }
    return nil
}

Type guard

null

Try / catch

// Kotlin/Java side: wrap the binding call
try {
    SiYuanKernel.Unzip(zipPath, dest)
} catch (e: Throwable) {
    Log.e(TAG, "unzip failed", e)
    showRestoreFailedDialog()
}

Prevention

When it happens

Trigger: Calling the exported Unzip(zipFilePath, destination) from native mobile code when the zip is missing, corrupt, password-protected in an unsupported way, or the destination is unwritable.

Common situations: Interrupted downloads producing truncated zips, restoring workspace/snapshot archives on Android/iOS, insufficient storage space, destination path not created or permission-restricted by the OS sandbox.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/a79466e34d42c4cc. Report an issue: GitHub.

Appendix: source

Thrown at kernel/mobile/kernel.go:381

	return filepath.Base(path)
}

func FilterUploadFileName(name string) string {
	return util.FilterUploadFileName(name)
}

func AssetName(name string) string {
	return util.AssetName(name, ast.NewNodeID())
}

func HTML2Markdown(html string) string {
	return util.NewLute().HTML2Md(html)
}

func Unzip(zipFilePath, destination string) {
	if err := gulu.Zip.Unzip(zipFilePath, destination); nil != err {
		logging.LogErrorf("unzip [%s] failed: %s", zipFilePath, err)
		panic(err)
	}
}

// GetExportFilePath 解析导出文件绝对路径,绕过 HTTP 层以避免锁屏密码拦截。
// exportPath 格式为 "/export/xxx.zip" 或 "assets/xxx"。
// 返回文件在磁盘上的绝对路径,以便原生端分块拷贝,避免大文件内存溢出。
// 解析失败返回空字符串。
func GetExportFilePath(exportPath string) (ret string) {
	var absPath string
	if after, ok := strings.CutPrefix(exportPath, "/export/"); ok {
		fileName := after
		if decoded, err := url.PathUnescape(fileName); err == nil {
			fileName = decoded
		}
		fileName = filepath.Clean(fileName)
		if strings.HasPrefix(fileName, "..") {
			logging.LogWarnf("get export file path [%s] blocked: path traversal attempt [%s]", exportPath, fileName)
			return

View on GitHub (pinned to 9f775e8a12)