1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ppapi/tests/test_file_mapping.h"
6
7#include <string.h>
8
9#include <limits>
10#include <string>
11
12#include "ppapi/c/pp_errors.h"
13#include "ppapi/c/ppb_file_io.h"
14#include "ppapi/c/ppb_file_mapping.h"
15#include "ppapi/cpp/file_io.h"
16#include "ppapi/cpp/file_ref.h"
17#include "ppapi/cpp/file_system.h"
18#include "ppapi/cpp/instance.h"
19#include "ppapi/cpp/module.h"
20#include "ppapi/tests/test_utils.h"
21
22REGISTER_TEST_CASE(FileMapping);
23
24namespace {
25
26// TODO(dmichael): Move these to test_utils so we can share them?
27int32_t ReadEntireFile(PP_Instance instance,
28                       pp::FileIO* file_io,
29                       int32_t offset,
30                       std::string* data,
31                       CallbackType callback_type) {
32  TestCompletionCallback callback(instance, callback_type);
33  char buf[256];
34  int32_t read_offset = offset;
35
36  for (;;) {
37    callback.WaitForResult(
38        file_io->Read(read_offset, buf, sizeof(buf), callback.GetCallback()));
39    if (callback.result() < 0)
40      return callback.result();
41    if (callback.result() == 0)
42      break;
43    read_offset += callback.result();
44    data->append(buf, callback.result());
45  }
46
47  return PP_OK;
48}
49
50int32_t WriteEntireBuffer(PP_Instance instance,
51                          pp::FileIO* file_io,
52                          int32_t offset,
53                          const std::string& data,
54                          CallbackType callback_type) {
55  TestCompletionCallback callback(instance, callback_type);
56  int32_t write_offset = offset;
57  const char* buf = data.c_str();
58  int32_t size = data.size();
59
60  while (write_offset < offset + size) {
61    callback.WaitForResult(file_io->Write(write_offset,
62                                          &buf[write_offset - offset],
63                                          size - write_offset + offset,
64                                          callback.GetCallback()));
65    if (callback.result() < 0)
66      return callback.result();
67    if (callback.result() == 0)
68      return PP_ERROR_FAILED;
69    write_offset += callback.result();
70  }
71  callback.WaitForResult(file_io->Flush(callback.GetCallback()));
72  return callback.result();
73}
74
75}  // namespace
76
77std::string TestFileMapping::MapAndCheckResults(uint32_t prot,
78                                                uint32_t flags) {
79  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
80
81  pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
82  pp::FileRef file_ref(file_system, "/mapped_file");
83
84  callback.WaitForResult(file_system.Open(1024, callback.GetCallback()));
85  ASSERT_EQ(PP_OK, callback.result());
86
87  const int64_t page_size =
88      file_mapping_if_->GetMapPageSize(instance_->pp_instance());
89  const int64_t kNumPages = 4;
90  // Make a string that's big enough that it spans all of the first |n-1| pages,
91  // plus a little bit of the |nth| page.
92  std::string file_contents((page_size * (kNumPages - 1)) + 128, 'a');
93
94  pp::FileIO file_io(instance_);
95  callback.WaitForResult(file_io.Open(file_ref,
96                                      PP_FILEOPENFLAG_CREATE |
97                                      PP_FILEOPENFLAG_TRUNCATE |
98                                      PP_FILEOPENFLAG_READ |
99                                      PP_FILEOPENFLAG_WRITE,
100                                      callback.GetCallback()));
101  ASSERT_EQ(PP_OK, callback.result());
102  ASSERT_EQ(PP_OK, WriteEntireBuffer(instance_->pp_instance(),
103                                     &file_io,
104                                     0,
105                                     file_contents,
106                                     callback_type()));
107
108  // TODO(dmichael): Use C++ interface.
109  void* address = NULL;
110  callback.WaitForResult(
111      file_mapping_if_->Map(
112          instance_->pp_instance(),
113          file_io.pp_resource(),
114          kNumPages * page_size,
115          prot,
116          flags,
117          0,
118          &address,
119          callback.GetCallback().pp_completion_callback()));
120  CHECK_CALLBACK_BEHAVIOR(callback);
121  ASSERT_EQ(PP_OK, callback.result());
122  ASSERT_NE(NULL, address);
123
124  if (prot & PP_FILEMAPPROTECTION_READ) {
125    // Make sure we can read.
126    std::string mapped_data(static_cast<char*>(address), file_contents.size());
127    // The initial data should match.
128    ASSERT_EQ(file_contents, mapped_data);
129
130    // Now write some data and flush it.
131    const std::string file_contents2(file_contents.size(), 'x');
132    ASSERT_EQ(PP_OK, WriteEntireBuffer(instance_->pp_instance(),
133                                       &file_io,
134                                       0,
135                                       file_contents2,
136                                       callback_type()));
137    // If the region was mapped SHARED, it should get updated.
138    std::string mapped_data2(static_cast<char*>(address), file_contents.size());
139    if (flags & PP_FILEMAPFLAG_SHARED)
140      ASSERT_EQ(file_contents2, mapped_data2);
141    // In POSIX, it is unspecified in the PRIVATE case whether changes to the
142    // file are visible to the mapped region. So we can't really test anything
143    // here in that case.
144    // TODO(dmichael): Make sure our Pepper documentation reflects this.
145  }
146  if (prot & PP_FILEMAPPROTECTION_WRITE) {
147    std::string old_file_contents;
148    ASSERT_EQ(PP_OK, ReadEntireFile(instance_->pp_instance(),
149                                    &file_io,
150                                    0,
151                                    &old_file_contents,
152                                    callback_type()));
153    // Write something else to the mapped region, then unmap, and see if it
154    // gets written to the file. (Note we have to Unmap to make sure that the
155    // write is committed).
156    memset(address, 'y', file_contents.size());
157    // Note, we might not have read access to the mapped region here, so we
158    // make a string with the same contents without actually reading.
159    std::string mapped_data3(file_contents.size(), 'y');
160    callback.WaitForResult(
161        file_mapping_if_->Unmap(
162            instance_->pp_instance(), address, file_contents.size(),
163            callback.GetCallback().pp_completion_callback()));
164    CHECK_CALLBACK_BEHAVIOR(callback);
165    ASSERT_EQ(PP_OK, callback.result());
166    std::string new_file_contents;
167    ASSERT_EQ(PP_OK, ReadEntireFile(instance_->pp_instance(),
168                                    &file_io,
169                                    0,
170                                    &new_file_contents,
171                                    callback_type()));
172
173    // Sanity-check that the data we wrote isn't the same as what was already
174    // there, otherwise our test is invalid.
175    ASSERT_NE(mapped_data3, old_file_contents);
176    // If it's SHARED, the file should match what we wrote to the mapped region.
177    // Otherwise, it should not have changed.
178    if (flags & PP_FILEMAPFLAG_SHARED)
179      ASSERT_EQ(mapped_data3, new_file_contents);
180    else
181      ASSERT_EQ(old_file_contents, new_file_contents);
182  } else {
183    // We didn't do the "WRITE" test, but we still want to Unmap.
184    callback.WaitForResult(
185        file_mapping_if_->Unmap(
186            instance_->pp_instance(), address, file_contents.size(),
187            callback.GetCallback().pp_completion_callback()));
188    CHECK_CALLBACK_BEHAVIOR(callback);
189    ASSERT_EQ(PP_OK, callback.result());
190  }
191  PASS();
192}
193
194bool TestFileMapping::Init() {
195  // TODO(dmichael): Use unversioned string when this goes to stable?
196  file_mapping_if_ = static_cast<const PPB_FileMapping_0_1*>(
197      pp::Module::Get()->GetBrowserInterface(PPB_FILEMAPPING_INTERFACE_0_1));
198  return !!file_mapping_if_ && CheckTestingInterface() &&
199         EnsureRunningOverHTTP();
200}
201
202void TestFileMapping::RunTests(const std::string& filter) {
203  RUN_CALLBACK_TEST(TestFileMapping, BadParameters, filter);
204  RUN_CALLBACK_TEST(TestFileMapping, Map, filter);
205  RUN_CALLBACK_TEST(TestFileMapping, PartialRegions, filter);
206}
207
208std::string TestFileMapping::TestBadParameters() {
209  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
210
211  pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
212  pp::FileRef file_ref(file_system, "/mapped_file");
213
214  callback.WaitForResult(file_system.Open(1024, callback.GetCallback()));
215  ASSERT_EQ(PP_OK, callback.result());
216
217  const int64_t page_size =
218      file_mapping_if_->GetMapPageSize(instance_->pp_instance());
219  // const int64_t kNumPages = 4;
220  // Make a string that's big enough that it spans 3 pages, plus a little extra.
221  //std::string file_contents((page_size * (kNumPages - 1)) + 128, 'a');
222  std::string file_contents(page_size, 'a');
223
224  pp::FileIO file_io(instance_);
225  callback.WaitForResult(file_io.Open(file_ref,
226                                      PP_FILEOPENFLAG_CREATE |
227                                      PP_FILEOPENFLAG_TRUNCATE |
228                                      PP_FILEOPENFLAG_READ |
229                                      PP_FILEOPENFLAG_WRITE,
230                                      callback.GetCallback()));
231  ASSERT_EQ(PP_OK, callback.result());
232  ASSERT_EQ(PP_OK, WriteEntireBuffer(instance_->pp_instance(),
233                                     &file_io,
234                                     0,
235                                     file_contents,
236                                     callback_type()));
237
238  // Bad instance.
239  void* address = NULL;
240  callback.WaitForResult(
241      file_mapping_if_->Map(
242          PP_Instance(0xbadbad),
243          file_io.pp_resource(),
244          page_size,
245          PP_FILEMAPPROTECTION_READ,
246          PP_FILEMAPFLAG_PRIVATE,
247          0,
248          &address,
249          callback.GetCallback().pp_completion_callback()));
250  CHECK_CALLBACK_BEHAVIOR(callback);
251  ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
252  ASSERT_EQ(NULL, address);
253
254  // Bad resource.
255  callback.WaitForResult(
256      file_mapping_if_->Map(
257          instance_->pp_instance(),
258          PP_Resource(0xbadbad),
259          page_size,
260          PP_FILEMAPPROTECTION_READ,
261          PP_FILEMAPFLAG_PRIVATE,
262          0,
263          &address,
264          callback.GetCallback().pp_completion_callback()));
265  CHECK_CALLBACK_BEHAVIOR(callback);
266  ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
267  ASSERT_EQ(NULL, address);
268
269  // Length too big.
270  callback.WaitForResult(
271      file_mapping_if_->Map(
272          instance_->pp_instance(),
273          file_io.pp_resource(),
274          std::numeric_limits<int64_t>::max(),
275          PP_FILEMAPPROTECTION_READ,
276          PP_FILEMAPFLAG_PRIVATE,
277          0,
278          &address,
279          callback.GetCallback().pp_completion_callback()));
280  CHECK_CALLBACK_BEHAVIOR(callback);
281  ASSERT_EQ(PP_ERROR_NOMEMORY, callback.result());
282  ASSERT_EQ(NULL, address);
283
284  // Length too small.
285  callback.WaitForResult(
286      file_mapping_if_->Map(
287          instance_->pp_instance(),
288          file_io.pp_resource(),
289          -1,
290          PP_FILEMAPPROTECTION_READ,
291          PP_FILEMAPFLAG_PRIVATE,
292          0,
293          &address,
294          callback.GetCallback().pp_completion_callback()));
295  CHECK_CALLBACK_BEHAVIOR(callback);
296  ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
297  ASSERT_EQ(NULL, address);
298  // TODO(dmichael): Check & test length that is not a multiple of page size???
299
300  // Bad protection.
301  callback.WaitForResult(
302      file_mapping_if_->Map(
303          instance_->pp_instance(),
304          file_io.pp_resource(),
305          page_size,
306          ~static_cast<uint32_t>(PP_FILEMAPPROTECTION_READ),
307          PP_FILEMAPFLAG_PRIVATE,
308          0,
309          &address,
310          callback.GetCallback().pp_completion_callback()));
311  CHECK_CALLBACK_BEHAVIOR(callback);
312  ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
313  ASSERT_EQ(NULL, address);
314
315  // No flags.
316  callback.WaitForResult(
317      file_mapping_if_->Map(
318          instance_->pp_instance(),
319          file_io.pp_resource(),
320          page_size,
321          PP_FILEMAPPROTECTION_READ,
322          0,
323          0,
324          &address,
325          callback.GetCallback().pp_completion_callback()));
326  CHECK_CALLBACK_BEHAVIOR(callback);
327  ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
328  ASSERT_EQ(NULL, address);
329
330  // Both flags.
331  callback.WaitForResult(
332      file_mapping_if_->Map(
333          instance_->pp_instance(),
334          file_io.pp_resource(),
335          page_size,
336          PP_FILEMAPPROTECTION_READ,
337          PP_FILEMAPFLAG_SHARED | PP_FILEMAPFLAG_PRIVATE,
338          0,
339          &address,
340          callback.GetCallback().pp_completion_callback()));
341  CHECK_CALLBACK_BEHAVIOR(callback);
342  ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
343  ASSERT_EQ(NULL, address);
344
345  // Bad flags.
346  callback.WaitForResult(
347      file_mapping_if_->Map(
348          instance_->pp_instance(),
349          file_io.pp_resource(),
350          page_size,
351          PP_FILEMAPPROTECTION_READ,
352          ~static_cast<uint32_t>(PP_FILEMAPFLAG_SHARED),
353          0,
354          &address,
355          callback.GetCallback().pp_completion_callback()));
356  CHECK_CALLBACK_BEHAVIOR(callback);
357  ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
358  ASSERT_EQ(NULL, address);
359
360  // Bad offset; not a multiple of page size.
361  callback.WaitForResult(
362      file_mapping_if_->Map(
363          instance_->pp_instance(),
364          file_io.pp_resource(),
365          page_size,
366          PP_FILEMAPPROTECTION_READ,
367          ~static_cast<uint32_t>(PP_FILEMAPFLAG_SHARED),
368          1,
369          &address,
370          callback.GetCallback().pp_completion_callback()));
371  CHECK_CALLBACK_BEHAVIOR(callback);
372  ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
373  ASSERT_EQ(NULL, address);
374
375  // Unmap NULL.
376  callback.WaitForResult(
377      file_mapping_if_->Unmap(
378          instance_->pp_instance(),
379          NULL,
380          page_size,
381          callback.GetCallback().pp_completion_callback()));
382  CHECK_CALLBACK_BEHAVIOR(callback);
383  ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
384
385  // Unmap bad address.
386  callback.WaitForResult(
387      file_mapping_if_->Unmap(
388          instance_->pp_instance(),
389          reinterpret_cast<const void*>(0xdeadbeef),
390          page_size,
391          callback.GetCallback().pp_completion_callback()));
392  CHECK_CALLBACK_BEHAVIOR(callback);
393  ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
394
395  PASS();
396}
397
398std::string TestFileMapping::TestMap() {
399  ASSERT_SUBTEST_SUCCESS(MapAndCheckResults(PP_FILEMAPPROTECTION_READ,
400                                            PP_FILEMAPFLAG_SHARED));
401  ASSERT_SUBTEST_SUCCESS(MapAndCheckResults(PP_FILEMAPPROTECTION_WRITE,
402                                            PP_FILEMAPFLAG_SHARED));
403  ASSERT_SUBTEST_SUCCESS(
404      MapAndCheckResults(PP_FILEMAPPROTECTION_WRITE | PP_FILEMAPPROTECTION_READ,
405                         PP_FILEMAPFLAG_SHARED));
406  ASSERT_SUBTEST_SUCCESS(MapAndCheckResults(PP_FILEMAPPROTECTION_READ,
407                                            PP_FILEMAPFLAG_PRIVATE));
408  ASSERT_SUBTEST_SUCCESS(MapAndCheckResults(PP_FILEMAPPROTECTION_WRITE,
409                                            PP_FILEMAPFLAG_PRIVATE));
410  ASSERT_SUBTEST_SUCCESS(
411      MapAndCheckResults(PP_FILEMAPPROTECTION_WRITE | PP_FILEMAPPROTECTION_READ,
412                         PP_FILEMAPFLAG_PRIVATE));
413  PASS();
414}
415
416std::string TestFileMapping::TestPartialRegions() {
417  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
418
419  pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY);
420  pp::FileRef file_ref1(file_system, "/mapped_file1");
421  pp::FileRef file_ref2(file_system, "/mapped_file2");
422
423  callback.WaitForResult(file_system.Open(1024, callback.GetCallback()));
424  ASSERT_EQ(PP_OK, callback.result());
425
426  const int64_t page_size =
427      file_mapping_if_->GetMapPageSize(instance_->pp_instance());
428  const int64_t kNumPages = 3;
429  std::string file_contents1(kNumPages * page_size, 'a');
430
431  pp::FileIO file_io1(instance_);
432  callback.WaitForResult(file_io1.Open(file_ref1,
433                                       PP_FILEOPENFLAG_CREATE |
434                                       PP_FILEOPENFLAG_TRUNCATE |
435                                       PP_FILEOPENFLAG_READ |
436                                       PP_FILEOPENFLAG_WRITE,
437                                       callback.GetCallback()));
438  ASSERT_EQ(PP_OK, callback.result());
439  ASSERT_EQ(PP_OK, WriteEntireBuffer(instance_->pp_instance(),
440                                     &file_io1,
441                                     0,
442                                     file_contents1,
443                                     callback_type()));
444
445  // TODO(dmichael): Use C++ interface.
446  void* address = NULL;
447  callback.WaitForResult(
448      file_mapping_if_->Map(
449          instance_->pp_instance(),
450          file_io1.pp_resource(),
451          kNumPages * page_size,
452          PP_FILEMAPPROTECTION_WRITE | PP_FILEMAPPROTECTION_READ,
453          PP_FILEMAPFLAG_SHARED,
454          0,
455          &address,
456          callback.GetCallback().pp_completion_callback()));
457  CHECK_CALLBACK_BEHAVIOR(callback);
458  ASSERT_EQ(PP_OK, callback.result());
459  ASSERT_NE(NULL, address);
460
461  // Unmap only the middle page.
462  void* address_of_middle_page =
463      reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) + page_size);
464  callback.WaitForResult(
465      file_mapping_if_->Unmap(
466          instance_->pp_instance(),
467          address_of_middle_page,
468          page_size,
469          callback.GetCallback().pp_completion_callback()));
470  CHECK_CALLBACK_BEHAVIOR(callback);
471  ASSERT_EQ(PP_OK, callback.result());
472
473  // Write another file, map it in to the middle hole that was left above.
474  pp::FileIO file_io2(instance_);
475  callback.WaitForResult(file_io2.Open(file_ref2,
476                                       PP_FILEOPENFLAG_CREATE |
477                                       PP_FILEOPENFLAG_TRUNCATE |
478                                       PP_FILEOPENFLAG_READ |
479                                       PP_FILEOPENFLAG_WRITE,
480                                       callback.GetCallback()));
481  ASSERT_EQ(PP_OK, callback.result());
482  // This second file will have 1 page worth of data.
483  std::string file_contents2(page_size, 'b');
484  ASSERT_EQ(PP_OK, WriteEntireBuffer(instance_->pp_instance(),
485                                     &file_io2,
486                                     0,
487                                     file_contents2,
488                                     callback_type()));
489  callback.WaitForResult(
490      file_mapping_if_->Map(
491          instance_->pp_instance(),
492          file_io2.pp_resource(),
493          page_size,
494          PP_FILEMAPPROTECTION_WRITE | PP_FILEMAPPROTECTION_READ,
495          PP_FILEMAPFLAG_SHARED | PP_FILEMAPFLAG_FIXED,
496          0,
497          &address_of_middle_page,
498          callback.GetCallback().pp_completion_callback()));
499  CHECK_CALLBACK_BEHAVIOR(callback);
500  ASSERT_EQ(PP_OK, callback.result());
501
502  // Write something else to the mapped region, then unmap, and see if it
503  // gets written to both files. (Note we have to Unmap to make sure that the
504  // write is committed).
505  memset(address, 'c', kNumPages * page_size);
506  callback.WaitForResult(
507      file_mapping_if_->Unmap(
508          instance_->pp_instance(), address, kNumPages * page_size,
509          callback.GetCallback().pp_completion_callback()));
510  CHECK_CALLBACK_BEHAVIOR(callback);
511  ASSERT_EQ(PP_OK, callback.result());
512  // The first and third page should have been written with 'c', but the
513  // second page should be untouched.
514  std::string expected_file_contents1 = std::string(page_size, 'c') +
515                                        std::string(page_size, 'a') +
516                                        std::string(page_size, 'c');
517  std::string new_file_contents1;
518  ASSERT_EQ(PP_OK, ReadEntireFile(instance_->pp_instance(),
519                                  &file_io1,
520                                  0,
521                                  &new_file_contents1,
522                                  callback_type()));
523  ASSERT_EQ(expected_file_contents1, new_file_contents1);
524
525  // The second file should have been entirely over-written.
526  std::string expected_file_contents2 = std::string(page_size, 'c');
527  std::string new_file_contents2;
528  ASSERT_EQ(PP_OK, ReadEntireFile(instance_->pp_instance(),
529                                  &file_io2,
530                                  0,
531                                  &new_file_contents2,
532                                  callback_type()));
533  ASSERT_EQ(expected_file_contents2, new_file_contents2);
534
535  // TODO(dmichael): Test non-zero offset
536
537  PASS();
538}
539
540