1/*
2 * Copyright (C) 2012 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#define LOG_TAG "cutils"
18
19/* These defines are only needed because prebuilt headers are out of date */
20#define __USE_XOPEN2K8 1
21#define _ATFILE_SOURCE 1
22#define _GNU_SOURCE 1
23
24#include <dirent.h>
25#include <errno.h>
26#include <fcntl.h>
27#include <limits.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <sys/stat.h>
32#include <sys/types.h>
33#include <unistd.h>
34
35#include <cutils/fs.h>
36#include <log/log.h>
37
38#define ALL_PERMS (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
39#define BUF_SIZE 64
40
41static int fs_prepare_path_impl(const char* path, mode_t mode, uid_t uid, gid_t gid,
42        int allow_fixup, int prepare_as_dir) {
43    // Check if path needs to be created
44    struct stat sb;
45    int create_result = -1;
46    if (TEMP_FAILURE_RETRY(lstat(path, &sb)) == -1) {
47        if (errno == ENOENT) {
48            goto create;
49        } else {
50            ALOGE("Failed to lstat(%s): %s", path, strerror(errno));
51            return -1;
52        }
53    }
54
55    // Exists, verify status
56    int type_ok = prepare_as_dir ? S_ISDIR(sb.st_mode) : S_ISREG(sb.st_mode);
57    if (!type_ok) {
58        ALOGE("Not a %s: %s", (prepare_as_dir ? "directory" : "regular file"), path);
59        return -1;
60    }
61
62    int owner_match = ((sb.st_uid == uid) && (sb.st_gid == gid));
63    int mode_match = ((sb.st_mode & ALL_PERMS) == mode);
64    if (owner_match && mode_match) {
65        return 0;
66    } else if (allow_fixup) {
67        goto fixup;
68    } else {
69        if (!owner_match) {
70            ALOGE("Expected path %s with owner %d:%d but found %d:%d",
71                    path, uid, gid, sb.st_uid, sb.st_gid);
72            return -1;
73        } else {
74            ALOGW("Expected path %s with mode %o but found %o",
75                    path, mode, (sb.st_mode & ALL_PERMS));
76            return 0;
77        }
78    }
79
80create:
81    create_result = prepare_as_dir
82        ? TEMP_FAILURE_RETRY(mkdir(path, mode))
83        : TEMP_FAILURE_RETRY(open(path, O_CREAT | O_CLOEXEC | O_NOFOLLOW | O_RDONLY, 0644));
84    if (create_result == -1) {
85        if (errno != EEXIST) {
86            ALOGE("Failed to %s(%s): %s",
87                    (prepare_as_dir ? "mkdir" : "open"), path, strerror(errno));
88            return -1;
89        }
90    } else if (!prepare_as_dir) {
91        // For regular files we need to make sure we close the descriptor
92        if (close(create_result) == -1) {
93            ALOGW("Failed to close file after create %s: %s", path, strerror(errno));
94        }
95    }
96fixup:
97    if (TEMP_FAILURE_RETRY(chmod(path, mode)) == -1) {
98        ALOGE("Failed to chmod(%s, %d): %s", path, mode, strerror(errno));
99        return -1;
100    }
101    if (TEMP_FAILURE_RETRY(chown(path, uid, gid)) == -1) {
102        ALOGE("Failed to chown(%s, %d, %d): %s", path, uid, gid, strerror(errno));
103        return -1;
104    }
105
106    return 0;
107}
108
109int fs_prepare_dir(const char* path, mode_t mode, uid_t uid, gid_t gid) {
110    return fs_prepare_path_impl(path, mode, uid, gid, /*allow_fixup*/ 1, /*prepare_as_dir*/ 1);
111}
112
113int fs_prepare_dir_strict(const char* path, mode_t mode, uid_t uid, gid_t gid) {
114    return fs_prepare_path_impl(path, mode, uid, gid, /*allow_fixup*/ 0, /*prepare_as_dir*/ 1);
115}
116
117int fs_prepare_file_strict(const char* path, mode_t mode, uid_t uid, gid_t gid) {
118    return fs_prepare_path_impl(path, mode, uid, gid, /*allow_fixup*/ 0, /*prepare_as_dir*/ 0);
119}
120
121int fs_read_atomic_int(const char* path, int* out_value) {
122    int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY));
123    if (fd == -1) {
124        ALOGE("Failed to read %s: %s", path, strerror(errno));
125        return -1;
126    }
127
128    char buf[BUF_SIZE];
129    if (TEMP_FAILURE_RETRY(read(fd, buf, BUF_SIZE)) == -1) {
130        ALOGE("Failed to read %s: %s", path, strerror(errno));
131        goto fail;
132    }
133    if (sscanf(buf, "%d", out_value) != 1) {
134        ALOGE("Failed to parse %s: %s", path, strerror(errno));
135        goto fail;
136    }
137    close(fd);
138    return 0;
139
140fail:
141    close(fd);
142    *out_value = -1;
143    return -1;
144}
145
146int fs_write_atomic_int(const char* path, int value) {
147    char temp[PATH_MAX];
148    if (snprintf(temp, PATH_MAX, "%s.XXXXXX", path) >= PATH_MAX) {
149        ALOGE("Path too long");
150        return -1;
151    }
152
153    int fd = TEMP_FAILURE_RETRY(mkstemp(temp));
154    if (fd == -1) {
155        ALOGE("Failed to open %s: %s", temp, strerror(errno));
156        return -1;
157    }
158
159    char buf[BUF_SIZE];
160    int len = snprintf(buf, BUF_SIZE, "%d", value) + 1;
161    if (len > BUF_SIZE) {
162        ALOGE("Value %d too large: %s", value, strerror(errno));
163        goto fail;
164    }
165    if (TEMP_FAILURE_RETRY(write(fd, buf, len)) < len) {
166        ALOGE("Failed to write %s: %s", temp, strerror(errno));
167        goto fail;
168    }
169    if (close(fd) == -1) {
170        ALOGE("Failed to close %s: %s", temp, strerror(errno));
171        goto fail_closed;
172    }
173
174    if (rename(temp, path) == -1) {
175        ALOGE("Failed to rename %s to %s: %s", temp, path, strerror(errno));
176        goto fail_closed;
177    }
178
179    return 0;
180
181fail:
182    close(fd);
183fail_closed:
184    unlink(temp);
185    return -1;
186}
187
188#ifndef __APPLE__
189
190int fs_mkdirs(const char* path, mode_t mode) {
191    int res = 0;
192    int fd = 0;
193    struct stat sb;
194    char* buf = strdup(path);
195
196    if (*buf != '/') {
197        ALOGE("Relative paths are not allowed: %s", buf);
198        res = -EINVAL;
199        goto done;
200    }
201
202    if ((fd = open("/", 0)) == -1) {
203        ALOGE("Failed to open(/): %s", strerror(errno));
204        res = -errno;
205        goto done;
206    }
207
208    char* segment = buf + 1;
209    char* p = segment;
210    while (*p != '\0') {
211        if (*p == '/') {
212            *p = '\0';
213
214            if (!strcmp(segment, "..") || !strcmp(segment, ".") || !strcmp(segment, "")) {
215                ALOGE("Invalid path: %s", buf);
216                res = -EINVAL;
217                goto done_close;
218            }
219
220            if (fstatat(fd, segment, &sb, AT_SYMLINK_NOFOLLOW) != 0) {
221                if (errno == ENOENT) {
222                    /* Nothing there yet; let's create it! */
223                    if (mkdirat(fd, segment, mode) != 0) {
224                        if (errno == EEXIST) {
225                            /* We raced with someone; ignore */
226                        } else {
227                            ALOGE("Failed to mkdirat(%s): %s", buf, strerror(errno));
228                            res = -errno;
229                            goto done_close;
230                        }
231                    }
232                } else {
233                    ALOGE("Failed to fstatat(%s): %s", buf, strerror(errno));
234                    res = -errno;
235                    goto done_close;
236                }
237            } else {
238                if (S_ISLNK(sb.st_mode)) {
239                    ALOGE("Symbolic links are not allowed: %s", buf);
240                    res = -ELOOP;
241                    goto done_close;
242                }
243                if (!S_ISDIR(sb.st_mode)) {
244                    ALOGE("Existing segment not a directory: %s", buf);
245                    res = -ENOTDIR;
246                    goto done_close;
247                }
248            }
249
250            /* Yay, segment is ready for us to step into */
251            int next_fd;
252            if ((next_fd = openat(fd, segment, O_NOFOLLOW | O_CLOEXEC)) == -1) {
253                ALOGE("Failed to openat(%s): %s", buf, strerror(errno));
254                res = -errno;
255                goto done_close;
256            }
257
258            close(fd);
259            fd = next_fd;
260
261            *p = '/';
262            segment = p + 1;
263        }
264        p++;
265    }
266
267done_close:
268    close(fd);
269done:
270    free(buf);
271    return res;
272}
273
274#endif
275