HaikuDepot: Check Working Files Writable

At start time, it is possible that the local disk
system is not able to be written to where the
application is trying to store working files.  This
change introduces a check at startup that ensures
working files can be written to and warns the user
if this is not the case.

Change-Id: I907bf41a3b4eceb0083119a082fd5e68e4d534c1
Reviewed-on: https://review.haiku-os.org/c/haiku/+/2397
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
Andrew Lindesay
2020-03-28 20:06:08 +00:00
committed by waddlesplash
parent 663c9749e1
commit 32ed76599d
8 changed files with 293 additions and 118 deletions
+3 -1
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2018-2019, Andrew Lindesay <[email protected]>. * Copyright 2018-2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef HAIKU_DEPOT_CONSTANTS_H #ifndef HAIKU_DEPOT_CONSTANTS_H
@@ -81,4 +81,6 @@ enum UserUsageConditionsSelectionMode {
#define ALERT_MSG_LOGS_USER_GUIDE "\nInformation about how to view the logs is " \ #define ALERT_MSG_LOGS_USER_GUIDE "\nInformation about how to view the logs is " \
"available in the HaikuDepot section of the user guide." "available in the HaikuDepot section of the user guide."
#define CACHE_DIRECTORY_APP "HaikuDepot"
#endif // HAIKU_DEPOT_CONSTANTS_H #endif // HAIKU_DEPOT_CONSTANTS_H
+15 -62
View File
@@ -1,7 +1,7 @@
/* /*
* Copyright 2013-2014, Stephan Aßmus <[email protected]>. * Copyright 2013-2014, Stephan Aßmus <[email protected]>.
* Copyright 2014, Axel Dörfler <[email protected]>. * Copyright 2014, Axel Dörfler <[email protected]>.
* Copyright 2016-2019, Andrew Lindesay <[email protected]>. * Copyright 2016-2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
@@ -847,36 +847,6 @@ Model::SetAuthorization(const BString& nickname, const BString& passwordClear,
} }
status_t
Model::_LocalDataPath(const BString leaf, BPath& path) const
{
BPath resultPath;
status_t result = B_OK;
if (result == B_OK)
result = find_directory(B_USER_CACHE_DIRECTORY, &resultPath);
if (result == B_OK)
result = resultPath.Append("HaikuDepot");
if (result == B_OK)
result = create_directory(resultPath.Path(), 0777);
if (result == B_OK)
result = resultPath.Append(leaf);
if (result == B_OK)
path.SetTo(resultPath.Path());
else {
path.Unset();
fprintf(stdout, "unable to find the user cache file for "
"[%s] data; %s\n", leaf.String(), strerror(result));
}
return result;
}
/*! When bulk repository data comes down from the server, it will /*! When bulk repository data comes down from the server, it will
arrive as a json.gz payload. This is stored locally as a cache arrive as a json.gz payload. This is stored locally as a cache
and this method will provide the on-disk storage location for and this method will provide the on-disk storage location for
@@ -889,7 +859,7 @@ Model::DumpExportRepositoryDataPath(BPath& path) const
BString leaf; BString leaf;
leaf.SetToFormat("repository-all_%s.json.gz", leaf.SetToFormat("repository-all_%s.json.gz",
LanguageModel().PreferredLanguage().Code()); LanguageModel().PreferredLanguage().Code());
return _LocalDataPath(leaf, path); return StorageUtils::LocalWorkingFilesPath(leaf, path);
} }
@@ -904,37 +874,14 @@ Model::DumpExportReferenceDataPath(BPath& path) const
BString leaf; BString leaf;
leaf.SetToFormat("reference-all_%s.json.gz", leaf.SetToFormat("reference-all_%s.json.gz",
LanguageModel().PreferredLanguage().Code()); LanguageModel().PreferredLanguage().Code());
return _LocalDataPath(leaf, path); return StorageUtils::LocalWorkingFilesPath(leaf, path);
} }
status_t status_t
Model::IconStoragePath(BPath& path) const Model::IconStoragePath(BPath& path) const
{ {
BPath iconStoragePath; return StorageUtils::LocalWorkingDirectoryPath("__allicons", path);
status_t result = B_OK;
if (result == B_OK)
result = find_directory(B_USER_CACHE_DIRECTORY, &iconStoragePath);
if (result == B_OK)
result = iconStoragePath.Append("HaikuDepot");
if (result == B_OK)
result = iconStoragePath.Append("__allicons");
if (result == B_OK)
result = create_directory(iconStoragePath.Path(), 0777);
if (result == B_OK)
path.SetTo(iconStoragePath.Path());
else {
path.Unset();
fprintf(stdout, "unable to find the user cache directory for "
"icons; %s\n", strerror(result));
}
return result;
} }
@@ -945,7 +892,7 @@ Model::DumpExportPkgDataPath(BPath& path,
BString leaf; BString leaf;
leaf.SetToFormat("pkg-all-%s-%s.json.gz", repositorySourceCode.String(), leaf.SetToFormat("pkg-all-%s-%s.json.gz", repositorySourceCode.String(),
LanguageModel().PreferredLanguage().Code()); LanguageModel().PreferredLanguage().Code());
return _LocalDataPath(leaf, path); return StorageUtils::LocalWorkingFilesPath(leaf, path);
} }
@@ -966,15 +913,21 @@ Model::_PopulatePackageScreenshot(const PackageInfoRef& package,
// See if there is a cached screenshot // See if there is a cached screenshot
BFile screenshotFile; BFile screenshotFile;
BPath screenshotCachePath; BPath screenshotCachePath;
status_t result = StorageUtils::LocalWorkingDirectoryPath(
"Screenshots", screenshotCachePath);
if (result != B_OK) {
printf("[!] unable to get the screenshot dir - unable to proceed");
return;
}
bool fileExists = false; bool fileExists = false;
BString screenshotName(info.Code()); BString screenshotName(info.Code());
screenshotName << "@" << scaledWidth; screenshotName << "@" << scaledWidth;
screenshotName << ".png"; screenshotName << ".png";
time_t modifiedTime; time_t modifiedTime;
if (find_directory(B_USER_CACHE_DIRECTORY, &screenshotCachePath) == B_OK if (screenshotCachePath.Append(screenshotName) == B_OK) {
&& screenshotCachePath.Append("HaikuDepot/Screenshots") == B_OK
&& create_directory(screenshotCachePath.Path(), 0777) == B_OK
&& screenshotCachePath.Append(screenshotName) == B_OK) {
// Try opening the file in read-only mode, which will fail if its // Try opening the file in read-only mode, which will fail if its
// not a file or does not exist. // not a file or does not exist.
fileExists = screenshotFile.SetTo(screenshotCachePath.Path(), fileExists = screenshotFile.SetTo(screenshotCachePath.Path(),
+1 -15
View File
@@ -1,12 +1,11 @@
/* /*
* Copyright 2013-2014, Stephan Aßmus <[email protected]>. * Copyright 2013-2014, Stephan Aßmus <[email protected]>.
* Copyright 2016-2019, Andrew Lindesay <[email protected]>. * Copyright 2016-2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef MODEL_H #ifndef MODEL_H
#define MODEL_H #define MODEL_H
#include <FindDirectory.h>
#include <Locker.h> #include <Locker.h>
#include "AbstractProcess.h" #include "AbstractProcess.h"
@@ -155,8 +154,6 @@ public:
private: private:
void _AddCategory(const CategoryRef& category); void _AddCategory(const CategoryRef& category);
status_t _LocalDataPath(const BString leaf,
BPath& path) const;
void _MaybeLogJsonRpcError( void _MaybeLogJsonRpcError(
const BMessage &responsePayload, const BMessage &responsePayload,
@@ -174,17 +171,6 @@ private:
const ScreenshotInfo& info, const ScreenshotInfo& info,
int32 scaledWidth, bool fromCacheOnly); int32 scaledWidth, bool fromCacheOnly);
bool _GetCacheFile(BPath& path, BFile& file,
directory_which directory,
const char* relativeLocation,
const char* fileName,
uint32 openMode) const;
bool _GetCacheFile(BPath& path, BFile& file,
directory_which directory,
const char* relativeLocation,
const char* fileName,
bool ignoreAge, time_t maxAge) const;
void _NotifyAuthorizationChanged(); void _NotifyAuthorizationChanged();
void _NotifyCategoryListChanged(); void _NotifyCategoryListChanged();
@@ -1,5 +1,5 @@
/* /*
* Copyright 2018-2019, Andrew Lindesay <[email protected]>. * Copyright 2018-2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
@@ -25,6 +25,7 @@
#include "ServerReferenceDataUpdateProcess.h" #include "ServerReferenceDataUpdateProcess.h"
#include "ServerRepositoryDataUpdateProcess.h" #include "ServerRepositoryDataUpdateProcess.h"
#include "ServerSettings.h" #include "ServerSettings.h"
#include "StorageUtils.h"
using namespace BPackageKit; using namespace BPackageKit;
@@ -36,6 +37,7 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
ProcessCoordinatorListener* processCoordinatorListener, ProcessCoordinatorListener* processCoordinatorListener,
Model* model, bool forceLocalUpdate) Model* model, bool forceLocalUpdate)
{ {
bool areWorkingFilesAvailable = StorageUtils::AreWorkingFilesAvailable();
uint32 serverProcessOptions = _CalculateServerProcessOptions(); uint32 serverProcessOptions = _CalculateServerProcessOptions();
BAutolock locker(model->Lock()); BAutolock locker(model->Lock());
ProcessCoordinator* processCoordinator = new ProcessCoordinator( ProcessCoordinator* processCoordinator = new ProcessCoordinator(
@@ -52,6 +54,7 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
localPkgDataLoad->AddPredecessor(localRepositoryUpdate); localPkgDataLoad->AddPredecessor(localRepositoryUpdate);
processCoordinator->AddNode(localPkgDataLoad); processCoordinator->AddNode(localPkgDataLoad);
if (areWorkingFilesAvailable) {
ProcessNode *serverIconExportUpdate = ProcessNode *serverIconExportUpdate =
new ProcessNode(new ServerIconExportUpdateProcess(model, new ProcessNode(new ServerIconExportUpdateProcess(model,
serverProcessOptions)); serverProcessOptions));
@@ -69,10 +72,11 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
serverProcessOptions)); serverProcessOptions));
processCoordinator->AddNode(serverReferenceDataUpdate); processCoordinator->AddNode(serverReferenceDataUpdate);
// create a process for each of the repositories that are configured on the // create a process for each of the repositories that are configured on
// local system. Later, only those that have a web-app repository server // the local system. Later, only those that have a web-app repository
// code will be actually processed, but this means that the creation of the // server code will be actually processed, but this means that the
// 'processes' does not need to be dynamic as the process coordinator runs. // creation of the 'processes' does not need to be dynamic as the
// process coordinator runs.
BPackageRoster roster; BPackageRoster roster;
BStringList repoNames; BStringList repoNames;
@@ -93,6 +97,7 @@ ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
} else { } else {
printf("a problem has arisen getting the repository names.\n"); printf("a problem has arisen getting the repository names.\n");
} }
}
return processCoordinator; return processCoordinator;
} }
+52 -1
View File
@@ -1,6 +1,6 @@
/* /*
* Copyright 2013, Stephan Aßmus <[email protected]>. * Copyright 2013, Stephan Aßmus <[email protected]>.
* Copyright 2017-2018, Andrew Lindesay <[email protected]>. * Copyright 2017-2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
@@ -29,6 +29,7 @@
#include "ServerHelper.h" #include "ServerHelper.h"
#include "ServerSettings.h" #include "ServerSettings.h"
#include "ScreenshotWindow.h" #include "ScreenshotWindow.h"
#include "StorageUtils.h"
#undef B_TRANSLATION_CONTEXT #undef B_TRANSLATION_CONTEXT
@@ -42,6 +43,7 @@ App::App()
fWindowCount(0), fWindowCount(0),
fSettingsRead(false) fSettingsRead(false)
{ {
srand((unsigned int) time(NULL));
_CheckPackageDaemonRuns(); _CheckPackageDaemonRuns();
} }
@@ -83,6 +85,12 @@ App::ReadyToRun()
BMessage settings; BMessage settings;
_LoadSettings(settings); _LoadSettings(settings);
if (!_CheckTestFile())
{
Quit();
return;
}
fMainWindow = new MainWindow(settings); fMainWindow = new MainWindow(settings);
_ShowWindow(fMainWindow); _ShowWindow(fMainWindow);
} }
@@ -506,3 +514,46 @@ App::_LaunchPackageDaemon()
return true; return true;
} }
/*! \brief Checks to ensure that a working file is able to be written.
\return false if the startup should be stopped and the application should
quit.
*/
bool
App::_CheckTestFile()
{
BPath testFilePath;
BString pathDescription = "???";
status_t result = StorageUtils::LocalWorkingFilesPath("testfile.txt",
testFilePath, false);
if (result == B_OK) {
pathDescription = testFilePath.Path();
result = StorageUtils::CheckCanWriteTo(testFilePath);
}
if (result != B_OK) {
StorageUtils::SetWorkingFilesUnavailable();
BString msg = B_TRANSLATE("This application writes and reads some"
" working files on your computer in order to function. It appears"
" that there are problems writing a test file at [%TestFilePath%]."
" Check that there are no issues with your local disk or"
" permissions that might prevent this application from writing"
" files into that directory location. You may choose to acknowledge"
" this problem and continue, but some functionality may be"
" disabled.");
msg.ReplaceAll("%TestFilePath%", pathDescription);
BAlert* alert = new(std::nothrow) BAlert(
B_TRANSLATE("Problem with working files"),
msg,
B_TRANSLATE("Quit"), B_TRANSLATE("Continue"));
if (alert->Go() == 0)
return false;
}
return true;
}
+3 -1
View File
@@ -1,6 +1,6 @@
/* /*
* Copyright 2013, Stephan Aßmus <[email protected]>. * Copyright 2013, Stephan Aßmus <[email protected]>.
* Copyright 2018, Andrew Lindesay <[email protected]> * Copyright 2018-2020, Andrew Lindesay <[email protected]>
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef APP_H #ifndef APP_H
@@ -36,6 +36,8 @@ private:
void _CheckPackageDaemonRuns(); void _CheckPackageDaemonRuns();
bool _LaunchPackageDaemon(); bool _LaunchPackageDaemon();
bool _CheckTestFile();
private: private:
MainWindow* fMainWindow; MainWindow* fMainWindow;
int32 fWindowCount; int32 fWindowCount;
+166 -2
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2017, Andrew Lindesay <[email protected]>. * Copyright 2017-2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
@@ -10,14 +10,33 @@
#include <Directory.h> #include <Directory.h>
#include <File.h> #include <File.h>
#include <FindDirectory.h>
#include <Entry.h> #include <Entry.h>
#include <String.h> #include <String.h>
#include "HaikuDepotConstants.h"
#include "Logger.h" #include "Logger.h"
#define FILE_TO_STRING_BUFFER_LEN 64 #define FILE_TO_STRING_BUFFER_LEN 64
static bool sAreWorkingFilesAvailable = true;
/*static*/ bool
StorageUtils::AreWorkingFilesAvailable()
{
return sAreWorkingFilesAvailable;
}
/*static*/ void
StorageUtils::SetWorkingFilesUnavailable()
{
sAreWorkingFilesAvailable = false;
}
/* This method will append the contents of the file at the supplied path to the /* This method will append the contents of the file at the supplied path to the
* string provided. * string provided.
*/ */
@@ -90,7 +109,7 @@ StorageUtils::RemoveDirectoryContents(BPath& path)
*/ */
status_t status_t
StorageUtils::ExistsObject(BPath& path, StorageUtils::ExistsObject(const BPath& path,
bool* exists, bool* exists,
bool* isDirectory, bool* isDirectory,
off_t* size) off_t* size)
@@ -122,3 +141,148 @@ StorageUtils::ExistsObject(BPath& path,
return B_OK; return B_OK;
} }
/*! This method will check that it is possible to write to the specified file.
This may create the file, write some data to it and then read that data
back again to be sure. This can be used as an effective safety measure as
the application starts up in order to ensure that the storage systems are
in place for the application to startup.
It is assumed here that the directory containing the test file exists.
*/
/*static*/ status_t
StorageUtils::CheckCanWriteTo(const BPath& path)
{
status_t result = B_OK;
bool exists = false;
uint8 buffer[16];
// create some random latin letters into the buffer to write.
for (int i = 0; i < 16; i++)
buffer[i] = 65 + (abs(rand()) % 26);
if (result == B_OK)
result = ExistsObject(path, &exists, NULL, NULL);
if (result == B_OK && exists) {
if (Logger::IsTraceEnabled()) {
printf("an object exists at the candidate path "
"[%s] - it will be deleted\n", path.Path());
}
if (remove(path.Path()) == 0) {
if (Logger::IsTraceEnabled()) {
printf("did delete the candidate file [%s]\n", path.Path());
}
} else {
printf("unable to delete the candidate file [%s]\n", path.Path());
result = B_ERROR;
}
}
if (result == B_OK) {
BFile file(path.Path(), O_WRONLY | O_CREAT);
if (file.Write(buffer, 16) != 16) {
printf("unable to write test data to candidate file [%s]\n",
path.Path());
result = B_ERROR;
}
}
if (result == B_OK) {
BFile file(path.Path(), O_RDONLY);
uint8 readBuffer[16];
if (file.Read(readBuffer, 16) != 16) {
printf("unable to read test data from candidate file [%s]\n",
path.Path());
result = B_ERROR;
}
for (int i = 0; result == B_OK && i < 16; i++) {
if (readBuffer[i] != buffer[i]) {
printf("mismatched read..write check on candidate file [%s]\n",
path.Path());
result = B_ERROR;
}
}
}
return result;
}
/*! As the application runs it will need to store some files into the local
disk system. This method, given a leafname, will write into the supplied
path variable, a final path where this leafname should be stored.
*/
/*static*/ status_t
StorageUtils::LocalWorkingFilesPath(const BString leaf, BPath& path,
bool failOnCreateDirectory)
{
BPath resultPath;
status_t result = B_OK;
if (result == B_OK)
result = find_directory(B_USER_CACHE_DIRECTORY, &resultPath);
if (result == B_OK)
result = resultPath.Append(CACHE_DIRECTORY_APP);
if (result == B_OK) {
if (failOnCreateDirectory)
result = create_directory(resultPath.Path(), 0777);
else
create_directory(resultPath.Path(), 0777);
}
if (result == B_OK)
result = resultPath.Append(leaf);
if (result == B_OK)
path.SetTo(resultPath.Path());
else {
path.Unset();
fprintf(stdout, "unable to find the user cache file for "
"[%s] data; %s\n", leaf.String(), strerror(result));
}
return result;
}
/*static*/ status_t
StorageUtils::LocalWorkingDirectoryPath(const BString leaf, BPath& path,
bool failOnCreateDirectory)
{
BPath resultPath;
status_t result = B_OK;
if (result == B_OK)
result = find_directory(B_USER_CACHE_DIRECTORY, &resultPath);
if (result == B_OK)
result = resultPath.Append(CACHE_DIRECTORY_APP);
if (result == B_OK)
result = resultPath.Append(leaf);
if (result == B_OK) {
if (failOnCreateDirectory)
result = create_directory(resultPath.Path(), 0777);
else
create_directory(resultPath.Path(), 0777);
}
if (result == B_OK)
path.SetTo(resultPath.Path());
else {
path.Unset();
fprintf(stdout, "unable to find the user cache directory for "
"[%s] data; %s\n", leaf.String(), strerror(result));
}
return result;
}
+14 -2
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2017, Andrew Lindesay <[email protected]>. * Copyright 2017-2020, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef PATH_UTILS_H #ifndef PATH_UTILS_H
@@ -10,9 +10,21 @@
class StorageUtils { class StorageUtils {
public: public:
static bool AreWorkingFilesAvailable();
static void SetWorkingFilesUnavailable();
static status_t LocalWorkingFilesPath(const BString leaf,
BPath& path,
bool failOnCreateDirectory = true);
static status_t LocalWorkingDirectoryPath(const BString leaf,
BPath& path,
bool failOnCreateDirectory = true);
static status_t CheckCanWriteTo(const BPath& path);
static status_t RemoveDirectoryContents(BPath& path); static status_t RemoveDirectoryContents(BPath& path);
static status_t AppendToString(BPath& path, BString& result); static status_t AppendToString(BPath& path, BString& result);
static status_t ExistsObject(BPath& directory, static status_t ExistsObject(const BPath& path,
bool* exists, bool* exists,
bool* isDirectory, bool* isDirectory,
off_t* size); off_t* size);