1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <errno.h>
18#include <fcntl.h>
19#include <net/if.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <sys/socket.h>
24#include <sys/mount.h>
25#include <sys/resource.h>
26#include <sys/time.h>
27#include <sys/types.h>
28#include <sys/stat.h>
29#include <sys/wait.h>
30#include <unistd.h>
31#include <linux/loop.h>
32#include <ext4_crypt_init_extensions.h>
33
34#include <selinux/selinux.h>
35#include <selinux/label.h>
36
37#include <fs_mgr.h>
38#include <base/stringprintf.h>
39#include <cutils/partition_utils.h>
40#include <cutils/android_reboot.h>
41#include <private/android_filesystem_config.h>
42
43#include "init.h"
44#include "keywords.h"
45#include "property_service.h"
46#include "devices.h"
47#include "init_parser.h"
48#include "util.h"
49#include "log.h"
50
51#define chmod DO_NOT_USE_CHMOD_USE_FCHMODAT_SYMLINK_NOFOLLOW
52
53int add_environment(const char *name, const char *value);
54
55// System call provided by bionic but not in any header file.
56extern "C" int init_module(void *, unsigned long, const char *);
57
58static int insmod(const char *filename, char *options)
59{
60    char filename_val[PROP_VALUE_MAX];
61    if (expand_props(filename_val, filename, sizeof(filename_val)) == -1) {
62        ERROR("insmod: cannot expand '%s'\n", filename);
63        return -EINVAL;
64    }
65
66    std::string module;
67    if (!read_file(filename_val, &module)) {
68        return -1;
69    }
70
71    // TODO: use finit_module for >= 3.8 kernels.
72    return init_module(&module[0], module.size(), options);
73}
74
75static int __ifupdown(const char *interface, int up)
76{
77    struct ifreq ifr;
78    int s, ret;
79
80    strlcpy(ifr.ifr_name, interface, IFNAMSIZ);
81
82    s = socket(AF_INET, SOCK_DGRAM, 0);
83    if (s < 0)
84        return -1;
85
86    ret = ioctl(s, SIOCGIFFLAGS, &ifr);
87    if (ret < 0) {
88        goto done;
89    }
90
91    if (up)
92        ifr.ifr_flags |= IFF_UP;
93    else
94        ifr.ifr_flags &= ~IFF_UP;
95
96    ret = ioctl(s, SIOCSIFFLAGS, &ifr);
97
98done:
99    close(s);
100    return ret;
101}
102
103static void service_start_if_not_disabled(struct service *svc)
104{
105    if (!(svc->flags & SVC_DISABLED)) {
106        service_start(svc, NULL);
107    } else {
108        svc->flags |= SVC_DISABLED_START;
109    }
110}
111
112int do_class_start(int nargs, char **args)
113{
114        /* Starting a class does not start services
115         * which are explicitly disabled.  They must
116         * be started individually.
117         */
118    service_for_each_class(args[1], service_start_if_not_disabled);
119    return 0;
120}
121
122int do_class_stop(int nargs, char **args)
123{
124    service_for_each_class(args[1], service_stop);
125    return 0;
126}
127
128int do_class_reset(int nargs, char **args)
129{
130    service_for_each_class(args[1], service_reset);
131    return 0;
132}
133
134int do_domainname(int nargs, char **args)
135{
136    return write_file("/proc/sys/kernel/domainname", args[1]);
137}
138
139int do_enable(int nargs, char **args)
140{
141    struct service *svc;
142    svc = service_find_by_name(args[1]);
143    if (svc) {
144        svc->flags &= ~(SVC_DISABLED | SVC_RC_DISABLED);
145        if (svc->flags & SVC_DISABLED_START) {
146            service_start(svc, NULL);
147        }
148    } else {
149        return -1;
150    }
151    return 0;
152}
153
154int do_exec(int nargs, char** args) {
155    service* svc = make_exec_oneshot_service(nargs, args);
156    if (svc == NULL) {
157        return -1;
158    }
159    service_start(svc, NULL);
160    return 0;
161}
162
163int do_export(int nargs, char **args)
164{
165    return add_environment(args[1], args[2]);
166}
167
168int do_hostname(int nargs, char **args)
169{
170    return write_file("/proc/sys/kernel/hostname", args[1]);
171}
172
173int do_ifup(int nargs, char **args)
174{
175    return __ifupdown(args[1], 1);
176}
177
178
179static int do_insmod_inner(int nargs, char **args, int opt_len)
180{
181    char options[opt_len + 1];
182    int i;
183
184    options[0] = '\0';
185    if (nargs > 2) {
186        strcpy(options, args[2]);
187        for (i = 3; i < nargs; ++i) {
188            strcat(options, " ");
189            strcat(options, args[i]);
190        }
191    }
192
193    return insmod(args[1], options);
194}
195
196int do_insmod(int nargs, char **args)
197{
198    int i;
199    int size = 0;
200
201    if (nargs > 2) {
202        for (i = 2; i < nargs; ++i)
203            size += strlen(args[i]) + 1;
204    }
205
206    return do_insmod_inner(nargs, args, size);
207}
208
209int do_mkdir(int nargs, char **args)
210{
211    mode_t mode = 0755;
212    int ret;
213
214    /* mkdir <path> [mode] [owner] [group] */
215
216    if (nargs >= 3) {
217        mode = strtoul(args[2], 0, 8);
218    }
219
220    ret = make_dir(args[1], mode);
221    /* chmod in case the directory already exists */
222    if (ret == -1 && errno == EEXIST) {
223        ret = fchmodat(AT_FDCWD, args[1], mode, AT_SYMLINK_NOFOLLOW);
224    }
225    if (ret == -1) {
226        return -errno;
227    }
228
229    if (nargs >= 4) {
230        uid_t uid = decode_uid(args[3]);
231        gid_t gid = -1;
232
233        if (nargs == 5) {
234            gid = decode_uid(args[4]);
235        }
236
237        if (lchown(args[1], uid, gid) == -1) {
238            return -errno;
239        }
240
241        /* chown may have cleared S_ISUID and S_ISGID, chmod again */
242        if (mode & (S_ISUID | S_ISGID)) {
243            ret = fchmodat(AT_FDCWD, args[1], mode, AT_SYMLINK_NOFOLLOW);
244            if (ret == -1) {
245                return -errno;
246            }
247        }
248    }
249
250    return e4crypt_set_directory_policy(args[1]);
251}
252
253static struct {
254    const char *name;
255    unsigned flag;
256} mount_flags[] = {
257    { "noatime",    MS_NOATIME },
258    { "noexec",     MS_NOEXEC },
259    { "nosuid",     MS_NOSUID },
260    { "nodev",      MS_NODEV },
261    { "nodiratime", MS_NODIRATIME },
262    { "ro",         MS_RDONLY },
263    { "rw",         0 },
264    { "remount",    MS_REMOUNT },
265    { "bind",       MS_BIND },
266    { "rec",        MS_REC },
267    { "unbindable", MS_UNBINDABLE },
268    { "private",    MS_PRIVATE },
269    { "slave",      MS_SLAVE },
270    { "shared",     MS_SHARED },
271    { "defaults",   0 },
272    { 0,            0 },
273};
274
275#define DATA_MNT_POINT "/data"
276
277/* mount <type> <device> <path> <flags ...> <options> */
278int do_mount(int nargs, char **args)
279{
280    char tmp[64];
281    char *source, *target, *system;
282    char *options = NULL;
283    unsigned flags = 0;
284    int n, i;
285    int wait = 0;
286
287    for (n = 4; n < nargs; n++) {
288        for (i = 0; mount_flags[i].name; i++) {
289            if (!strcmp(args[n], mount_flags[i].name)) {
290                flags |= mount_flags[i].flag;
291                break;
292            }
293        }
294
295        if (!mount_flags[i].name) {
296            if (!strcmp(args[n], "wait"))
297                wait = 1;
298            /* if our last argument isn't a flag, wolf it up as an option string */
299            else if (n + 1 == nargs)
300                options = args[n];
301        }
302    }
303
304    system = args[1];
305    source = args[2];
306    target = args[3];
307
308    if (!strncmp(source, "mtd@", 4)) {
309        n = mtd_name_to_number(source + 4);
310        if (n < 0) {
311            return -1;
312        }
313
314        snprintf(tmp, sizeof(tmp), "/dev/block/mtdblock%d", n);
315
316        if (wait)
317            wait_for_file(tmp, COMMAND_RETRY_TIMEOUT);
318        if (mount(tmp, target, system, flags, options) < 0) {
319            return -1;
320        }
321
322        goto exit_success;
323    } else if (!strncmp(source, "loop@", 5)) {
324        int mode, loop, fd;
325        struct loop_info info;
326
327        mode = (flags & MS_RDONLY) ? O_RDONLY : O_RDWR;
328        fd = open(source + 5, mode | O_CLOEXEC);
329        if (fd < 0) {
330            return -1;
331        }
332
333        for (n = 0; ; n++) {
334            snprintf(tmp, sizeof(tmp), "/dev/block/loop%d", n);
335            loop = open(tmp, mode | O_CLOEXEC);
336            if (loop < 0) {
337                close(fd);
338                return -1;
339            }
340
341            /* if it is a blank loop device */
342            if (ioctl(loop, LOOP_GET_STATUS, &info) < 0 && errno == ENXIO) {
343                /* if it becomes our loop device */
344                if (ioctl(loop, LOOP_SET_FD, fd) >= 0) {
345                    close(fd);
346
347                    if (mount(tmp, target, system, flags, options) < 0) {
348                        ioctl(loop, LOOP_CLR_FD, 0);
349                        close(loop);
350                        return -1;
351                    }
352
353                    close(loop);
354                    goto exit_success;
355                }
356            }
357
358            close(loop);
359        }
360
361        close(fd);
362        ERROR("out of loopback devices");
363        return -1;
364    } else {
365        if (wait)
366            wait_for_file(source, COMMAND_RETRY_TIMEOUT);
367        if (mount(source, target, system, flags, options) < 0) {
368            return -1;
369        }
370
371    }
372
373exit_success:
374    return 0;
375
376}
377
378static int wipe_data_via_recovery()
379{
380    mkdir("/cache/recovery", 0700);
381    int fd = open("/cache/recovery/command", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0600);
382    if (fd >= 0) {
383        write(fd, "--wipe_data\n", strlen("--wipe_data\n") + 1);
384        write(fd, "--reason=wipe_data_via_recovery\n", strlen("--reason=wipe_data_via_recovery\n") + 1);
385        close(fd);
386    } else {
387        ERROR("could not open /cache/recovery/command\n");
388        return -1;
389    }
390    android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
391    while (1) { pause(); }  // never reached
392}
393
394/*
395 * This function might request a reboot, in which case it will
396 * not return.
397 */
398int do_mount_all(int nargs, char **args)
399{
400    pid_t pid;
401    int ret = -1;
402    int child_ret = -1;
403    int status;
404    struct fstab *fstab;
405
406    if (nargs != 2) {
407        return -1;
408    }
409
410    /*
411     * Call fs_mgr_mount_all() to mount all filesystems.  We fork(2) and
412     * do the call in the child to provide protection to the main init
413     * process if anything goes wrong (crash or memory leak), and wait for
414     * the child to finish in the parent.
415     */
416    pid = fork();
417    if (pid > 0) {
418        /* Parent.  Wait for the child to return */
419        int wp_ret = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
420        if (wp_ret < 0) {
421            /* Unexpected error code. We will continue anyway. */
422            NOTICE("waitpid failed rc=%d: %s\n", wp_ret, strerror(errno));
423        }
424
425        if (WIFEXITED(status)) {
426            ret = WEXITSTATUS(status);
427        } else {
428            ret = -1;
429        }
430    } else if (pid == 0) {
431        /* child, call fs_mgr_mount_all() */
432        klog_set_level(6);  /* So we can see what fs_mgr_mount_all() does */
433        fstab = fs_mgr_read_fstab(args[1]);
434        child_ret = fs_mgr_mount_all(fstab);
435        fs_mgr_free_fstab(fstab);
436        if (child_ret == -1) {
437            ERROR("fs_mgr_mount_all returned an error\n");
438        }
439        _exit(child_ret);
440    } else {
441        /* fork failed, return an error */
442        return -1;
443    }
444
445    if (ret == FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION) {
446        property_set("vold.decrypt", "trigger_encryption");
447    } else if (ret == FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED) {
448        property_set("ro.crypto.state", "encrypted");
449        property_set("ro.crypto.type", "block");
450        property_set("vold.decrypt", "trigger_default_encryption");
451    } else if (ret == FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) {
452        property_set("ro.crypto.state", "unencrypted");
453        /* If fs_mgr determined this is an unencrypted device, then trigger
454         * that action.
455         */
456        action_for_each_trigger("nonencrypted", action_add_queue_tail);
457    } else if (ret == FS_MGR_MNTALL_DEV_NEEDS_RECOVERY) {
458        /* Setup a wipe via recovery, and reboot into recovery */
459        ERROR("fs_mgr_mount_all suggested recovery, so wiping data via recovery.\n");
460        ret = wipe_data_via_recovery();
461        /* If reboot worked, there is no return. */
462    } else if (ret == FS_MGR_MNTALL_DEV_DEFAULT_FILE_ENCRYPTED) {
463        if (e4crypt_install_keyring()) {
464            return -1;
465        }
466        property_set("ro.crypto.state", "encrypted");
467        property_set("ro.crypto.type", "file");
468
469        // Although encrypted, we have device key, so we do not need to
470        // do anything different from the nonencrypted case.
471        action_for_each_trigger("nonencrypted", action_add_queue_tail);
472    } else if (ret == FS_MGR_MNTALL_DEV_NON_DEFAULT_FILE_ENCRYPTED) {
473        if (e4crypt_install_keyring()) {
474            return -1;
475        }
476        property_set("ro.crypto.state", "encrypted");
477        property_set("ro.crypto.type", "file");
478        property_set("vold.decrypt", "trigger_restart_min_framework");
479    } else if (ret > 0) {
480        ERROR("fs_mgr_mount_all returned unexpected error %d\n", ret);
481    }
482    /* else ... < 0: error */
483
484    return ret;
485}
486
487int do_swapon_all(int nargs, char **args)
488{
489    struct fstab *fstab;
490    int ret;
491
492    fstab = fs_mgr_read_fstab(args[1]);
493    ret = fs_mgr_swapon_all(fstab);
494    fs_mgr_free_fstab(fstab);
495
496    return ret;
497}
498
499int do_setprop(int nargs, char **args)
500{
501    const char *name = args[1];
502    const char *value = args[2];
503    char prop_val[PROP_VALUE_MAX];
504    int ret;
505
506    ret = expand_props(prop_val, value, sizeof(prop_val));
507    if (ret) {
508        ERROR("cannot expand '%s' while assigning to '%s'\n", value, name);
509        return -EINVAL;
510    }
511    property_set(name, prop_val);
512    return 0;
513}
514
515int do_setrlimit(int nargs, char **args)
516{
517    struct rlimit limit;
518    int resource;
519    resource = atoi(args[1]);
520    limit.rlim_cur = atoi(args[2]);
521    limit.rlim_max = atoi(args[3]);
522    return setrlimit(resource, &limit);
523}
524
525int do_start(int nargs, char **args)
526{
527    struct service *svc;
528    svc = service_find_by_name(args[1]);
529    if (svc) {
530        service_start(svc, NULL);
531    }
532    return 0;
533}
534
535int do_stop(int nargs, char **args)
536{
537    struct service *svc;
538    svc = service_find_by_name(args[1]);
539    if (svc) {
540        service_stop(svc);
541    }
542    return 0;
543}
544
545int do_restart(int nargs, char **args)
546{
547    struct service *svc;
548    svc = service_find_by_name(args[1]);
549    if (svc) {
550        service_restart(svc);
551    }
552    return 0;
553}
554
555int do_powerctl(int nargs, char **args)
556{
557    char command[PROP_VALUE_MAX];
558    int res;
559    int len = 0;
560    int cmd = 0;
561    const char *reboot_target;
562
563    res = expand_props(command, args[1], sizeof(command));
564    if (res) {
565        ERROR("powerctl: cannot expand '%s'\n", args[1]);
566        return -EINVAL;
567    }
568
569    if (strncmp(command, "shutdown", 8) == 0) {
570        cmd = ANDROID_RB_POWEROFF;
571        len = 8;
572    } else if (strncmp(command, "reboot", 6) == 0) {
573        cmd = ANDROID_RB_RESTART2;
574        len = 6;
575    } else {
576        ERROR("powerctl: unrecognized command '%s'\n", command);
577        return -EINVAL;
578    }
579
580    if (command[len] == ',') {
581        reboot_target = &command[len + 1];
582    } else if (command[len] == '\0') {
583        reboot_target = "";
584    } else {
585        ERROR("powerctl: unrecognized reboot target '%s'\n", &command[len]);
586        return -EINVAL;
587    }
588
589    return android_reboot(cmd, 0, reboot_target);
590}
591
592int do_trigger(int nargs, char **args)
593{
594    action_for_each_trigger(args[1], action_add_queue_tail);
595    return 0;
596}
597
598int do_symlink(int nargs, char **args)
599{
600    return symlink(args[1], args[2]);
601}
602
603int do_rm(int nargs, char **args)
604{
605    return unlink(args[1]);
606}
607
608int do_rmdir(int nargs, char **args)
609{
610    return rmdir(args[1]);
611}
612
613int do_sysclktz(int nargs, char **args)
614{
615    struct timezone tz;
616
617    if (nargs != 2)
618        return -1;
619
620    memset(&tz, 0, sizeof(tz));
621    tz.tz_minuteswest = atoi(args[1]);
622    if (settimeofday(NULL, &tz))
623        return -1;
624    return 0;
625}
626
627int do_verity_load_state(int nargs, char **args) {
628    int mode = -1;
629    int rc = fs_mgr_load_verity_state(&mode);
630    if (rc == 0 && mode == VERITY_MODE_LOGGING) {
631        action_for_each_trigger("verity-logging", action_add_queue_tail);
632    }
633    return rc;
634}
635
636static void verity_update_property(fstab_rec *fstab, const char *mount_point, int mode, int status) {
637    property_set(android::base::StringPrintf("partition.%s.verified", mount_point).c_str(),
638                 android::base::StringPrintf("%d", mode).c_str());
639}
640
641int do_verity_update_state(int nargs, char** args) {
642    return fs_mgr_update_verity_state(verity_update_property);
643}
644
645int do_write(int nargs, char **args)
646{
647    const char *path = args[1];
648    const char *value = args[2];
649
650    char expanded_value[256];
651    if (expand_props(expanded_value, value, sizeof(expanded_value))) {
652        ERROR("cannot expand '%s' while writing to '%s'\n", value, path);
653        return -EINVAL;
654    }
655    return write_file(path, expanded_value);
656}
657
658int do_copy(int nargs, char **args)
659{
660    char *buffer = NULL;
661    int rc = 0;
662    int fd1 = -1, fd2 = -1;
663    struct stat info;
664    int brtw, brtr;
665    char *p;
666
667    if (nargs != 3)
668        return -1;
669
670    if (stat(args[1], &info) < 0)
671        return -1;
672
673    if ((fd1 = open(args[1], O_RDONLY|O_CLOEXEC)) < 0)
674        goto out_err;
675
676    if ((fd2 = open(args[2], O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0660)) < 0)
677        goto out_err;
678
679    if (!(buffer = (char*) malloc(info.st_size)))
680        goto out_err;
681
682    p = buffer;
683    brtr = info.st_size;
684    while(brtr) {
685        rc = read(fd1, p, brtr);
686        if (rc < 0)
687            goto out_err;
688        if (rc == 0)
689            break;
690        p += rc;
691        brtr -= rc;
692    }
693
694    p = buffer;
695    brtw = info.st_size;
696    while(brtw) {
697        rc = write(fd2, p, brtw);
698        if (rc < 0)
699            goto out_err;
700        if (rc == 0)
701            break;
702        p += rc;
703        brtw -= rc;
704    }
705
706    rc = 0;
707    goto out;
708out_err:
709    rc = -1;
710out:
711    if (buffer)
712        free(buffer);
713    if (fd1 >= 0)
714        close(fd1);
715    if (fd2 >= 0)
716        close(fd2);
717    return rc;
718}
719
720int do_chown(int nargs, char **args) {
721    /* GID is optional. */
722    if (nargs == 3) {
723        if (lchown(args[2], decode_uid(args[1]), -1) == -1)
724            return -errno;
725    } else if (nargs == 4) {
726        if (lchown(args[3], decode_uid(args[1]), decode_uid(args[2])) == -1)
727            return -errno;
728    } else {
729        return -1;
730    }
731    return 0;
732}
733
734static mode_t get_mode(const char *s) {
735    mode_t mode = 0;
736    while (*s) {
737        if (*s >= '0' && *s <= '7') {
738            mode = (mode<<3) | (*s-'0');
739        } else {
740            return -1;
741        }
742        s++;
743    }
744    return mode;
745}
746
747int do_chmod(int nargs, char **args) {
748    mode_t mode = get_mode(args[1]);
749    if (fchmodat(AT_FDCWD, args[2], mode, AT_SYMLINK_NOFOLLOW) < 0) {
750        return -errno;
751    }
752    return 0;
753}
754
755int do_restorecon(int nargs, char **args) {
756    int i;
757    int ret = 0;
758
759    for (i = 1; i < nargs; i++) {
760        if (restorecon(args[i]) < 0)
761            ret = -errno;
762    }
763    return ret;
764}
765
766int do_restorecon_recursive(int nargs, char **args) {
767    int i;
768    int ret = 0;
769
770    for (i = 1; i < nargs; i++) {
771        if (restorecon_recursive(args[i]) < 0)
772            ret = -errno;
773    }
774    return ret;
775}
776
777int do_loglevel(int nargs, char **args) {
778    int log_level;
779    char log_level_str[PROP_VALUE_MAX] = "";
780    if (nargs != 2) {
781        ERROR("loglevel: missing argument\n");
782        return -EINVAL;
783    }
784
785    if (expand_props(log_level_str, args[1], sizeof(log_level_str))) {
786        ERROR("loglevel: cannot expand '%s'\n", args[1]);
787        return -EINVAL;
788    }
789    log_level = atoi(log_level_str);
790    if (log_level < KLOG_ERROR_LEVEL || log_level > KLOG_DEBUG_LEVEL) {
791        ERROR("loglevel: invalid log level'%d'\n", log_level);
792        return -EINVAL;
793    }
794    klog_set_level(log_level);
795    return 0;
796}
797
798int do_load_persist_props(int nargs, char **args) {
799    if (nargs == 1) {
800        load_persist_props();
801        return 0;
802    }
803    return -1;
804}
805
806int do_load_all_props(int nargs, char **args) {
807    if (nargs == 1) {
808        load_all_props();
809        return 0;
810    }
811    return -1;
812}
813
814int do_wait(int nargs, char **args)
815{
816    if (nargs == 2) {
817        return wait_for_file(args[1], COMMAND_RETRY_TIMEOUT);
818    } else if (nargs == 3) {
819        return wait_for_file(args[1], atoi(args[2]));
820    } else
821        return -1;
822}
823
824/*
825 * Callback to make a directory from the ext4 code
826 */
827static int do_installkeys_ensure_dir_exists(const char* dir)
828{
829    if (make_dir(dir, 0700) && errno != EEXIST) {
830        return -1;
831    }
832
833    return 0;
834}
835
836int do_installkey(int nargs, char **args)
837{
838    if (nargs != 2) {
839        return -1;
840    }
841
842    char prop_value[PROP_VALUE_MAX] = {0};
843    property_get("ro.crypto.type", prop_value);
844    if (strcmp(prop_value, "file")) {
845        return 0;
846    }
847
848    return e4crypt_create_device_key(args[1],
849                                     do_installkeys_ensure_dir_exists);
850}
851