9f2317f14e8d8f12c71b30944138d7a6c877b406
[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 #include "common.h"
15
16 #include <linux/loop.h>
17
18 #define ROOTDEV_OVERLAY_ALIGN (64ULL * 1024ULL)
19 #define F2FS_MINSIZE (100ULL * 1024ULL * 1024ULL)
20
21 struct squashfs_super_block {
22 uint32_t s_magic;
23 uint32_t pad0[9];
24 uint64_t bytes_used;
25 };
26
27 struct rootdev_volume {
28 struct volume v;
29 uint64_t offset;
30 char loop_name[32];
31 };
32
33 static const char *rootdev;
34 static struct driver rootdisk_driver;
35
36 static char *get_blockdev(dev_t dev)
37 {
38 const char *dirname = "/dev";
39 DIR *dir = opendir(dirname);
40 struct dirent *d;
41 struct stat st;
42 static char buf[256];
43 char *ret = NULL;
44
45 if (!dir)
46 return ret;
47
48 while ((d = readdir(dir)) != NULL) {
49 snprintf(buf, sizeof(buf), "%s/%s", dirname, d->d_name);
50
51 if (lstat(buf, &st) != 0)
52 continue;
53
54 if (!S_ISBLK(st.st_mode))
55 continue;
56
57 if (st.st_rdev != dev)
58 continue;
59
60 ret = buf;
61 break;
62 }
63
64 closedir(dir);
65 return ret;
66 }
67
68 static char *get_rootdev(const char *dir)
69 {
70 struct stat st;
71
72 if (stat(dir, &st))
73 return NULL;
74
75 return get_blockdev(S_ISBLK(st.st_mode) ? st.st_rdev : st.st_dev);
76 }
77
78 static int get_squashfs(struct squashfs_super_block *sb)
79 {
80 FILE *f;
81 int len;
82
83 f = fopen(rootdev, "r");
84 if (!f)
85 return -1;
86
87 len = fread(sb, sizeof(*sb), 1, f);
88 fclose(f);
89
90 if (len != 1)
91 return -1;
92
93 return 0;
94 }
95
96 static struct volume *rootdisk_volume_find(char *name)
97 {
98 struct squashfs_super_block sb;
99 struct rootdev_volume *p;
100
101 if (strcmp(name, "rootfs_data") != 0)
102 return NULL;
103
104 if (!rootdev)
105 rootdev = get_rootdev("/");
106 if (!rootdev)
107 rootdev = get_rootdev("/rom");
108 if (!rootdev)
109 return NULL;
110
111 if (strstr(rootdev, "mtdblock") ||
112 strstr(rootdev, "ubiblock"))
113 return NULL;
114
115 if (get_squashfs(&sb))
116 return NULL;
117
118 if (memcmp(&sb.s_magic, "hsqs", sizeof(sb.s_magic)) != 0)
119 return NULL;
120
121 p = calloc(1, sizeof(*p));
122 p->v.drv = &rootdisk_driver;
123 p->v.name = "rootfs_data";
124
125 p->offset = le64_to_cpu(sb.bytes_used);
126 p->offset = ((p->offset + (ROOTDEV_OVERLAY_ALIGN - 1)) &
127 ~(ROOTDEV_OVERLAY_ALIGN - 1));
128
129 return &p->v;
130 }
131
132 static int rootdisk_volume_identify(struct volume *v)
133 {
134 struct rootdev_volume *p = container_of(v, struct rootdev_volume, v);
135 FILE *f;
136 int ret = FS_NONE;
137 f = fopen(rootdev, "r");
138 if (!f)
139 return ret;
140
141 ret = block_file_identify(f, p->offset);
142
143 fclose(f);
144
145 return ret;
146 }
147
148 static int rootdisk_create_loop(struct rootdev_volume *p)
149 {
150 struct loop_info64 info;
151 int ret = -1;
152 int fd = -1;
153 int i, ffd;
154
155 ffd = open(rootdev, O_RDWR);
156 if (ffd < 0)
157 return -1;
158
159 for (i = 0; i < 8; i++) {
160 snprintf(p->loop_name, sizeof(p->loop_name), "/dev/loop%d",
161 i);
162
163 if (fd >= 0)
164 close(fd);
165
166 fd = open(p->loop_name, O_RDWR);
167 if (fd < 0)
168 continue;
169
170 if (ioctl(fd, LOOP_GET_STATUS64, &info) == 0) {
171 if (strcmp((char *) info.lo_file_name, rootdev) != 0)
172 continue;
173 if (info.lo_offset != p->offset)
174 continue;
175 ret = 0;
176 break;
177 }
178
179 if (errno != ENXIO)
180 continue;
181
182 if (ioctl(fd, LOOP_SET_FD, ffd) != 0)
183 continue;
184
185 memset(&info, 0, sizeof(info));
186 snprintf((char *) info.lo_file_name, sizeof(info.lo_file_name), "%s",
187 rootdev);
188 info.lo_offset = p->offset;
189 info.lo_flags |= LO_FLAGS_AUTOCLEAR;
190
191 if (ioctl(fd, LOOP_SET_STATUS64, &info) != 0) {
192 ioctl(fd, LOOP_CLR_FD, 0);
193 continue;
194 }
195
196 /*
197 * Don't close fd. Leave it open until this process exits, to avoid
198 * the autoclear from happening too soon.
199 */
200 fd = -1;
201
202 ret = 0;
203 break;
204 }
205
206 if (fd >= 0)
207 close(fd);
208
209 close(ffd);
210
211 if (ret)
212 p->loop_name[0] = 0;
213
214 return ret;
215 }
216
217 static int rootdisk_volume_init(struct volume *v)
218 {
219 struct rootdev_volume *p = container_of(v, struct rootdev_volume, v);
220
221 if (!p->loop_name[0] && rootdisk_create_loop(p) != 0) {
222 ULOG_ERR("unable to create loop device\n");
223 return -1;
224 }
225
226 v->type = BLOCKDEV;
227 v->blk = p->loop_name;
228
229 return block_volume_format(v, p->offset, rootdev);
230 }
231
232 static struct driver rootdisk_driver = {
233 .name = "rootdisk",
234 .find = rootdisk_volume_find,
235 .init = rootdisk_volume_init,
236 .identify = rootdisk_volume_identify,
237 };
238
239 DRIVER(rootdisk_driver);