From 338ce732bd1e5b34e8cbc060dc969f4d3b4e9cda Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 8 Oct 2026 09:42:29 -0400 Subject: [PATCH] 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 --- chrome/content/zotero/osfile.mjs | 1 + chrome/content/zotero/xpcom/dataDirectory.js | 38 +++++--------------- test/tests/dataDirectoryTest.js | 37 +++++++++++++++++++ 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/chrome/content/zotero/osfile.mjs b/chrome/content/zotero/osfile.mjs index f8e192008a..f0845bc6e2 100644 --- a/chrome/content/zotero/osfile.mjs +++ b/chrome/content/zotero/osfile.mjs @@ -116,6 +116,7 @@ export let OS = { throw osFileError; } } + throw e; } }), diff --git a/chrome/content/zotero/xpcom/dataDirectory.js b/chrome/content/zotero/xpcom/dataDirectory.js index 7f47816f5f..be5285255b 100644 --- a/chrome/content/zotero/xpcom/dataDirectory.js +++ b/chrome/content/zotero/xpcom/dataDirectory.js @@ -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, diff --git a/test/tests/dataDirectoryTest.js b/test/tests/dataDirectoryTest.js index dd200c620b..24956bcdd0 100644 --- a/test/tests/dataDirectoryTest.js +++ b/test/tests/dataDirectoryTest.js @@ -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;