mirror of
https://github.com/zotero/zotero.git
synced 2026-08-28 05:25:31 +00:00
Use offline database backups on network filesystems on Linux
The online backup API writes the backup file through SQLite's default VFS, whose locking can hang on network filesystems -- its lock upgrades conflict with the SMB byte-range lock mapping on CIFS mounts and retry forever -- so fall back to an offline backup, which copies the file with the connection closed. Extends getFileSystemInfo() to Linux using statfs f_type magic numbers.
This commit is contained in:
parent
18f6fa50d5
commit
ad1bec62f2
2 changed files with 60 additions and 23 deletions
|
|
@ -1311,6 +1311,18 @@ Zotero.DBConnection.prototype.backUpDatabase = async function ({ force, suffix,
|
|||
online = false;
|
||||
}
|
||||
|
||||
// On Linux, use an offline backup on a network filesystem. The online backup API writes
|
||||
// the backup file through SQLite's default VFS, whose locking can hang there -- its lock
|
||||
// upgrades conflict with the SMB byte-range lock mapping on CIFS mounts. Check the
|
||||
// directory rather than the database file, which could be a symlink to another volume,
|
||||
// since the directory is where the backup files are written.
|
||||
if (online && Zotero.isLinux
|
||||
&& ['cifs', 'smb', 'smb2', 'nfs'].includes(
|
||||
Zotero.File.getFileSystemInfo(PathUtils.parent(this._dbPath))?.fsType
|
||||
)) {
|
||||
online = false;
|
||||
}
|
||||
|
||||
var resolveOfflineBackupPromise;
|
||||
var success = false;
|
||||
if (online) {
|
||||
|
|
|
|||
|
|
@ -1112,18 +1112,20 @@ Zotero.File = new function () {
|
|||
var _fsInfoCache = {};
|
||||
|
||||
/**
|
||||
* Get information about the filesystem containing a path (macOS only)
|
||||
* Get information about the filesystem containing a path (macOS and Linux)
|
||||
*
|
||||
* statfs() is called on the path itself, following symlinks, so a symlinked file is
|
||||
* classified by its target's volume. The parent directory is used if the path doesn't
|
||||
* exist.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {Object|null} - { fsType: statfs f_fstypename (e.g., 'apfs', 'smbfs'),
|
||||
* readOnly: Boolean }, or null on other platforms or if the check fails
|
||||
* @return {Object|null} - { fsType: statfs f_fstypename on macOS (e.g., 'apfs', 'smbfs')
|
||||
* or a name derived from the statfs f_type magic on Linux (e.g., 'ext4', 'cifs',
|
||||
* 'nfs', or a hex string if unrecognized), readOnly: Boolean }, or null on other
|
||||
* platforms or if the check fails
|
||||
*/
|
||||
this.getFileSystemInfo = function (path) {
|
||||
if (!Zotero.isMac) return null;
|
||||
if (!Zotero.isMac && !Zotero.isLinux) return null;
|
||||
|
||||
if (path in _fsInfoCache) {
|
||||
return _fsInfoCache[path];
|
||||
|
|
@ -1134,15 +1136,8 @@ Zotero.File = new function () {
|
|||
let { ctypes } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/ctypes.sys.mjs"
|
||||
);
|
||||
// struct statfs -- f_flags is a uint32 at byte offset 64, f_fstypename is a
|
||||
// char[16] at byte offset 72
|
||||
const STATFS_SIZE = 2168;
|
||||
const FLAGS_OFFSET = 64;
|
||||
const FSTYPENAME_OFFSET = 72;
|
||||
const FSTYPENAME_LEN = 16;
|
||||
const MNT_RDONLY = 0x1;
|
||||
let buf = new (ctypes.ArrayType(ctypes.uint8_t, STATFS_SIZE))();
|
||||
let lib = ctypes.open("/usr/lib/libSystem.B.dylib");
|
||||
let buf = new (ctypes.ArrayType(ctypes.uint8_t, 2168))();
|
||||
let lib = ctypes.open(Zotero.isMac ? "/usr/lib/libSystem.B.dylib" : "libc.so.6");
|
||||
try {
|
||||
let statfs = lib.declare(
|
||||
"statfs",
|
||||
|
|
@ -1156,17 +1151,47 @@ Zotero.File = new function () {
|
|||
throw new Error("statfs() failed");
|
||||
}
|
||||
}
|
||||
let typeName = '';
|
||||
for (let i = FSTYPENAME_OFFSET; i < FSTYPENAME_OFFSET + FSTYPENAME_LEN; i++) {
|
||||
if (buf[i] === 0) break;
|
||||
typeName += String.fromCharCode(buf[i]);
|
||||
let readUint32 = offset => buf[offset] | (buf[offset + 1] << 8)
|
||||
| (buf[offset + 2] << 16) | (buf[offset + 3] << 24);
|
||||
if (Zotero.isMac) {
|
||||
// struct statfs -- f_flags is a uint32 at byte offset 64, f_fstypename
|
||||
// is a char[16] at byte offset 72
|
||||
const FLAGS_OFFSET = 64;
|
||||
const FSTYPENAME_OFFSET = 72;
|
||||
const FSTYPENAME_LEN = 16;
|
||||
const MNT_RDONLY = 0x1;
|
||||
let typeName = '';
|
||||
for (let i = FSTYPENAME_OFFSET; i < FSTYPENAME_OFFSET + FSTYPENAME_LEN; i++) {
|
||||
if (buf[i] === 0) break;
|
||||
typeName += String.fromCharCode(buf[i]);
|
||||
}
|
||||
result = {
|
||||
fsType: typeName,
|
||||
readOnly: !!(readUint32(FLAGS_OFFSET) & MNT_RDONLY)
|
||||
};
|
||||
}
|
||||
else {
|
||||
// 64-bit struct statfs -- f_type is a word at byte offset 0 and f_flags
|
||||
// a word at byte offset 80. Filesystem type is a magic number (see
|
||||
// linux/magic.h) rather than a name.
|
||||
const FLAGS_OFFSET = 80;
|
||||
const ST_RDONLY = 0x1;
|
||||
const FS_MAGICS = {
|
||||
0xEF53: 'ext4',
|
||||
0x58465342: 'xfs',
|
||||
0x9123683E: 'btrfs',
|
||||
0xFF534D42: 'cifs',
|
||||
0xFE534D42: 'smb2',
|
||||
0x517B: 'smb',
|
||||
0x6969: 'nfs',
|
||||
0x65735546: 'fuse'
|
||||
};
|
||||
let magic = readUint32(0) >>> 0;
|
||||
result = {
|
||||
fsType: FS_MAGICS[magic] || '0x' + magic.toString(16),
|
||||
readOnly: !!(readUint32(FLAGS_OFFSET) & ST_RDONLY)
|
||||
};
|
||||
}
|
||||
let flags = buf[FLAGS_OFFSET] | (buf[FLAGS_OFFSET + 1] << 8)
|
||||
| (buf[FLAGS_OFFSET + 2] << 16) | (buf[FLAGS_OFFSET + 3] << 24);
|
||||
result = {
|
||||
fsType: typeName,
|
||||
readOnly: !!(flags & MNT_RDONLY)
|
||||
};
|
||||
}
|
||||
finally {
|
||||
lib.close();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue