1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <fcntl.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <unistd.h>
10
11 #include <lib/fdio/watcher.h>
12
13 // for guid printing
14 #include <gpt/gpt.h>
15
16 #include <zircon/listnode.h>
17 #include <zircon/syscalls.h>
18
19 #include <zircon/device/block.h>
20 #include <zircon/device/device.h>
21
usage(void)22 void usage(void) {
23 fprintf(stderr,
24 "usage: waitfor <expr>+ wait for devices to be published\n"
25 "\n"
26 "expr: class=<name> device class <name> (required)\n"
27 "\n"
28 " topo=<path> topological path starts with <path>\n"
29 " part.guid=<guid> block device GUID matches <guid>\n"
30 " part.type.guid=<guid> partition type GUID matches <guid>\n"
31 " part.name=<name> partition name matches <name>\n"
32 "\n"
33 " timeout=<msec> fail if no match after <msec> milliseconds\n"
34 " print write name of matching devices to stdout\n"
35 " forever don't stop after the first match\n"
36 " also don't fail on timeout after first match\n"
37 " verbose print debug chatter to stderr\n"
38 "\n"
39 "example: waitfor class=block part.name=system print\n"
40 );
41 }
42
43 static bool verbose = false;
44 static bool print = false;
45 static bool forever = false;
46 static bool matched = false;
47 static zx_duration_t timeout = 0;
48 const char* devclass = NULL;
49
50 typedef struct rule {
51 zx_status_t (*func)(const char* arg, int fd);
52 const char* arg;
53 list_node_t node;
54 } rule_t;
55
56 static list_node_t rules = LIST_INITIAL_VALUE(rules);
57
watchcb(int dirfd,int event,const char * fn,void * cookie)58 zx_status_t watchcb(int dirfd, int event, const char* fn, void* cookie) {
59 if (event != WATCH_EVENT_ADD_FILE) {
60 return ZX_OK;
61 }
62 if (verbose) {
63 fprintf(stderr, "waitfor: device='/dev/class/%s/%s'\n", devclass, fn);
64 }
65 int fd;
66 if ((fd = openat(dirfd, fn, O_RDONLY)) < 0) {
67 fprintf(stderr, "waitfor: warning: failed to open '/dev/class/%s/%s'\n",
68 devclass, fn);
69 return ZX_OK;
70 }
71
72 rule_t* r;
73 list_for_every_entry(&rules, r, rule_t, node) {
74 zx_status_t status = r->func(r->arg, fd);
75 switch (status) {
76 case ZX_OK:
77 // rule matched
78 continue;
79 case ZX_ERR_NEXT:
80 // rule did not match
81 close(fd);
82 return ZX_OK;
83 default:
84 // fatal error
85 close(fd);
86 return status;
87 }
88 }
89
90 matched = true;
91 close(fd);
92
93 if (print) {
94 printf("/dev/class/%s/%s\n", devclass, fn);
95 }
96
97 if (forever) {
98 return ZX_OK;
99 } else {
100 return ZX_ERR_STOP;
101 }
102 }
103
104 // Expression evaluators return OK on match, NEXT on no-match
105 // any other error is fatal
106
expr_topo(const char * arg,int fd)107 zx_status_t expr_topo(const char* arg, int fd) {
108 char topo[1024+1];
109 int r = ioctl_device_get_topo_path(fd, topo, sizeof(topo) - 1);
110 if (r < 0) {
111 fprintf(stderr, "waitfor: warning: cannot read topo path\n");
112 return ZX_ERR_NEXT;
113 }
114 topo[r] = 0;
115 if (verbose) {
116 fprintf(stderr, "waitfor: topo='%s'\n", topo);
117 }
118 int len = strlen(arg);
119 if ((r < len) || strncmp(arg, topo, len)) {
120 return ZX_ERR_NEXT;
121 } else {
122 return ZX_OK;
123 }
124 }
125
expr_part_guid(const char * arg,int fd)126 zx_status_t expr_part_guid(const char* arg, int fd) {
127 uint8_t guid[GPT_GUID_LEN];
128 if (ioctl_block_get_partition_guid(fd, guid, sizeof(guid)) != sizeof(guid)) {
129 fprintf(stderr, "waitfor: warning: cannot read partition guid\n");
130 return ZX_ERR_NEXT;
131 }
132 char text[GPT_GUID_STRLEN];
133 uint8_to_guid_string(text, guid);
134 if (verbose) {
135 fprintf(stderr, "waitfor: part.guid='%s'\n", text);
136 }
137 if (strcasecmp(text, arg)) {
138 return ZX_ERR_NEXT;
139 } else {
140 return ZX_OK;
141 }
142 }
143
expr_part_type_guid(const char * arg,int fd)144 zx_status_t expr_part_type_guid(const char* arg, int fd) {
145 uint8_t guid[GPT_GUID_LEN];
146 if (ioctl_block_get_type_guid(fd, guid, sizeof(guid)) != sizeof(guid)) {
147 fprintf(stderr, "waitfor: warning: cannot read partition type guid\n");
148 return ZX_ERR_NEXT;
149 }
150 char text[GPT_GUID_STRLEN];
151 uint8_to_guid_string(text, guid);
152 if (verbose) {
153 fprintf(stderr, "waitfor: part.type.guid='%s'\n", text);
154 }
155 if (strcasecmp(text, arg)) {
156 return ZX_ERR_NEXT;
157 } else {
158 return ZX_OK;
159 }
160 }
161
expr_part_name(const char * arg,int fd)162 zx_status_t expr_part_name(const char* arg, int fd) {
163 char name[256 + 1];
164 int r = ioctl_block_get_name(fd, name, sizeof(name) - 1);
165 if (r < 0) {
166 fprintf(stderr, "waitfor: warning: cannot read partition name\n");
167 return ZX_ERR_NEXT;
168 }
169 name[r] = 0;
170 if (verbose) {
171 fprintf(stderr, "waitfor: part.name='%s'\n", name);
172 }
173 if (strcmp(arg, name)) {
174 return ZX_ERR_NEXT;
175 } else {
176 return ZX_OK;
177 }
178 }
179
180
181
new_rule(const char * arg,zx_status_t (* func)(const char * arg,int fd))182 void new_rule(const char* arg, zx_status_t (*func)(const char* arg, int fd)) {
183 rule_t* r = malloc(sizeof(rule_t));
184 if (r == NULL) {
185 fprintf(stderr, "waitfor: error: out of memory\n");
186 exit(1);
187 }
188 r->func = func;
189 r->arg = arg;
190 list_add_tail(&rules, &r->node);
191 }
192
main(int argc,char ** argv)193 int main(int argc, char** argv) {
194 int dirfd = -1;
195
196 if (argc == 1) {
197 usage();
198 exit(1);
199 }
200
201 while (argc > 1) {
202 if (!strcmp(argv[1], "print")) {
203 print = true;
204 } else if (!strcmp(argv[1], "verbose")) {
205 verbose = true;
206 } else if (!strcmp(argv[1], "forever")) {
207 forever = true;
208 } else if (!strncmp(argv[1], "timeout=", 8)) {
209 timeout = ZX_MSEC(atoi(argv[1] + 8));
210 if (timeout == 0) {
211 fprintf(stderr, "waitfor: error: timeout of 0 not allowed\n");
212 exit(1);
213 }
214 } else if (!strncmp(argv[1], "class=", 6)) {
215 devclass = argv[1] + 6;
216 } else if (!strncmp(argv[1], "topo=", 5)) {
217 new_rule(argv[1] + 5, expr_topo);
218 } else if (!strncmp(argv[1], "part.guid=", 10)) {
219 new_rule(argv[1] + 10, expr_part_guid);
220 } else if (!strncmp(argv[1], "part.type.guid=", 15)) {
221 new_rule(argv[1] + 15, expr_part_guid);
222 } else if (!strncmp(argv[1], "part.name=", 10)) {
223 new_rule(argv[1] + 10, expr_part_name);
224 } else {
225 fprintf(stderr, "waitfor: error: unknown expr '%s'\n\n", argv[1]);
226 usage();
227 exit(1);
228 }
229 argc--;
230 argv++;
231 }
232
233 if (devclass == NULL) {
234 fprintf(stderr, "waitfor: error: no class specified\n");
235 exit(1);
236 }
237
238 if (list_is_empty(&rules)) {
239 fprintf(stderr, "waitfor: error: no match expressions specified\n");
240 exit(1);
241 }
242
243 char path[strlen(devclass) + strlen("/dev/class/") + 1];
244 sprintf(path, "/dev/class/%s", devclass);
245
246 if ((dirfd = open(path, O_DIRECTORY | O_RDONLY)) < 0) {
247 fprintf(stderr, "waitfor: error: cannot watch class '%s'\n", devclass);
248 exit(1);
249 }
250
251 zx_time_t deadline;
252 if (timeout == 0) {
253 deadline = ZX_TIME_INFINITE;
254 } else {
255 deadline = zx_deadline_after(timeout);
256 }
257 zx_status_t status = fdio_watch_directory(dirfd, watchcb, deadline, NULL);
258 close(dirfd);
259
260 switch (status) {
261 case ZX_ERR_STOP:
262 // clean exit on a match
263 return 0;
264 case ZX_ERR_TIMED_OUT:
265 // timeout, but if we're in forever mode and matched any, its good
266 if (matched && forever) {
267 return 0;
268 }
269 break;
270 default:
271 // any other situation? failure
272 return 1;
273 }
274 }
275