system/core
Revision | 6137ed3ba2cc1fbfabaa9b3c26ffa02c8f488949 (tree) |
---|---|
Zeit | 2019-07-03 12:58:54 |
Autor | Chih-Wei Huang <cwhuang@linu...> |
Commiter | Chih-Wei Huang |
Revert "Remove FUSE logic; it's only a sdcardfs wrapper."
This reverts commit 68e1093028185cde31dcacf34a59f6c08848d3d4.
@@ -1,17 +0,0 @@ | ||
1 | -cc_binary { | |
2 | - srcs: ["sdcard.cpp"], | |
3 | - name: "sdcard", | |
4 | - cflags: [ | |
5 | - "-Wall", | |
6 | - "-Wno-unused-parameter", | |
7 | - "-Werror", | |
8 | - ], | |
9 | - shared_libs: [ | |
10 | - "libbase", | |
11 | - "libcutils", | |
12 | - "libminijail", | |
13 | - ], | |
14 | - sanitize: { | |
15 | - misc_undefined: ["integer"], | |
16 | - }, | |
17 | -} |
@@ -0,0 +1,12 @@ | ||
1 | +LOCAL_PATH := $(call my-dir) | |
2 | + | |
3 | +include $(CLEAR_VARS) | |
4 | + | |
5 | +LOCAL_SRC_FILES := sdcard.cpp fuse.cpp | |
6 | +LOCAL_MODULE := sdcard | |
7 | +LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror | |
8 | +LOCAL_SHARED_LIBRARIES := libbase libcutils libminijail libpackagelistparser | |
9 | + | |
10 | +LOCAL_SANITIZE := integer | |
11 | + | |
12 | +include $(BUILD_EXECUTABLE) |
@@ -0,0 +1,1476 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2010 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 <string.h> | |
19 | +#include <unistd.h> | |
20 | + | |
21 | +#define LOG_TAG "sdcard" | |
22 | + | |
23 | +#include "fuse.h" | |
24 | + | |
25 | +#include <android-base/logging.h> | |
26 | + | |
27 | +/* FUSE_CANONICAL_PATH is not currently upstreamed */ | |
28 | +#define FUSE_CANONICAL_PATH 2016 | |
29 | + | |
30 | +#define FUSE_UNKNOWN_INO 0xffffffff | |
31 | + | |
32 | +/* Pseudo-error constant used to indicate that no fuse status is needed | |
33 | + * or that a reply has already been written. */ | |
34 | +#define NO_STATUS 1 | |
35 | + | |
36 | +static inline void *id_to_ptr(__u64 nid) | |
37 | +{ | |
38 | + return (void *) (uintptr_t) nid; | |
39 | +} | |
40 | + | |
41 | +static inline __u64 ptr_to_id(void *ptr) | |
42 | +{ | |
43 | + return (__u64) (uintptr_t) ptr; | |
44 | +} | |
45 | + | |
46 | +static void acquire_node_locked(struct node* node) | |
47 | +{ | |
48 | + node->refcount++; | |
49 | + DLOG(INFO) << "ACQUIRE " << std::hex << node << std::dec | |
50 | + << " (" << node->name << ") rc=" << node->refcount; | |
51 | +} | |
52 | + | |
53 | +static void remove_node_from_parent_locked(struct node* node); | |
54 | + | |
55 | +static void release_node_locked(struct node* node) | |
56 | +{ | |
57 | + DLOG(INFO) << "RELEASE " << std::hex << node << std::dec | |
58 | + << " (" << node->name << ") rc=" << node->refcount; | |
59 | + if (node->refcount > 0) { | |
60 | + node->refcount--; | |
61 | + if (!node->refcount) { | |
62 | + DLOG(INFO) << "DESTROY " << std::hex << node << std::dec << " (" << node->name << ")"; | |
63 | + remove_node_from_parent_locked(node); | |
64 | + | |
65 | + /* TODO: remove debugging - poison memory */ | |
66 | + memset(node->name, 0xef, node->namelen); | |
67 | + free(node->name); | |
68 | + free(node->actual_name); | |
69 | + memset(node, 0xfc, sizeof(*node)); | |
70 | + free(node); | |
71 | + } | |
72 | + } else { | |
73 | + LOG(ERROR) << std::hex << node << std::dec << " refcount=0"; | |
74 | + } | |
75 | +} | |
76 | + | |
77 | +static void add_node_to_parent_locked(struct node *node, struct node *parent) { | |
78 | + node->parent = parent; | |
79 | + node->next = parent->child; | |
80 | + parent->child = node; | |
81 | + acquire_node_locked(parent); | |
82 | +} | |
83 | + | |
84 | +static void remove_node_from_parent_locked(struct node* node) | |
85 | +{ | |
86 | + if (node->parent) { | |
87 | + if (node->parent->child == node) { | |
88 | + node->parent->child = node->parent->child->next; | |
89 | + } else { | |
90 | + struct node *node2; | |
91 | + node2 = node->parent->child; | |
92 | + while (node2->next != node) | |
93 | + node2 = node2->next; | |
94 | + node2->next = node->next; | |
95 | + } | |
96 | + release_node_locked(node->parent); | |
97 | + node->parent = NULL; | |
98 | + node->next = NULL; | |
99 | + } | |
100 | +} | |
101 | + | |
102 | +/* Gets the absolute path to a node into the provided buffer. | |
103 | + * | |
104 | + * Populates 'buf' with the path and returns the length of the path on success, | |
105 | + * or returns -1 if the path is too long for the provided buffer. | |
106 | + */ | |
107 | +static ssize_t get_node_path_locked(struct node* node, char* buf, size_t bufsize) { | |
108 | + const char* name; | |
109 | + size_t namelen; | |
110 | + if (node->graft_path) { | |
111 | + name = node->graft_path; | |
112 | + namelen = node->graft_pathlen; | |
113 | + } else if (node->actual_name) { | |
114 | + name = node->actual_name; | |
115 | + namelen = node->namelen; | |
116 | + } else { | |
117 | + name = node->name; | |
118 | + namelen = node->namelen; | |
119 | + } | |
120 | + | |
121 | + if (bufsize < namelen + 1) { | |
122 | + return -1; | |
123 | + } | |
124 | + | |
125 | + ssize_t pathlen = 0; | |
126 | + if (node->parent && node->graft_path == NULL) { | |
127 | + pathlen = get_node_path_locked(node->parent, buf, bufsize - namelen - 1); | |
128 | + if (pathlen < 0) { | |
129 | + return -1; | |
130 | + } | |
131 | + buf[pathlen++] = '/'; | |
132 | + } | |
133 | + | |
134 | + memcpy(buf + pathlen, name, namelen + 1); /* include trailing \0 */ | |
135 | + return pathlen + namelen; | |
136 | +} | |
137 | + | |
138 | +/* Finds the absolute path of a file within a given directory. | |
139 | + * Performs a case-insensitive search for the file and sets the buffer to the path | |
140 | + * of the first matching file. If 'search' is zero or if no match is found, sets | |
141 | + * the buffer to the path that the file would have, assuming the name were case-sensitive. | |
142 | + * | |
143 | + * Populates 'buf' with the path and returns the actual name (within 'buf') on success, | |
144 | + * or returns NULL if the path is too long for the provided buffer. | |
145 | + */ | |
146 | +static char* find_file_within(const char* path, const char* name, | |
147 | + char* buf, size_t bufsize, int search) | |
148 | +{ | |
149 | + size_t pathlen = strlen(path); | |
150 | + size_t namelen = strlen(name); | |
151 | + size_t childlen = pathlen + namelen + 1; | |
152 | + char* actual; | |
153 | + | |
154 | + if (bufsize <= childlen) { | |
155 | + return NULL; | |
156 | + } | |
157 | + | |
158 | + memcpy(buf, path, pathlen); | |
159 | + buf[pathlen] = '/'; | |
160 | + actual = buf + pathlen + 1; | |
161 | + memcpy(actual, name, namelen + 1); | |
162 | + | |
163 | + if (search && access(buf, F_OK)) { | |
164 | + struct dirent* entry; | |
165 | + DIR* dir = opendir(path); | |
166 | + if (!dir) { | |
167 | + PLOG(ERROR) << "opendir(" << path << ") failed"; | |
168 | + return actual; | |
169 | + } | |
170 | + while ((entry = readdir(dir))) { | |
171 | + if (!strcasecmp(entry->d_name, name)) { | |
172 | + /* we have a match - replace the name, don't need to copy the null again */ | |
173 | + memcpy(actual, entry->d_name, namelen); | |
174 | + break; | |
175 | + } | |
176 | + } | |
177 | + closedir(dir); | |
178 | + } | |
179 | + return actual; | |
180 | +} | |
181 | + | |
182 | +static void attr_from_stat(struct fuse* fuse, struct fuse_attr *attr, | |
183 | + const struct stat *s, const struct node* node) { | |
184 | + attr->ino = node->ino; | |
185 | + attr->size = s->st_size; | |
186 | + attr->blocks = s->st_blocks; | |
187 | + attr->atime = s->st_atim.tv_sec; | |
188 | + attr->mtime = s->st_mtim.tv_sec; | |
189 | + attr->ctime = s->st_ctim.tv_sec; | |
190 | + attr->atimensec = s->st_atim.tv_nsec; | |
191 | + attr->mtimensec = s->st_mtim.tv_nsec; | |
192 | + attr->ctimensec = s->st_ctim.tv_nsec; | |
193 | + attr->mode = s->st_mode; | |
194 | + attr->nlink = s->st_nlink; | |
195 | + | |
196 | + attr->uid = node->uid; | |
197 | + | |
198 | + if (fuse->gid == AID_SDCARD_RW) { | |
199 | + /* As an optimization, certain trusted system components only run | |
200 | + * as owner but operate across all users. Since we're now handing | |
201 | + * out the sdcard_rw GID only to trusted apps, we're okay relaxing | |
202 | + * the user boundary enforcement for the default view. The UIDs | |
203 | + * assigned to app directories are still multiuser aware. */ | |
204 | + attr->gid = AID_SDCARD_RW; | |
205 | + } else { | |
206 | + attr->gid = multiuser_get_uid(node->userid, fuse->gid); | |
207 | + } | |
208 | + | |
209 | + int visible_mode = 0775 & ~fuse->mask; | |
210 | + if (node->perm == PERM_PRE_ROOT) { | |
211 | + /* Top of multi-user view should always be visible to ensure | |
212 | + * secondary users can traverse inside. */ | |
213 | + visible_mode = 0711; | |
214 | + } else if (node->under_android) { | |
215 | + /* Block "other" access to Android directories, since only apps | |
216 | + * belonging to a specific user should be in there; we still | |
217 | + * leave +x open for the default view. */ | |
218 | + if (fuse->gid == AID_SDCARD_RW) { | |
219 | + visible_mode = visible_mode & ~0006; | |
220 | + } else { | |
221 | + visible_mode = visible_mode & ~0007; | |
222 | + } | |
223 | + } | |
224 | + int owner_mode = s->st_mode & 0700; | |
225 | + int filtered_mode = visible_mode & (owner_mode | (owner_mode >> 3) | (owner_mode >> 6)); | |
226 | + attr->mode = (attr->mode & S_IFMT) | filtered_mode; | |
227 | +} | |
228 | + | |
229 | +static int touch(char* path, mode_t mode) { | |
230 | + int fd = TEMP_FAILURE_RETRY(open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, | |
231 | + mode)); | |
232 | + if (fd == -1) { | |
233 | + if (errno == EEXIST) { | |
234 | + return 0; | |
235 | + } else { | |
236 | + PLOG(ERROR) << "open(" << path << ") failed"; | |
237 | + return -1; | |
238 | + } | |
239 | + } | |
240 | + close(fd); | |
241 | + return 0; | |
242 | +} | |
243 | + | |
244 | +static void derive_permissions_locked(struct fuse* fuse, struct node *parent, | |
245 | + struct node *node) { | |
246 | + appid_t appid; | |
247 | + | |
248 | + /* By default, each node inherits from its parent */ | |
249 | + node->perm = PERM_INHERIT; | |
250 | + node->userid = parent->userid; | |
251 | + node->uid = parent->uid; | |
252 | + node->under_android = parent->under_android; | |
253 | + | |
254 | + /* Derive custom permissions based on parent and current node */ | |
255 | + switch (parent->perm) { | |
256 | + case PERM_INHERIT: | |
257 | + /* Already inherited above */ | |
258 | + break; | |
259 | + case PERM_PRE_ROOT: | |
260 | + /* Legacy internal layout places users at top level */ | |
261 | + node->perm = PERM_ROOT; | |
262 | + node->userid = strtoul(node->name, NULL, 10); | |
263 | + break; | |
264 | + case PERM_ROOT: | |
265 | + /* Assume masked off by default. */ | |
266 | + if (!strcasecmp(node->name, "Android")) { | |
267 | + /* App-specific directories inside; let anyone traverse */ | |
268 | + node->perm = PERM_ANDROID; | |
269 | + node->under_android = true; | |
270 | + } | |
271 | + break; | |
272 | + case PERM_ANDROID: | |
273 | + if (!strcasecmp(node->name, "data")) { | |
274 | + /* App-specific directories inside; let anyone traverse */ | |
275 | + node->perm = PERM_ANDROID_DATA; | |
276 | + } else if (!strcasecmp(node->name, "obb")) { | |
277 | + /* App-specific directories inside; let anyone traverse */ | |
278 | + node->perm = PERM_ANDROID_OBB; | |
279 | + /* Single OBB directory is always shared */ | |
280 | + node->graft_path = fuse->global->obb_path; | |
281 | + node->graft_pathlen = strlen(fuse->global->obb_path); | |
282 | + } else if (!strcasecmp(node->name, "media")) { | |
283 | + /* App-specific directories inside; let anyone traverse */ | |
284 | + node->perm = PERM_ANDROID_MEDIA; | |
285 | + } | |
286 | + break; | |
287 | + case PERM_ANDROID_DATA: | |
288 | + case PERM_ANDROID_OBB: | |
289 | + case PERM_ANDROID_MEDIA: | |
290 | + const auto& iter = fuse->global->package_to_appid->find(node->name); | |
291 | + if (iter != fuse->global->package_to_appid->end()) { | |
292 | + appid = iter->second; | |
293 | + node->uid = multiuser_get_uid(parent->userid, appid); | |
294 | + } | |
295 | + break; | |
296 | + } | |
297 | +} | |
298 | + | |
299 | +void derive_permissions_recursive_locked(struct fuse* fuse, struct node *parent) { | |
300 | + struct node *node; | |
301 | + for (node = parent->child; node; node = node->next) { | |
302 | + derive_permissions_locked(fuse, parent, node); | |
303 | + if (node->child) { | |
304 | + derive_permissions_recursive_locked(fuse, node); | |
305 | + } | |
306 | + } | |
307 | +} | |
308 | + | |
309 | +/* Kernel has already enforced everything we returned through | |
310 | + * derive_permissions_locked(), so this is used to lock down access | |
311 | + * even further, such as enforcing that apps hold sdcard_rw. */ | |
312 | +static bool check_caller_access_to_name(struct fuse* fuse, | |
313 | + const struct fuse_in_header *hdr, const struct node* parent_node, | |
314 | + const char* name, int mode) { | |
315 | + /* Always block security-sensitive files at root */ | |
316 | + if (parent_node && parent_node->perm == PERM_ROOT) { | |
317 | + if (!strcasecmp(name, "autorun.inf") | |
318 | + || !strcasecmp(name, ".android_secure") | |
319 | + || !strcasecmp(name, "android_secure")) { | |
320 | + return false; | |
321 | + } | |
322 | + } | |
323 | + | |
324 | + /* Root always has access; access for any other UIDs should always | |
325 | + * be controlled through packages.list. */ | |
326 | + if (hdr->uid == AID_ROOT) { | |
327 | + return true; | |
328 | + } | |
329 | + | |
330 | + /* No extra permissions to enforce */ | |
331 | + return true; | |
332 | +} | |
333 | + | |
334 | +static bool check_caller_access_to_node(struct fuse* fuse, | |
335 | + const struct fuse_in_header *hdr, const struct node* node, int mode) { | |
336 | + return check_caller_access_to_name(fuse, hdr, node->parent, node->name, mode); | |
337 | +} | |
338 | + | |
339 | +struct node *create_node_locked(struct fuse* fuse, | |
340 | + struct node *parent, const char *name, const char* actual_name) | |
341 | +{ | |
342 | + struct node *node; | |
343 | + size_t namelen = strlen(name); | |
344 | + | |
345 | + // Detect overflows in the inode counter. "4 billion nodes should be enough | |
346 | + // for everybody". | |
347 | + if (fuse->global->inode_ctr == 0) { | |
348 | + LOG(ERROR) << "No more inode numbers available"; | |
349 | + return NULL; | |
350 | + } | |
351 | + | |
352 | + node = static_cast<struct node*>(calloc(1, sizeof(struct node))); | |
353 | + if (!node) { | |
354 | + return NULL; | |
355 | + } | |
356 | + node->name = static_cast<char*>(malloc(namelen + 1)); | |
357 | + if (!node->name) { | |
358 | + free(node); | |
359 | + return NULL; | |
360 | + } | |
361 | + memcpy(node->name, name, namelen + 1); | |
362 | + if (strcmp(name, actual_name)) { | |
363 | + node->actual_name = static_cast<char*>(malloc(namelen + 1)); | |
364 | + if (!node->actual_name) { | |
365 | + free(node->name); | |
366 | + free(node); | |
367 | + return NULL; | |
368 | + } | |
369 | + memcpy(node->actual_name, actual_name, namelen + 1); | |
370 | + } | |
371 | + node->namelen = namelen; | |
372 | + node->nid = ptr_to_id(node); | |
373 | + node->ino = fuse->global->inode_ctr++; | |
374 | + node->gen = fuse->global->next_generation++; | |
375 | + | |
376 | + node->deleted = false; | |
377 | + | |
378 | + derive_permissions_locked(fuse, parent, node); | |
379 | + acquire_node_locked(node); | |
380 | + add_node_to_parent_locked(node, parent); | |
381 | + return node; | |
382 | +} | |
383 | + | |
384 | +static int rename_node_locked(struct node *node, const char *name, | |
385 | + const char* actual_name) | |
386 | +{ | |
387 | + size_t namelen = strlen(name); | |
388 | + int need_actual_name = strcmp(name, actual_name); | |
389 | + | |
390 | + /* make the storage bigger without actually changing the name | |
391 | + * in case an error occurs part way */ | |
392 | + if (namelen > node->namelen) { | |
393 | + char* new_name = static_cast<char*>(realloc(node->name, namelen + 1)); | |
394 | + if (!new_name) { | |
395 | + return -ENOMEM; | |
396 | + } | |
397 | + node->name = new_name; | |
398 | + if (need_actual_name && node->actual_name) { | |
399 | + char* new_actual_name = static_cast<char*>(realloc(node->actual_name, namelen + 1)); | |
400 | + if (!new_actual_name) { | |
401 | + return -ENOMEM; | |
402 | + } | |
403 | + node->actual_name = new_actual_name; | |
404 | + } | |
405 | + } | |
406 | + | |
407 | + /* update the name, taking care to allocate storage before overwriting the old name */ | |
408 | + if (need_actual_name) { | |
409 | + if (!node->actual_name) { | |
410 | + node->actual_name = static_cast<char*>(malloc(namelen + 1)); | |
411 | + if (!node->actual_name) { | |
412 | + return -ENOMEM; | |
413 | + } | |
414 | + } | |
415 | + memcpy(node->actual_name, actual_name, namelen + 1); | |
416 | + } else { | |
417 | + free(node->actual_name); | |
418 | + node->actual_name = NULL; | |
419 | + } | |
420 | + memcpy(node->name, name, namelen + 1); | |
421 | + node->namelen = namelen; | |
422 | + return 0; | |
423 | +} | |
424 | + | |
425 | +static struct node *lookup_node_by_id_locked(struct fuse *fuse, __u64 nid) | |
426 | +{ | |
427 | + if (nid == FUSE_ROOT_ID) { | |
428 | + return &fuse->global->root; | |
429 | + } else { | |
430 | + return static_cast<struct node*>(id_to_ptr(nid)); | |
431 | + } | |
432 | +} | |
433 | + | |
434 | +static struct node* lookup_node_and_path_by_id_locked(struct fuse* fuse, __u64 nid, | |
435 | + char* buf, size_t bufsize) | |
436 | +{ | |
437 | + struct node* node = lookup_node_by_id_locked(fuse, nid); | |
438 | + if (node && get_node_path_locked(node, buf, bufsize) < 0) { | |
439 | + node = NULL; | |
440 | + } | |
441 | + return node; | |
442 | +} | |
443 | + | |
444 | +static struct node *lookup_child_by_name_locked(struct node *node, const char *name) | |
445 | +{ | |
446 | + for (node = node->child; node; node = node->next) { | |
447 | + /* use exact string comparison, nodes that differ by case | |
448 | + * must be considered distinct even if they refer to the same | |
449 | + * underlying file as otherwise operations such as "mv x x" | |
450 | + * will not work because the source and target nodes are the same. */ | |
451 | + if (!strcmp(name, node->name) && !node->deleted) { | |
452 | + return node; | |
453 | + } | |
454 | + } | |
455 | + return 0; | |
456 | +} | |
457 | + | |
458 | +static struct node* acquire_or_create_child_locked( | |
459 | + struct fuse* fuse, struct node* parent, | |
460 | + const char* name, const char* actual_name) | |
461 | +{ | |
462 | + struct node* child = lookup_child_by_name_locked(parent, name); | |
463 | + if (child) { | |
464 | + acquire_node_locked(child); | |
465 | + } else { | |
466 | + child = create_node_locked(fuse, parent, name, actual_name); | |
467 | + } | |
468 | + return child; | |
469 | +} | |
470 | + | |
471 | +static void fuse_status(struct fuse *fuse, __u64 unique, int err) | |
472 | +{ | |
473 | + struct fuse_out_header hdr; | |
474 | + hdr.len = sizeof(hdr); | |
475 | + hdr.error = err; | |
476 | + hdr.unique = unique; | |
477 | + ssize_t ret = TEMP_FAILURE_RETRY(write(fuse->fd, &hdr, sizeof(hdr))); | |
478 | + if (ret == -1) { | |
479 | + PLOG(ERROR) << "*** STATUS FAILED ***"; | |
480 | + } else if (static_cast<size_t>(ret) != sizeof(hdr)) { | |
481 | + LOG(ERROR) << "*** STATUS FAILED: written " << ret << " expected " | |
482 | + << sizeof(hdr) << " ***"; | |
483 | + } | |
484 | +} | |
485 | + | |
486 | +static void fuse_reply(struct fuse *fuse, __u64 unique, void *data, int len) | |
487 | +{ | |
488 | + struct fuse_out_header hdr; | |
489 | + hdr.len = len + sizeof(hdr); | |
490 | + hdr.error = 0; | |
491 | + hdr.unique = unique; | |
492 | + | |
493 | + struct iovec vec[2]; | |
494 | + vec[0].iov_base = &hdr; | |
495 | + vec[0].iov_len = sizeof(hdr); | |
496 | + vec[1].iov_base = data; | |
497 | + vec[1].iov_len = len; | |
498 | + | |
499 | + ssize_t ret = TEMP_FAILURE_RETRY(writev(fuse->fd, vec, 2)); | |
500 | + if (ret == -1) { | |
501 | + PLOG(ERROR) << "*** REPLY FAILED ***"; | |
502 | + } else if (static_cast<size_t>(ret) != sizeof(hdr) + len) { | |
503 | + LOG(ERROR) << "*** REPLY FAILED: written " << ret << " expected " | |
504 | + << sizeof(hdr) + len << " ***"; | |
505 | + } | |
506 | +} | |
507 | + | |
508 | +static int fuse_reply_entry(struct fuse* fuse, __u64 unique, | |
509 | + struct node* parent, const char* name, const char* actual_name, | |
510 | + const char* path) | |
511 | +{ | |
512 | + struct node* node; | |
513 | + struct fuse_entry_out out; | |
514 | + struct stat s; | |
515 | + | |
516 | + if (lstat(path, &s) == -1) { | |
517 | + return -errno; | |
518 | + } | |
519 | + | |
520 | + pthread_mutex_lock(&fuse->global->lock); | |
521 | + node = acquire_or_create_child_locked(fuse, parent, name, actual_name); | |
522 | + if (!node) { | |
523 | + pthread_mutex_unlock(&fuse->global->lock); | |
524 | + return -ENOMEM; | |
525 | + } | |
526 | + memset(&out, 0, sizeof(out)); | |
527 | + attr_from_stat(fuse, &out.attr, &s, node); | |
528 | + out.attr_valid = 10; | |
529 | + out.entry_valid = 10; | |
530 | + out.nodeid = node->nid; | |
531 | + out.generation = node->gen; | |
532 | + pthread_mutex_unlock(&fuse->global->lock); | |
533 | + fuse_reply(fuse, unique, &out, sizeof(out)); | |
534 | + return NO_STATUS; | |
535 | +} | |
536 | + | |
537 | +static int fuse_reply_attr(struct fuse* fuse, __u64 unique, const struct node* node, | |
538 | + const char* path) | |
539 | +{ | |
540 | + struct fuse_attr_out out; | |
541 | + struct stat s; | |
542 | + | |
543 | + if (lstat(path, &s) == -1) { | |
544 | + return -errno; | |
545 | + } | |
546 | + memset(&out, 0, sizeof(out)); | |
547 | + attr_from_stat(fuse, &out.attr, &s, node); | |
548 | + out.attr_valid = 10; | |
549 | + fuse_reply(fuse, unique, &out, sizeof(out)); | |
550 | + return NO_STATUS; | |
551 | +} | |
552 | + | |
553 | +static void fuse_notify_delete(struct fuse* fuse, const __u64 parent, | |
554 | + const __u64 child, const char* name) { | |
555 | + struct fuse_out_header hdr; | |
556 | + struct fuse_notify_delete_out data; | |
557 | + size_t namelen = strlen(name); | |
558 | + hdr.len = sizeof(hdr) + sizeof(data) + namelen + 1; | |
559 | + hdr.error = FUSE_NOTIFY_DELETE; | |
560 | + hdr.unique = 0; | |
561 | + | |
562 | + data.parent = parent; | |
563 | + data.child = child; | |
564 | + data.namelen = namelen; | |
565 | + data.padding = 0; | |
566 | + | |
567 | + struct iovec vec[3]; | |
568 | + vec[0].iov_base = &hdr; | |
569 | + vec[0].iov_len = sizeof(hdr); | |
570 | + vec[1].iov_base = &data; | |
571 | + vec[1].iov_len = sizeof(data); | |
572 | + vec[2].iov_base = (void*) name; | |
573 | + vec[2].iov_len = namelen + 1; | |
574 | + | |
575 | + ssize_t ret = TEMP_FAILURE_RETRY(writev(fuse->fd, vec, 3)); | |
576 | + /* Ignore ENOENT, since other views may not have seen the entry */ | |
577 | + if (ret == -1) { | |
578 | + if (errno != ENOENT) { | |
579 | + PLOG(ERROR) << "*** NOTIFY FAILED ***"; | |
580 | + } | |
581 | + } else if (static_cast<size_t>(ret) != sizeof(hdr) + sizeof(data) + namelen + 1) { | |
582 | + LOG(ERROR) << "*** NOTIFY FAILED: written " << ret << " expected " | |
583 | + << sizeof(hdr) + sizeof(data) + namelen + 1 << " ***"; | |
584 | + } | |
585 | +} | |
586 | + | |
587 | +static int handle_lookup(struct fuse* fuse, struct fuse_handler* handler, | |
588 | + const struct fuse_in_header *hdr, const char* name) | |
589 | +{ | |
590 | + struct node* parent_node; | |
591 | + char parent_path[PATH_MAX]; | |
592 | + char child_path[PATH_MAX]; | |
593 | + const char* actual_name; | |
594 | + | |
595 | + pthread_mutex_lock(&fuse->global->lock); | |
596 | + parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, | |
597 | + parent_path, sizeof(parent_path)); | |
598 | + DLOG(INFO) << "[" << handler->token << "] LOOKUP " << name << " @ " << hdr->nodeid | |
599 | + << " (" << (parent_node ? parent_node->name : "?") << ")"; | |
600 | + pthread_mutex_unlock(&fuse->global->lock); | |
601 | + | |
602 | + if (!parent_node || !(actual_name = find_file_within(parent_path, name, | |
603 | + child_path, sizeof(child_path), 1))) { | |
604 | + return -ENOENT; | |
605 | + } | |
606 | + if (!check_caller_access_to_name(fuse, hdr, parent_node, name, R_OK)) { | |
607 | + return -EACCES; | |
608 | + } | |
609 | + | |
610 | + return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path); | |
611 | +} | |
612 | + | |
613 | +static int handle_forget(struct fuse* fuse, struct fuse_handler* handler, | |
614 | + const struct fuse_in_header *hdr, const struct fuse_forget_in *req) | |
615 | +{ | |
616 | + struct node* node; | |
617 | + | |
618 | + pthread_mutex_lock(&fuse->global->lock); | |
619 | + node = lookup_node_by_id_locked(fuse, hdr->nodeid); | |
620 | + DLOG(INFO) << "[" << handler->token << "] FORGET #" << req->nlookup | |
621 | + << " @ " << std::hex << hdr->nodeid | |
622 | + << " (" << (node ? node->name : "?") << ")"; | |
623 | + if (node) { | |
624 | + __u64 n = req->nlookup; | |
625 | + while (n) { | |
626 | + n--; | |
627 | + release_node_locked(node); | |
628 | + } | |
629 | + } | |
630 | + pthread_mutex_unlock(&fuse->global->lock); | |
631 | + return NO_STATUS; /* no reply */ | |
632 | +} | |
633 | + | |
634 | +static int handle_getattr(struct fuse* fuse, struct fuse_handler* handler, | |
635 | + const struct fuse_in_header *hdr, const struct fuse_getattr_in *req) | |
636 | +{ | |
637 | + struct node* node; | |
638 | + char path[PATH_MAX]; | |
639 | + | |
640 | + pthread_mutex_lock(&fuse->global->lock); | |
641 | + node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path)); | |
642 | + DLOG(INFO) << "[" << handler->token << "] GETATTR flags=" << req->getattr_flags | |
643 | + << " fh=" << std::hex << req->fh << " @ " << hdr->nodeid << std::dec | |
644 | + << " (" << (node ? node->name : "?") << ")"; | |
645 | + pthread_mutex_unlock(&fuse->global->lock); | |
646 | + | |
647 | + if (!node) { | |
648 | + return -ENOENT; | |
649 | + } | |
650 | + if (!check_caller_access_to_node(fuse, hdr, node, R_OK)) { | |
651 | + return -EACCES; | |
652 | + } | |
653 | + | |
654 | + return fuse_reply_attr(fuse, hdr->unique, node, path); | |
655 | +} | |
656 | + | |
657 | +static int handle_setattr(struct fuse* fuse, struct fuse_handler* handler, | |
658 | + const struct fuse_in_header *hdr, const struct fuse_setattr_in *req) | |
659 | +{ | |
660 | + struct node* node; | |
661 | + char path[PATH_MAX]; | |
662 | + struct timespec times[2]; | |
663 | + | |
664 | + pthread_mutex_lock(&fuse->global->lock); | |
665 | + node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path)); | |
666 | + DLOG(INFO) << "[" << handler->token << "] SETATTR fh=" << std::hex << req->fh | |
667 | + << " valid=" << std::hex << req->valid << " @ " << hdr->nodeid << std::dec | |
668 | + << " (" << (node ? node->name : "?") << ")"; | |
669 | + pthread_mutex_unlock(&fuse->global->lock); | |
670 | + | |
671 | + if (!node) { | |
672 | + return -ENOENT; | |
673 | + } | |
674 | + | |
675 | + if (!(req->valid & FATTR_FH) && | |
676 | + !check_caller_access_to_node(fuse, hdr, node, W_OK)) { | |
677 | + return -EACCES; | |
678 | + } | |
679 | + | |
680 | + /* XXX: incomplete implementation on purpose. | |
681 | + * chmod/chown should NEVER be implemented.*/ | |
682 | + | |
683 | + if ((req->valid & FATTR_SIZE) && TEMP_FAILURE_RETRY(truncate64(path, req->size)) == -1) { | |
684 | + return -errno; | |
685 | + } | |
686 | + | |
687 | + /* Handle changing atime and mtime. If FATTR_ATIME_and FATTR_ATIME_NOW | |
688 | + * are both set, then set it to the current time. Else, set it to the | |
689 | + * time specified in the request. Same goes for mtime. Use utimensat(2) | |
690 | + * as it allows ATIME and MTIME to be changed independently, and has | |
691 | + * nanosecond resolution which fuse also has. | |
692 | + */ | |
693 | + if (req->valid & (FATTR_ATIME | FATTR_MTIME)) { | |
694 | + times[0].tv_nsec = UTIME_OMIT; | |
695 | + times[1].tv_nsec = UTIME_OMIT; | |
696 | + if (req->valid & FATTR_ATIME) { | |
697 | + if (req->valid & FATTR_ATIME_NOW) { | |
698 | + times[0].tv_nsec = UTIME_NOW; | |
699 | + } else { | |
700 | + times[0].tv_sec = req->atime; | |
701 | + times[0].tv_nsec = req->atimensec; | |
702 | + } | |
703 | + } | |
704 | + if (req->valid & FATTR_MTIME) { | |
705 | + if (req->valid & FATTR_MTIME_NOW) { | |
706 | + times[1].tv_nsec = UTIME_NOW; | |
707 | + } else { | |
708 | + times[1].tv_sec = req->mtime; | |
709 | + times[1].tv_nsec = req->mtimensec; | |
710 | + } | |
711 | + } | |
712 | + DLOG(INFO) << "[" << handler->token << "] Calling utimensat on " << path | |
713 | + << " with atime " << times[0].tv_sec << ", mtime=" << times[1].tv_sec; | |
714 | + if (utimensat(-1, path, times, 0) < 0) { | |
715 | + return -errno; | |
716 | + } | |
717 | + } | |
718 | + return fuse_reply_attr(fuse, hdr->unique, node, path); | |
719 | +} | |
720 | + | |
721 | +static int handle_mknod(struct fuse* fuse, struct fuse_handler* handler, | |
722 | + const struct fuse_in_header* hdr, const struct fuse_mknod_in* req, const char* name) | |
723 | +{ | |
724 | + struct node* parent_node; | |
725 | + char parent_path[PATH_MAX]; | |
726 | + char child_path[PATH_MAX]; | |
727 | + const char* actual_name; | |
728 | + | |
729 | + pthread_mutex_lock(&fuse->global->lock); | |
730 | + parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, | |
731 | + parent_path, sizeof(parent_path)); | |
732 | + DLOG(INFO) << "[" << handler->token << "] MKNOD " << name << " 0" << std::oct << req->mode | |
733 | + << " @ " << std::hex << hdr->nodeid | |
734 | + << " (" << (parent_node ? parent_node->name : "?") << ")"; | |
735 | + pthread_mutex_unlock(&fuse->global->lock); | |
736 | + | |
737 | + if (!parent_node || !(actual_name = find_file_within(parent_path, name, | |
738 | + child_path, sizeof(child_path), 1))) { | |
739 | + return -ENOENT; | |
740 | + } | |
741 | + if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) { | |
742 | + return -EACCES; | |
743 | + } | |
744 | + __u32 mode = (req->mode & (~0777)) | 0664; | |
745 | + if (mknod(child_path, mode, req->rdev) == -1) { | |
746 | + return -errno; | |
747 | + } | |
748 | + return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path); | |
749 | +} | |
750 | + | |
751 | +static int handle_mkdir(struct fuse* fuse, struct fuse_handler* handler, | |
752 | + const struct fuse_in_header* hdr, const struct fuse_mkdir_in* req, const char* name) | |
753 | +{ | |
754 | + struct node* parent_node; | |
755 | + char parent_path[PATH_MAX]; | |
756 | + char child_path[PATH_MAX]; | |
757 | + const char* actual_name; | |
758 | + | |
759 | + pthread_mutex_lock(&fuse->global->lock); | |
760 | + parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, | |
761 | + parent_path, sizeof(parent_path)); | |
762 | + DLOG(INFO) << "[" << handler->token << "] MKDIR " << name << " 0" << std::oct << req->mode | |
763 | + << " @ " << std::hex << hdr->nodeid | |
764 | + << " (" << (parent_node ? parent_node->name : "?") << ")"; | |
765 | + pthread_mutex_unlock(&fuse->global->lock); | |
766 | + | |
767 | + if (!parent_node || !(actual_name = find_file_within(parent_path, name, | |
768 | + child_path, sizeof(child_path), 1))) { | |
769 | + return -ENOENT; | |
770 | + } | |
771 | + if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) { | |
772 | + return -EACCES; | |
773 | + } | |
774 | + __u32 mode = (req->mode & (~0777)) | 0775; | |
775 | + if (mkdir(child_path, mode) == -1) { | |
776 | + return -errno; | |
777 | + } | |
778 | + | |
779 | + /* When creating /Android/data and /Android/obb, mark them as .nomedia */ | |
780 | + if (parent_node->perm == PERM_ANDROID && !strcasecmp(name, "data")) { | |
781 | + char nomedia[PATH_MAX]; | |
782 | + snprintf(nomedia, PATH_MAX, "%s/.nomedia", child_path); | |
783 | + if (touch(nomedia, 0664) != 0) { | |
784 | + PLOG(ERROR) << "touch(" << nomedia << ") failed"; | |
785 | + return -ENOENT; | |
786 | + } | |
787 | + } | |
788 | + if (parent_node->perm == PERM_ANDROID && !strcasecmp(name, "obb")) { | |
789 | + char nomedia[PATH_MAX]; | |
790 | + snprintf(nomedia, PATH_MAX, "%s/.nomedia", fuse->global->obb_path); | |
791 | + if (touch(nomedia, 0664) != 0) { | |
792 | + PLOG(ERROR) << "touch(" << nomedia << ") failed"; | |
793 | + return -ENOENT; | |
794 | + } | |
795 | + } | |
796 | + | |
797 | + return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path); | |
798 | +} | |
799 | + | |
800 | +static int handle_unlink(struct fuse* fuse, struct fuse_handler* handler, | |
801 | + const struct fuse_in_header* hdr, const char* name) | |
802 | +{ | |
803 | + struct node* parent_node; | |
804 | + struct node* child_node; | |
805 | + char parent_path[PATH_MAX]; | |
806 | + char child_path[PATH_MAX]; | |
807 | + | |
808 | + pthread_mutex_lock(&fuse->global->lock); | |
809 | + parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, | |
810 | + parent_path, sizeof(parent_path)); | |
811 | + DLOG(INFO) << "[" << handler->token << "] UNLINK " << name << " @ " << std::hex << hdr->nodeid | |
812 | + << " (" << (parent_node ? parent_node->name : "?") << ")"; | |
813 | + pthread_mutex_unlock(&fuse->global->lock); | |
814 | + | |
815 | + if (!parent_node || !find_file_within(parent_path, name, | |
816 | + child_path, sizeof(child_path), 1)) { | |
817 | + return -ENOENT; | |
818 | + } | |
819 | + if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) { | |
820 | + return -EACCES; | |
821 | + } | |
822 | + if (unlink(child_path) == -1) { | |
823 | + return -errno; | |
824 | + } | |
825 | + pthread_mutex_lock(&fuse->global->lock); | |
826 | + child_node = lookup_child_by_name_locked(parent_node, name); | |
827 | + if (child_node) { | |
828 | + child_node->deleted = true; | |
829 | + } | |
830 | + pthread_mutex_unlock(&fuse->global->lock); | |
831 | + if (parent_node && child_node) { | |
832 | + /* Tell all other views that node is gone */ | |
833 | + DLOG(INFO) << "[" << handler->token << "] fuse_notify_delete" | |
834 | + << " parent=" << std::hex << parent_node->nid | |
835 | + << ", child=" << std::hex << child_node->nid << std::dec | |
836 | + << ", name=" << name; | |
837 | + if (fuse != fuse->global->fuse_default) { | |
838 | + fuse_notify_delete(fuse->global->fuse_default, parent_node->nid, child_node->nid, name); | |
839 | + } | |
840 | + if (fuse != fuse->global->fuse_read) { | |
841 | + fuse_notify_delete(fuse->global->fuse_read, parent_node->nid, child_node->nid, name); | |
842 | + } | |
843 | + if (fuse != fuse->global->fuse_write) { | |
844 | + fuse_notify_delete(fuse->global->fuse_write, parent_node->nid, child_node->nid, name); | |
845 | + } | |
846 | + } | |
847 | + return 0; | |
848 | +} | |
849 | + | |
850 | +static int handle_rmdir(struct fuse* fuse, struct fuse_handler* handler, | |
851 | + const struct fuse_in_header* hdr, const char* name) | |
852 | +{ | |
853 | + struct node* child_node; | |
854 | + struct node* parent_node; | |
855 | + char parent_path[PATH_MAX]; | |
856 | + char child_path[PATH_MAX]; | |
857 | + | |
858 | + pthread_mutex_lock(&fuse->global->lock); | |
859 | + parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, | |
860 | + parent_path, sizeof(parent_path)); | |
861 | + DLOG(INFO) << "[" << handler->token << "] UNLINK " << name << " @ " << std::hex << hdr->nodeid | |
862 | + << " (" << (parent_node ? parent_node->name : "?") << ")"; | |
863 | + pthread_mutex_unlock(&fuse->global->lock); | |
864 | + | |
865 | + if (!parent_node || !find_file_within(parent_path, name, | |
866 | + child_path, sizeof(child_path), 1)) { | |
867 | + return -ENOENT; | |
868 | + } | |
869 | + if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) { | |
870 | + return -EACCES; | |
871 | + } | |
872 | + if (rmdir(child_path) == -1) { | |
873 | + return -errno; | |
874 | + } | |
875 | + pthread_mutex_lock(&fuse->global->lock); | |
876 | + child_node = lookup_child_by_name_locked(parent_node, name); | |
877 | + if (child_node) { | |
878 | + child_node->deleted = true; | |
879 | + } | |
880 | + pthread_mutex_unlock(&fuse->global->lock); | |
881 | + if (parent_node && child_node) { | |
882 | + /* Tell all other views that node is gone */ | |
883 | + DLOG(INFO) << "[" << handler->token << "] fuse_notify_delete" | |
884 | + << " parent=" << std::hex << parent_node->nid | |
885 | + << ", child=" << std::hex << child_node->nid << std::dec | |
886 | + << ", name=" << name; | |
887 | + if (fuse != fuse->global->fuse_default) { | |
888 | + fuse_notify_delete(fuse->global->fuse_default, parent_node->nid, child_node->nid, name); | |
889 | + } | |
890 | + if (fuse != fuse->global->fuse_read) { | |
891 | + fuse_notify_delete(fuse->global->fuse_read, parent_node->nid, child_node->nid, name); | |
892 | + } | |
893 | + if (fuse != fuse->global->fuse_write) { | |
894 | + fuse_notify_delete(fuse->global->fuse_write, parent_node->nid, child_node->nid, name); | |
895 | + } | |
896 | + } | |
897 | + return 0; | |
898 | +} | |
899 | + | |
900 | +static int handle_rename(struct fuse* fuse, struct fuse_handler* handler, | |
901 | + const struct fuse_in_header* hdr, const struct fuse_rename_in* req, | |
902 | + const char* old_name, const char* new_name) | |
903 | +{ | |
904 | + struct node* old_parent_node; | |
905 | + struct node* new_parent_node; | |
906 | + struct node* child_node; | |
907 | + char old_parent_path[PATH_MAX]; | |
908 | + char new_parent_path[PATH_MAX]; | |
909 | + char old_child_path[PATH_MAX]; | |
910 | + char new_child_path[PATH_MAX]; | |
911 | + const char* new_actual_name; | |
912 | + int search; | |
913 | + int res; | |
914 | + | |
915 | + pthread_mutex_lock(&fuse->global->lock); | |
916 | + old_parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, | |
917 | + old_parent_path, sizeof(old_parent_path)); | |
918 | + new_parent_node = lookup_node_and_path_by_id_locked(fuse, req->newdir, | |
919 | + new_parent_path, sizeof(new_parent_path)); | |
920 | + DLOG(INFO) << "[" << handler->token << "] RENAME " << old_name << "->" << new_name | |
921 | + << " @ " << std::hex << hdr->nodeid | |
922 | + << " (" << (old_parent_node ? old_parent_node->name : "?") << ") -> " | |
923 | + << std::hex << req->newdir | |
924 | + << " (" << (new_parent_node ? new_parent_node->name : "?") << ")"; | |
925 | + if (!old_parent_node || !new_parent_node) { | |
926 | + res = -ENOENT; | |
927 | + goto lookup_error; | |
928 | + } | |
929 | + if (!check_caller_access_to_name(fuse, hdr, old_parent_node, old_name, W_OK)) { | |
930 | + res = -EACCES; | |
931 | + goto lookup_error; | |
932 | + } | |
933 | + if (!check_caller_access_to_name(fuse, hdr, new_parent_node, new_name, W_OK)) { | |
934 | + res = -EACCES; | |
935 | + goto lookup_error; | |
936 | + } | |
937 | + child_node = lookup_child_by_name_locked(old_parent_node, old_name); | |
938 | + if (!child_node || get_node_path_locked(child_node, | |
939 | + old_child_path, sizeof(old_child_path)) < 0) { | |
940 | + res = -ENOENT; | |
941 | + goto lookup_error; | |
942 | + } | |
943 | + acquire_node_locked(child_node); | |
944 | + pthread_mutex_unlock(&fuse->global->lock); | |
945 | + | |
946 | + /* Special case for renaming a file where destination is same path | |
947 | + * differing only by case. In this case we don't want to look for a case | |
948 | + * insensitive match. This allows commands like "mv foo FOO" to work as expected. | |
949 | + */ | |
950 | + search = old_parent_node != new_parent_node | |
951 | + || strcasecmp(old_name, new_name); | |
952 | + if (!(new_actual_name = find_file_within(new_parent_path, new_name, | |
953 | + new_child_path, sizeof(new_child_path), search))) { | |
954 | + res = -ENOENT; | |
955 | + goto io_error; | |
956 | + } | |
957 | + | |
958 | + DLOG(INFO) << "[" << handler->token << "] RENAME " << old_child_path << "->" << new_child_path; | |
959 | + res = rename(old_child_path, new_child_path); | |
960 | + if (res == -1) { | |
961 | + res = -errno; | |
962 | + goto io_error; | |
963 | + } | |
964 | + | |
965 | + pthread_mutex_lock(&fuse->global->lock); | |
966 | + res = rename_node_locked(child_node, new_name, new_actual_name); | |
967 | + if (!res) { | |
968 | + remove_node_from_parent_locked(child_node); | |
969 | + derive_permissions_locked(fuse, new_parent_node, child_node); | |
970 | + derive_permissions_recursive_locked(fuse, child_node); | |
971 | + add_node_to_parent_locked(child_node, new_parent_node); | |
972 | + } | |
973 | + goto done; | |
974 | + | |
975 | +io_error: | |
976 | + pthread_mutex_lock(&fuse->global->lock); | |
977 | +done: | |
978 | + release_node_locked(child_node); | |
979 | +lookup_error: | |
980 | + pthread_mutex_unlock(&fuse->global->lock); | |
981 | + return res; | |
982 | +} | |
983 | + | |
984 | +static int open_flags_to_access_mode(int open_flags) { | |
985 | + if ((open_flags & O_ACCMODE) == O_RDONLY) { | |
986 | + return R_OK; | |
987 | + } else if ((open_flags & O_ACCMODE) == O_WRONLY) { | |
988 | + return W_OK; | |
989 | + } else { | |
990 | + /* Probably O_RDRW, but treat as default to be safe */ | |
991 | + return R_OK | W_OK; | |
992 | + } | |
993 | +} | |
994 | + | |
995 | +static int handle_open(struct fuse* fuse, struct fuse_handler* handler, | |
996 | + const struct fuse_in_header* hdr, const struct fuse_open_in* req) | |
997 | +{ | |
998 | + struct node* node; | |
999 | + char path[PATH_MAX]; | |
1000 | + struct fuse_open_out out = {}; | |
1001 | + struct handle *h; | |
1002 | + | |
1003 | + pthread_mutex_lock(&fuse->global->lock); | |
1004 | + node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path)); | |
1005 | + DLOG(INFO) << "[" << handler->token << "] OPEN 0" << std::oct << req->flags | |
1006 | + << " @ " << std::hex << hdr->nodeid << std::dec | |
1007 | + << " (" << (node ? node->name : "?") << ")"; | |
1008 | + pthread_mutex_unlock(&fuse->global->lock); | |
1009 | + | |
1010 | + if (!node) { | |
1011 | + return -ENOENT; | |
1012 | + } | |
1013 | + if (!check_caller_access_to_node(fuse, hdr, node, | |
1014 | + open_flags_to_access_mode(req->flags))) { | |
1015 | + return -EACCES; | |
1016 | + } | |
1017 | + h = static_cast<struct handle*>(malloc(sizeof(*h))); | |
1018 | + if (!h) { | |
1019 | + return -ENOMEM; | |
1020 | + } | |
1021 | + DLOG(INFO) << "[" << handler->token << "] OPEN " << path; | |
1022 | + h->fd = TEMP_FAILURE_RETRY(open(path, req->flags)); | |
1023 | + if (h->fd == -1) { | |
1024 | + free(h); | |
1025 | + return -errno; | |
1026 | + } | |
1027 | + out.fh = ptr_to_id(h); | |
1028 | + out.open_flags = 0; | |
1029 | + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); | |
1030 | + return NO_STATUS; | |
1031 | +} | |
1032 | + | |
1033 | +static int handle_read(struct fuse* fuse, struct fuse_handler* handler, | |
1034 | + const struct fuse_in_header* hdr, const struct fuse_read_in* req) | |
1035 | +{ | |
1036 | + struct handle *h = static_cast<struct handle*>(id_to_ptr(req->fh)); | |
1037 | + __u64 unique = hdr->unique; | |
1038 | + __u32 size = req->size; | |
1039 | + __u64 offset = req->offset; | |
1040 | + int res; | |
1041 | + __u8 *read_buffer = (__u8 *) ((uintptr_t)(handler->read_buffer + PAGE_SIZE) & ~((uintptr_t)PAGE_SIZE-1)); | |
1042 | + | |
1043 | + /* Don't access any other fields of hdr or req beyond this point, the read buffer | |
1044 | + * overlaps the request buffer and will clobber data in the request. This | |
1045 | + * saves us 128KB per request handler thread at the cost of this scary comment. */ | |
1046 | + | |
1047 | + DLOG(INFO) << "[" << handler->token << "] READ " << std::hex << h << std::dec | |
1048 | + << "(" << h->fd << ") " << size << "@" << offset; | |
1049 | + if (size > MAX_READ) { | |
1050 | + return -EINVAL; | |
1051 | + } | |
1052 | + res = TEMP_FAILURE_RETRY(pread64(h->fd, read_buffer, size, offset)); | |
1053 | + if (res == -1) { | |
1054 | + return -errno; | |
1055 | + } | |
1056 | + fuse_reply(fuse, unique, read_buffer, res); | |
1057 | + return NO_STATUS; | |
1058 | +} | |
1059 | + | |
1060 | +static int handle_write(struct fuse* fuse, struct fuse_handler* handler, | |
1061 | + const struct fuse_in_header* hdr, const struct fuse_write_in* req, | |
1062 | + const void* buffer) | |
1063 | +{ | |
1064 | + struct fuse_write_out out; | |
1065 | + struct handle *h = static_cast<struct handle*>(id_to_ptr(req->fh)); | |
1066 | + int res; | |
1067 | + __u8 aligned_buffer[req->size] __attribute__((__aligned__(PAGE_SIZE))); | |
1068 | + | |
1069 | + if (req->flags & O_DIRECT) { | |
1070 | + memcpy(aligned_buffer, buffer, req->size); | |
1071 | + buffer = (const __u8*) aligned_buffer; | |
1072 | + } | |
1073 | + | |
1074 | + DLOG(INFO) << "[" << handler->token << "] WRITE " << std::hex << h << std::dec | |
1075 | + << "(" << h->fd << ") " << req->size << "@" << req->offset; | |
1076 | + res = TEMP_FAILURE_RETRY(pwrite64(h->fd, buffer, req->size, req->offset)); | |
1077 | + if (res == -1) { | |
1078 | + return -errno; | |
1079 | + } | |
1080 | + out.size = res; | |
1081 | + out.padding = 0; | |
1082 | + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); | |
1083 | + return NO_STATUS; | |
1084 | +} | |
1085 | + | |
1086 | +static int handle_statfs(struct fuse* fuse, struct fuse_handler* handler, | |
1087 | + const struct fuse_in_header* hdr) | |
1088 | +{ | |
1089 | + char path[PATH_MAX]; | |
1090 | + struct statfs stat; | |
1091 | + struct fuse_statfs_out out; | |
1092 | + int res; | |
1093 | + | |
1094 | + pthread_mutex_lock(&fuse->global->lock); | |
1095 | + DLOG(INFO) << "[" << handler->token << "] STATFS"; | |
1096 | + res = get_node_path_locked(&fuse->global->root, path, sizeof(path)); | |
1097 | + pthread_mutex_unlock(&fuse->global->lock); | |
1098 | + if (res < 0) { | |
1099 | + return -ENOENT; | |
1100 | + } | |
1101 | + if (TEMP_FAILURE_RETRY(statfs(fuse->global->root.name, &stat)) == -1) { | |
1102 | + return -errno; | |
1103 | + } | |
1104 | + memset(&out, 0, sizeof(out)); | |
1105 | + out.st.blocks = stat.f_blocks; | |
1106 | + out.st.bfree = stat.f_bfree; | |
1107 | + out.st.bavail = stat.f_bavail; | |
1108 | + out.st.files = stat.f_files; | |
1109 | + out.st.ffree = stat.f_ffree; | |
1110 | + out.st.bsize = stat.f_bsize; | |
1111 | + out.st.namelen = stat.f_namelen; | |
1112 | + out.st.frsize = stat.f_frsize; | |
1113 | + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); | |
1114 | + return NO_STATUS; | |
1115 | +} | |
1116 | + | |
1117 | +static int handle_release(struct fuse* fuse, struct fuse_handler* handler, | |
1118 | + const struct fuse_in_header* hdr, const struct fuse_release_in* req) | |
1119 | +{ | |
1120 | + struct handle *h = static_cast<struct handle*>(id_to_ptr(req->fh)); | |
1121 | + | |
1122 | + DLOG(INFO) << "[" << handler->token << "] RELEASE " << std::hex << h << std::dec | |
1123 | + << "(" << h->fd << ")"; | |
1124 | + close(h->fd); | |
1125 | + free(h); | |
1126 | + return 0; | |
1127 | +} | |
1128 | + | |
1129 | +static int handle_fsync(struct fuse* fuse, struct fuse_handler* handler, | |
1130 | + const struct fuse_in_header* hdr, const struct fuse_fsync_in* req) | |
1131 | +{ | |
1132 | + bool is_dir = (hdr->opcode == FUSE_FSYNCDIR); | |
1133 | + bool is_data_sync = req->fsync_flags & 1; | |
1134 | + | |
1135 | + int fd = -1; | |
1136 | + if (is_dir) { | |
1137 | + struct dirhandle *dh = static_cast<struct dirhandle*>(id_to_ptr(req->fh)); | |
1138 | + fd = dirfd(dh->d); | |
1139 | + } else { | |
1140 | + struct handle *h = static_cast<struct handle*>(id_to_ptr(req->fh)); | |
1141 | + fd = h->fd; | |
1142 | + } | |
1143 | + | |
1144 | + DLOG(INFO) << "[" << handler->token << "] " << (is_dir ? "FSYNCDIR" : "FSYNC") << " " | |
1145 | + << std::hex << req->fh << std::dec << "(" << fd << ") is_data_sync=" << is_data_sync; | |
1146 | + int res = is_data_sync ? fdatasync(fd) : fsync(fd); | |
1147 | + if (res == -1) { | |
1148 | + return -errno; | |
1149 | + } | |
1150 | + return 0; | |
1151 | +} | |
1152 | + | |
1153 | +static int handle_flush(struct fuse* fuse, struct fuse_handler* handler, | |
1154 | + const struct fuse_in_header* hdr) | |
1155 | +{ | |
1156 | + DLOG(INFO) << "[" << handler->token << "] FLUSH"; | |
1157 | + return 0; | |
1158 | +} | |
1159 | + | |
1160 | +static int handle_opendir(struct fuse* fuse, struct fuse_handler* handler, | |
1161 | + const struct fuse_in_header* hdr, const struct fuse_open_in* req) | |
1162 | +{ | |
1163 | + struct node* node; | |
1164 | + char path[PATH_MAX]; | |
1165 | + struct fuse_open_out out = {}; | |
1166 | + struct dirhandle *h; | |
1167 | + | |
1168 | + pthread_mutex_lock(&fuse->global->lock); | |
1169 | + node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path)); | |
1170 | + DLOG(INFO) << "[" << handler->token << "] OPENDIR @ " << std::hex << hdr->nodeid | |
1171 | + << " (" << (node ? node->name : "?") << ")"; | |
1172 | + pthread_mutex_unlock(&fuse->global->lock); | |
1173 | + | |
1174 | + if (!node) { | |
1175 | + return -ENOENT; | |
1176 | + } | |
1177 | + if (!check_caller_access_to_node(fuse, hdr, node, R_OK)) { | |
1178 | + return -EACCES; | |
1179 | + } | |
1180 | + h = static_cast<struct dirhandle*>(malloc(sizeof(*h))); | |
1181 | + if (!h) { | |
1182 | + return -ENOMEM; | |
1183 | + } | |
1184 | + DLOG(INFO) << "[" << handler->token << "] OPENDIR " << path; | |
1185 | + h->d = opendir(path); | |
1186 | + if (!h->d) { | |
1187 | + free(h); | |
1188 | + return -errno; | |
1189 | + } | |
1190 | + out.fh = ptr_to_id(h); | |
1191 | + out.open_flags = 0; | |
1192 | + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); | |
1193 | + return NO_STATUS; | |
1194 | +} | |
1195 | + | |
1196 | +static int handle_readdir(struct fuse* fuse, struct fuse_handler* handler, | |
1197 | + const struct fuse_in_header* hdr, const struct fuse_read_in* req) | |
1198 | +{ | |
1199 | + char buffer[8192]; | |
1200 | + struct fuse_dirent *fde = (struct fuse_dirent*) buffer; | |
1201 | + struct dirent *de; | |
1202 | + struct dirhandle *h = static_cast<struct dirhandle*>(id_to_ptr(req->fh)); | |
1203 | + | |
1204 | + DLOG(INFO) << "[" << handler->token << "] READDIR " << h; | |
1205 | + if (req->offset == 0) { | |
1206 | + /* rewinddir() might have been called above us, so rewind here too */ | |
1207 | + DLOG(INFO) << "[" << handler->token << "] calling rewinddir()"; | |
1208 | + rewinddir(h->d); | |
1209 | + } | |
1210 | + de = readdir(h->d); | |
1211 | + if (!de) { | |
1212 | + return 0; | |
1213 | + } | |
1214 | + fde->ino = FUSE_UNKNOWN_INO; | |
1215 | + /* increment the offset so we can detect when rewinddir() seeks back to the beginning */ | |
1216 | + fde->off = req->offset + 1; | |
1217 | + fde->type = de->d_type; | |
1218 | + fde->namelen = strlen(de->d_name); | |
1219 | + memcpy(fde->name, de->d_name, fde->namelen + 1); | |
1220 | + fuse_reply(fuse, hdr->unique, fde, | |
1221 | + FUSE_DIRENT_ALIGN(sizeof(struct fuse_dirent) + fde->namelen)); | |
1222 | + return NO_STATUS; | |
1223 | +} | |
1224 | + | |
1225 | +static int handle_releasedir(struct fuse* fuse, struct fuse_handler* handler, | |
1226 | + const struct fuse_in_header* hdr, const struct fuse_release_in* req) | |
1227 | +{ | |
1228 | + struct dirhandle *h = static_cast<struct dirhandle*>(id_to_ptr(req->fh)); | |
1229 | + | |
1230 | + DLOG(INFO) << "[" << handler->token << "] RELEASEDIR " << h; | |
1231 | + closedir(h->d); | |
1232 | + free(h); | |
1233 | + return 0; | |
1234 | +} | |
1235 | + | |
1236 | +static int handle_init(struct fuse* fuse, struct fuse_handler* handler, | |
1237 | + const struct fuse_in_header* hdr, const struct fuse_init_in* req) | |
1238 | +{ | |
1239 | + struct fuse_init_out out; | |
1240 | + size_t fuse_struct_size; | |
1241 | + | |
1242 | + DLOG(INFO) << "[" << handler->token << "] INIT ver=" << req->major << "." << req->minor | |
1243 | + << " maxread=" << req->max_readahead << " flags=" << std::hex << req->flags; | |
1244 | + | |
1245 | + /* Kernel 2.6.16 is the first stable kernel with struct fuse_init_out | |
1246 | + * defined (fuse version 7.6). The structure is the same from 7.6 through | |
1247 | + * 7.22. Beginning with 7.23, the structure increased in size and added | |
1248 | + * new parameters. | |
1249 | + */ | |
1250 | + if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) { | |
1251 | + LOG(ERROR) << "Fuse kernel version mismatch: Kernel version " | |
1252 | + << req->major << "." << req->minor | |
1253 | + << ", Expected at least " << FUSE_KERNEL_VERSION << ".6"; | |
1254 | + return -1; | |
1255 | + } | |
1256 | + | |
1257 | + /* We limit ourselves to 15 because we don't handle BATCH_FORGET yet */ | |
1258 | + out.minor = MIN(req->minor, 15); | |
1259 | + fuse_struct_size = sizeof(out); | |
1260 | +#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) | |
1261 | + /* FUSE_KERNEL_VERSION >= 23. */ | |
1262 | + | |
1263 | + /* Since we return minor version 15, the kernel does not accept the latest | |
1264 | + * fuse_init_out size. We need to use FUSE_COMPAT_22_INIT_OUT_SIZE always.*/ | |
1265 | + fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE; | |
1266 | +#endif | |
1267 | + | |
1268 | + out.major = FUSE_KERNEL_VERSION; | |
1269 | + out.max_readahead = req->max_readahead; | |
1270 | + out.flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES; | |
1271 | + out.max_background = 32; | |
1272 | + out.congestion_threshold = 32; | |
1273 | + out.max_write = MAX_WRITE; | |
1274 | + fuse_reply(fuse, hdr->unique, &out, fuse_struct_size); | |
1275 | + return NO_STATUS; | |
1276 | +} | |
1277 | + | |
1278 | +static int handle_canonical_path(struct fuse* fuse, struct fuse_handler* handler, | |
1279 | + const struct fuse_in_header *hdr) | |
1280 | +{ | |
1281 | + struct node* node; | |
1282 | + char path[PATH_MAX]; | |
1283 | + int len; | |
1284 | + | |
1285 | + pthread_mutex_lock(&fuse->global->lock); | |
1286 | + node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, | |
1287 | + path, sizeof(path)); | |
1288 | + DLOG(INFO) << "[" << handler->token << "] CANONICAL_PATH @ " << std::hex << hdr->nodeid | |
1289 | + << std::dec << " (" << (node ? node->name : "?") << ")"; | |
1290 | + pthread_mutex_unlock(&fuse->global->lock); | |
1291 | + | |
1292 | + if (!node) { | |
1293 | + return -ENOENT; | |
1294 | + } | |
1295 | + if (!check_caller_access_to_node(fuse, hdr, node, R_OK)) { | |
1296 | + return -EACCES; | |
1297 | + } | |
1298 | + len = strlen(path); | |
1299 | + if (len + 1 > PATH_MAX) | |
1300 | + len = PATH_MAX - 1; | |
1301 | + path[PATH_MAX - 1] = 0; | |
1302 | + fuse_reply(fuse, hdr->unique, path, len + 1); | |
1303 | + return NO_STATUS; | |
1304 | +} | |
1305 | + | |
1306 | +static int handle_fuse_request(struct fuse *fuse, struct fuse_handler* handler, | |
1307 | + const struct fuse_in_header *hdr, const void *data, size_t data_len) | |
1308 | +{ | |
1309 | + switch (hdr->opcode) { | |
1310 | + case FUSE_LOOKUP: { /* bytez[] -> entry_out */ | |
1311 | + const char *name = static_cast<const char*>(data); | |
1312 | + return handle_lookup(fuse, handler, hdr, name); | |
1313 | + } | |
1314 | + | |
1315 | + case FUSE_FORGET: { | |
1316 | + const struct fuse_forget_in *req = static_cast<const struct fuse_forget_in*>(data); | |
1317 | + return handle_forget(fuse, handler, hdr, req); | |
1318 | + } | |
1319 | + | |
1320 | + case FUSE_GETATTR: { /* getattr_in -> attr_out */ | |
1321 | + const struct fuse_getattr_in *req = static_cast<const struct fuse_getattr_in*>(data); | |
1322 | + return handle_getattr(fuse, handler, hdr, req); | |
1323 | + } | |
1324 | + | |
1325 | + case FUSE_SETATTR: { /* setattr_in -> attr_out */ | |
1326 | + const struct fuse_setattr_in *req = static_cast<const struct fuse_setattr_in*>(data); | |
1327 | + return handle_setattr(fuse, handler, hdr, req); | |
1328 | + } | |
1329 | + | |
1330 | +// case FUSE_READLINK: | |
1331 | +// case FUSE_SYMLINK: | |
1332 | + case FUSE_MKNOD: { /* mknod_in, bytez[] -> entry_out */ | |
1333 | + const struct fuse_mknod_in *req = static_cast<const struct fuse_mknod_in*>(data); | |
1334 | + const char *name = ((const char*) data) + sizeof(*req); | |
1335 | + return handle_mknod(fuse, handler, hdr, req, name); | |
1336 | + } | |
1337 | + | |
1338 | + case FUSE_MKDIR: { /* mkdir_in, bytez[] -> entry_out */ | |
1339 | + const struct fuse_mkdir_in *req = static_cast<const struct fuse_mkdir_in*>(data); | |
1340 | + const char *name = ((const char*) data) + sizeof(*req); | |
1341 | + return handle_mkdir(fuse, handler, hdr, req, name); | |
1342 | + } | |
1343 | + | |
1344 | + case FUSE_UNLINK: { /* bytez[] -> */ | |
1345 | + const char *name = static_cast<const char*>(data); | |
1346 | + return handle_unlink(fuse, handler, hdr, name); | |
1347 | + } | |
1348 | + | |
1349 | + case FUSE_RMDIR: { /* bytez[] -> */ | |
1350 | + const char *name = static_cast<const char*>(data); | |
1351 | + return handle_rmdir(fuse, handler, hdr, name); | |
1352 | + } | |
1353 | + | |
1354 | + case FUSE_RENAME: { /* rename_in, oldname, newname -> */ | |
1355 | + const struct fuse_rename_in *req = static_cast<const struct fuse_rename_in*>(data); | |
1356 | + const char *old_name = ((const char*) data) + sizeof(*req); | |
1357 | + const char *new_name = old_name + strlen(old_name) + 1; | |
1358 | + return handle_rename(fuse, handler, hdr, req, old_name, new_name); | |
1359 | + } | |
1360 | + | |
1361 | +// case FUSE_LINK: | |
1362 | + case FUSE_OPEN: { /* open_in -> open_out */ | |
1363 | + const struct fuse_open_in *req = static_cast<const struct fuse_open_in*>(data); | |
1364 | + return handle_open(fuse, handler, hdr, req); | |
1365 | + } | |
1366 | + | |
1367 | + case FUSE_READ: { /* read_in -> byte[] */ | |
1368 | + const struct fuse_read_in *req = static_cast<const struct fuse_read_in*>(data); | |
1369 | + return handle_read(fuse, handler, hdr, req); | |
1370 | + } | |
1371 | + | |
1372 | + case FUSE_WRITE: { /* write_in, byte[write_in.size] -> write_out */ | |
1373 | + const struct fuse_write_in *req = static_cast<const struct fuse_write_in*>(data); | |
1374 | + const void* buffer = (const __u8*)data + sizeof(*req); | |
1375 | + return handle_write(fuse, handler, hdr, req, buffer); | |
1376 | + } | |
1377 | + | |
1378 | + case FUSE_STATFS: { /* getattr_in -> attr_out */ | |
1379 | + return handle_statfs(fuse, handler, hdr); | |
1380 | + } | |
1381 | + | |
1382 | + case FUSE_RELEASE: { /* release_in -> */ | |
1383 | + const struct fuse_release_in *req = static_cast<const struct fuse_release_in*>(data); | |
1384 | + return handle_release(fuse, handler, hdr, req); | |
1385 | + } | |
1386 | + | |
1387 | + case FUSE_FSYNC: | |
1388 | + case FUSE_FSYNCDIR: { | |
1389 | + const struct fuse_fsync_in *req = static_cast<const struct fuse_fsync_in*>(data); | |
1390 | + return handle_fsync(fuse, handler, hdr, req); | |
1391 | + } | |
1392 | + | |
1393 | +// case FUSE_SETXATTR: | |
1394 | +// case FUSE_GETXATTR: | |
1395 | +// case FUSE_LISTXATTR: | |
1396 | +// case FUSE_REMOVEXATTR: | |
1397 | + case FUSE_FLUSH: { | |
1398 | + return handle_flush(fuse, handler, hdr); | |
1399 | + } | |
1400 | + | |
1401 | + case FUSE_OPENDIR: { /* open_in -> open_out */ | |
1402 | + const struct fuse_open_in *req = static_cast<const struct fuse_open_in*>(data); | |
1403 | + return handle_opendir(fuse, handler, hdr, req); | |
1404 | + } | |
1405 | + | |
1406 | + case FUSE_READDIR: { | |
1407 | + const struct fuse_read_in *req = static_cast<const struct fuse_read_in*>(data); | |
1408 | + return handle_readdir(fuse, handler, hdr, req); | |
1409 | + } | |
1410 | + | |
1411 | + case FUSE_RELEASEDIR: { /* release_in -> */ | |
1412 | + const struct fuse_release_in *req = static_cast<const struct fuse_release_in*>(data); | |
1413 | + return handle_releasedir(fuse, handler, hdr, req); | |
1414 | + } | |
1415 | + | |
1416 | + case FUSE_INIT: { /* init_in -> init_out */ | |
1417 | + const struct fuse_init_in *req = static_cast<const struct fuse_init_in*>(data); | |
1418 | + return handle_init(fuse, handler, hdr, req); | |
1419 | + } | |
1420 | + | |
1421 | + case FUSE_CANONICAL_PATH: { /* nodeid -> bytez[] */ | |
1422 | + return handle_canonical_path(fuse, handler, hdr); | |
1423 | + } | |
1424 | + | |
1425 | + default: { | |
1426 | + DLOG(INFO) << "[" << handler->token << "] NOTIMPL op=" << hdr->opcode | |
1427 | + << "uniq=" << std::hex << hdr->unique << "nid=" << hdr->nodeid << std::dec; | |
1428 | + return -ENOSYS; | |
1429 | + } | |
1430 | + } | |
1431 | +} | |
1432 | + | |
1433 | +void handle_fuse_requests(struct fuse_handler* handler) | |
1434 | +{ | |
1435 | + struct fuse* fuse = handler->fuse; | |
1436 | + for (;;) { | |
1437 | + ssize_t len = TEMP_FAILURE_RETRY(read(fuse->fd, | |
1438 | + handler->request_buffer, sizeof(handler->request_buffer))); | |
1439 | + if (len == -1) { | |
1440 | + if (errno == ENODEV) { | |
1441 | + LOG(ERROR) << "[" << handler->token << "] someone stole our marbles!"; | |
1442 | + exit(2); | |
1443 | + } | |
1444 | + PLOG(ERROR) << "[" << handler->token << "] handle_fuse_requests"; | |
1445 | + continue; | |
1446 | + } | |
1447 | + | |
1448 | + if (static_cast<size_t>(len) < sizeof(struct fuse_in_header)) { | |
1449 | + LOG(ERROR) << "[" << handler->token << "] request too short: len=" << len; | |
1450 | + continue; | |
1451 | + } | |
1452 | + | |
1453 | + const struct fuse_in_header* hdr = | |
1454 | + reinterpret_cast<const struct fuse_in_header*>(handler->request_buffer); | |
1455 | + if (hdr->len != static_cast<size_t>(len)) { | |
1456 | + LOG(ERROR) << "[" << handler->token << "] malformed header: len=" << len | |
1457 | + << ", hdr->len=" << hdr->len; | |
1458 | + continue; | |
1459 | + } | |
1460 | + | |
1461 | + const void *data = handler->request_buffer + sizeof(struct fuse_in_header); | |
1462 | + size_t data_len = len - sizeof(struct fuse_in_header); | |
1463 | + __u64 unique = hdr->unique; | |
1464 | + int res = handle_fuse_request(fuse, handler, hdr, data, data_len); | |
1465 | + | |
1466 | + /* We do not access the request again after this point because the underlying | |
1467 | + * buffer storage may have been reused while processing the request. */ | |
1468 | + | |
1469 | + if (res != NO_STATUS) { | |
1470 | + if (res) { | |
1471 | + DLOG(INFO) << "[" << handler->token << "] ERROR " << res; | |
1472 | + } | |
1473 | + fuse_status(fuse, unique, res); | |
1474 | + } | |
1475 | + } | |
1476 | +} |
@@ -0,0 +1,209 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2016 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_H_ | |
18 | +#define FUSE_H_ | |
19 | + | |
20 | +#include <dirent.h> | |
21 | +#include <fcntl.h> | |
22 | +#include <linux/fuse.h> | |
23 | +#include <pthread.h> | |
24 | +#include <stdbool.h> | |
25 | +#include <stdlib.h> | |
26 | +#include <sys/param.h> | |
27 | +#include <sys/stat.h> | |
28 | +#include <sys/statfs.h> | |
29 | +#include <sys/types.h> | |
30 | +#include <sys/uio.h> | |
31 | +#include <unistd.h> | |
32 | + | |
33 | +#include <map> | |
34 | +#include <string> | |
35 | + | |
36 | +#include <android-base/logging.h> | |
37 | +#include <cutils/fs.h> | |
38 | +#include <cutils/multiuser.h> | |
39 | +#include <packagelistparser/packagelistparser.h> | |
40 | + | |
41 | +#include <private/android_filesystem_config.h> | |
42 | + | |
43 | +#define FUSE_TRACE 0 | |
44 | + | |
45 | +#if FUSE_TRACE | |
46 | +static constexpr bool kEnableDLog = true; | |
47 | +#else // FUSE_TRACE == 0 | |
48 | +static constexpr bool kEnableDLog = false; | |
49 | +#endif | |
50 | + | |
51 | +// Use same strategy as DCHECK(). | |
52 | +#define DLOG(x) \ | |
53 | + if (kEnableDLog) LOG(x) | |
54 | + | |
55 | +/* Maximum number of bytes to write in one request. */ | |
56 | +#define MAX_WRITE (256 * 1024) | |
57 | + | |
58 | +/* Maximum number of bytes to read in one request. */ | |
59 | +#define MAX_READ (128 * 1024) | |
60 | + | |
61 | +/* Largest possible request. | |
62 | + * The request size is bounded by the maximum size of a FUSE_WRITE request because it has | |
63 | + * the largest possible data payload. */ | |
64 | +#define MAX_REQUEST_SIZE (sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + MAX_WRITE) | |
65 | + | |
66 | +namespace { | |
67 | +struct CaseInsensitiveCompare { | |
68 | + bool operator()(const std::string& lhs, const std::string& rhs) const { | |
69 | + return strcasecmp(lhs.c_str(), rhs.c_str()) < 0; | |
70 | + } | |
71 | +}; | |
72 | +} | |
73 | + | |
74 | +using AppIdMap = std::map<std::string, appid_t, CaseInsensitiveCompare>; | |
75 | + | |
76 | +/* Permission mode for a specific node. Controls how file permissions | |
77 | + * are derived for children nodes. */ | |
78 | +typedef enum { | |
79 | + /* Nothing special; this node should just inherit from its parent. */ | |
80 | + PERM_INHERIT, | |
81 | + /* This node is one level above a normal root; used for legacy layouts | |
82 | + * which use the first level to represent user_id. */ | |
83 | + PERM_PRE_ROOT, | |
84 | + /* This node is "/" */ | |
85 | + PERM_ROOT, | |
86 | + /* This node is "/Android" */ | |
87 | + PERM_ANDROID, | |
88 | + /* This node is "/Android/data" */ | |
89 | + PERM_ANDROID_DATA, | |
90 | + /* This node is "/Android/obb" */ | |
91 | + PERM_ANDROID_OBB, | |
92 | + /* This node is "/Android/media" */ | |
93 | + PERM_ANDROID_MEDIA, | |
94 | +} perm_t; | |
95 | + | |
96 | +struct handle { | |
97 | + int fd; | |
98 | +}; | |
99 | + | |
100 | +struct dirhandle { | |
101 | + DIR *d; | |
102 | +}; | |
103 | + | |
104 | +struct node { | |
105 | + __u32 refcount; | |
106 | + __u64 nid; | |
107 | + __u64 gen; | |
108 | + /* | |
109 | + * The inode number for this FUSE node. Note that this isn't stable across | |
110 | + * multiple invocations of the FUSE daemon. | |
111 | + */ | |
112 | + __u32 ino; | |
113 | + | |
114 | + /* State derived based on current position in hierarchy. */ | |
115 | + perm_t perm; | |
116 | + userid_t userid; | |
117 | + uid_t uid; | |
118 | + bool under_android; | |
119 | + | |
120 | + struct node *next; /* per-dir sibling list */ | |
121 | + struct node *child; /* first contained file by this dir */ | |
122 | + struct node *parent; /* containing directory */ | |
123 | + | |
124 | + size_t namelen; | |
125 | + char *name; | |
126 | + /* If non-null, this is the real name of the file in the underlying storage. | |
127 | + * This may differ from the field "name" only by case. | |
128 | + * strlen(actual_name) will always equal strlen(name), so it is safe to use | |
129 | + * namelen for both fields. | |
130 | + */ | |
131 | + char *actual_name; | |
132 | + | |
133 | + /* If non-null, an exact underlying path that should be grafted into this | |
134 | + * position. Used to support things like OBB. */ | |
135 | + char* graft_path; | |
136 | + size_t graft_pathlen; | |
137 | + | |
138 | + bool deleted; | |
139 | +}; | |
140 | + | |
141 | +/* Global data for all FUSE mounts */ | |
142 | +struct fuse_global { | |
143 | + pthread_mutex_t lock; | |
144 | + | |
145 | + uid_t uid; | |
146 | + gid_t gid; | |
147 | + bool multi_user; | |
148 | + | |
149 | + char source_path[PATH_MAX]; | |
150 | + char obb_path[PATH_MAX]; | |
151 | + | |
152 | + AppIdMap* package_to_appid; | |
153 | + | |
154 | + __u64 next_generation; | |
155 | + struct node root; | |
156 | + | |
157 | + /* Used to allocate unique inode numbers for fuse nodes. We use | |
158 | + * a simple counter based scheme where inode numbers from deleted | |
159 | + * nodes aren't reused. Note that inode allocations are not stable | |
160 | + * across multiple invocation of the sdcard daemon, but that shouldn't | |
161 | + * be a huge problem in practice. | |
162 | + * | |
163 | + * Note that we restrict inodes to 32 bit unsigned integers to prevent | |
164 | + * truncation on 32 bit processes when unsigned long long stat.st_ino is | |
165 | + * assigned to an unsigned long ino_t type in an LP32 process. | |
166 | + * | |
167 | + * Also note that fuse_attr and fuse_dirent inode values are 64 bits wide | |
168 | + * on both LP32 and LP64, but the fuse kernel code doesn't squash 64 bit | |
169 | + * inode numbers into 32 bit values on 64 bit kernels (see fuse_squash_ino | |
170 | + * in fs/fuse/inode.c). | |
171 | + * | |
172 | + * Accesses must be guarded by |lock|. | |
173 | + */ | |
174 | + __u32 inode_ctr; | |
175 | + | |
176 | + struct fuse* fuse_default; | |
177 | + struct fuse* fuse_read; | |
178 | + struct fuse* fuse_write; | |
179 | +}; | |
180 | + | |
181 | +/* Single FUSE mount */ | |
182 | +struct fuse { | |
183 | + struct fuse_global* global; | |
184 | + | |
185 | + char dest_path[PATH_MAX]; | |
186 | + | |
187 | + int fd; | |
188 | + | |
189 | + gid_t gid; | |
190 | + mode_t mask; | |
191 | +}; | |
192 | + | |
193 | +/* Private data used by a single FUSE handler */ | |
194 | +struct fuse_handler { | |
195 | + struct fuse* fuse; | |
196 | + int token; | |
197 | + | |
198 | + /* To save memory, we never use the contents of the request buffer and the read | |
199 | + * buffer at the same time. This allows us to share the underlying storage. */ | |
200 | + union { | |
201 | + __u8 request_buffer[MAX_REQUEST_SIZE]; | |
202 | + __u8 read_buffer[MAX_READ + PAGE_SIZE]; | |
203 | + }; | |
204 | +}; | |
205 | + | |
206 | +void handle_fuse_requests(struct fuse_handler* handler); | |
207 | +void derive_permissions_recursive_locked(struct fuse* fuse, struct node *parent); | |
208 | + | |
209 | +#endif /* FUSE_H_ */ |
@@ -38,18 +38,164 @@ | ||
38 | 38 | #include <cutils/multiuser.h> |
39 | 39 | #include <cutils/properties.h> |
40 | 40 | |
41 | +#include <packagelistparser/packagelistparser.h> | |
42 | + | |
41 | 43 | #include <libminijail.h> |
42 | 44 | #include <scoped_minijail.h> |
43 | 45 | |
44 | 46 | #include <private/android_filesystem_config.h> |
45 | 47 | |
46 | -// NOTE: This is a vestigial program that simply exists to mount the in-kernel | |
47 | -// sdcardfs filesystem. The older FUSE-based design that used to live here has | |
48 | -// been completely removed to avoid confusion. | |
48 | +// README | |
49 | +// | |
50 | +// What is this? | |
51 | +// | |
52 | +// sdcard is a program that uses FUSE to emulate FAT-on-sdcard style | |
53 | +// directory permissions (all files are given fixed owner, group, and | |
54 | +// permissions at creation, owner, group, and permissions are not | |
55 | +// changeable, symlinks and hardlinks are not createable, etc. | |
56 | +// | |
57 | +// See usage() for command line options. | |
58 | +// | |
59 | +// It must be run as root, but will drop to requested UID/GID as soon as it | |
60 | +// mounts a filesystem. It will refuse to run if requested UID/GID are zero. | |
61 | +// | |
62 | +// Things I believe to be true: | |
63 | +// | |
64 | +// - ops that return a fuse_entry (LOOKUP, MKNOD, MKDIR, LINK, SYMLINK, | |
65 | +// CREAT) must bump that node's refcount | |
66 | +// - don't forget that FORGET can forget multiple references (req->nlookup) | |
67 | +// - if an op that returns a fuse_entry fails writing the reply to the | |
68 | +// kernel, you must rollback the refcount to reflect the reference the | |
69 | +// kernel did not actually acquire | |
70 | +// | |
71 | +// This daemon can also derive custom filesystem permissions based on directory | |
72 | +// structure when requested. These custom permissions support several features: | |
73 | +// | |
74 | +// - Apps can access their own files in /Android/data/com.example/ without | |
75 | +// requiring any additional GIDs. | |
76 | +// - Separate permissions for protecting directories like Pictures and Music. | |
77 | +// - Multi-user separation on the same physical device. | |
78 | + | |
79 | +#include "fuse.h" | |
80 | + | |
81 | +#define PROP_SDCARDFS_DEVICE "ro.sys.sdcardfs" | |
82 | +#define PROP_SDCARDFS_USER "persist.sys.sdcardfs" | |
49 | 83 | |
50 | 84 | /* Supplementary groups to execute with. */ |
51 | 85 | static const gid_t kGroups[1] = { AID_PACKAGE_INFO }; |
52 | 86 | |
87 | +static bool package_parse_callback(pkg_info *info, void *userdata) { | |
88 | + struct fuse_global *global = (struct fuse_global *)userdata; | |
89 | + bool res = global->package_to_appid->emplace(info->name, info->uid).second; | |
90 | + packagelist_free(info); | |
91 | + return res; | |
92 | +} | |
93 | + | |
94 | +static bool read_package_list(struct fuse_global* global) { | |
95 | + pthread_mutex_lock(&global->lock); | |
96 | + | |
97 | + global->package_to_appid->clear(); | |
98 | + bool rc = packagelist_parse(package_parse_callback, global); | |
99 | + DLOG(INFO) << "read_package_list: found " << global->package_to_appid->size() << " packages"; | |
100 | + | |
101 | + // Regenerate ownership details using newly loaded mapping. | |
102 | + derive_permissions_recursive_locked(global->fuse_default, &global->root); | |
103 | + | |
104 | + pthread_mutex_unlock(&global->lock); | |
105 | + | |
106 | + return rc; | |
107 | +} | |
108 | + | |
109 | +static void watch_package_list(struct fuse_global* global) { | |
110 | + struct inotify_event *event; | |
111 | + char event_buf[512]; | |
112 | + | |
113 | + int nfd = inotify_init(); | |
114 | + if (nfd == -1) { | |
115 | + PLOG(ERROR) << "inotify_init failed"; | |
116 | + return; | |
117 | + } | |
118 | + | |
119 | + bool active = false; | |
120 | + while (1) { | |
121 | + if (!active) { | |
122 | + int res = inotify_add_watch(nfd, PACKAGES_LIST_FILE, IN_DELETE_SELF); | |
123 | + if (res == -1) { | |
124 | + if (errno == ENOENT || errno == EACCES) { | |
125 | + /* Framework may not have created the file yet, sleep and retry. */ | |
126 | + LOG(ERROR) << "missing \"" << PACKAGES_LIST_FILE << "\"; retrying..."; | |
127 | + sleep(3); | |
128 | + continue; | |
129 | + } else { | |
130 | + PLOG(ERROR) << "inotify_add_watch failed"; | |
131 | + return; | |
132 | + } | |
133 | + } | |
134 | + | |
135 | + /* Watch above will tell us about any future changes, so | |
136 | + * read the current state. */ | |
137 | + if (read_package_list(global) == false) { | |
138 | + LOG(ERROR) << "read_package_list failed"; | |
139 | + return; | |
140 | + } | |
141 | + active = true; | |
142 | + } | |
143 | + | |
144 | + int event_pos = 0; | |
145 | + ssize_t res = TEMP_FAILURE_RETRY(read(nfd, event_buf, sizeof(event_buf))); | |
146 | + if (res == -1) { | |
147 | + PLOG(ERROR) << "failed to read inotify event"; | |
148 | + return; | |
149 | + } else if (static_cast<size_t>(res) < sizeof(*event)) { | |
150 | + LOG(ERROR) << "failed to read inotify event: read " << res << " expected " | |
151 | + << sizeof(event_buf); | |
152 | + return; | |
153 | + } | |
154 | + | |
155 | + while (res >= static_cast<ssize_t>(sizeof(*event))) { | |
156 | + int event_size; | |
157 | + event = reinterpret_cast<struct inotify_event*>(event_buf + event_pos); | |
158 | + | |
159 | + DLOG(INFO) << "inotify event: " << std::hex << event->mask << std::dec; | |
160 | + if ((event->mask & IN_IGNORED) == IN_IGNORED) { | |
161 | + /* Previously watched file was deleted, probably due to move | |
162 | + * that swapped in new data; re-arm the watch and read. */ | |
163 | + active = false; | |
164 | + } | |
165 | + | |
166 | + event_size = sizeof(*event) + event->len; | |
167 | + res -= event_size; | |
168 | + event_pos += event_size; | |
169 | + } | |
170 | + } | |
171 | +} | |
172 | + | |
173 | +static int fuse_setup(struct fuse* fuse, gid_t gid, mode_t mask) { | |
174 | + char opts[256]; | |
175 | + | |
176 | + fuse->fd = TEMP_FAILURE_RETRY(open("/dev/fuse", O_RDWR | O_CLOEXEC)); | |
177 | + if (fuse->fd == -1) { | |
178 | + PLOG(ERROR) << "failed to open fuse device"; | |
179 | + return -1; | |
180 | + } | |
181 | + | |
182 | + umount2(fuse->dest_path, MNT_DETACH); | |
183 | + | |
184 | + snprintf(opts, sizeof(opts), | |
185 | + "fd=%i,rootmode=40000,default_permissions,allow_other,user_id=%d,group_id=%d", | |
186 | + fuse->fd, fuse->global->uid, fuse->global->gid); | |
187 | + if (mount("/dev/fuse", fuse->dest_path, "fuse", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, | |
188 | + opts) == -1) { | |
189 | + PLOG(ERROR) << "failed to mount fuse filesystem"; | |
190 | + return -1; | |
191 | + } | |
192 | + | |
193 | + fuse->gid = gid; | |
194 | + fuse->mask = mask; | |
195 | + | |
196 | + return 0; | |
197 | +} | |
198 | + | |
53 | 199 | static void drop_privs(uid_t uid, gid_t gid) { |
54 | 200 | ScopedMinijail j(minijail_new()); |
55 | 201 | minijail_set_supplementary_gids(j.get(), arraysize(kGroups), kGroups); |
@@ -59,6 +205,120 @@ static void drop_privs(uid_t uid, gid_t gid) { | ||
59 | 205 | minijail_enter(j.get()); |
60 | 206 | } |
61 | 207 | |
208 | +static void* start_handler(void* data) { | |
209 | + struct fuse_handler* handler = static_cast<fuse_handler*>(data); | |
210 | + handle_fuse_requests(handler); | |
211 | + return NULL; | |
212 | +} | |
213 | + | |
214 | +static void run(const char* source_path, const char* label, uid_t uid, | |
215 | + gid_t gid, userid_t userid, bool multi_user, bool full_write) { | |
216 | + struct fuse_global global; | |
217 | + struct fuse fuse_default; | |
218 | + struct fuse fuse_read; | |
219 | + struct fuse fuse_write; | |
220 | + struct fuse_handler handler_default; | |
221 | + struct fuse_handler handler_read; | |
222 | + struct fuse_handler handler_write; | |
223 | + pthread_t thread_default; | |
224 | + pthread_t thread_read; | |
225 | + pthread_t thread_write; | |
226 | + | |
227 | + memset(&global, 0, sizeof(global)); | |
228 | + memset(&fuse_default, 0, sizeof(fuse_default)); | |
229 | + memset(&fuse_read, 0, sizeof(fuse_read)); | |
230 | + memset(&fuse_write, 0, sizeof(fuse_write)); | |
231 | + memset(&handler_default, 0, sizeof(handler_default)); | |
232 | + memset(&handler_read, 0, sizeof(handler_read)); | |
233 | + memset(&handler_write, 0, sizeof(handler_write)); | |
234 | + | |
235 | + pthread_mutex_init(&global.lock, NULL); | |
236 | + global.package_to_appid = new AppIdMap; | |
237 | + global.uid = uid; | |
238 | + global.gid = gid; | |
239 | + global.multi_user = multi_user; | |
240 | + global.next_generation = 0; | |
241 | + global.inode_ctr = 1; | |
242 | + | |
243 | + memset(&global.root, 0, sizeof(global.root)); | |
244 | + global.root.nid = FUSE_ROOT_ID; /* 1 */ | |
245 | + global.root.refcount = 2; | |
246 | + global.root.namelen = strlen(source_path); | |
247 | + global.root.name = strdup(source_path); | |
248 | + global.root.userid = userid; | |
249 | + global.root.uid = AID_ROOT; | |
250 | + global.root.under_android = false; | |
251 | + | |
252 | + // Clang static analyzer think strcpy potentially overwrites other fields | |
253 | + // in global. Use snprintf() to mute the false warning. | |
254 | + snprintf(global.source_path, sizeof(global.source_path), "%s", source_path); | |
255 | + | |
256 | + if (multi_user) { | |
257 | + global.root.perm = PERM_PRE_ROOT; | |
258 | + snprintf(global.obb_path, sizeof(global.obb_path), "%s/obb", source_path); | |
259 | + } else { | |
260 | + global.root.perm = PERM_ROOT; | |
261 | + snprintf(global.obb_path, sizeof(global.obb_path), "%s/Android/obb", source_path); | |
262 | + } | |
263 | + | |
264 | + fuse_default.global = &global; | |
265 | + fuse_read.global = &global; | |
266 | + fuse_write.global = &global; | |
267 | + | |
268 | + global.fuse_default = &fuse_default; | |
269 | + global.fuse_read = &fuse_read; | |
270 | + global.fuse_write = &fuse_write; | |
271 | + | |
272 | + snprintf(fuse_default.dest_path, PATH_MAX, "/mnt/runtime/default/%s", label); | |
273 | + snprintf(fuse_read.dest_path, PATH_MAX, "/mnt/runtime/read/%s", label); | |
274 | + snprintf(fuse_write.dest_path, PATH_MAX, "/mnt/runtime/write/%s", label); | |
275 | + | |
276 | + handler_default.fuse = &fuse_default; | |
277 | + handler_read.fuse = &fuse_read; | |
278 | + handler_write.fuse = &fuse_write; | |
279 | + | |
280 | + handler_default.token = 0; | |
281 | + handler_read.token = 1; | |
282 | + handler_write.token = 2; | |
283 | + | |
284 | + umask(0); | |
285 | + | |
286 | + if (multi_user) { | |
287 | + /* Multi-user storage is fully isolated per user, so "other" | |
288 | + * permissions are completely masked off. */ | |
289 | + if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006) | |
290 | + || fuse_setup(&fuse_read, AID_EVERYBODY, 0027) | |
291 | + || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0027)) { | |
292 | + PLOG(FATAL) << "failed to fuse_setup"; | |
293 | + } | |
294 | + } else { | |
295 | + /* Physical storage is readable by all users on device, but | |
296 | + * the Android directories are masked off to a single user | |
297 | + * deep inside attr_from_stat(). */ | |
298 | + if (fuse_setup(&fuse_default, AID_SDCARD_RW, 0006) | |
299 | + || fuse_setup(&fuse_read, AID_EVERYBODY, full_write ? 0027 : 0022) | |
300 | + || fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0022)) { | |
301 | + PLOG(FATAL) << "failed to fuse_setup"; | |
302 | + } | |
303 | + } | |
304 | + | |
305 | + // Will abort if priv-dropping fails. | |
306 | + drop_privs(uid, gid); | |
307 | + | |
308 | + if (multi_user) { | |
309 | + fs_prepare_dir(global.obb_path, 0775, uid, gid); | |
310 | + } | |
311 | + | |
312 | + if (pthread_create(&thread_default, NULL, start_handler, &handler_default) | |
313 | + || pthread_create(&thread_read, NULL, start_handler, &handler_read) | |
314 | + || pthread_create(&thread_write, NULL, start_handler, &handler_write)) { | |
315 | + LOG(FATAL) << "failed to pthread_create"; | |
316 | + } | |
317 | + | |
318 | + watch_package_list(&global); | |
319 | + LOG(FATAL) << "terminated prematurely"; | |
320 | +} | |
321 | + | |
62 | 322 | static bool sdcardfs_setup(const std::string& source_path, const std::string& dest_path, |
63 | 323 | uid_t fsuid, gid_t fsgid, bool multi_user, userid_t userid, gid_t gid, |
64 | 324 | mode_t mask, bool derive_gid, bool default_normal) { |
@@ -147,6 +407,41 @@ static void run_sdcardfs(const std::string& source_path, const std::string& labe | ||
147 | 407 | exit(0); |
148 | 408 | } |
149 | 409 | |
410 | +static bool supports_sdcardfs(void) { | |
411 | + std::string filesystems; | |
412 | + if (!android::base::ReadFileToString("/proc/filesystems", &filesystems)) { | |
413 | + PLOG(ERROR) << "Could not read /proc/filesystems"; | |
414 | + return false; | |
415 | + } | |
416 | + for (const auto& fs : android::base::Split(filesystems, "\n")) { | |
417 | + if (fs.find("sdcardfs") != std::string::npos) return true; | |
418 | + } | |
419 | + return false; | |
420 | +} | |
421 | + | |
422 | +static bool should_use_sdcardfs(void) { | |
423 | + char property[PROPERTY_VALUE_MAX]; | |
424 | + | |
425 | + // Allow user to have a strong opinion about state | |
426 | + property_get(PROP_SDCARDFS_USER, property, ""); | |
427 | + if (!strcmp(property, "force_on")) { | |
428 | + LOG(WARNING) << "User explicitly enabled sdcardfs"; | |
429 | + return supports_sdcardfs(); | |
430 | + } else if (!strcmp(property, "force_off")) { | |
431 | + LOG(WARNING) << "User explicitly disabled sdcardfs"; | |
432 | + return false; | |
433 | + } | |
434 | + | |
435 | + // Fall back to device opinion about state | |
436 | + if (property_get_bool(PROP_SDCARDFS_DEVICE, true)) { | |
437 | + LOG(WARNING) << "Device explicitly enabled sdcardfs"; | |
438 | + return supports_sdcardfs(); | |
439 | + } else { | |
440 | + LOG(WARNING) << "Device explicitly disabled sdcardfs"; | |
441 | + return false; | |
442 | + } | |
443 | +} | |
444 | + | |
150 | 445 | static int usage() { |
151 | 446 | LOG(ERROR) << "usage: sdcard [OPTIONS] <source_path> <label>" |
152 | 447 | << " -u: specify UID to run as" |
@@ -241,7 +536,11 @@ int main(int argc, char **argv) { | ||
241 | 536 | sleep(1); |
242 | 537 | } |
243 | 538 | |
244 | - run_sdcardfs(source_path, label, uid, gid, userid, multi_user, full_write, derive_gid, | |
245 | - default_normal); | |
539 | + if (should_use_sdcardfs()) { | |
540 | + run_sdcardfs(source_path, label, uid, gid, userid, multi_user, full_write, derive_gid, | |
541 | + default_normal); | |
542 | + } else { | |
543 | + run(source_path, label, uid, gid, userid, multi_user, full_write); | |
544 | + } | |
246 | 545 | return 1; |
247 | 546 | } |