blob: 45f4b30ca1b4e47908b1ba3c74be393e25326962 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <fidl/fuchsia.device/cpp/wire.h>
#include <fidl/fuchsia.hardware.block.partition/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/watcher.h>
#include <memory>
#include <fbl/unique_fd.h>
#include "src/lib/uuid/uuid.h"
void usage() {
fprintf(stderr,
"usage: waitfor <expr>+ wait for devices to be published\n"
"\n"
"expr: class=<name> device class <name> (required)\n"
"\n"
" topo=<path> topological path contains <path>\n"
" part.guid=<guid> block device GUID matches <guid>\n"
" part.type.guid=<guid> partition type GUID matches <guid>\n"
" part.name=<name> partition name matches <name>\n"
"\n"
" timeout=<msec> fail if no match after <msec> milliseconds\n"
" print write name of matching devices to stdout\n"
" forever don't stop after the first match\n"
" also don't fail on timeout after first match\n"
" verbose print debug chatter to stderr\n"
"\n"
"example: waitfor class=block part.name=system print\n");
}
struct App;
class Rule {
public:
using Func = zx_status_t (*)(const App& app, const char* arg,
fidl::UnownedClientEnd<fuchsia_io::Directory> dir, const char* name);
Rule(const char* arg, Func func) : func_(func), arg_(arg) {}
zx_status_t Call(const App& app, fidl::UnownedClientEnd<fuchsia_io::Directory> dir,
const char* name) const {
return func_(app, arg_, dir, name);
}
private:
Func func_;
const char* arg_;
};
struct App {
bool matched;
const bool verbose;
const bool print;
const bool forever;
const char* devclass;
const std::vector<Rule> rules;
};
zx_status_t watchcb(int dirfd, int event, const char* fn, void* cookie) {
if (std::string_view{fn} == ".") {
return ZX_OK;
}
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
App& app = *static_cast<App*>(cookie);
if (app.verbose) {
fprintf(stderr, "waitfor: device='/dev/class/%s/%s'\n", app.devclass, fn);
}
const fdio_cpp::UnownedFdioCaller caller(dirfd);
for (const Rule& r : app.rules) {
switch (const zx_status_t status = r.Call(app, caller.directory(), fn); status) {
case ZX_OK:
// rule matched
continue;
case ZX_ERR_NEXT:
// rule did not match
return ZX_OK;
default:
// fatal error
return status;
}
}
app.matched = true;
if (app.print) {
printf("/dev/class/%s/%s\n", app.devclass, fn);
}
if (app.forever) {
return ZX_OK;
}
return ZX_ERR_STOP;
}
// Expression evaluators return OK on match, NEXT on no-match
// any other error is fatal
zx_status_t expr_topo(const App& app, const char* arg,
fidl::UnownedClientEnd<fuchsia_io::Directory> dir, const char* name) {
std::string controller_path = std::string(name) + "/device_controller";
zx::result controller =
component::ConnectAt<fuchsia_device::Controller>(dir, controller_path.c_str());
if (controller.is_error()) {
fprintf(stderr, "waitfor: warning: failed to open: '%s' :%s \n", name,
controller.status_string());
return controller.error_value();
}
const fidl::WireResult result = fidl::WireCall(controller.value())->GetTopologicalPath();
if (!result.ok()) {
fprintf(stderr, "waitfor: warning: cannot request topological path: '%s': %s\n",
controller_path.c_str(), result.FormatDescription().c_str());
return result.status();
}
const auto* res = result.Unwrap();
if (res->is_error()) {
const zx_status_t status = res->error_value();
fprintf(stderr, "waitfor: warning: failed to get topological path: %s\n",
zx_status_get_string(status));
return status;
}
const std::string_view got = res->value()->path.get();
const std::string_view expected(arg);
if (app.verbose) {
fprintf(stderr, "waitfor: topological path='%s'\n", std::string(got).c_str());
}
// Check if the topo path contains our needle.
if (got.find(expected) != std::string::npos) {
return ZX_OK;
}
return ZX_ERR_NEXT;
}
zx_status_t expr_part_guid(const App& app, const char* arg,
fidl::UnownedClientEnd<fuchsia_io::Directory> dir, const char* name) {
zx::result partition =
component::ConnectAt<fuchsia_hardware_block_partition::Partition>(dir, name);
if (partition.is_error()) {
fprintf(stderr, "waitfor: warning: failed to open: '%s :%s \n", name,
partition.status_string());
return partition.error_value();
}
const fidl::WireResult result = fidl::WireCall(partition.value())->GetInstanceGuid();
if (!result.ok()) {
fprintf(stderr, "waitfor: warning: cannot request instance guid: %s\n",
result.FormatDescription().c_str());
return result.status();
}
const fidl::WireResponse response = result.value();
if (const zx_status_t status = response.status; status != ZX_OK) {
fprintf(stderr, "waitfor: warning: cannot get instance guid: %s\n",
zx_status_get_string(status));
return result.status();
}
fidl::Array value = response.guid->value;
static_assert(decltype(value)::size() == uuid::kUuidSize);
const std::string text = uuid::Uuid(value.data()).ToString();
if (app.verbose) {
fprintf(stderr, "waitfor: part.guid='%s'\n", text.c_str());
}
if (strcasecmp(text.c_str(), arg) == 0) {
return ZX_OK;
}
return ZX_ERR_NEXT;
}
zx_status_t expr_part_type_guid(const App& app, const char* arg,
fidl::UnownedClientEnd<fuchsia_io::Directory> dir,
const char* name) {
zx::result partition =
component::ConnectAt<fuchsia_hardware_block_partition::Partition>(dir, name);
if (partition.is_error()) {
fprintf(stderr, "waitfor: warning: failed to open: '%s' :%s \n", name,
partition.status_string());
return partition.error_value();
}
const fidl::WireResult result = fidl::WireCall(partition.value())->GetTypeGuid();
if (!result.ok()) {
fprintf(stderr, "waitfor: warning: cannot request type guid: %s\n",
result.FormatDescription().c_str());
return result.status();
}
const fidl::WireResponse response = result.value();
if (const zx_status_t status = response.status; status != ZX_OK) {
fprintf(stderr, "waitfor: warning: cannot get type guid: %s\n", zx_status_get_string(status));
return result.status();
}
fidl::Array value = response.guid->value;
static_assert(decltype(value)::size() == uuid::kUuidSize);
const std::string text = uuid::Uuid(value.data()).ToString();
if (app.verbose) {
fprintf(stderr, "waitfor: part.type.guid='%s'\n", text.c_str());
}
if (strcasecmp(text.c_str(), arg) == 0) {
return ZX_OK;
}
return ZX_ERR_NEXT;
}
zx_status_t expr_part_name(const App& app, const char* arg,
fidl::UnownedClientEnd<fuchsia_io::Directory> dir, const char* name) {
zx::result partition =
component::ConnectAt<fuchsia_hardware_block_partition::Partition>(dir, name);
if (partition.is_error()) {
fprintf(stderr, "waitfor: warning: failed to open: '%s' :%s \n", name,
partition.status_string());
return partition.error_value();
}
const fidl::WireResult result = fidl::WireCall(partition.value())->GetName();
if (!result.ok()) {
fprintf(stderr, "waitfor: warning: cannot request partition name: %s\n",
result.FormatDescription().c_str());
return result.status();
}
const fidl::WireResponse response = result.value();
if (const zx_status_t status = response.status; status != ZX_OK) {
fprintf(stderr, "waitfor: warning: cannot get type guid: %s\n", zx_status_get_string(status));
return result.status();
}
const std::string partition_name(response.name.get());
if (app.verbose) {
fprintf(stderr, "waitfor: part.name='%s'\n", partition_name.c_str());
}
if (strcmp(arg, partition_name.c_str()) == 0) {
return ZX_OK;
}
return ZX_ERR_NEXT;
}
int main(int argc, char** argv) {
if (argc == 1) {
usage();
exit(1);
}
bool print = false;
bool verbose = false;
bool forever = false;
zx_duration_t timeout = 0;
const char* devclass = nullptr;
std::vector<Rule> rules;
while (argc > 1) {
if (!strcmp(argv[1], "print")) {
print = true;
} else if (!strcmp(argv[1], "verbose")) {
verbose = true;
} else if (!strcmp(argv[1], "forever")) {
forever = true;
} else if (!strncmp(argv[1], "timeout=", 8)) {
timeout = ZX_MSEC(atoi(argv[1] + 8));
if (timeout == 0) {
fprintf(stderr, "waitfor: error: timeout of 0 not allowed\n");
exit(1);
}
} else if (!strncmp(argv[1], "class=", 6)) {
devclass = argv[1] + 6;
} else if (!strncmp(argv[1], "topo=", 5)) {
rules.emplace_back(argv[1] + 5, expr_topo);
} else if (!strncmp(argv[1], "part.guid=", 10)) {
rules.emplace_back(argv[1] + 10, expr_part_guid);
} else if (!strncmp(argv[1], "part.type.guid=", 15)) {
rules.emplace_back(argv[1] + 15, expr_part_guid);
} else if (!strncmp(argv[1], "part.name=", 10)) {
rules.emplace_back(argv[1] + 10, expr_part_name);
} else {
fprintf(stderr, "waitfor: error: unknown expr '%s'\n\n", argv[1]);
usage();
exit(1);
}
argc--;
argv++;
}
if (devclass == nullptr) {
fprintf(stderr, "waitfor: error: no class specified\n");
exit(1);
}
if (rules.empty()) {
fprintf(stderr, "waitfor: error: no match expressions specified\n");
exit(1);
}
const std::string path = std::string("/dev/class/") + devclass;
const fbl::unique_fd dirfd(open(path.c_str(), O_DIRECTORY | O_RDONLY));
if (!dirfd.is_valid()) {
fprintf(stderr, "waitfor: error: cannot watch class '%s': %s\n", devclass, strerror(errno));
exit(1);
}
zx_time_t deadline;
if (timeout == 0) {
deadline = ZX_TIME_INFINITE;
} else {
deadline = zx_deadline_after(timeout);
}
App app = {
.verbose = verbose,
.print = print,
.forever = forever,
.devclass = devclass,
.rules = std::move(rules),
};
switch (const zx_status_t status = fdio_watch_directory(dirfd.get(), watchcb, deadline, &app);
status) {
case ZX_ERR_STOP:
// clean exit on a match
return 0;
case ZX_ERR_TIMED_OUT:
// timeout, but if we're in forever mode and matched any, its good
if (app.matched && app.forever) {
return 0;
}
break;
default:
// any other situation? failure
return 1;
}
}