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 22 REGISTER_TEST_CASE(FileMapping); 23 24 namespace { 25 26 // TODO(dmichael): Move these to test_utils so we can share them? 27 int32_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 50 int32_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 77 std::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 194 bool 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 202 void 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 208 std::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 398 std::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 416 std::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