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