libfstools: properly label ext4 overlay
[project/fstools.git] / libfstools / rootdisk.c
1 /*
2  * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License version 2.1
6  * as published by the Free Software Foundation
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
13
14 #define F2FS_MINSIZE    (100 * 1024 * 1024)
15 #define _FILE_OFFSET_BITS 64
16
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <sys/ioctl.h>
20 #include <sys/mount.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <dirent.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27
28 #include "libfstools.h"
29 #include "volume.h"
30
31 #include <linux/loop.h>
32
33 #define ROOTDEV_OVERLAY_ALIGN   (64ULL * 1024ULL)
34
35 struct squashfs_super_block {
36         uint32_t s_magic;
37         uint32_t pad0[9];
38         uint64_t bytes_used;
39 };
40
41 struct rootdev_volume {
42         struct volume v;
43         uint64_t offset;
44         char loop_name[32];
45 };
46
47 static const char *rootdev;
48 static struct driver rootdisk_driver;
49
50 static char *get_blockdev(dev_t dev)
51 {
52         const char *dirname = "/dev";
53         DIR *dir = opendir(dirname);
54         struct dirent *d;
55         struct stat st;
56         static char buf[256];
57         char *ret = NULL;
58
59         if (!dir)
60                 return ret;
61
62         while ((d = readdir(dir)) != NULL) {
63                 snprintf(buf, sizeof(buf), "%s/%s", dirname, d->d_name);
64
65                 if (lstat(buf, &st) != 0)
66                         continue;
67
68                 if (!S_ISBLK(st.st_mode))
69                         continue;
70
71                 if (st.st_rdev != dev)
72                         continue;
73
74                 ret = buf;
75                 break;
76         }
77
78         closedir(dir);
79         return ret;
80 }
81
82 static char *get_rootdev(const char *dir)
83 {
84         struct stat st;
85
86         if (stat(dir, &st))
87                 return NULL;
88
89         return get_blockdev(S_ISBLK(st.st_mode) ? st.st_rdev : st.st_dev);
90 }
91
92 static int get_squashfs(struct squashfs_super_block *sb)
93 {
94         FILE *f;
95         int len;
96
97         f = fopen(rootdev, "r");
98         if (!f)
99                 return -1;
100
101         len = fread(sb, sizeof(*sb), 1, f);
102         fclose(f);
103
104         if (len != 1)
105                 return -1;
106
107         return 0;
108 }
109
110 static bool rootdisk_use_f2fs(struct rootdev_volume *p)
111 {
112         uint64_t size = 0;
113         bool ret = false;
114         int fd;
115
116         fd = open(rootdev, O_RDONLY);
117         if (ioctl(fd, BLKGETSIZE64, &size) == 0)
118                 ret = size - p->offset > F2FS_MINSIZE;
119         close(fd);
120
121         return ret;
122 }
123
124 static struct volume *rootdisk_volume_find(char *name)
125 {
126         struct squashfs_super_block sb;
127         struct rootdev_volume *p;
128
129         if (strcmp(name, "rootfs_data") != 0)
130                 return NULL;
131
132         if (!rootdev)
133                 rootdev = get_rootdev("/");
134         if (!rootdev)
135                 rootdev = get_rootdev("/rom");
136         if (!rootdev)
137                 return NULL;
138
139         if (strstr(rootdev, "mtdblock") ||
140             strstr(rootdev, "ubiblock"))
141                 return NULL;
142
143         if (get_squashfs(&sb))
144                 return NULL;
145
146         if (memcmp(&sb.s_magic, "hsqs", sizeof(sb.s_magic)) != 0)
147                 return NULL;
148
149         p = calloc(1, sizeof(*p));
150         p->v.drv = &rootdisk_driver;
151         p->v.name = "rootfs_data";
152
153         p->offset = le64_to_cpu(sb.bytes_used);
154         p->offset = ((p->offset + (ROOTDEV_OVERLAY_ALIGN - 1)) &
155                      ~(ROOTDEV_OVERLAY_ALIGN - 1));
156
157         return &p->v;
158 }
159
160 static int rootdisk_volume_identify(struct volume *v)
161 {
162         struct rootdev_volume *p = container_of(v, struct rootdev_volume, v);
163         int ret = FS_NONE;
164         uint32_t magic = 0;
165         FILE *f;
166
167         f = fopen(rootdev, "r");
168         if (!f)
169                 return ret;
170
171         fseeko(f, p->offset + 0x400, SEEK_SET);
172         fread(&magic, sizeof(magic), 1, f);
173
174         if (magic == cpu_to_le32(0xF2F52010))
175                 ret = FS_F2FS;
176
177         magic = 0;
178         fseeko(f, p->offset + 0x438, SEEK_SET);
179         fread(&magic, sizeof(magic), 1, f);
180         if ((le32_to_cpu(magic) & 0xffff) == 0xef53)
181                 ret = FS_EXT4;
182
183         fclose(f);
184
185         return ret;
186 }
187
188 static int rootdisk_create_loop(struct rootdev_volume *p)
189 {
190         struct loop_info64 info;
191         int ret = -1;
192         int fd = -1;
193         int i, ffd;
194
195         ffd = open(rootdev, O_RDWR);
196         if (ffd < 0)
197                 return -1;
198
199         for (i = 0; i < 8; i++) {
200                 snprintf(p->loop_name, sizeof(p->loop_name), "/dev/loop%d",
201                          i);
202
203                 if (fd >= 0)
204                         close(fd);
205
206                 fd = open(p->loop_name, O_RDWR);
207                 if (fd < 0)
208                         continue;
209
210                 if (ioctl(fd, LOOP_GET_STATUS64, &info) == 0) {
211                         if (strcmp((char *) info.lo_file_name, rootdev) != 0)
212                                 continue;
213                         if (info.lo_offset != p->offset)
214                                 continue;
215                         ret = 0;
216                         break;
217                 }
218
219                 if (errno != ENXIO)
220                         continue;
221
222                 if (ioctl(fd, LOOP_SET_FD, ffd) != 0)
223                         continue;
224
225                 memset(&info, 0, sizeof(info));
226                 snprintf((char *) info.lo_file_name, sizeof(info.lo_file_name), "%s",
227                          rootdev);
228                 info.lo_offset = p->offset;
229
230                 if (ioctl(fd, LOOP_SET_STATUS64, &info) != 0) {
231                         ioctl(fd, LOOP_CLR_FD, 0);
232                         continue;
233                 }
234
235                 ret = 0;
236                 break;
237         }
238
239         if (fd >= 0)
240                 close(fd);
241
242         close(ffd);
243
244         if (ret)
245                 p->loop_name[0] = 0;
246
247         return ret;
248 }
249
250 static int rootdisk_volume_init(struct volume *v)
251 {
252         struct rootdev_volume *p = container_of(v, struct rootdev_volume, v);
253         char str[128];
254
255         if (!p->loop_name[0] && rootdisk_create_loop(p) != 0)
256                 return -1;
257
258         v->type = BLOCKDEV;
259         v->blk = p->loop_name;
260
261         switch (rootdisk_volume_identify(v)) {
262         case FS_NONE:
263                 ULOG_INFO("rootdisk overlay filesystem has not been formatted yet\n");
264                 if (rootdisk_use_f2fs(p))
265                         snprintf(str, sizeof(str), "mkfs.f2fs -l rootfs_data %s", v->blk);
266                 else
267                         snprintf(str, sizeof(str), "mkfs.ext4 -L rootfs_data %s", v->blk);
268                 system(str);
269                 break;
270         default:
271                 break;
272         }
273         return 0;
274 }
275
276 static struct driver rootdisk_driver = {
277         .name = "rootdisk",
278         .find = rootdisk_volume_find,
279         .init = rootdisk_volume_init,
280         .identify = rootdisk_volume_identify,
281 };
282
283 DRIVER(rootdisk_driver);