Show correct access-denied error when data directory can't be created

The OS.File.makeDir() shim swallowed IOUtils errors, so a failure to
create the data directory showed as a generic "database cannot be
opened" error. For the default location, just explain and quit instead
of offering to choose another directory.

https://forums.zotero.org/discussion/134124/zotero-not-working-on-a-mac
This commit is contained in:
Dan Stillman 2026-10-08 09:42:29 -04:00
parent 9cbba8c4d2
commit 338ce732bd
3 changed files with 46 additions and 30 deletions

View file

@ -116,6 +116,7 @@ export let OS = {
throw osFileError;
}
}
throw e;
}
}),

View file

@ -230,47 +230,25 @@ Zotero.DataDirectory = {
await Zotero.File.createDirectoryIfMissingAsync(dataDir);
}
catch (e) {
// TEMP: OS.Constants.Win.ERROR_ACCESS_DENIED no longer available, but we should
// switch to IOUtils anyway
const WIN_ERROR_ACCESS_DENIED = 5;
if (e instanceof OS.File.Error
&& (('unixErrno' in e && e.unixErrno == ChromeUtils.getLibcConstants().EACCES)
|| ('winLastError' in e && e.winLastError == WIN_ERROR_ACCESS_DENIED))) {
if (DOMException.isInstance(e) && e.name == 'NotAllowedError') {
Zotero.restarting = true;
let isDefaultDir = dataDir == Zotero.DataDirectory.defaultDir;
let ps = Services.prompt;
let buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
if (!isDefaultDir) {
buttonFlags += ps.BUTTON_POS_2 * ps.BUTTON_TITLE_IS_STRING;
}
let title = Zotero.getString('general.accessDenied');
let msg = Zotero.getString('dataDir.dirCannotBeCreated', [Zotero.appName, dataDir])
+ "\n\n"
+ Zotero.getString('dataDir.checkDirWriteAccess', Zotero.appName);
let index;
// The default location needs to be writable, so don't offer an alternative
if (isDefaultDir) {
index = ps.confirmEx(null,
title,
msg,
buttonFlags,
Zotero.getString('dataDir.chooseNewDataDirectory'),
Zotero.getString('general.quit'),
null, null, {}
);
if (index == 0) {
let changed = await Zotero.DataDirectory.choose(true);
if (!changed) {
Zotero.Utilities.Internal.quit();
}
}
else if (index == 1) {
Zotero.Utilities.Internal.quit();
}
ps.alert(null, title, msg);
Zotero.Utilities.Internal.quit();
}
else {
index = ps.confirmEx(null,
let buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING
+ ps.BUTTON_POS_2 * ps.BUTTON_TITLE_IS_STRING;
let index = ps.confirmEx(null,
title,
msg,
buttonFlags,

View file

@ -156,6 +156,43 @@ describe("Zotero.DataDirectory", function () {
};
describe("#init()", function () {
it("should show an error and quit if the default directory can't be created", async function () {
if (Zotero.isWin) {
this.skip();
}
var parentDir = await getTempDirectory();
var dataDir = OS.Path.join(parentDir, 'Zotero');
var forceDataDir = Zotero.forceDataDir;
var defaultDirStub = sinon.stub(Zotero.DataDirectory, 'defaultDir').get(() => dataDir);
var promptService = Services.prompt;
var alertStub = sinon.stub();
var confirmStub = sinon.stub();
Services.prompt = { alert: alertStub, confirmEx: confirmStub };
var quitStub = sinon.stub(Zotero.Utilities.Internal, 'quit');
await IOUtils.setPermissions(parentDir, 0o555);
try {
Zotero.forceDataDir = dataDir;
await Zotero.DataDirectory.init();
assert.isTrue(alertStub.calledOnce);
assert.include(alertStub.firstCall.args[2], dataDir);
assert.isFalse(confirmStub.called);
assert.isTrue(quitStub.calledOnce);
assert.isFalse(await IOUtils.exists(dataDir));
}
finally {
Zotero.forceDataDir = forceDataDir;
Zotero.restarting = false;
Services.prompt = promptService;
defaultDirStub.restore();
quitStub.restore();
await IOUtils.setPermissions(parentDir, 0o755);
}
});
});
describe("#checkForMigration()", function () {
let fileMoveStub;