2
0

blockd.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. #define _GNU_SOURCE
  2. #include <sys/stat.h>
  3. #include <sys/mount.h>
  4. #include <sys/wait.h>
  5. #include <stdlib.h>
  6. #include <stdio.h>
  7. #include <unistd.h>
  8. #include <fcntl.h>
  9. #include <errno.h>
  10. #include <linux/limits.h>
  11. #include <linux/auto_fs4.h>
  12. #include <libubox/uloop.h>
  13. #include <libubox/utils.h>
  14. #include <libubox/vlist.h>
  15. #include <libubox/ulog.h>
  16. #include <libubox/avl-cmp.h>
  17. #include <libubus.h>
  18. #include "libfstools/libfstools.h"
  19. #define AUTOFS_MOUNT_PATH "/tmp/run/blockd/"
  20. #define AUTOFS_TIMEOUT 30
  21. #define AUTOFS_EXPIRE_TIMER (5 * 1000)
  22. struct hotplug_context {
  23. struct uloop_process process;
  24. void *priv;
  25. };
  26. struct device {
  27. struct vlist_node node;
  28. struct blob_attr *msg;
  29. char *name;
  30. char *target;
  31. int autofs;
  32. int anon;
  33. };
  34. static struct uloop_fd fd_autofs_read;
  35. static int fd_autofs_write = 0;
  36. static struct ubus_auto_conn conn;
  37. struct blob_buf bb = { 0 };
  38. enum {
  39. MOUNT_UUID,
  40. MOUNT_LABEL,
  41. MOUNT_ENABLE,
  42. MOUNT_TARGET,
  43. MOUNT_DEVICE,
  44. MOUNT_OPTIONS,
  45. MOUNT_AUTOFS,
  46. MOUNT_ANON,
  47. MOUNT_REMOVE,
  48. __MOUNT_MAX
  49. };
  50. static const struct blobmsg_policy mount_policy[__MOUNT_MAX] = {
  51. [MOUNT_UUID] = { .name = "uuid", .type = BLOBMSG_TYPE_STRING },
  52. [MOUNT_LABEL] = { .name = "label", .type = BLOBMSG_TYPE_STRING },
  53. [MOUNT_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
  54. [MOUNT_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
  55. [MOUNT_OPTIONS] = { .name = "options", .type = BLOBMSG_TYPE_STRING },
  56. [MOUNT_ENABLE] = { .name = "enabled", .type = BLOBMSG_TYPE_INT32 },
  57. [MOUNT_AUTOFS] = { .name = "autofs", .type = BLOBMSG_TYPE_INT32 },
  58. [MOUNT_ANON] = { .name = "anon", .type = BLOBMSG_TYPE_INT32 },
  59. [MOUNT_REMOVE] = { .name = "remove", .type = BLOBMSG_TYPE_INT32 },
  60. };
  61. enum {
  62. INFO_DEVICE,
  63. __INFO_MAX
  64. };
  65. static const struct blobmsg_policy info_policy[__INFO_MAX] = {
  66. [INFO_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
  67. };
  68. static char*
  69. _find_mount_point(char *device)
  70. {
  71. char *dev, *mp;
  72. if (asprintf(&dev, "/dev/%s", device) == -1)
  73. exit(ENOMEM);
  74. mp = find_mount_point(dev, 0);
  75. free(dev);
  76. return mp;
  77. }
  78. static int
  79. block(char *cmd, char *action, char *device, int sync, struct uloop_process *process)
  80. {
  81. pid_t pid = fork();
  82. int ret = sync;
  83. int status;
  84. char *argv[5] = { 0 };
  85. int a = 0;
  86. switch (pid) {
  87. case -1:
  88. ULOG_ERR("failed to fork block process\n");
  89. break;
  90. case 0:
  91. uloop_end();
  92. argv[a++] = "/sbin/block";
  93. argv[a++] = cmd;
  94. argv[a++] = action;
  95. argv[a++] = device;
  96. execvp(argv[0], argv);
  97. ULOG_ERR("failed to spawn %s %s %s\n", *argv, action, device);
  98. exit(EXIT_FAILURE);
  99. default:
  100. if (!sync && process) {
  101. process->pid = pid;
  102. uloop_process_add(process);
  103. } else if (sync) {
  104. waitpid(pid, &status, 0);
  105. ret = WEXITSTATUS(status);
  106. if (ret)
  107. ULOG_ERR("failed to run block. %s/%s\n", action, device);
  108. }
  109. break;
  110. }
  111. return ret;
  112. }
  113. static int send_block_notification(struct ubus_context *ctx, const char *action,
  114. const char *devname, const char *target);
  115. static int hotplug_call_mount(struct ubus_context *ctx, const char *action,
  116. const char *devname, uloop_process_handler cb, void *priv)
  117. {
  118. char * const argv[] = { "hotplug-call", "mount", NULL };
  119. struct hotplug_context *c = NULL;
  120. pid_t pid;
  121. int err;
  122. if (cb) {
  123. c = calloc(1, sizeof(*c));
  124. if (!c)
  125. return -ENOMEM;
  126. }
  127. pid = fork();
  128. switch (pid) {
  129. case -1:
  130. if (c)
  131. free(c);
  132. err = -errno;
  133. ULOG_ERR("fork() failed\n");
  134. return err;
  135. case 0:
  136. uloop_end();
  137. setenv("ACTION", action, 1);
  138. setenv("DEVICE", devname, 1);
  139. execv("/sbin/hotplug-call", argv);
  140. exit(-1);
  141. break;
  142. default:
  143. if (c) {
  144. c->process.pid = pid;
  145. c->process.cb = cb;
  146. c->priv = priv;
  147. uloop_process_add(&c->process);
  148. }
  149. break;
  150. }
  151. return 0;
  152. }
  153. static void device_mount_remove_hotplug_cb(struct uloop_process *p, int stat)
  154. {
  155. struct hotplug_context *hctx = container_of(p, struct hotplug_context, process);
  156. struct device *device = hctx->priv;
  157. char *mp;
  158. if (device->target)
  159. unlink(device->target);
  160. mp = _find_mount_point(device->name);
  161. if (mp) {
  162. block("autofs", "remove", device->name, 0, NULL);
  163. free(mp);
  164. }
  165. free(device);
  166. free(hctx);
  167. }
  168. static void device_mount_remove(struct ubus_context *ctx, struct device *device)
  169. {
  170. static const char *action = "remove";
  171. hotplug_call_mount(ctx, action, device->name,
  172. device_mount_remove_hotplug_cb, device);
  173. send_block_notification(ctx, action, device->name, device->target);
  174. }
  175. static void device_mount_add(struct ubus_context *ctx, struct device *device)
  176. {
  177. struct stat st;
  178. char *path, *tmp;
  179. if (asprintf(&path, "/tmp/run/blockd/%s", device->name) == -1)
  180. exit(ENOMEM);
  181. if (!lstat(device->target, &st)) {
  182. if (S_ISLNK(st.st_mode))
  183. unlink(device->target);
  184. else if (S_ISDIR(st.st_mode))
  185. rmdir(device->target);
  186. }
  187. tmp = strrchr(device->target, '/');
  188. if (tmp && tmp != device->target && tmp != &device->target[strlen(path)-1]) {
  189. *tmp = '\0';
  190. mkdir_p(device->target, 0755);
  191. *tmp = '/';
  192. }
  193. if (symlink(path, device->target)) {
  194. ULOG_ERR("failed to symlink %s->%s (%d) - %m\n", device->target, path, errno);
  195. } else {
  196. static const char *action = "add";
  197. hotplug_call_mount(ctx, action, device->name, NULL, NULL);
  198. send_block_notification(ctx, action, device->name, device->target);
  199. }
  200. free(path);
  201. }
  202. static int
  203. device_move(struct device *device_o, struct device *device_n)
  204. {
  205. char *path;
  206. if (device_o->autofs != device_n->autofs)
  207. return -1;
  208. if (device_o->anon || device_n->anon)
  209. return -1;
  210. if (device_o->autofs) {
  211. unlink(device_o->target);
  212. if (asprintf(&path, "/tmp/run/blockd/%s", device_n->name) == -1)
  213. exit(ENOMEM);
  214. if (symlink(path, device_n->target))
  215. ULOG_ERR("failed to symlink %s->%s (%d) - %m\n", device_n->target, path, errno);
  216. free(path);
  217. } else {
  218. mkdir(device_n->target, 0755);
  219. if (mount(device_o->target, device_n->target, NULL, MS_MOVE, NULL))
  220. rmdir(device_n->target);
  221. else
  222. rmdir(device_o->target);
  223. }
  224. return 0;
  225. }
  226. static void vlist_nop_update(struct vlist_tree *tree,
  227. struct vlist_node *node_new,
  228. struct vlist_node *node_old)
  229. {
  230. }
  231. VLIST_TREE(devices, avl_strcmp, vlist_nop_update, false, false);
  232. static int
  233. block_hotplug(struct ubus_context *ctx, struct ubus_object *obj,
  234. struct ubus_request_data *req, const char *method,
  235. struct blob_attr *msg)
  236. {
  237. struct blob_attr *data[__MOUNT_MAX];
  238. struct device *device;
  239. struct blob_attr *_msg;
  240. char *devname, *_name;
  241. char *target = NULL, *__target;
  242. char *_target = NULL;
  243. blobmsg_parse(mount_policy, __MOUNT_MAX, data, blob_data(msg), blob_len(msg));
  244. if (!data[MOUNT_DEVICE])
  245. return UBUS_STATUS_INVALID_ARGUMENT;
  246. devname = blobmsg_get_string(data[MOUNT_DEVICE]);
  247. if (data[MOUNT_TARGET]) {
  248. target = blobmsg_get_string(data[MOUNT_TARGET]);
  249. } else {
  250. if (asprintf(&_target, "/mnt/%s",
  251. blobmsg_get_string(data[MOUNT_DEVICE])) == -1)
  252. exit(ENOMEM);
  253. target = _target;
  254. }
  255. if (data[MOUNT_REMOVE])
  256. device = vlist_find(&devices, devname, device, node);
  257. else
  258. device = calloc_a(sizeof(*device), &_msg, blob_raw_len(msg),
  259. &_name, strlen(devname) + 1, &__target, strlen(target) + 1);
  260. if (!device) {
  261. if (_target)
  262. free(_target);
  263. return UBUS_STATUS_UNKNOWN_ERROR;
  264. }
  265. if (data[MOUNT_REMOVE]) {
  266. vlist_delete(&devices, &device->node);
  267. if (device->autofs)
  268. device_mount_remove(ctx, device);
  269. else
  270. free(device);
  271. if (_target)
  272. free(_target);
  273. } else {
  274. struct device *old = vlist_find(&devices, devname, device, node);
  275. device->autofs = data[MOUNT_AUTOFS] ? blobmsg_get_u32(data[MOUNT_AUTOFS]) : 0;
  276. device->anon = data[MOUNT_ANON] ? blobmsg_get_u32(data[MOUNT_ANON]) : 0;
  277. device->msg = _msg;
  278. memcpy(_msg, msg, blob_raw_len(msg));
  279. device->name = _name;
  280. strcpy(_name, devname);
  281. device->target = __target;
  282. strcpy(__target, target);
  283. if (_target)
  284. free(_target);
  285. vlist_add(&devices, &device->node, device->name);
  286. if (old && device_move(old, device)) {
  287. device_mount_remove(ctx, old);
  288. device_mount_add(ctx, device);
  289. if (!device->autofs)
  290. block("mount", NULL, NULL, 0, NULL);
  291. } else if (device->autofs) {
  292. device_mount_add(ctx, device);
  293. }
  294. }
  295. return 0;
  296. }
  297. static int blockd_mount(struct ubus_context *ctx, struct ubus_object *obj,
  298. struct ubus_request_data *req, const char *method,
  299. struct blob_attr *msg)
  300. {
  301. static const char *action = "add";
  302. struct blob_attr *data[__MOUNT_MAX];
  303. struct device *device;
  304. char *devname;
  305. blobmsg_parse(mount_policy, __MOUNT_MAX, data, blob_data(msg), blob_len(msg));
  306. if (!data[MOUNT_DEVICE])
  307. return UBUS_STATUS_INVALID_ARGUMENT;
  308. devname = blobmsg_get_string(data[MOUNT_DEVICE]);
  309. device = vlist_find(&devices, devname, device, node);
  310. if (!device)
  311. return UBUS_STATUS_UNKNOWN_ERROR;
  312. hotplug_call_mount(ctx, action, device->name, NULL, NULL);
  313. send_block_notification(ctx, action, device->name, device->target);
  314. return 0;
  315. }
  316. struct blockd_umount_context {
  317. struct ubus_context *ctx;
  318. struct ubus_request_data req;
  319. };
  320. static void blockd_umount_hotplug_cb(struct uloop_process *p, int stat)
  321. {
  322. struct hotplug_context *hctx = container_of(p, struct hotplug_context, process);
  323. struct blockd_umount_context *c = hctx->priv;
  324. ubus_complete_deferred_request(c->ctx, &c->req, 0);
  325. free(c);
  326. free(hctx);
  327. }
  328. static int blockd_umount(struct ubus_context *ctx, struct ubus_object *obj,
  329. struct ubus_request_data *req, const char *method,
  330. struct blob_attr *msg)
  331. {
  332. struct blob_attr *data[__MOUNT_MAX];
  333. struct blockd_umount_context *c;
  334. static const char *action = "remove";
  335. char *devname;
  336. static char oldtarget[PATH_MAX];
  337. struct device *device;
  338. int err;
  339. blobmsg_parse(mount_policy, __MOUNT_MAX, data, blob_data(msg), blob_len(msg));
  340. if (!data[MOUNT_DEVICE])
  341. return UBUS_STATUS_INVALID_ARGUMENT;
  342. devname = blobmsg_get_string(data[MOUNT_DEVICE]);
  343. device = vlist_find(&devices, devname, device, node);
  344. if (device) {
  345. strncpy(oldtarget, device->target, sizeof(oldtarget)-1);
  346. oldtarget[PATH_MAX - 1] = '\0';
  347. }
  348. c = calloc(1, sizeof(*c));
  349. if (!c)
  350. return UBUS_STATUS_UNKNOWN_ERROR;
  351. c->ctx = ctx;
  352. ubus_defer_request(ctx, req, &c->req);
  353. err = hotplug_call_mount(ctx, action, devname, blockd_umount_hotplug_cb, c);
  354. if (err) {
  355. free(c);
  356. return UBUS_STATUS_UNKNOWN_ERROR;
  357. }
  358. send_block_notification(ctx, action, devname, oldtarget);
  359. return 0;
  360. }
  361. static void block_info_dump(struct blob_buf *b, struct device *device)
  362. {
  363. struct blob_attr *v;
  364. char *mp;
  365. int rem;
  366. blob_for_each_attr(v, device->msg, rem)
  367. blobmsg_add_blob(b, v);
  368. mp = _find_mount_point(device->name);
  369. if (mp) {
  370. blobmsg_add_string(b, "mount", mp);
  371. free(mp);
  372. } else if (device->autofs && device->target) {
  373. blobmsg_add_string(b, "mount", device->target);
  374. }
  375. }
  376. static int
  377. block_info(struct ubus_context *ctx, struct ubus_object *obj,
  378. struct ubus_request_data *req, const char *method,
  379. struct blob_attr *msg)
  380. {
  381. struct blob_attr *data[__INFO_MAX];
  382. struct device *device = NULL;
  383. blobmsg_parse(info_policy, __INFO_MAX, data, blob_data(msg), blob_len(msg));
  384. if (data[INFO_DEVICE]) {
  385. device = vlist_find(&devices, blobmsg_get_string(data[INFO_DEVICE]), device, node);
  386. if (!device)
  387. return UBUS_STATUS_INVALID_ARGUMENT;
  388. }
  389. blob_buf_init(&bb, 0);
  390. if (device) {
  391. block_info_dump(&bb, device);
  392. } else {
  393. void *a;
  394. a = blobmsg_open_array(&bb, "devices");
  395. vlist_for_each_element(&devices, device, node) {
  396. void *t;
  397. t = blobmsg_open_table(&bb, "");
  398. block_info_dump(&bb, device);
  399. blobmsg_close_table(&bb, t);
  400. }
  401. blobmsg_close_array(&bb, a);
  402. }
  403. ubus_send_reply(ctx, req, bb.head);
  404. return 0;
  405. }
  406. static const struct ubus_method block_methods[] = {
  407. UBUS_METHOD("hotplug", block_hotplug, mount_policy),
  408. UBUS_METHOD("mount", blockd_mount, mount_policy),
  409. UBUS_METHOD("umount", blockd_umount, mount_policy),
  410. UBUS_METHOD("info", block_info, info_policy),
  411. };
  412. static struct ubus_object_type block_object_type =
  413. UBUS_OBJECT_TYPE("block", block_methods);
  414. static struct ubus_object block_object = {
  415. .name = "block",
  416. .type = &block_object_type,
  417. .methods = block_methods,
  418. .n_methods = ARRAY_SIZE(block_methods),
  419. };
  420. /* send ubus event for successful mounts, useful for procd triggers */
  421. static int send_block_notification(struct ubus_context *ctx, const char *action,
  422. const char *devname, const char *target)
  423. {
  424. struct blob_buf buf = { 0 };
  425. char evname[16] = "mount.";
  426. int err;
  427. if (!ctx)
  428. return -ENXIO;
  429. strncat(evname, action, sizeof(evname) - 1);
  430. blob_buf_init(&buf, 0);
  431. if (devname)
  432. blobmsg_add_string(&buf, "device", devname);
  433. if (target)
  434. blobmsg_add_string(&buf, "target", target);
  435. err = ubus_notify(ctx, &block_object, evname, buf.head, -1);
  436. return err;
  437. }
  438. static void
  439. ubus_connect_handler(struct ubus_context *ctx)
  440. {
  441. int ret;
  442. ret = ubus_add_object(ctx, &block_object);
  443. if (ret)
  444. fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret));
  445. }
  446. static int autofs_umount(void)
  447. {
  448. umount2(AUTOFS_MOUNT_PATH, MNT_DETACH);
  449. return 0;
  450. }
  451. static void autofs_read_handler(struct uloop_fd *u, unsigned int events)
  452. {
  453. union autofs_v5_packet_union pktu;
  454. const struct autofs_v5_packet *pkt;
  455. int cmd = AUTOFS_IOC_READY;
  456. struct stat st;
  457. while (read(u->fd, &pktu, sizeof(pktu)) == -1) {
  458. if (errno != EINTR)
  459. return;
  460. continue;
  461. }
  462. if (pktu.hdr.type != autofs_ptype_missing_indirect) {
  463. ULOG_ERR("unknown packet type %d\n", pktu.hdr.type);
  464. return;
  465. }
  466. pkt = &pktu.missing_indirect;
  467. ULOG_ERR("kernel is requesting a mount -> %s\n", pkt->name);
  468. if (lstat(pkt->name, &st) == -1)
  469. if (block("autofs", "add", (char *)pkt->name, 1, NULL))
  470. cmd = AUTOFS_IOC_FAIL;
  471. if (ioctl(fd_autofs_write, cmd, pkt->wait_queue_token) < 0)
  472. ULOG_ERR("failed to report back to kernel\n");
  473. }
  474. static void autofs_expire(struct uloop_timeout *t)
  475. {
  476. struct autofs_packet_expire pkt;
  477. while (ioctl(fd_autofs_write, AUTOFS_IOC_EXPIRE, &pkt) == 0)
  478. block("autofs", "remove", pkt.name, 1, NULL);
  479. uloop_timeout_set(t, AUTOFS_EXPIRE_TIMER);
  480. }
  481. struct uloop_timeout autofs_expire_timer = {
  482. .cb = autofs_expire,
  483. };
  484. static int autofs_mount(void)
  485. {
  486. unsigned long autofs_timeout = AUTOFS_TIMEOUT;
  487. int kproto_version;
  488. int pipefd[2];
  489. char source[64];
  490. char opts[64];
  491. if (pipe(pipefd) < 0) {
  492. ULOG_ERR("failed to get kernel pipe\n");
  493. return -1;
  494. }
  495. snprintf(source, sizeof(source), "mountd(pid%u)", getpid());
  496. snprintf(opts, sizeof(opts), "fd=%d,pgrp=%u,minproto=5,maxproto=5", pipefd[1], (unsigned) getpgrp());
  497. mkdir(AUTOFS_MOUNT_PATH, 0555);
  498. if (mount(source, AUTOFS_MOUNT_PATH, "autofs", 0, opts)) {
  499. ULOG_ERR("unable to mount autofs on %s\n", AUTOFS_MOUNT_PATH);
  500. close(pipefd[0]);
  501. close(pipefd[1]);
  502. return -1;
  503. }
  504. close(pipefd[1]);
  505. fd_autofs_read.fd = pipefd[0];
  506. fd_autofs_read.cb = autofs_read_handler;
  507. uloop_fd_add(&fd_autofs_read, ULOOP_READ);
  508. fd_autofs_write = open(AUTOFS_MOUNT_PATH, O_RDONLY);
  509. if(fd_autofs_write < 0) {
  510. autofs_umount();
  511. ULOG_ERR("failed to open direcory\n");
  512. return -1;
  513. }
  514. ioctl(fd_autofs_write, AUTOFS_IOC_PROTOVER, &kproto_version);
  515. if (kproto_version != 5) {
  516. ULOG_ERR("only kernel protocol version 5 is tested. You have %d.\n",
  517. kproto_version);
  518. exit(EXIT_FAILURE);
  519. }
  520. if (ioctl(fd_autofs_write, AUTOFS_IOC_SETTIMEOUT, &autofs_timeout))
  521. ULOG_ERR("failed to set autofs timeout\n");
  522. uloop_timeout_set(&autofs_expire_timer, AUTOFS_EXPIRE_TIMER);
  523. fcntl(fd_autofs_write, F_SETFD, fcntl(fd_autofs_write, F_GETFD) | FD_CLOEXEC);
  524. fcntl(fd_autofs_read.fd, F_SETFD, fcntl(fd_autofs_read.fd, F_GETFD) | FD_CLOEXEC);
  525. return 0;
  526. }
  527. static void blockd_startup_cb(struct uloop_process *p, int stat)
  528. {
  529. send_block_notification(&conn.ctx, "ready", NULL, NULL);
  530. }
  531. static struct uloop_process startup_process = {
  532. .cb = blockd_startup_cb,
  533. };
  534. static void blockd_startup(struct uloop_timeout *t)
  535. {
  536. block("autofs", "start", NULL, 0, &startup_process);
  537. }
  538. struct uloop_timeout startup = {
  539. .cb = blockd_startup,
  540. };
  541. int main(int argc, char **argv)
  542. {
  543. /* make sure blockd is in it's own POSIX process group */
  544. setpgrp();
  545. ulog_open(ULOG_SYSLOG | ULOG_STDIO, LOG_DAEMON, "blockd");
  546. uloop_init();
  547. autofs_mount();
  548. conn.cb = ubus_connect_handler;
  549. ubus_auto_connect(&conn);
  550. uloop_timeout_set(&startup, 1000);
  551. uloop_run();
  552. uloop_done();
  553. autofs_umount();
  554. vlist_flush_all(&devices);
  555. return 0;
  556. }