1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <assert.h>
18#include <errno.h>
19#include <fcntl.h>
20#include <getopt.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <unistd.h>
25
26#include <logwrap/logwrap.h>
27#include <sys/stat.h>
28#include <sys/statvfs.h>
29#include <utils/Log.h>
30
31#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
32
33#define MAX_IO_WRITE_CHUNK_SIZE 0x100000
34
35#ifndef min
36#define min(a,b) ((a) < (b) ? (a) : (b))
37#endif
38
39typedef unsigned long u64;
40
41static void usage(const char * const progname) {
42    fprintf(stderr,
43            "Usage: %s [-s <seed>] -h <hole size in bytes> -t <total hole size in bytes> "
44            "path\n",
45            progname);
46}
47
48static u64 get_free_space(const char * const path) {
49    struct statvfs s;
50
51    if (statvfs(path, &s) < 0) {
52        fprintf(stderr, "\nerrno: %d. Failed to get free disk space on %s\n",
53                errno, path);
54        return 0;
55    } else {
56        return (u64)s.f_bsize * (u64)s.f_bfree;
57    }
58}
59
60static u64 get_random_num(const u64 start, const u64 end) {
61    if (end - start <= 0)
62        return start;
63    assert(RAND_MAX >= 0x7FFFFFFF);
64    if ((end - start) > 0x7FFFFFFF)
65        return start + (((u64)random() << 31) | (u64)random()) % (end - start);
66    return start + (random() % (end - start));
67}
68
69static char get_random_char() {
70    return 'A' + random() % ('Z' - 'A');
71}
72
73static bool create_unique_file(const char * const dir_path, const u64 size,
74                               const u64 id, char * const base,
75                               const u64 base_length) {
76    u64 length = 0;
77    int fd;
78    char file_path[FILENAME_MAX];
79    bool ret = true;
80
81    base[random() % min(base_length, size)] = get_random_char();
82
83    sprintf(file_path, "%s/file_%lu", dir_path, id);
84    fd = open(file_path, O_WRONLY | O_CREAT | O_SYNC, 0777);
85    if (fd < 0) {
86        // We suppress ENOSPC erros as that is common as we approach the
87        // last few MBs of the fs as we don't account for the size of the newly
88        // added meta data after the initial free space computation.
89        if (errno != 28) {
90            fprintf(stderr, "\nerrno: %d. Failed to create %s\n", errno, file_path);
91        }
92        return false;
93    }
94    while (length + base_length < size) {
95        if (write(fd, base, base_length) < 0) {
96            if (errno != 28) {
97                fprintf(stderr, "\nerrno: %d. Failed to write %lu bytes to %s\n",
98                        errno, base_length, file_path);
99            }
100            ret = false;
101            goto done;
102        }
103        length += base_length;
104    }
105    if (write(fd, base, size - length) < 0) {
106        if (errno != 28) {
107            fprintf(stderr, "\nerrno: %d. Failed to write last %lu bytes to %s\n",
108                    errno, size - length, file_path);
109        }
110        ret = false;
111        goto done;
112    }
113done:
114    if (close(fd) < 0) {
115        fprintf(stderr, "\nFailed to close %s\n", file_path);
116        ret = false;
117    }
118    return ret;
119}
120
121static bool create_unique_dir(char *dir, const char * const root_path) {
122    char random_string[15];
123    int i;
124
125    for (i = 0; i < 14; ++i) {
126        random_string[i] = get_random_char();
127    }
128    random_string[14] = '\0';
129
130    sprintf(dir, "%s/%s", root_path, random_string);
131
132    if (mkdir(dir, 0777) < 0) {
133        fprintf(stderr, "\nerrno: %d. Failed to create %s\n", errno, dir);
134        return false;
135    }
136    return true;
137}
138
139static bool puncture_fs (const char * const path, const u64 total_size,
140                         const u64 hole_size, const u64 total_hole_size) {
141    u64 increments = (hole_size * total_size) / total_hole_size;
142    u64 hole_max;
143    u64 starting_max = 0;
144    u64 ending_max = increments;
145    char stay_dir[FILENAME_MAX], delete_dir[FILENAME_MAX];
146    char *rm_bin_argv[] = { "/system/bin/rm", "-rf", ""};
147    u64 file_id = 1;
148    char *base_file_data;
149    u64 i = 0;
150
151    if (!create_unique_dir(stay_dir, path) ||
152        !create_unique_dir(delete_dir, path)) {
153        return false;
154    }
155
156    base_file_data = (char*) malloc(MAX_IO_WRITE_CHUNK_SIZE);
157    for (i = 0; i < MAX_IO_WRITE_CHUNK_SIZE; ++i) {
158        base_file_data[i] = get_random_char();
159    }
160    fprintf(stderr, "\n");
161    while (ending_max <= total_size) {
162        fprintf(stderr, "\rSTAGE 1/2: %d%% Complete",
163                (int) (100.0 * starting_max / total_size));
164        hole_max = get_random_num(starting_max, ending_max);
165
166	do {
167		hole_max = get_random_num(starting_max, ending_max);
168	} while (hole_max == starting_max);
169
170        create_unique_file(stay_dir,
171                           hole_max - starting_max,
172                           file_id++,
173                           base_file_data,
174                           MAX_IO_WRITE_CHUNK_SIZE);
175        create_unique_file(delete_dir,
176                           hole_size,
177                           file_id++,
178                           base_file_data,
179                           MAX_IO_WRITE_CHUNK_SIZE);
180
181        starting_max = hole_max + hole_size;
182        ending_max += increments;
183    }
184    create_unique_file(stay_dir,
185                       (ending_max - increments - starting_max),
186                       file_id++,
187                       base_file_data,
188                       MAX_IO_WRITE_CHUNK_SIZE);
189    fprintf(stderr, "\rSTAGE 1/2: 100%% Complete\n");
190    fprintf(stderr, "\rSTAGE 2/2: 0%% Complete");
191    free(base_file_data);
192    rm_bin_argv[2] = delete_dir;
193    if (android_fork_execvp_ext(ARRAY_SIZE(rm_bin_argv), rm_bin_argv,
194                                NULL, 1, LOG_KLOG, 0, NULL, NULL, 0) < 0) {
195        fprintf(stderr, "\nFailed to delete %s\n", rm_bin_argv[2]);
196        return false;
197    }
198    fprintf(stderr, "\rSTAGE 2/2: 100%% Complete\n");
199    return true;
200}
201
202int main (const int argc, char ** const argv) {
203    int opt;
204    int mandatory_opt;
205    char *path = NULL;
206    int seed = time(NULL);
207
208    u64 total_size = 0;
209    u64 hole_size = 0;
210    u64 total_hole_size = 0;
211
212    mandatory_opt = 2;
213    while ((opt = getopt(argc, argv, "s:h:t:")) != -1) {
214        switch(opt) {
215            case 's':
216                seed = atoi(optarg);
217                break;
218            case 'h':
219                hole_size = atoll(optarg);
220                mandatory_opt--;
221                break;
222            case 't':
223                total_hole_size = atoll(optarg);
224                mandatory_opt--;
225                break;
226            default:
227                usage(argv[0]);
228                exit(EXIT_FAILURE);
229        }
230    }
231    if (mandatory_opt) {
232        usage(argv[0]);
233        exit(EXIT_FAILURE);
234    }
235    if (optind >= argc) {
236        fprintf(stderr, "\nExpected path name after options.\n");
237        usage(argv[0]);
238        exit(EXIT_FAILURE);
239    }
240    path = argv[optind++];
241
242    if (optind < argc) {
243        fprintf(stderr, "\nUnexpected argument: %s\n", argv[optind]);
244        usage(argv[0]);
245        exit(EXIT_FAILURE);
246    }
247
248    srandom(seed);
249    fprintf(stderr, "\nRandom seed is: %d\n", seed);
250
251    total_size = get_free_space(path);
252    if (!total_size) {
253        exit(EXIT_FAILURE);
254    }
255    if (total_size < total_hole_size || total_hole_size < hole_size) {
256        fprintf(stderr, "\nInvalid sizes: total available size should be "
257                        "larger than total hole size which is larger than "
258                        "hole size\n");
259        exit(EXIT_FAILURE);
260    }
261
262    if (!puncture_fs(path, total_size, hole_size, total_hole_size)) {
263        exit(EXIT_FAILURE);
264    }
265    return 0;
266}
267