1/*
2 * Copyright (C) 2007 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 <stdlib.h>
19#include <sys/mount.h>
20#include <sys/stat.h>
21#include <sys/types.h>
22#include <unistd.h>
23#include <ctype.h>
24
25#include "mtdutils/mtdutils.h"
26#include "mtdutils/mounts.h"
27#include "roots.h"
28#include "common.h"
29#include "make_ext4fs.h"
30
31static int num_volumes = 0;
32static Volume* device_volumes = NULL;
33
34extern struct selabel_handle *sehandle;
35
36static int parse_options(char* options, Volume* volume) {
37    char* option;
38    while ((option = strtok(options, ","))) {
39        options = NULL;
40
41        if (strncmp(option, "length=", 7) == 0) {
42            volume->length = strtoll(option+7, NULL, 10);
43        } else {
44            LOGE("bad option \"%s\"\n", option);
45            return -1;
46        }
47    }
48    return 0;
49}
50
51void load_volume_table() {
52    int alloc = 2;
53    device_volumes = (Volume*)malloc(alloc * sizeof(Volume));
54
55    // Insert an entry for /tmp, which is the ramdisk and is always mounted.
56    device_volumes[0].mount_point = "/tmp";
57    device_volumes[0].fs_type = "ramdisk";
58    device_volumes[0].device = NULL;
59    device_volumes[0].device2 = NULL;
60    device_volumes[0].length = 0;
61    num_volumes = 1;
62
63    FILE* fstab = fopen("/etc/recovery.fstab", "r");
64    if (fstab == NULL) {
65        LOGE("failed to open /etc/recovery.fstab (%s)\n", strerror(errno));
66        return;
67    }
68
69    char buffer[1024];
70    int i;
71    while (fgets(buffer, sizeof(buffer)-1, fstab)) {
72        for (i = 0; buffer[i] && isspace(buffer[i]); ++i);
73        if (buffer[i] == '\0' || buffer[i] == '#') continue;
74
75        char* original = strdup(buffer);
76
77        char* mount_point = strtok(buffer+i, " \t\n");
78        char* fs_type = strtok(NULL, " \t\n");
79        char* device = strtok(NULL, " \t\n");
80        // lines may optionally have a second device, to use if
81        // mounting the first one fails.
82        char* options = NULL;
83        char* device2 = strtok(NULL, " \t\n");
84        if (device2) {
85            if (device2[0] == '/') {
86                options = strtok(NULL, " \t\n");
87            } else {
88                options = device2;
89                device2 = NULL;
90            }
91        }
92
93        if (mount_point && fs_type && device) {
94            while (num_volumes >= alloc) {
95                alloc *= 2;
96                device_volumes = (Volume*)realloc(device_volumes, alloc*sizeof(Volume));
97            }
98            device_volumes[num_volumes].mount_point = strdup(mount_point);
99            device_volumes[num_volumes].fs_type = strdup(fs_type);
100            device_volumes[num_volumes].device = strdup(device);
101            device_volumes[num_volumes].device2 =
102                device2 ? strdup(device2) : NULL;
103
104            device_volumes[num_volumes].length = 0;
105            if (parse_options(options, device_volumes + num_volumes) != 0) {
106                LOGE("skipping malformed recovery.fstab line: %s\n", original);
107            } else {
108                ++num_volumes;
109            }
110        } else {
111            LOGE("skipping malformed recovery.fstab line: %s\n", original);
112        }
113        free(original);
114    }
115
116    fclose(fstab);
117
118    printf("recovery filesystem table\n");
119    printf("=========================\n");
120    for (i = 0; i < num_volumes; ++i) {
121        Volume* v = &device_volumes[i];
122        printf("  %d %s %s %s %s %lld\n", i, v->mount_point, v->fs_type,
123               v->device, v->device2, v->length);
124    }
125    printf("\n");
126}
127
128Volume* volume_for_path(const char* path) {
129    int i;
130    for (i = 0; i < num_volumes; ++i) {
131        Volume* v = device_volumes+i;
132        int len = strlen(v->mount_point);
133        if (strncmp(path, v->mount_point, len) == 0 &&
134            (path[len] == '\0' || path[len] == '/')) {
135            return v;
136        }
137    }
138    return NULL;
139}
140
141int ensure_path_mounted(const char* path) {
142    Volume* v = volume_for_path(path);
143    if (v == NULL) {
144        LOGE("unknown volume for path [%s]\n", path);
145        return -1;
146    }
147    if (strcmp(v->fs_type, "ramdisk") == 0) {
148        // the ramdisk is always mounted.
149        return 0;
150    }
151
152    int result;
153    result = scan_mounted_volumes();
154    if (result < 0) {
155        LOGE("failed to scan mounted volumes\n");
156        return -1;
157    }
158
159    const MountedVolume* mv =
160        find_mounted_volume_by_mount_point(v->mount_point);
161    if (mv) {
162        // volume is already mounted
163        return 0;
164    }
165
166    mkdir(v->mount_point, 0755);  // in case it doesn't already exist
167
168    if (strcmp(v->fs_type, "yaffs2") == 0) {
169        // mount an MTD partition as a YAFFS2 filesystem.
170        mtd_scan_partitions();
171        const MtdPartition* partition;
172        partition = mtd_find_partition_by_name(v->device);
173        if (partition == NULL) {
174            LOGE("failed to find \"%s\" partition to mount at \"%s\"\n",
175                 v->device, v->mount_point);
176            return -1;
177        }
178        return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0);
179    } else if (strcmp(v->fs_type, "ext4") == 0 ||
180               strcmp(v->fs_type, "vfat") == 0) {
181        result = mount(v->device, v->mount_point, v->fs_type,
182                       MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
183        if (result == 0) return 0;
184
185        if (v->device2) {
186            LOGW("failed to mount %s (%s); trying %s\n",
187                 v->device, strerror(errno), v->device2);
188            result = mount(v->device2, v->mount_point, v->fs_type,
189                           MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
190            if (result == 0) return 0;
191        }
192
193        LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));
194        return -1;
195    }
196
197    LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point);
198    return -1;
199}
200
201int ensure_path_unmounted(const char* path) {
202    Volume* v = volume_for_path(path);
203    if (v == NULL) {
204        LOGE("unknown volume for path [%s]\n", path);
205        return -1;
206    }
207    if (strcmp(v->fs_type, "ramdisk") == 0) {
208        // the ramdisk is always mounted; you can't unmount it.
209        return -1;
210    }
211
212    int result;
213    result = scan_mounted_volumes();
214    if (result < 0) {
215        LOGE("failed to scan mounted volumes\n");
216        return -1;
217    }
218
219    const MountedVolume* mv =
220        find_mounted_volume_by_mount_point(v->mount_point);
221    if (mv == NULL) {
222        // volume is already unmounted
223        return 0;
224    }
225
226    return unmount_mounted_volume(mv);
227}
228
229int format_volume(const char* volume) {
230    Volume* v = volume_for_path(volume);
231    if (v == NULL) {
232        LOGE("unknown volume \"%s\"\n", volume);
233        return -1;
234    }
235    if (strcmp(v->fs_type, "ramdisk") == 0) {
236        // you can't format the ramdisk.
237        LOGE("can't format_volume \"%s\"", volume);
238        return -1;
239    }
240    if (strcmp(v->mount_point, volume) != 0) {
241        LOGE("can't give path \"%s\" to format_volume\n", volume);
242        return -1;
243    }
244
245    if (ensure_path_unmounted(volume) != 0) {
246        LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point);
247        return -1;
248    }
249
250    if (strcmp(v->fs_type, "yaffs2") == 0 || strcmp(v->fs_type, "mtd") == 0) {
251        mtd_scan_partitions();
252        const MtdPartition* partition = mtd_find_partition_by_name(v->device);
253        if (partition == NULL) {
254            LOGE("format_volume: no MTD partition \"%s\"\n", v->device);
255            return -1;
256        }
257
258        MtdWriteContext *write = mtd_write_partition(partition);
259        if (write == NULL) {
260            LOGW("format_volume: can't open MTD \"%s\"\n", v->device);
261            return -1;
262        } else if (mtd_erase_blocks(write, -1) == (off_t) -1) {
263            LOGW("format_volume: can't erase MTD \"%s\"\n", v->device);
264            mtd_write_close(write);
265            return -1;
266        } else if (mtd_write_close(write)) {
267            LOGW("format_volume: can't close MTD \"%s\"\n", v->device);
268            return -1;
269        }
270        return 0;
271    }
272
273    if (strcmp(v->fs_type, "ext4") == 0) {
274        int result = make_ext4fs(v->device, v->length, volume, sehandle);
275        if (result != 0) {
276            LOGE("format_volume: make_extf4fs failed on %s\n", v->device);
277            return -1;
278        }
279        return 0;
280    }
281
282    LOGE("format_volume: fs_type \"%s\" unsupported\n", v->fs_type);
283    return -1;
284}
285