Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove lock file and cleanup file handling #1231

Merged
merged 4 commits into from
Jan 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 52 additions & 158 deletions src/gui/DatabaseTabWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
#include "DatabaseTabWidget.h"

#include <QFileInfo>
#include <QLockFile>
#include <QTabWidget>
#include <QPushButton>

Expand All @@ -42,8 +41,6 @@

DatabaseManagerStruct::DatabaseManagerStruct()
: dbWidget(nullptr)
, lockFile(nullptr)
, saveToFilename(false)
, modified(false)
, readOnly(false)
{
Expand Down Expand Up @@ -107,7 +104,7 @@ void DatabaseTabWidget::newDatabase()
void DatabaseTabWidget::openDatabase()
{
QString filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files"));
QString fileName = fileDialog()->getOpenFileName(this, tr("Open database"), QString(),
QString fileName = fileDialog()->getOpenFileName(this, tr("Open database"), QDir::homePath(),
filter);
if (!fileName.isEmpty()) {
openDatabase(fileName);
Expand All @@ -128,10 +125,10 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
QHashIterator<Database*, DatabaseManagerStruct> i(m_dbList);
while (i.hasNext()) {
i.next();
if (i.value().canonicalFilePath == canonicalFilePath) {
if (i.value().fileInfo.canonicalFilePath() == canonicalFilePath) {
if (!i.value().dbWidget->dbHasKey() && !(pw.isNull() && keyFile.isEmpty())) {
// If the database is locked and a pw or keyfile is provided, unlock it
i.value().dbWidget->switchToOpenDatabase(i.value().filePath, pw, keyFile);
i.value().dbWidget->switchToOpenDatabase(i.value().fileInfo.absoluteFilePath(), pw, keyFile);
} else {
setCurrentIndex(databaseIndex(i.key()));
}
Expand All @@ -157,63 +154,22 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
}
file.close();

QLockFile* lockFile = new QLockFile(QString("%1/.%2.lock").arg(fileInfo.canonicalPath(), fileInfo.fileName()));
lockFile->setStaleLockTime(0);

if (!dbStruct.readOnly && !lockFile->tryLock()) {
// for now silently ignore if we can't create a lock file
// due to lack of permissions
if (lockFile->error() != QLockFile::PermissionError) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Database already opened"));
msgBox.setText(tr("The database you are trying to open is locked by another instance of KeePassXC.\n\n"
"Do you want to open it anyway?"));
msgBox.setIcon(QMessageBox::Question);
msgBox.addButton(QMessageBox::Yes);
msgBox.addButton(QMessageBox::No);
auto readOnlyButton = msgBox.addButton(tr("Open read-only"), QMessageBox::NoRole);
msgBox.setDefaultButton(readOnlyButton);
msgBox.setEscapeButton(QMessageBox::No);
auto result = msgBox.exec();

if (msgBox.clickedButton() == readOnlyButton) {
dbStruct.readOnly = true;
delete lockFile;
lockFile = nullptr;
} else if (result == QMessageBox::Yes) {
// take over the lock file if possible
if (lockFile->removeStaleLockFile()) {
lockFile->tryLock();
}
} else {
delete lockFile;
return;
}
}
}

Database* db = new Database();
dbStruct.dbWidget = new DatabaseWidget(db, this);
dbStruct.lockFile = lockFile;
dbStruct.saveToFilename = !dbStruct.readOnly;

dbStruct.filePath = fileInfo.absoluteFilePath();
dbStruct.canonicalFilePath = canonicalFilePath;
dbStruct.fileName = fileInfo.fileName();
dbStruct.fileInfo = fileInfo;

insertDatabase(db, dbStruct);

if (dbStruct.readOnly) {
emit messageTab(tr("File opened in read only mode."), MessageWidget::Warning);
}

updateLastDatabases(dbStruct.filePath);
updateLastDatabases(dbStruct.fileInfo.absoluteFilePath());

if (!(pw.isNull() && keyFile.isEmpty())) {
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, pw, keyFile);
}
else {
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath);
if (!pw.isNull() || !keyFile.isEmpty()) {
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.fileInfo.absoluteFilePath(), pw, keyFile);
} else {
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.fileInfo.absoluteFilePath());
}
emit messageDismissGlobal();
}
Expand Down Expand Up @@ -322,15 +278,14 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
void DatabaseTabWidget::deleteDatabase(Database* db)
{
const DatabaseManagerStruct dbStruct = m_dbList.value(db);
bool emitDatabaseWithFileClosed = dbStruct.saveToFilename;
QString filePath = dbStruct.filePath;
bool emitDatabaseWithFileClosed = dbStruct.fileInfo.exists() && !dbStruct.readOnly;
QString filePath = dbStruct.fileInfo.absoluteFilePath();

int index = databaseIndex(db);

removeTab(index);
toggleTabbar();
m_dbList.remove(db);
delete dbStruct.lockFile;
delete dbStruct.dbWidget;
delete db;

Expand All @@ -349,24 +304,31 @@ bool DatabaseTabWidget::closeAllDatabases()
return true;
}

bool DatabaseTabWidget::saveDatabase(Database* db)
bool DatabaseTabWidget::saveDatabase(Database* db, QString filePath)
{
DatabaseManagerStruct& dbStruct = m_dbList[db];

// Never allow saving a locked database; it causes corruption
Q_ASSERT(dbStruct.dbWidget->currentMode() != DatabaseWidget::LockedMode);
// Release build interlock
if (dbStruct.dbWidget->currentMode() == DatabaseWidget::LockedMode) {
Copy link
Contributor

@TheZ3ro TheZ3ro Dec 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if won't be reached if true since the above assert will abort the execution. Better remove one of them

EDIT: Nevermind, the assert will only be executed in debug mode.

// Never allow saving a locked database; it causes corruption
// We return true since a save is not required
return true;
}

if (dbStruct.saveToFilename) {
if (!dbStruct.readOnly) {
if (filePath.isEmpty()) {
filePath = dbStruct.fileInfo.canonicalFilePath();
}

dbStruct.dbWidget->blockAutoReload(true);
QString errorMessage = db->saveToFile(dbStruct.canonicalFilePath);
QString errorMessage = db->saveToFile(filePath);
dbStruct.dbWidget->blockAutoReload(false);

if (errorMessage.isEmpty()) {
// successfully saved database file
dbStruct.modified = false;
dbStruct.fileInfo = QFileInfo(filePath);
dbStruct.dbWidget->databaseSaved();
updateTabName(db);
emit messageDismissTab();
Expand All @@ -387,76 +349,31 @@ bool DatabaseTabWidget::saveDatabaseAs(Database* db)
{
while (true) {
DatabaseManagerStruct& dbStruct = m_dbList[db];
QString oldFileName;
if (dbStruct.saveToFilename) {
oldFileName = dbStruct.filePath;
QString oldFilePath;
if (dbStruct.fileInfo.exists()) {
oldFilePath = dbStruct.fileInfo.absoluteFilePath();
} else {
oldFileName = tr("Passwords").append(".kdbx");
oldFilePath = QDir::toNativeSeparators(QDir::homePath() + "/" + tr("Passwords").append(".kdbx"));
}
QString fileName = fileDialog()->getSaveFileName(this, tr("Save database as"),
oldFileName, tr("KeePass 2 Database").append(" (*.kdbx)"),
nullptr, 0, "kdbx");
if (!fileName.isEmpty()) {
QFileInfo fileInfo(fileName);
QString lockFilePath;
if (fileInfo.exists()) {
// returns empty string when file doesn't exist
lockFilePath = fileInfo.canonicalPath();
} else {
lockFilePath = fileInfo.absolutePath();
}
QString lockFileName = QString("%1/.%2.lock").arg(lockFilePath, fileInfo.fileName());
QScopedPointer<QLockFile> lockFile(new QLockFile(lockFileName));
lockFile->setStaleLockTime(0);
if (!lockFile->tryLock()) {
// for now silently ignore if we can't create a lock file
// due to lack of permissions
if (lockFile->error() != QLockFile::PermissionError) {
QMessageBox::StandardButton result = MessageBox::question(this, tr("Save database as"),
tr("The database you are trying to save as is locked by another instance of KeePassXC.\n"
"Do you want to save it anyway?"),
QMessageBox::Yes | QMessageBox::No);

if (result == QMessageBox::No) {
return false;
} else {
// take over the lock file if possible
if (lockFile->removeStaleLockFile()) {
lockFile->tryLock();
}
}
}
}

// setup variables so saveDatabase succeeds
dbStruct.saveToFilename = true;
dbStruct.canonicalFilePath = fileName;
QString newFilePath = fileDialog()->getSaveFileName(this, tr("Save database as"), oldFilePath,
tr("KeePass 2 Database").append(" (*.kdbx)"),
nullptr, 0, "kdbx");
if (!newFilePath.isEmpty()) {
// Ensure we don't recurse back into this function
dbStruct.readOnly = false;

if (!saveDatabase(db)) {
// failed to save, revert back
dbStruct.saveToFilename = false;
dbStruct.canonicalFilePath = oldFileName;
if (!saveDatabase(db, newFilePath)) {
// Failed to save, try again
continue;
}

// refresh fileinfo since the file didn't exist before
fileInfo.refresh();

dbStruct.modified = false;
dbStruct.saveToFilename = true;
dbStruct.readOnly = false;
dbStruct.filePath = fileInfo.absoluteFilePath();
dbStruct.canonicalFilePath = fileInfo.canonicalFilePath();
dbStruct.fileName = fileInfo.fileName();
dbStruct.dbWidget->updateFilename(dbStruct.filePath);
delete dbStruct.lockFile;
dbStruct.lockFile = lockFile.take();
updateTabName(db);
updateLastDatabases(dbStruct.filePath);
dbStruct.dbWidget->updateFilePath(dbStruct.fileInfo.absoluteFilePath());
updateLastDatabases(dbStruct.fileInfo.absoluteFilePath());
return true;
} else {
return false;
}

// Canceled file selection
return false;
}
}

Expand Down Expand Up @@ -548,7 +465,7 @@ bool DatabaseTabWidget::canSave(int index)
}

const DatabaseManagerStruct& dbStruct = indexDatabaseManagerStruct(index);
return !dbStruct.saveToFilename || (dbStruct.modified && !dbStruct.readOnly);
return dbStruct.modified && !dbStruct.readOnly;
}

bool DatabaseTabWidget::isModified(int index)
Expand All @@ -566,7 +483,7 @@ QString DatabaseTabWidget::databasePath(int index)
index = currentIndex();
}

return indexDatabaseManagerStruct(index).filePath;
return indexDatabaseManagerStruct(index).fileInfo.absoluteFilePath();
}


Expand All @@ -579,21 +496,18 @@ void DatabaseTabWidget::updateTabName(Database* db)

QString tabName;

if (dbStruct.saveToFilename || dbStruct.readOnly) {
if (dbStruct.fileInfo.exists()) {
if (db->metadata()->name().isEmpty()) {
tabName = dbStruct.fileName;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@droidmonkey was there a possibility that the database was readOnly and the fileName was not defined?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what you mean by this. readOnly is only defined (with these new fixes) when the database file cannot be opened using QIODevice::ReadWrite and it can be opened using QIODevice::ReadOnly (see Line 152).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@droidmonkey nevermind, I was thinking about a possible execution path that might have occurred with the previous code.

}
else {
tabName = dbStruct.fileInfo.fileName();
} else {
tabName = db->metadata()->name();
}

setTabToolTip(index, dbStruct.filePath);
}
else {
setTabToolTip(index, dbStruct.fileInfo.absoluteFilePath());
} else {
if (db->metadata()->name().isEmpty()) {
tabName = tr("New database");
}
else {
} else {
tabName = QString("%1 [%2]").arg(db->metadata()->name(), tr("New database"));
}
}
Expand Down Expand Up @@ -629,8 +543,7 @@ void DatabaseTabWidget::updateTabNameFromDbWidgetSender()
Group *autoload = db->rootGroup()->findChildByName("AutoOpen");
if (autoload) {
const DatabaseManagerStruct& dbStruct = m_dbList.value(db);
QFileInfo dbpath(dbStruct.canonicalFilePath);
QDir dbFolder(dbpath.canonicalPath());
QDir dbFolder(dbStruct.fileInfo.canonicalPath());
for (auto entry : autoload->entries()) {
if (entry->url().isEmpty() || entry->password().isEmpty()) {
continue;
Expand Down Expand Up @@ -756,17 +669,14 @@ void DatabaseTabWidget::lockDatabases()
DatabaseWidget* dbWidget = static_cast<DatabaseWidget*>(widget(i));
Database* db = databaseFromDatabaseWidget(dbWidget);

DatabaseWidget::Mode mode = dbWidget->currentMode();

if ((mode != DatabaseWidget::ViewMode && mode != DatabaseWidget::EditMode)
|| !dbWidget->dbHasKey()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@droidmonkey shouldn't we keep the dbHasKey() check to prevent locking?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added back in, good idea.

if (dbWidget->currentMode() == DatabaseWidget::LockedMode || !dbWidget->dbHasKey()) {
continue;
}

// show the correct tab widget before we are asking questions about it
setCurrentWidget(dbWidget);

if (mode == DatabaseWidget::EditMode && dbWidget->isEditWidgetModified()) {
if (dbWidget->currentMode() == DatabaseWidget::EditMode && dbWidget->isEditWidgetModified()) {
QMessageBox::StandardButton result =
MessageBox::question(
this, tr("Lock database"),
Expand All @@ -777,23 +687,7 @@ void DatabaseTabWidget::lockDatabases()
}
}


if (m_dbList[db].modified && !m_dbList[db].saveToFilename) {
QMessageBox::StandardButton result =
MessageBox::question(
this, tr("Lock database"),
tr("This database has never been saved.\nYou can save the database or stop locking it."),
QMessageBox::Save | QMessageBox::Cancel, QMessageBox::Cancel);
if (result == QMessageBox::Save) {
if (!saveDatabase(db)) {
continue;
}
}
else if (result == QMessageBox::Cancel) {
continue;
}
}
else if (m_dbList[db].modified) {
if (m_dbList[db].modified) {
QMessageBox::StandardButton result =
MessageBox::question(
this, tr("Lock database"),
Expand Down Expand Up @@ -828,7 +722,7 @@ void DatabaseTabWidget::modified()
Database* db = static_cast<Database*>(sender());
DatabaseManagerStruct& dbStruct = m_dbList[db];

if (config()->get("AutoSaveAfterEveryChange").toBool() && dbStruct.saveToFilename) {
if (config()->get("AutoSaveAfterEveryChange").toBool() && !dbStruct.readOnly) {
saveDatabase(db);
Copy link
Member

@phoerious phoerious Nov 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like an assertion here for dbWidget->currentMode() != DatabaseWidget::LockedMode (or perhaps even inside the saveDatabase() method?).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put one inside saveDatabase() in place of the interlock.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps you should leave both. The assertion will ensure we notice in debug mode, but the safeguard condition will ensure it never happens in production either.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that too. Will do!

return;
}
Expand Down
Loading