diff options
Diffstat (limited to 'fuse_sideload')
-rw-r--r-- | fuse_sideload/Android.bp | 37 | ||||
-rw-r--r-- | fuse_sideload/fuse_sideload.cpp | 492 | ||||
-rw-r--r-- | fuse_sideload/include/fuse_sideload.h | 40 |
3 files changed, 569 insertions, 0 deletions
diff --git a/fuse_sideload/Android.bp b/fuse_sideload/Android.bp new file mode 100644 index 00000000..76bc16df --- /dev/null +++ b/fuse_sideload/Android.bp | |||
@@ -0,0 +1,37 @@ | |||
1 | // Copyright (C) 2018 The Android Open Source Project | ||
2 | // | ||
3 | // Licensed under the Apache License, Version 2.0 (the "License"); | ||
4 | // you may not use this file except in compliance with the License. | ||
5 | // You may obtain a copy of the License at | ||
6 | // | ||
7 | // http://www.apache.org/licenses/LICENSE-2.0 | ||
8 | // | ||
9 | // Unless required by applicable law or agreed to in writing, software | ||
10 | // distributed under the License is distributed on an "AS IS" BASIS, | ||
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
12 | // See the License for the specific language governing permissions and | ||
13 | // limitations under the License. | ||
14 | |||
15 | cc_library_static { | ||
16 | name: "libfusesideload", | ||
17 | |||
18 | cflags: [ | ||
19 | "-D_XOPEN_SOURCE", | ||
20 | "-D_GNU_SOURCE", | ||
21 | "-Wall", | ||
22 | "-Werror", | ||
23 | ], | ||
24 | |||
25 | srcs: [ | ||
26 | "fuse_sideload.cpp", | ||
27 | ], | ||
28 | |||
29 | export_include_dirs: [ | ||
30 | "include", | ||
31 | ], | ||
32 | |||
33 | static_libs: [ | ||
34 | "libbase", | ||
35 | "libcrypto", | ||
36 | ], | ||
37 | } | ||
diff --git a/fuse_sideload/fuse_sideload.cpp b/fuse_sideload/fuse_sideload.cpp new file mode 100644 index 00000000..1c7e98f0 --- /dev/null +++ b/fuse_sideload/fuse_sideload.cpp | |||
@@ -0,0 +1,492 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 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 | // This module creates a special filesystem containing two files. | ||
18 | // | ||
19 | // "/sideload/package.zip" appears to be a normal file, but reading | ||
20 | // from it causes data to be fetched from the adb host. We can use | ||
21 | // this to sideload packages over an adb connection without having to | ||
22 | // store the entire package in RAM on the device. | ||
23 | // | ||
24 | // Because we may not trust the adb host, this filesystem maintains | ||
25 | // the following invariant: each read of a given position returns the | ||
26 | // same data as the first read at that position. That is, once a | ||
27 | // section of the file is read, future reads of that section return | ||
28 | // the same data. (Otherwise, a malicious adb host process could | ||
29 | // return one set of bits when the package is read for signature | ||
30 | // verification, and then different bits for when the package is | ||
31 | // accessed by the installer.) If the adb host returns something | ||
32 | // different than it did on the first read, the reader of the file | ||
33 | // will see their read fail with EINVAL. | ||
34 | // | ||
35 | // The other file, "/sideload/exit", is used to control the subprocess | ||
36 | // that creates this filesystem. Calling stat() on the exit file | ||
37 | // causes the filesystem to be unmounted and the adb process on the | ||
38 | // device shut down. | ||
39 | // | ||
40 | // Note that only the minimal set of file operations needed for these | ||
41 | // two files is implemented. In particular, you can't opendir() or | ||
42 | // readdir() on the "/sideload" directory; ls on it won't work. | ||
43 | |||
44 | #include "fuse_sideload.h" | ||
45 | |||
46 | #include <errno.h> | ||
47 | #include <fcntl.h> | ||
48 | #include <limits.h> // PATH_MAX | ||
49 | #include <linux/fuse.h> | ||
50 | #include <stdint.h> | ||
51 | #include <stdio.h> | ||
52 | #include <stdlib.h> | ||
53 | #include <string.h> | ||
54 | #include <sys/mount.h> | ||
55 | #include <sys/param.h> // MIN | ||
56 | #include <sys/stat.h> | ||
57 | #include <sys/uio.h> | ||
58 | #include <unistd.h> | ||
59 | |||
60 | #include <array> | ||
61 | #include <string> | ||
62 | #include <vector> | ||
63 | |||
64 | #include <android-base/stringprintf.h> | ||
65 | #include <android-base/unique_fd.h> | ||
66 | #include <openssl/sha.h> | ||
67 | |||
68 | static constexpr uint64_t PACKAGE_FILE_ID = FUSE_ROOT_ID + 1; | ||
69 | static constexpr uint64_t EXIT_FLAG_ID = FUSE_ROOT_ID + 2; | ||
70 | |||
71 | static constexpr int NO_STATUS = 1; | ||
72 | static constexpr int NO_STATUS_EXIT = 2; | ||
73 | |||
74 | using SHA256Digest = std::array<uint8_t, SHA256_DIGEST_LENGTH>; | ||
75 | |||
76 | struct fuse_data { | ||
77 | android::base::unique_fd ffd; // file descriptor for the fuse socket | ||
78 | |||
79 | provider_vtab vtab; | ||
80 | |||
81 | uint64_t file_size; // bytes | ||
82 | |||
83 | uint32_t block_size; // block size that the adb host is using to send the file to us | ||
84 | uint32_t file_blocks; // file size in block_size blocks | ||
85 | |||
86 | uid_t uid; | ||
87 | gid_t gid; | ||
88 | |||
89 | uint32_t curr_block; // cache the block most recently read from the host | ||
90 | uint8_t* block_data; | ||
91 | |||
92 | uint8_t* extra_block; // another block of storage for reads that span two blocks | ||
93 | |||
94 | std::vector<SHA256Digest> | ||
95 | hashes; // SHA-256 hash of each block (all zeros if block hasn't been read yet) | ||
96 | }; | ||
97 | |||
98 | static void fuse_reply(const fuse_data* fd, uint64_t unique, const void* data, size_t len) { | ||
99 | fuse_out_header hdr; | ||
100 | hdr.len = len + sizeof(hdr); | ||
101 | hdr.error = 0; | ||
102 | hdr.unique = unique; | ||
103 | |||
104 | struct iovec vec[2]; | ||
105 | vec[0].iov_base = &hdr; | ||
106 | vec[0].iov_len = sizeof(hdr); | ||
107 | vec[1].iov_base = const_cast<void*>(data); | ||
108 | vec[1].iov_len = len; | ||
109 | |||
110 | int res = writev(fd->ffd, vec, 2); | ||
111 | if (res == -1) { | ||
112 | printf("*** REPLY FAILED *** %s\n", strerror(errno)); | ||
113 | } | ||
114 | } | ||
115 | |||
116 | static int handle_init(void* data, fuse_data* fd, const fuse_in_header* hdr) { | ||
117 | const fuse_init_in* req = static_cast<const fuse_init_in*>(data); | ||
118 | |||
119 | // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out defined (fuse version 7.6). | ||
120 | // The structure is the same from 7.6 through 7.22. Beginning with 7.23, the structure increased | ||
121 | // in size and added new parameters. | ||
122 | if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) { | ||
123 | printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6", req->major, | ||
124 | req->minor, FUSE_KERNEL_VERSION); | ||
125 | return -1; | ||
126 | } | ||
127 | |||
128 | fuse_init_out out; | ||
129 | out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION); | ||
130 | size_t fuse_struct_size = sizeof(out); | ||
131 | #if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) | ||
132 | /* FUSE_KERNEL_VERSION >= 23. */ | ||
133 | |||
134 | // If the kernel only works on minor revs older than or equal to 22, then use the older structure | ||
135 | // size since this code only uses the 7.22 version of the structure. | ||
136 | if (req->minor <= 22) { | ||
137 | fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE; | ||
138 | } | ||
139 | #endif | ||
140 | |||
141 | out.major = FUSE_KERNEL_VERSION; | ||
142 | out.max_readahead = req->max_readahead; | ||
143 | out.flags = 0; | ||
144 | out.max_background = 32; | ||
145 | out.congestion_threshold = 32; | ||
146 | out.max_write = 4096; | ||
147 | fuse_reply(fd, hdr->unique, &out, fuse_struct_size); | ||
148 | |||
149 | return NO_STATUS; | ||
150 | } | ||
151 | |||
152 | static void fill_attr(fuse_attr* attr, const fuse_data* fd, uint64_t nodeid, uint64_t size, | ||
153 | uint32_t mode) { | ||
154 | *attr = {}; | ||
155 | attr->nlink = 1; | ||
156 | attr->uid = fd->uid; | ||
157 | attr->gid = fd->gid; | ||
158 | attr->blksize = 4096; | ||
159 | |||
160 | attr->ino = nodeid; | ||
161 | attr->size = size; | ||
162 | attr->blocks = (size == 0) ? 0 : (((size - 1) / attr->blksize) + 1); | ||
163 | attr->mode = mode; | ||
164 | } | ||
165 | |||
166 | static int handle_getattr(void* /* data */, const fuse_data* fd, const fuse_in_header* hdr) { | ||
167 | fuse_attr_out out = {}; | ||
168 | out.attr_valid = 10; | ||
169 | |||
170 | if (hdr->nodeid == FUSE_ROOT_ID) { | ||
171 | fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555); | ||
172 | } else if (hdr->nodeid == PACKAGE_FILE_ID) { | ||
173 | fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); | ||
174 | } else if (hdr->nodeid == EXIT_FLAG_ID) { | ||
175 | fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); | ||
176 | } else { | ||
177 | return -ENOENT; | ||
178 | } | ||
179 | |||
180 | fuse_reply(fd, hdr->unique, &out, sizeof(out)); | ||
181 | return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; | ||
182 | } | ||
183 | |||
184 | static int handle_lookup(void* data, const fuse_data* fd, const fuse_in_header* hdr) { | ||
185 | if (data == nullptr) return -ENOENT; | ||
186 | |||
187 | fuse_entry_out out = {}; | ||
188 | out.entry_valid = 10; | ||
189 | out.attr_valid = 10; | ||
190 | |||
191 | std::string filename(static_cast<const char*>(data)); | ||
192 | if (filename == FUSE_SIDELOAD_HOST_FILENAME) { | ||
193 | out.nodeid = PACKAGE_FILE_ID; | ||
194 | out.generation = PACKAGE_FILE_ID; | ||
195 | fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); | ||
196 | } else if (filename == FUSE_SIDELOAD_HOST_EXIT_FLAG) { | ||
197 | out.nodeid = EXIT_FLAG_ID; | ||
198 | out.generation = EXIT_FLAG_ID; | ||
199 | fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); | ||
200 | } else { | ||
201 | return -ENOENT; | ||
202 | } | ||
203 | |||
204 | fuse_reply(fd, hdr->unique, &out, sizeof(out)); | ||
205 | return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; | ||
206 | } | ||
207 | |||
208 | static int handle_open(void* /* data */, const fuse_data* fd, const fuse_in_header* hdr) { | ||
209 | if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM; | ||
210 | if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; | ||
211 | |||
212 | fuse_open_out out = {}; | ||
213 | out.fh = 10; // an arbitrary number; we always use the same handle | ||
214 | fuse_reply(fd, hdr->unique, &out, sizeof(out)); | ||
215 | return NO_STATUS; | ||
216 | } | ||
217 | |||
218 | static int handle_flush(void* /* data */, fuse_data* /* fd */, const fuse_in_header* /* hdr */) { | ||
219 | return 0; | ||
220 | } | ||
221 | |||
222 | static int handle_release(void* /* data */, fuse_data* /* fd */, const fuse_in_header* /* hdr */) { | ||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | // Fetch a block from the host into fd->curr_block and fd->block_data. | ||
227 | // Returns 0 on successful fetch, negative otherwise. | ||
228 | static int fetch_block(fuse_data* fd, uint32_t block) { | ||
229 | if (block == fd->curr_block) { | ||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | if (block >= fd->file_blocks) { | ||
234 | memset(fd->block_data, 0, fd->block_size); | ||
235 | fd->curr_block = block; | ||
236 | return 0; | ||
237 | } | ||
238 | |||
239 | size_t fetch_size = fd->block_size; | ||
240 | if (block * fd->block_size + fetch_size > fd->file_size) { | ||
241 | // If we're reading the last (partial) block of the file, expect a shorter response from the | ||
242 | // host, and pad the rest of the block with zeroes. | ||
243 | fetch_size = fd->file_size - (block * fd->block_size); | ||
244 | memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size); | ||
245 | } | ||
246 | |||
247 | int result = fd->vtab.read_block(block, fd->block_data, fetch_size); | ||
248 | if (result < 0) return result; | ||
249 | |||
250 | fd->curr_block = block; | ||
251 | |||
252 | // Verify the hash of the block we just got from the host. | ||
253 | // | ||
254 | // - If the hash of the just-received data matches the stored hash for the block, accept it. | ||
255 | // - If the stored hash is all zeroes, store the new hash and accept the block (this is the first | ||
256 | // time we've read this block). | ||
257 | // - Otherwise, return -EINVAL for the read. | ||
258 | |||
259 | SHA256Digest hash; | ||
260 | SHA256(fd->block_data, fd->block_size, hash.data()); | ||
261 | |||
262 | const SHA256Digest& blockhash = fd->hashes[block]; | ||
263 | if (hash == blockhash) { | ||
264 | return 0; | ||
265 | } | ||
266 | |||
267 | for (uint8_t i : blockhash) { | ||
268 | if (i != 0) { | ||
269 | fd->curr_block = -1; | ||
270 | return -EIO; | ||
271 | } | ||
272 | } | ||
273 | |||
274 | fd->hashes[block] = hash; | ||
275 | return 0; | ||
276 | } | ||
277 | |||
278 | static int handle_read(void* data, fuse_data* fd, const fuse_in_header* hdr) { | ||
279 | if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; | ||
280 | |||
281 | const fuse_read_in* req = static_cast<const fuse_read_in*>(data); | ||
282 | uint64_t offset = req->offset; | ||
283 | uint32_t size = req->size; | ||
284 | |||
285 | // The docs on the fuse kernel interface are vague about what to do when a read request extends | ||
286 | // past the end of the file. We can return a short read -- the return structure does include a | ||
287 | // length field -- but in testing that caused the program using the file to segfault. (I | ||
288 | // speculate that this is due to the reading program accessing it via mmap; maybe mmap dislikes | ||
289 | // when you return something short of a whole page?) To fix this we zero-pad reads that extend | ||
290 | // past the end of the file so we're always returning exactly as many bytes as were requested. | ||
291 | // (Users of the mapped file have to know its real length anyway.) | ||
292 | |||
293 | fuse_out_header outhdr; | ||
294 | outhdr.len = sizeof(outhdr) + size; | ||
295 | outhdr.error = 0; | ||
296 | outhdr.unique = hdr->unique; | ||
297 | |||
298 | struct iovec vec[3]; | ||
299 | vec[0].iov_base = &outhdr; | ||
300 | vec[0].iov_len = sizeof(outhdr); | ||
301 | |||
302 | uint32_t block = offset / fd->block_size; | ||
303 | int result = fetch_block(fd, block); | ||
304 | if (result != 0) return result; | ||
305 | |||
306 | // Two cases: | ||
307 | // | ||
308 | // - the read request is entirely within this block. In this case we can reply immediately. | ||
309 | // | ||
310 | // - the read request goes over into the next block. Note that since we mount the filesystem | ||
311 | // with max_read=block_size, a read can never span more than two blocks. In this case we copy | ||
312 | // the block to extra_block and issue a fetch for the following block. | ||
313 | |||
314 | uint32_t block_offset = offset - (block * fd->block_size); | ||
315 | |||
316 | int vec_used; | ||
317 | if (size + block_offset <= fd->block_size) { | ||
318 | // First case: the read fits entirely in the first block. | ||
319 | |||
320 | vec[1].iov_base = fd->block_data + block_offset; | ||
321 | vec[1].iov_len = size; | ||
322 | vec_used = 2; | ||
323 | } else { | ||
324 | // Second case: the read spills over into the next block. | ||
325 | |||
326 | memcpy(fd->extra_block, fd->block_data + block_offset, fd->block_size - block_offset); | ||
327 | vec[1].iov_base = fd->extra_block; | ||
328 | vec[1].iov_len = fd->block_size - block_offset; | ||
329 | |||
330 | result = fetch_block(fd, block + 1); | ||
331 | if (result != 0) return result; | ||
332 | vec[2].iov_base = fd->block_data; | ||
333 | vec[2].iov_len = size - vec[1].iov_len; | ||
334 | vec_used = 3; | ||
335 | } | ||
336 | |||
337 | if (writev(fd->ffd, vec, vec_used) == -1) { | ||
338 | printf("*** READ REPLY FAILED: %s ***\n", strerror(errno)); | ||
339 | } | ||
340 | return NO_STATUS; | ||
341 | } | ||
342 | |||
343 | int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size, | ||
344 | const char* mount_point) { | ||
345 | // If something's already mounted on our mountpoint, try to remove it. (Mostly in case of a | ||
346 | // previous abnormal exit.) | ||
347 | umount2(mount_point, MNT_FORCE); | ||
348 | |||
349 | // fs/fuse/inode.c in kernel code uses the greater of 4096 and the passed-in max_read. | ||
350 | if (block_size < 4096) { | ||
351 | fprintf(stderr, "block size (%u) is too small\n", block_size); | ||
352 | return -1; | ||
353 | } | ||
354 | if (block_size > (1 << 22)) { // 4 MiB | ||
355 | fprintf(stderr, "block size (%u) is too large\n", block_size); | ||
356 | return -1; | ||
357 | } | ||
358 | |||
359 | fuse_data fd = {}; | ||
360 | fd.vtab = vtab; | ||
361 | fd.file_size = file_size; | ||
362 | fd.block_size = block_size; | ||
363 | fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1); | ||
364 | |||
365 | int result; | ||
366 | if (fd.file_blocks > (1 << 18)) { | ||
367 | fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks); | ||
368 | result = -1; | ||
369 | goto done; | ||
370 | } | ||
371 | |||
372 | // All hashes will be zero-initialized. | ||
373 | fd.hashes.resize(fd.file_blocks); | ||
374 | fd.uid = getuid(); | ||
375 | fd.gid = getgid(); | ||
376 | |||
377 | fd.curr_block = -1; | ||
378 | fd.block_data = static_cast<uint8_t*>(malloc(block_size)); | ||
379 | if (fd.block_data == nullptr) { | ||
380 | fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size); | ||
381 | result = -1; | ||
382 | goto done; | ||
383 | } | ||
384 | fd.extra_block = static_cast<uint8_t*>(malloc(block_size)); | ||
385 | if (fd.extra_block == nullptr) { | ||
386 | fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size); | ||
387 | result = -1; | ||
388 | goto done; | ||
389 | } | ||
390 | |||
391 | fd.ffd.reset(open("/dev/fuse", O_RDWR)); | ||
392 | if (!fd.ffd) { | ||
393 | perror("open /dev/fuse"); | ||
394 | result = -1; | ||
395 | goto done; | ||
396 | } | ||
397 | |||
398 | { | ||
399 | std::string opts = android::base::StringPrintf( | ||
400 | "fd=%d,user_id=%d,group_id=%d,max_read=%u,allow_other,rootmode=040000", fd.ffd.get(), | ||
401 | fd.uid, fd.gid, block_size); | ||
402 | |||
403 | result = mount("/dev/fuse", mount_point, "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, | ||
404 | opts.c_str()); | ||
405 | if (result == -1) { | ||
406 | perror("mount"); | ||
407 | goto done; | ||
408 | } | ||
409 | } | ||
410 | |||
411 | uint8_t request_buffer[sizeof(fuse_in_header) + PATH_MAX * 8]; | ||
412 | for (;;) { | ||
413 | ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer))); | ||
414 | if (len == -1) { | ||
415 | perror("read request"); | ||
416 | if (errno == ENODEV) { | ||
417 | result = -1; | ||
418 | break; | ||
419 | } | ||
420 | continue; | ||
421 | } | ||
422 | |||
423 | if (static_cast<size_t>(len) < sizeof(fuse_in_header)) { | ||
424 | fprintf(stderr, "request too short: len=%zd\n", len); | ||
425 | continue; | ||
426 | } | ||
427 | |||
428 | fuse_in_header* hdr = reinterpret_cast<fuse_in_header*>(request_buffer); | ||
429 | void* data = request_buffer + sizeof(fuse_in_header); | ||
430 | |||
431 | result = -ENOSYS; | ||
432 | |||
433 | switch (hdr->opcode) { | ||
434 | case FUSE_INIT: | ||
435 | result = handle_init(data, &fd, hdr); | ||
436 | break; | ||
437 | |||
438 | case FUSE_LOOKUP: | ||
439 | result = handle_lookup(data, &fd, hdr); | ||
440 | break; | ||
441 | |||
442 | case FUSE_GETATTR: | ||
443 | result = handle_getattr(data, &fd, hdr); | ||
444 | break; | ||
445 | |||
446 | case FUSE_OPEN: | ||
447 | result = handle_open(data, &fd, hdr); | ||
448 | break; | ||
449 | |||
450 | case FUSE_READ: | ||
451 | result = handle_read(data, &fd, hdr); | ||
452 | break; | ||
453 | |||
454 | case FUSE_FLUSH: | ||
455 | result = handle_flush(data, &fd, hdr); | ||
456 | break; | ||
457 | |||
458 | case FUSE_RELEASE: | ||
459 | result = handle_release(data, &fd, hdr); | ||
460 | break; | ||
461 | |||
462 | default: | ||
463 | fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode); | ||
464 | break; | ||
465 | } | ||
466 | |||
467 | if (result == NO_STATUS_EXIT) { | ||
468 | result = 0; | ||
469 | break; | ||
470 | } | ||
471 | |||
472 | if (result != NO_STATUS) { | ||
473 | fuse_out_header outhdr; | ||
474 | outhdr.len = sizeof(outhdr); | ||
475 | outhdr.error = result; | ||
476 | outhdr.unique = hdr->unique; | ||
477 | TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr))); | ||
478 | } | ||
479 | } | ||
480 | |||
481 | done: | ||
482 | fd.vtab.close(); | ||
483 | |||
484 | if (umount2(mount_point, MNT_DETACH) == -1) { | ||
485 | fprintf(stderr, "fuse_sideload umount failed: %s\n", strerror(errno)); | ||
486 | } | ||
487 | |||
488 | free(fd.block_data); | ||
489 | free(fd.extra_block); | ||
490 | |||
491 | return result; | ||
492 | } | ||
diff --git a/fuse_sideload/include/fuse_sideload.h b/fuse_sideload/include/fuse_sideload.h new file mode 100644 index 00000000..1b34cbdb --- /dev/null +++ b/fuse_sideload/include/fuse_sideload.h | |||
@@ -0,0 +1,40 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 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 | #ifndef __FUSE_SIDELOAD_H | ||
18 | #define __FUSE_SIDELOAD_H | ||
19 | |||
20 | #include <functional> | ||
21 | |||
22 | // Define the filenames created by the sideload FUSE filesystem. | ||
23 | static constexpr const char* FUSE_SIDELOAD_HOST_MOUNTPOINT = "/sideload"; | ||
24 | static constexpr const char* FUSE_SIDELOAD_HOST_FILENAME = "package.zip"; | ||
25 | static constexpr const char* FUSE_SIDELOAD_HOST_PATHNAME = "/sideload/package.zip"; | ||
26 | static constexpr const char* FUSE_SIDELOAD_HOST_EXIT_FLAG = "exit"; | ||
27 | static constexpr const char* FUSE_SIDELOAD_HOST_EXIT_PATHNAME = "/sideload/exit"; | ||
28 | |||
29 | struct provider_vtab { | ||
30 | // read a block | ||
31 | std::function<int(uint32_t block, uint8_t* buffer, uint32_t fetch_size)> read_block; | ||
32 | |||
33 | // close down | ||
34 | std::function<void(void)> close; | ||
35 | }; | ||
36 | |||
37 | int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size, | ||
38 | const char* mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT); | ||
39 | |||
40 | #endif | ||