init: introduce Result<T> for return values and error handling
init tries to propagate error information up to build context before
logging errors. This is a good thing, however too often init has the
overly verbose paradigm for error handling, below:
bool CalculateResult(const T& input, U* output, std::string* err)
bool CalculateAndUseResult(const T& input, std::string* err) {
U output;
std::string calculate_result_err;
if (!CalculateResult(input, &output, &calculate_result_err)) {
*err = "CalculateResult " + input + " failed: " +
calculate_result_err;
return false;
}
UseResult(output);
return true;
}
Even more common are functions that return only true/false but also
require passing a std::string* err in order to see the error message.
This change introduces a Result<T> that is use to either hold a
successful return value of type T or to hold an error message as a
std::string. If the functional only returns success or a failure with
an error message, Result<Success> may be used. The classes Error and
ErrnoError are used to indicate a failed Result<T>.
A successful Result<T> is constructed implicitly from any type that
can be implicitly converted to T or from the constructor arguments for
T. This allows you to return a type T directly from a function that
returns Result<T>.
Error and ErrnoError are used to construct a Result<T> has
failed. Each of these classes take an ostream as an input and are
implicitly cast to a Result<T> containing that failure. ErrnoError()
additionally appends ": " + strerror(errno) to the end of the failure
string to aid in interacting with C APIs.
The end result is that the above code snippet is turned into the much
clearer example below:
Result<U> CalculateResult(const T& input);
Result<Success> CalculateAndUseResult(const T& input) {
auto output = CalculateResult(input);
if (!output) {
return Error() << "CalculateResult " << input << " failed: "
<< output.error();
}
UseResult(*output);
return Success();
}
This change also makes this conversion for some of the util.cpp
functions that used the old paradigm.
Test: boot bullhead, init unit tests
Merged-In: I1e7d3a8820a79362245041251057fbeed2f7979b
Change-Id: I1e7d3a8820a79362245041251057fbeed2f7979b
diff --git a/init/util.cpp b/init/util.cpp
index e037987..fcf7ca8 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -57,30 +57,20 @@
const std::string kDefaultAndroidDtDir("/proc/device-tree/firmware/android/");
// DecodeUid() - decodes and returns the given string, which can be either the
-// numeric or name representation, into the integer uid or gid. Returns
-// UINT_MAX on error.
-bool DecodeUid(const std::string& name, uid_t* uid, std::string* err) {
- *uid = UINT_MAX;
- *err = "";
-
+// numeric or name representation, into the integer uid or gid.
+Result<uid_t> DecodeUid(const std::string& name) {
if (isalpha(name[0])) {
passwd* pwd = getpwnam(name.c_str());
- if (!pwd) {
- *err = "getpwnam failed: "s + strerror(errno);
- return false;
- }
- *uid = pwd->pw_uid;
- return true;
+ if (!pwd) return ErrnoError() << "getpwnam failed";
+
+ return pwd->pw_uid;
}
errno = 0;
uid_t result = static_cast<uid_t>(strtoul(name.c_str(), 0, 0));
- if (errno) {
- *err = "strtoul failed: "s + strerror(errno);
- return false;
- }
- *uid = result;
- return true;
+ if (errno) return ErrnoError() << "strtoul failed";
+
+ return result;
}
/*
@@ -164,50 +154,40 @@
return -1;
}
-bool ReadFile(const std::string& path, std::string* content, std::string* err) {
- content->clear();
- *err = "";
-
+Result<std::string> ReadFile(const std::string& path) {
android::base::unique_fd fd(
TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
if (fd == -1) {
- *err = "Unable to open '" + path + "': " + strerror(errno);
- return false;
+ return ErrnoError() << "open() failed";
}
// For security reasons, disallow world-writable
// or group-writable files.
struct stat sb;
if (fstat(fd, &sb) == -1) {
- *err = "fstat failed for '" + path + "': " + strerror(errno);
- return false;
+ return ErrnoError() << "fstat failed()";
}
if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
- *err = "Skipping insecure file '" + path + "'";
- return false;
+ return Error() << "Skipping insecure file";
}
- if (!android::base::ReadFdToString(fd, content)) {
- *err = "Unable to read '" + path + "': " + strerror(errno);
- return false;
+ std::string content;
+ if (!android::base::ReadFdToString(fd, &content)) {
+ return ErrnoError() << "Unable to read file contents";
}
- return true;
+ return content;
}
-bool WriteFile(const std::string& path, const std::string& content, std::string* err) {
- *err = "";
-
+Result<Success> WriteFile(const std::string& path, const std::string& content) {
android::base::unique_fd fd(TEMP_FAILURE_RETRY(
open(path.c_str(), O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC | O_CLOEXEC, 0600)));
if (fd == -1) {
- *err = "Unable to open '" + path + "': " + strerror(errno);
- return false;
+ return ErrnoError() << "open() failed";
}
if (!android::base::WriteStringToFd(content, fd)) {
- *err = "Unable to write to '" + path + "': " + strerror(errno);
- return false;
+ return ErrnoError() << "Unable to write file contents";
}
- return true;
+ return Success();
}
bool mkdir_recursive(const std::string& path, mode_t mode) {