| // 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 <fuchsia/device/llcpp/fidl.h> | |
| #include <fuchsia/hardware/block/partition/c/fidl.h> | |
| #include <lib/fdio/unsafe.h> | |
| #include <lib/fdio/watcher.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <memory> | |
| // for guid printing | |
| #include <zircon/device/block.h> | |
| #include <zircon/syscalls.h> | |
| #include <fbl/intrusive_double_list.h> | |
| #include <gpt/c/gpt.h> | |
| void usage(void) { | |
| 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 starts with <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"); | |
| } | |
| static bool verbose = false; | |
| static bool print = false; | |
| static bool forever = false; | |
| static bool matched = false; | |
| static zx_duration_t timeout = 0; | |
| const char* devclass = NULL; | |
| class Rule : public fbl::DoublyLinkedListable<std::unique_ptr<Rule>> { | |
| public: | |
| using Func = zx_status_t (*)(const char* arg, int fd); | |
| Rule(Func func, const char* arg) : func_(func), arg_(arg) {} | |
| zx_status_t CallWithFd(int fd) const { return func_(arg_, fd); } | |
| private: | |
| Func func_; | |
| const char* arg_; | |
| }; | |
| static fbl::DoublyLinkedList<std::unique_ptr<Rule>> rules; | |
| zx_status_t watchcb(int dirfd, int event, const char* fn, void* cookie) { | |
| if (event != WATCH_EVENT_ADD_FILE) { | |
| return ZX_OK; | |
| } | |
| if (verbose) { | |
| fprintf(stderr, "waitfor: device='/dev/class/%s/%s'\n", devclass, fn); | |
| } | |
| int fd; | |
| if ((fd = openat(dirfd, fn, O_RDONLY)) < 0) { | |
| fprintf(stderr, "waitfor: warning: failed to open '/dev/class/%s/%s'\n", devclass, fn); | |
| return ZX_OK; | |
| } | |
| for (const Rule& r : rules) { | |
| zx_status_t status = r.CallWithFd(fd); | |
| switch (status) { | |
| case ZX_OK: | |
| // rule matched | |
| continue; | |
| case ZX_ERR_NEXT: | |
| // rule did not match | |
| close(fd); | |
| return ZX_OK; | |
| default: | |
| // fatal error | |
| close(fd); | |
| return status; | |
| } | |
| } | |
| matched = true; | |
| close(fd); | |
| if (print) { | |
| printf("/dev/class/%s/%s\n", devclass, fn); | |
| } | |
| if (forever) { | |
| return ZX_OK; | |
| } else { | |
| 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 char* arg, int fd) { | |
| char topo[1024 + 1]; | |
| fdio_t* io = fdio_unsafe_fd_to_io(fd); | |
| if (io == NULL) { | |
| return ZX_ERR_NEXT; | |
| } | |
| zx_status_t call_status = ZX_OK; | |
| size_t path_len; | |
| auto resp = ::llcpp::fuchsia::device::Controller::Call::GetTopologicalPath( | |
| zx::unowned_channel(fdio_unsafe_borrow_channel(io))); | |
| zx_status_t status = resp.status(); | |
| if (resp->result.is_err()) { | |
| call_status = resp->result.err(); | |
| } else { | |
| path_len = resp->result.response().path.size(); | |
| auto& r = resp->result.response(); | |
| memcpy(topo, r.path.data(), r.path.size()); | |
| } | |
| fdio_unsafe_release(io); | |
| if (status != ZX_OK || call_status != ZX_OK) { | |
| fprintf(stderr, "waitfor: warning: cannot read topo path\n"); | |
| return ZX_ERR_NEXT; | |
| } | |
| topo[path_len] = 0; | |
| if (verbose) { | |
| fprintf(stderr, "waitfor: topo='%s'\n", topo); | |
| } | |
| size_t len = strlen(arg); | |
| if ((path_len < len) || strncmp(arg, topo, len)) { | |
| return ZX_ERR_NEXT; | |
| } else { | |
| return ZX_OK; | |
| } | |
| } | |
| zx_status_t expr_part_guid(const char* arg, int fd) { | |
| fdio_t* io = fdio_unsafe_fd_to_io(fd); | |
| if (io == NULL) { | |
| return ZX_ERR_NEXT; | |
| } | |
| fuchsia_hardware_block_partition_GUID guid; | |
| zx_status_t status; | |
| zx_status_t io_status = fuchsia_hardware_block_partition_PartitionGetInstanceGuid( | |
| fdio_unsafe_borrow_channel(io), &status, &guid); | |
| fdio_unsafe_release(io); | |
| if (io_status != ZX_OK || status != ZX_OK) { | |
| fprintf(stderr, "waitfor: warning: cannot read partition guid\n"); | |
| return ZX_ERR_NEXT; | |
| } | |
| char text[GPT_GUID_STRLEN]; | |
| uint8_to_guid_string(text, guid.value); | |
| if (verbose) { | |
| fprintf(stderr, "waitfor: part.guid='%s'\n", text); | |
| } | |
| if (strcasecmp(text, arg)) { | |
| return ZX_ERR_NEXT; | |
| } else { | |
| return ZX_OK; | |
| } | |
| } | |
| zx_status_t expr_part_type_guid(const char* arg, int fd) { | |
| fdio_t* io = fdio_unsafe_fd_to_io(fd); | |
| if (io == NULL) { | |
| return ZX_ERR_NEXT; | |
| } | |
| fuchsia_hardware_block_partition_GUID guid; | |
| zx_status_t status; | |
| zx_status_t io_status = fuchsia_hardware_block_partition_PartitionGetTypeGuid( | |
| fdio_unsafe_borrow_channel(io), &status, &guid); | |
| fdio_unsafe_release(io); | |
| if (io_status != ZX_OK || status != ZX_OK) { | |
| fprintf(stderr, "waitfor: warning: cannot read type guid\n"); | |
| return ZX_ERR_NEXT; | |
| } | |
| char text[GPT_GUID_STRLEN]; | |
| uint8_to_guid_string(text, guid.value); | |
| if (verbose) { | |
| fprintf(stderr, "waitfor: part.type.guid='%s'\n", text); | |
| } | |
| if (strcasecmp(text, arg)) { | |
| return ZX_ERR_NEXT; | |
| } else { | |
| return ZX_OK; | |
| } | |
| } | |
| zx_status_t expr_part_name(const char* arg, int fd) { | |
| char name[NAME_MAX + 1]; | |
| fdio_t* io = fdio_unsafe_fd_to_io(fd); | |
| if (io == NULL) { | |
| return ZX_ERR_NEXT; | |
| } | |
| zx_status_t status; | |
| size_t actual; | |
| zx_status_t io_status = fuchsia_hardware_block_partition_PartitionGetName( | |
| fdio_unsafe_borrow_channel(io), &status, name, sizeof(name), &actual); | |
| fdio_unsafe_release(io); | |
| if (io_status != ZX_OK || status != ZX_OK) { | |
| fprintf(stderr, "waitfor: warning: cannot read partition name\n"); | |
| return ZX_ERR_NEXT; | |
| } | |
| if (verbose) { | |
| fprintf(stderr, "waitfor: part.name='%s'\n", name); | |
| } | |
| if (strcmp(arg, name)) { | |
| return ZX_ERR_NEXT; | |
| } else { | |
| return ZX_OK; | |
| } | |
| } | |
| void new_rule(const char* arg, zx_status_t (*func)(const char* arg, int fd)) { | |
| auto r = std::make_unique<Rule>(func, arg); | |
| if (r == nullptr) { | |
| fprintf(stderr, "waitfor: error: out of memory\n"); | |
| exit(1); | |
| } | |
| rules.push_back(std::move(r)); | |
| } | |
| int main(int argc, char** argv) { | |
| int dirfd = -1; | |
| if (argc == 1) { | |
| usage(); | |
| exit(1); | |
| } | |
| 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)) { | |
| new_rule(argv[1] + 5, expr_topo); | |
| } else if (!strncmp(argv[1], "part.guid=", 10)) { | |
| new_rule(argv[1] + 10, expr_part_guid); | |
| } else if (!strncmp(argv[1], "part.type.guid=", 15)) { | |
| new_rule(argv[1] + 15, expr_part_guid); | |
| } else if (!strncmp(argv[1], "part.name=", 10)) { | |
| new_rule(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 == NULL) { | |
| fprintf(stderr, "waitfor: error: no class specified\n"); | |
| exit(1); | |
| } | |
| if (rules.is_empty()) { | |
| fprintf(stderr, "waitfor: error: no match expressions specified\n"); | |
| exit(1); | |
| } | |
| char path[strlen(devclass) + strlen("/dev/class/") + 1]; | |
| sprintf(path, "/dev/class/%s", devclass); | |
| if ((dirfd = open(path, O_DIRECTORY | O_RDONLY)) < 0) { | |
| fprintf(stderr, "waitfor: error: cannot watch class '%s'\n", devclass); | |
| exit(1); | |
| } | |
| zx_time_t deadline; | |
| if (timeout == 0) { | |
| deadline = ZX_TIME_INFINITE; | |
| } else { | |
| deadline = zx_deadline_after(timeout); | |
| } | |
| zx_status_t status = fdio_watch_directory(dirfd, watchcb, deadline, NULL); | |
| close(dirfd); | |
| switch (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 (matched && forever) { | |
| return 0; | |
| } | |
| break; | |
| default: | |
| // any other situation? failure | |
| return 1; | |
| } | |
| } |