1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  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 "apr_file_io.h"
18#include "apr_file_info.h"
19#include "apr_errno.h"
20#include "apr_general.h"
21#include "apr_poll.h"
22#include "apr_strings.h"
23#include "apr_lib.h"
24#include "apr_mmap.h"
25#include "testutil.h"
26
27/* TODO: in 1.3.0 this becomes APR_HAS_SPARSE_FILES, HOWEVER we will
28 * still need to test csize before proceeding, because having sparse
29 * file support in the OS/APR does not mean this volume supports it!
30 */
31#if APR_HAS_LARGE_FILES
32
33/* Tests which create an 8GB sparse file and then check it can be used
34 * as normal. */
35
36static apr_off_t oneMB = APR_INT64_C(2) << 19;
37static apr_off_t eightGB = APR_INT64_C(2) << 32;
38
39static int madefile = 0;
40
41#define PRECOND if (!madefile) { ABTS_NOT_IMPL(tc, "Large file tests not enabled"); return; }
42
43#define TESTDIR "lfstests"
44#define TESTFILE "large.bin"
45#define TESTFN "lfstests/large.bin"
46
47static void test_open(abts_case *tc, void *data)
48{
49    apr_file_t *f;
50    apr_finfo_t testsize;
51    apr_status_t rv;
52
53    rv = apr_dir_make(TESTDIR, APR_OS_DEFAULT, p);
54    if (rv && !APR_STATUS_IS_EEXIST(rv)) {
55        APR_ASSERT_SUCCESS(tc, "make test directory", rv);
56    }
57
58    /* First attempt a 1MB sparse file so we don't tax the poor test box */
59    rv = apr_file_open(&f, TESTFN, APR_FOPEN_CREATE | APR_FOPEN_WRITE
60                                 | APR_FOPEN_TRUNCATE | APR_FOPEN_SPARSE,
61                       APR_OS_DEFAULT, p);
62
63    APR_ASSERT_SUCCESS(tc, "open file", rv);
64
65    APR_ASSERT_SUCCESS(tc, "Truncate to 1MB", rv = apr_file_trunc(f, oneMB+1));
66
67    if (rv == APR_SUCCESS) {
68        rv = apr_file_info_get(&testsize, APR_FINFO_CSIZE, f);
69    }
70
71    /* give up if we can't determine the allocation size of the file,
72     * or if it's not an obviously small allocation but the allocation
73     * unit doesn't appear insanely large - on most platforms, it's just
74     * zero physical bytes at this point.
75     */
76    if (rv != APR_SUCCESS || (testsize.csize > oneMB
77                              && testsize.csize < oneMB * 2)) {
78        ABTS_NOT_IMPL(tc, "Creation of large file (apparently not sparse)");
79
80        madefile = 0;
81    }
82    else {
83        /* Proceed with our 8GB sparse file now */
84        rv = apr_file_trunc(f, eightGB);
85
86        /* 8GB may pass rlimits or filesystem limits */
87
88        if (APR_STATUS_IS_EINVAL(rv)
89#ifdef EFBIG
90            || rv == EFBIG
91#endif
92            ) {
93            ABTS_NOT_IMPL(tc, "Creation of large file (rlimit, quota or fs)");
94        }
95        else {
96            APR_ASSERT_SUCCESS(tc, "truncate file to 8gb", rv);
97        }
98        madefile = rv == APR_SUCCESS;
99    }
100
101    APR_ASSERT_SUCCESS(tc, "close large file", apr_file_close(f));
102
103    if (!madefile) {
104        APR_ASSERT_SUCCESS(tc, "remove large file", apr_file_remove(TESTFN, p));
105    }
106}
107
108static void test_reopen(abts_case *tc, void *data)
109{
110    apr_file_t *fh;
111    apr_finfo_t finfo;
112    apr_status_t rv;
113
114    PRECOND;
115
116    rv = apr_file_open(&fh, TESTFN, APR_FOPEN_SPARSE | APR_FOPEN_READ,
117                       APR_OS_DEFAULT, p);
118    APR_ASSERT_SUCCESS(tc, "re-open 8GB file", rv);
119
120    APR_ASSERT_SUCCESS(tc, "file_info_get failed",
121                       apr_file_info_get(&finfo, APR_FINFO_NORM, fh));
122
123    ABTS_ASSERT(tc, "file_info_get gave incorrect size",
124             finfo.size == eightGB);
125
126    APR_ASSERT_SUCCESS(tc, "re-close large file", apr_file_close(fh));
127}
128
129static void test_stat(abts_case *tc, void *data)
130{
131    apr_finfo_t finfo;
132
133    PRECOND;
134
135    APR_ASSERT_SUCCESS(tc, "stat large file",
136                       apr_stat(&finfo, TESTFN, APR_FINFO_NORM, p));
137
138    ABTS_ASSERT(tc, "stat gave incorrect size", finfo.size == eightGB);
139}
140
141static void test_readdir(abts_case *tc, void *data)
142{
143    apr_dir_t *dh;
144    apr_status_t rv;
145
146    PRECOND;
147
148    APR_ASSERT_SUCCESS(tc, "open test directory",
149                       apr_dir_open(&dh, TESTDIR, p));
150
151    do {
152        apr_finfo_t finfo;
153
154        rv = apr_dir_read(&finfo, APR_FINFO_MIN, dh);
155
156        if (rv == APR_SUCCESS && strcmp(finfo.name, TESTFILE) == 0) {
157            ABTS_ASSERT(tc, "apr_dir_read gave incorrect size for large file",
158                     finfo.size == eightGB);
159        }
160
161    } while (rv == APR_SUCCESS);
162
163    if (!APR_STATUS_IS_ENOENT(rv)) {
164        APR_ASSERT_SUCCESS(tc, "apr_dir_read failed", rv);
165    }
166
167    APR_ASSERT_SUCCESS(tc, "close test directory",
168                       apr_dir_close(dh));
169}
170
171#define TESTSTR "Hello, world."
172
173static void test_append(abts_case *tc, void *data)
174{
175    apr_file_t *fh;
176    apr_finfo_t finfo;
177    apr_status_t rv;
178
179    PRECOND;
180
181    rv = apr_file_open(&fh, TESTFN, APR_FOPEN_SPARSE | APR_FOPEN_WRITE
182                                  | APR_FOPEN_APPEND,
183                       APR_OS_DEFAULT, p);
184    APR_ASSERT_SUCCESS(tc, "open 8GB file for append", rv);
185
186    APR_ASSERT_SUCCESS(tc, "append to 8GB file",
187                       apr_file_write_full(fh, TESTSTR, strlen(TESTSTR), NULL));
188
189    APR_ASSERT_SUCCESS(tc, "file_info_get failed",
190                       apr_file_info_get(&finfo, APR_FINFO_NORM, fh));
191
192    ABTS_ASSERT(tc, "file_info_get gave incorrect size",
193             finfo.size == eightGB + strlen(TESTSTR));
194
195    APR_ASSERT_SUCCESS(tc, "close 8GB file", apr_file_close(fh));
196}
197
198static void test_seek(abts_case *tc, void *data)
199{
200    apr_file_t *fh;
201    apr_off_t pos;
202    apr_status_t rv;
203
204    PRECOND;
205
206    rv = apr_file_open(&fh, TESTFN, APR_FOPEN_SPARSE | APR_FOPEN_WRITE,
207                       APR_OS_DEFAULT, p);
208    APR_ASSERT_SUCCESS(tc, "open 8GB file for writing", rv);
209
210    pos = 0;
211    APR_ASSERT_SUCCESS(tc, "relative seek to end",
212                       apr_file_seek(fh, APR_END, &pos));
213    ABTS_ASSERT(tc, "seek to END gave 8GB", pos == eightGB);
214
215    pos = eightGB;
216    APR_ASSERT_SUCCESS(tc, "seek to 8GB", apr_file_seek(fh, APR_SET, &pos));
217    ABTS_ASSERT(tc, "seek gave 8GB offset", pos == eightGB);
218
219    pos = 0;
220    APR_ASSERT_SUCCESS(tc, "relative seek to 0", apr_file_seek(fh, APR_CUR, &pos));
221    ABTS_ASSERT(tc, "relative seek gave 8GB offset", pos == eightGB);
222
223    apr_file_close(fh);
224}
225
226static void test_write(abts_case *tc, void *data)
227{
228    apr_file_t *fh;
229    apr_off_t pos = eightGB - 4;
230    apr_status_t rv;
231
232    PRECOND;
233
234    rv = apr_file_open(&fh, TESTFN, APR_FOPEN_SPARSE | APR_FOPEN_WRITE,
235                       APR_OS_DEFAULT, p);
236    APR_ASSERT_SUCCESS(tc, "re-open 8GB file", rv);
237
238    APR_ASSERT_SUCCESS(tc, "seek to 8GB - 4",
239                       apr_file_seek(fh, APR_SET, &pos));
240    ABTS_ASSERT(tc, "seek gave 8GB-4 offset", pos == eightGB - 4);
241
242    APR_ASSERT_SUCCESS(tc, "write magic string to 8GB-4",
243                       apr_file_write_full(fh, "FISH", 4, NULL));
244
245    APR_ASSERT_SUCCESS(tc, "close 8GB file", apr_file_close(fh));
246}
247
248
249#if APR_HAS_MMAP
250static void test_mmap(abts_case *tc, void *data)
251{
252    apr_mmap_t *map;
253    apr_file_t *fh;
254    apr_size_t len = 65536; /* hopefully a multiple of the page size */
255    apr_off_t off = eightGB - len;
256    apr_status_t rv;
257    void *ptr;
258
259    PRECOND;
260
261    rv = apr_file_open(&fh, TESTFN, APR_FOPEN_SPARSE | APR_FOPEN_READ,
262                       APR_OS_DEFAULT, p);
263    APR_ASSERT_SUCCESS(tc, "open 8gb file for mmap", rv);
264
265    APR_ASSERT_SUCCESS(tc, "mmap 8GB file",
266                       apr_mmap_create(&map, fh, off, len, APR_MMAP_READ, p));
267
268    APR_ASSERT_SUCCESS(tc, "close file", apr_file_close(fh));
269
270    ABTS_ASSERT(tc, "mapped a 64K block", map->size == len);
271
272    APR_ASSERT_SUCCESS(tc, "get pointer into mmaped region",
273                       apr_mmap_offset(&ptr, map, len - 4));
274    ABTS_ASSERT(tc, "pointer was not NULL", ptr != NULL);
275
276    ABTS_ASSERT(tc, "found the magic string", memcmp(ptr, "FISH", 4) == 0);
277
278    APR_ASSERT_SUCCESS(tc, "delete mmap handle", apr_mmap_delete(map));
279}
280#endif /* APR_HAS_MMAP */
281
282static void test_format(abts_case *tc, void *data)
283{
284    apr_off_t off;
285
286    PRECOND;
287
288    off = apr_atoi64(apr_off_t_toa(p, eightGB));
289
290    ABTS_ASSERT(tc, "apr_atoi64 parsed apr_off_t_toa result incorrectly",
291             off == eightGB);
292}
293
294#define TESTBUFFN TESTDIR "/buffer.bin"
295
296static void test_buffered(abts_case *tc, void *data)
297{
298    apr_off_t off;
299    apr_file_t *f;
300    apr_status_t rv;
301
302    PRECOND;
303
304    rv = apr_file_open(&f, TESTBUFFN, APR_FOPEN_CREATE | APR_FOPEN_WRITE
305                                    | APR_FOPEN_TRUNCATE | APR_FOPEN_BUFFERED
306                                    | APR_FOPEN_SPARSE,
307                       APR_OS_DEFAULT, p);
308    APR_ASSERT_SUCCESS(tc, "open buffered file", rv);
309
310    APR_ASSERT_SUCCESS(tc, "truncate to 8GB",
311                       apr_file_trunc(f, eightGB));
312
313    off = eightGB;
314    APR_ASSERT_SUCCESS(tc, "seek to 8GB",
315                       apr_file_seek(f, APR_SET, &off));
316    ABTS_ASSERT(tc, "returned seek position still 8GB",
317                off == eightGB);
318
319    off = 0;
320    APR_ASSERT_SUCCESS(tc, "relative seek",
321                       apr_file_seek(f, APR_CUR, &off));
322    ABTS_ASSERT(tc, "relative seek still at 8GB",
323                off == eightGB);
324
325    off = 0;
326    APR_ASSERT_SUCCESS(tc, "end-relative seek",
327                       apr_file_seek(f, APR_END, &off));
328    ABTS_ASSERT(tc, "end-relative seek still at 8GB",
329                off == eightGB);
330
331    off = -eightGB;
332    APR_ASSERT_SUCCESS(tc, "relative seek to beginning",
333                       apr_file_seek(f, APR_CUR, &off));
334    ABTS_ASSERT(tc, "seek to beginning got zero",
335                off == 0);
336
337    APR_ASSERT_SUCCESS(tc, "close buffered file",
338                       apr_file_close(f));
339}
340
341#else /* !APR_HAS_LARGE_FILES */
342
343static void test_nolfs(abts_case *tc, void *data)
344{
345    ABTS_NOT_IMPL(tc, "Large Files not supported");
346}
347
348#endif
349
350abts_suite *testlfs(abts_suite *suite)
351{
352    suite = ADD_SUITE(suite)
353
354#if APR_HAS_LARGE_FILES
355    abts_run_test(suite, test_open, NULL);
356    abts_run_test(suite, test_reopen, NULL);
357    abts_run_test(suite, test_stat, NULL);
358    abts_run_test(suite, test_readdir, NULL);
359    abts_run_test(suite, test_seek, NULL);
360    abts_run_test(suite, test_append, NULL);
361    abts_run_test(suite, test_write, NULL);
362#if APR_HAS_MMAP
363    abts_run_test(suite, test_mmap, NULL);
364#endif
365    abts_run_test(suite, test_format, NULL);
366    abts_run_test(suite, test_buffered, NULL);
367#else
368    abts_run_test(suite, test_nolfs, NULL);
369#endif
370
371    return suite;
372}
373
374