1/*
2 * Copyright (C) 2015 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 <memory>
18#include <string>
19#include <vector>
20
21#include <stdio.h>
22#include <stdlib.h>
23#include <errno.h>
24#include <sys/types.h>
25#include <sys/stat.h>
26#include <fcntl.h>
27#include <linux/fs.h>
28#include <linux/fiemap.h>
29#include <mntent.h>
30
31#include <android-base/logging.h>
32
33#include <AutoCloseFD.h>
34
35namespace {
36
37struct Options {
38    std::vector<std::string> targets;
39    bool unlink{true};
40};
41
42constexpr uint32_t max_extents = 32;
43
44bool read_command_line(int argc, const char * const argv[], Options &options);
45void usage(const char *progname);
46bool secdiscard_path(const std::string &path);
47std::unique_ptr<struct fiemap> path_fiemap(const std::string &path, uint32_t extent_count);
48bool check_fiemap(const struct fiemap &fiemap, const std::string &path);
49std::unique_ptr<struct fiemap> alloc_fiemap(uint32_t extent_count);
50std::string block_device_for_path(const std::string &path);
51bool overwrite_with_zeros(int fd, off64_t start, off64_t length);
52
53}
54
55int main(int argc, const char * const argv[]) {
56    android::base::InitLogging(const_cast<char **>(argv));
57    Options options;
58    if (!read_command_line(argc, argv, options)) {
59        usage(argv[0]);
60        return -1;
61    }
62    for (auto const &target: options.targets) {
63        LOG(DEBUG) << "Securely discarding '" << target << "' unlink=" << options.unlink;
64        if (!secdiscard_path(target)) {
65            LOG(ERROR) << "Secure discard failed for: " << target;
66        }
67        if (options.unlink) {
68            if (unlink(target.c_str()) != 0 && errno != ENOENT) {
69                PLOG(ERROR) << "Unable to unlink: " << target;
70            }
71        }
72        LOG(DEBUG) << "Discarded: " << target;
73    }
74    return 0;
75}
76
77namespace {
78
79bool read_command_line(int argc, const char * const argv[], Options &options) {
80    for (int i = 1; i < argc; i++) {
81        if (!strcmp("--no-unlink", argv[i])) {
82            options.unlink = false;
83        } else if (!strcmp("--", argv[i])) {
84            for (int j = i+1; j < argc; j++) {
85                if (argv[j][0] != '/') return false; // Must be absolute path
86                options.targets.emplace_back(argv[j]);
87            }
88            return options.targets.size() > 0;
89        } else {
90            return false; // Unknown option
91        }
92    }
93    return false; // "--" not found
94}
95
96void usage(const char *progname) {
97    fprintf(stderr, "Usage: %s [--no-unlink] -- <absolute path> ...\n", progname);
98}
99
100// BLKSECDISCARD all content in "path", if it's small enough.
101bool secdiscard_path(const std::string &path) {
102    auto fiemap = path_fiemap(path, max_extents);
103    if (!fiemap || !check_fiemap(*fiemap, path)) {
104        return false;
105    }
106    auto block_device = block_device_for_path(path);
107    if (block_device.empty()) {
108        return false;
109    }
110    AutoCloseFD fs_fd(block_device, O_RDWR | O_LARGEFILE);
111    if (!fs_fd) {
112        PLOG(ERROR) << "Failed to open device " << block_device;
113        return false;
114    }
115    for (uint32_t i = 0; i < fiemap->fm_mapped_extents; i++) {
116        uint64_t range[2];
117        range[0] = fiemap->fm_extents[i].fe_physical;
118        range[1] = fiemap->fm_extents[i].fe_length;
119        if (ioctl(fs_fd.get(), BLKSECDISCARD, range) == -1) {
120            PLOG(ERROR) << "Unable to BLKSECDISCARD " << path;
121            if (!overwrite_with_zeros(fs_fd.get(), range[0], range[1])) return false;
122            LOG(DEBUG) << "Used zero overwrite";
123        }
124    }
125    return true;
126}
127
128// Read the file's FIEMAP
129std::unique_ptr<struct fiemap> path_fiemap(const std::string &path, uint32_t extent_count)
130{
131    AutoCloseFD fd(path);
132    if (!fd) {
133        if (errno == ENOENT) {
134            PLOG(DEBUG) << "Unable to open " << path;
135        } else {
136            PLOG(ERROR) << "Unable to open " << path;
137        }
138        return nullptr;
139    }
140    auto fiemap = alloc_fiemap(extent_count);
141    if (ioctl(fd.get(), FS_IOC_FIEMAP, fiemap.get()) != 0) {
142        PLOG(ERROR) << "Unable to FIEMAP " << path;
143        return nullptr;
144    }
145    auto mapped = fiemap->fm_mapped_extents;
146    if (mapped < 1 || mapped > extent_count) {
147        LOG(ERROR) << "Extent count not in bounds 1 <= " << mapped << " <= " << extent_count
148            << " in " << path;
149        return nullptr;
150    }
151    return fiemap;
152}
153
154// Ensure that the FIEMAP covers the file and is OK to discard
155bool check_fiemap(const struct fiemap &fiemap, const std::string &path) {
156    auto mapped = fiemap.fm_mapped_extents;
157    if (!(fiemap.fm_extents[mapped - 1].fe_flags & FIEMAP_EXTENT_LAST)) {
158        LOG(ERROR) << "Extent " << mapped -1 << " was not the last in " << path;
159        return false;
160    }
161    for (uint32_t i = 0; i < mapped; i++) {
162        auto flags = fiemap.fm_extents[i].fe_flags;
163        if (flags & (FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_DELALLOC | FIEMAP_EXTENT_NOT_ALIGNED)) {
164            LOG(ERROR) << "Extent " << i << " has unexpected flags " << flags << ": " << path;
165            return false;
166        }
167    }
168    return true;
169}
170
171std::unique_ptr<struct fiemap> alloc_fiemap(uint32_t extent_count)
172{
173    size_t allocsize = offsetof(struct fiemap, fm_extents[extent_count]);
174    std::unique_ptr<struct fiemap> res(new (::operator new (allocsize)) struct fiemap);
175    memset(res.get(), 0, allocsize);
176    res->fm_start = 0;
177    res->fm_length = UINT64_MAX;
178    res->fm_flags = 0;
179    res->fm_extent_count = extent_count;
180    res->fm_mapped_extents = 0;
181    return res;
182}
183
184// Given a file path, look for the corresponding block device in /proc/mount
185std::string block_device_for_path(const std::string &path)
186{
187    std::unique_ptr<FILE, int(*)(FILE*)> mnts(setmntent("/proc/mounts", "re"), endmntent);
188    if (!mnts) {
189        PLOG(ERROR) << "Unable to open /proc/mounts";
190        return "";
191    }
192    std::string result;
193    size_t best_length = 0;
194    struct mntent *mnt; // getmntent returns a thread local, so it's safe.
195    while ((mnt = getmntent(mnts.get())) != nullptr) {
196        auto l = strlen(mnt->mnt_dir);
197        if (l > best_length &&
198            path.size() > l &&
199            path[l] == '/' &&
200            path.compare(0, l, mnt->mnt_dir) == 0) {
201                result = mnt->mnt_fsname;
202                best_length = l;
203        }
204    }
205    if (result.empty()) {
206        LOG(ERROR) <<"Didn't find a mountpoint to match path " << path;
207        return "";
208    }
209    LOG(DEBUG) << "For path " << path << " block device is " << result;
210    return result;
211}
212
213bool overwrite_with_zeros(int fd, off64_t start, off64_t length) {
214    if (lseek64(fd, start, SEEK_SET) != start) {
215        PLOG(ERROR) << "Seek failed for zero overwrite";
216        return false;
217    }
218    char buf[BUFSIZ];
219    memset(buf, 0, sizeof(buf));
220    while (length > 0) {
221        size_t wlen = static_cast<size_t>(std::min(static_cast<off64_t>(sizeof(buf)), length));
222        auto written = write(fd, buf, wlen);
223        if (written < 1) {
224            PLOG(ERROR) << "Write of zeroes failed";
225            return false;
226        }
227        length -= written;
228    }
229    return true;
230}
231
232}
233