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#define LOG_TAG "incidentd"
18
19#include "report_directory.h"
20
21#include <cutils/log.h>
22#include <private/android_filesystem_config.h>
23#include <utils/String8.h>
24
25#include <sys/types.h>
26#include <sys/stat.h>
27#include <dirent.h>
28#include <libgen.h>
29#include <unistd.h>
30
31#include <vector>
32
33using namespace android;
34using namespace std;
35
36status_t
37create_directory(const char* directory)
38{
39    struct stat st;
40    status_t err = NO_ERROR;
41    char* dir = strdup(directory);
42
43    // Skip first slash
44    char* d = dir + 1;
45
46    // Create directories, assigning them to the system user
47    bool last = false;
48    while (!last) {
49        d = strchr(d, '/');
50        if (d != NULL) {
51            *d = '\0';
52        } else {
53            last = true;
54        }
55        if (stat(dir, &st) == 0) {
56            if (!S_ISDIR(st.st_mode)) {
57                err = ALREADY_EXISTS;
58                goto done;
59            }
60        } else {
61            if (mkdir(dir, 0770)) {
62                ALOGE("No incident reports today. "
63                        "Unable to create incident report dir %s: %s", dir,
64                        strerror(errno));
65                err = -errno;
66                goto done;
67            }
68            if (chmod(dir, 0770)) {
69                ALOGE("No incident reports today. "
70                        "Unable to set permissions for incident report dir %s: %s", dir,
71                        strerror(errno));
72                err = -errno;
73                goto done;
74            }
75            if (chown(dir, AID_SYSTEM, AID_SYSTEM)) {
76                ALOGE("No incident reports today. Unable to change ownership of dir %s: %s\n",
77                        dir, strerror(errno));
78                err = -errno;
79                goto done;
80            }
81        }
82        if (!last) {
83            *d++ = '/';
84        }
85    }
86
87    // Ensure that the final directory is owned by the system with 0770. If it isn't
88    // we won't write into it.
89    if (stat(directory, &st) != 0) {
90        ALOGE("No incident reports today. Can't stat: %s", directory);
91        err = -errno;
92        goto done;
93    }
94    if ((st.st_mode & 0777) != 0770) {
95        ALOGE("No incident reports today. Mode is %0o on report directory %s",
96                st.st_mode, directory);
97        err = BAD_VALUE;
98        goto done;
99    }
100    if (st.st_uid != AID_SYSTEM || st.st_gid != AID_SYSTEM) {
101        ALOGE("No incident reports today. Owner is %d and group is %d on report directory %s",
102                st.st_uid, st.st_gid, directory);
103        err = BAD_VALUE;
104        goto done;
105    }
106
107done:
108    free(dir);
109    return err;
110}
111
112static bool
113stat_mtime_cmp(const pair<String8,struct stat>& a, const pair<String8,struct stat>& b)
114{
115    return a.second.st_mtime < b.second.st_mtime;
116}
117
118void
119clean_directory(const char* directory, off_t maxSize, size_t maxCount)
120{
121    DIR* dir;
122    struct dirent* entry;
123    struct stat st;
124
125    vector<pair<String8,struct stat>> files;
126
127    if ((dir = opendir(directory)) == NULL) {
128        ALOGE("Couldn't open incident directory: %s", directory);
129        return;
130    }
131
132    String8 dirbase(String8(directory) + "/");
133
134    off_t totalSize = 0;
135    size_t totalCount = 0;
136
137    // Enumerate, count and add up size
138    while ((entry = readdir(dir)) != NULL) {
139        if (entry->d_name[0] == '.') {
140            continue;
141        }
142        String8 filename = dirbase + entry->d_name;
143        if (stat(filename.string(), &st) != 0) {
144            ALOGE("Unable to stat file %s", filename.string());
145            continue;
146        }
147        if (!S_ISREG(st.st_mode)) {
148            continue;
149        }
150        files.push_back(pair<String8,struct stat>(filename, st));
151
152        totalSize += st.st_size;
153        totalCount++;
154    }
155
156    closedir(dir);
157
158    // Count or size is less than max, then we're done.
159    if (totalSize < maxSize && totalCount < maxCount) {
160        return;
161    }
162
163    // Oldest files first.
164    sort(files.begin(), files.end(), stat_mtime_cmp);
165
166    // Remove files until we're under our limits.
167    for (vector<pair<String8,struct stat>>::iterator it = files.begin();
168            it != files.end() && totalSize >= maxSize && totalCount >= maxCount; it++) {
169        remove(it->first.string());
170        totalSize -= it->second.st_size;
171        totalCount--;
172    }
173}
174