Upstream version 8.37.180.0
[platform/framework/web/crosswalk.git] / src / native_client_sdk / src / tests / nacl_io_test / html5_fs_test.cc
1 // Copyright 2013 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 <errno.h>
6 #include <fcntl.h>
7
8 #include <set>
9 #include <string>
10
11 #include <gmock/gmock.h>
12 #include <ppapi/c/ppb_file_io.h>
13 #include <ppapi/c/pp_directory_entry.h>
14 #include <ppapi/c/pp_errors.h>
15 #include <ppapi/c/pp_instance.h>
16 #if defined(WIN32)
17 #include <windows.h>  // For Sleep()
18 #endif
19
20 #include "fake_ppapi/fake_pepper_interface_html5_fs.h"
21 #include "nacl_io/kernel_handle.h"
22 #include "nacl_io/html5fs/html5_fs.h"
23 #include "nacl_io/osdirent.h"
24 #include "nacl_io/osunistd.h"
25 #include "nacl_io/pepper_interface_delegate.h"
26 #include "sdk_util/scoped_ref.h"
27 #include "mock_util.h"
28 #include "pepper_interface_mock.h"
29
30 using namespace nacl_io;
31 using namespace sdk_util;
32
33 using ::testing::_;
34 using ::testing::DoAll;
35 using ::testing::Invoke;
36 using ::testing::Mock;
37 using ::testing::Return;
38
39 namespace {
40
41 class Html5FsForTesting : public Html5Fs {
42  public:
43   Html5FsForTesting(StringMap_t& string_map, PepperInterface* ppapi,
44                     int expected_error = 0) {
45     FsInitArgs args;
46     args.string_map = string_map;
47     args.ppapi = ppapi;
48     Error error = Init(args);
49     EXPECT_EQ(expected_error, error);
50   }
51 };
52
53 class Html5FsTest : public ::testing::Test {
54  public:
55   Html5FsTest();
56
57  protected:
58   FakePepperInterfaceHtml5Fs ppapi_html5_;
59   PepperInterfaceMock ppapi_mock_;
60   PepperInterfaceDelegate ppapi_;
61 };
62
63 Html5FsTest::Html5FsTest()
64     : ppapi_mock_(ppapi_html5_.GetInstance()),
65       ppapi_(ppapi_html5_.GetInstance()) {
66   // Default delegation to the html5 pepper interface.
67   ppapi_.SetCoreInterfaceDelegate(ppapi_html5_.GetCoreInterface());
68   ppapi_.SetFileSystemInterfaceDelegate(ppapi_html5_.GetFileSystemInterface());
69   ppapi_.SetFileRefInterfaceDelegate(ppapi_html5_.GetFileRefInterface());
70   ppapi_.SetFileIoInterfaceDelegate(ppapi_html5_.GetFileIoInterface());
71   ppapi_.SetVarInterfaceDelegate(ppapi_html5_.GetVarInterface());
72 }
73
74 }  // namespace
75
76 TEST_F(Html5FsTest, FilesystemType) {
77   const char* filesystem_type_strings[] = {"", "PERSISTENT", "TEMPORARY", NULL};
78   PP_FileSystemType filesystem_type_values[] = {
79       PP_FILESYSTEMTYPE_LOCALPERSISTENT,  // Default to persistent.
80       PP_FILESYSTEMTYPE_LOCALPERSISTENT, PP_FILESYSTEMTYPE_LOCALTEMPORARY};
81
82   const char* expected_size_strings[] = {"100", "12345", NULL};
83   const int expected_size_values[] = {100, 12345};
84
85   FileSystemInterfaceMock* filesystem_mock =
86       ppapi_mock_.GetFileSystemInterface();
87
88   FakeFileSystemInterface* filesystem_fake =
89       static_cast<FakeFileSystemInterface*>(
90           ppapi_html5_.GetFileSystemInterface());
91
92   for (int i = 0; filesystem_type_strings[i] != NULL; ++i) {
93     const char* filesystem_type_string = filesystem_type_strings[i];
94     PP_FileSystemType expected_filesystem_type = filesystem_type_values[i];
95
96     for (int j = 0; expected_size_strings[j] != NULL; ++j) {
97       const char* expected_size_string = expected_size_strings[j];
98       int64_t expected_expected_size = expected_size_values[j];
99
100       ppapi_.SetFileSystemInterfaceDelegate(filesystem_mock);
101
102       ON_CALL(*filesystem_mock, Create(_, _)).WillByDefault(
103           Invoke(filesystem_fake, &FakeFileSystemInterface::Create));
104
105       EXPECT_CALL(*filesystem_mock,
106                   Create(ppapi_.GetInstance(), expected_filesystem_type));
107
108       EXPECT_CALL(*filesystem_mock, Open(_, expected_expected_size, _))
109           .WillOnce(DoAll(CallCallback<2>(int32_t(PP_OK)),
110                           Return(int32_t(PP_OK_COMPLETIONPENDING))));
111
112       StringMap_t map;
113       map["type"] = filesystem_type_string;
114       map["expected_size"] = expected_size_string;
115       ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
116
117       Mock::VerifyAndClearExpectations(&filesystem_mock);
118     }
119   }
120 }
121
122 TEST_F(Html5FsTest, PassFilesystemResource) {
123   // Fail if given a bad resource.
124   {
125     StringMap_t map;
126     map["filesystem_resource"] = "0";
127     ScopedRef<Html5FsForTesting> fs(
128         new Html5FsForTesting(map, &ppapi_, EINVAL));
129   }
130
131   {
132     EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL));
133     PP_Resource filesystem = ppapi_html5_.GetFileSystemInterface()->Create(
134         ppapi_html5_.GetInstance(), PP_FILESYSTEMTYPE_LOCALPERSISTENT);
135
136     ASSERT_EQ(int32_t(PP_OK), ppapi_html5_.GetFileSystemInterface()->Open(
137               filesystem, 0, PP_BlockUntilComplete()));
138
139     StringMap_t map;
140     char buffer[30];
141     snprintf(buffer, 30, "%d", filesystem);
142     map["filesystem_resource"] = buffer;
143     ScopedRef<Html5FsForTesting> fs(
144         new Html5FsForTesting(map, &ppapi_));
145
146     ASSERT_EQ(0, fs->Access(Path("/foo"), R_OK | W_OK | X_OK));
147
148     ppapi_html5_.GetCoreInterface()->ReleaseResource(filesystem);
149   }
150 }
151
152 TEST_F(Html5FsTest, MountSubtree) {
153   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo/bar",
154                                                                NULL));
155   StringMap_t map;
156   map["SOURCE"] = "/foo";
157   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
158
159   ASSERT_EQ(0, fs->Access(Path("/bar"), R_OK | W_OK | X_OK));
160   ASSERT_EQ(ENOENT, fs->Access(Path("/foo/bar"), F_OK));
161 }
162
163 TEST_F(Html5FsTest, Access) {
164   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL));
165
166   StringMap_t map;
167   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
168
169   ASSERT_EQ(0, fs->Access(Path("/foo"), R_OK | W_OK | X_OK));
170   ASSERT_EQ(ENOENT, fs->Access(Path("/bar"), F_OK));
171 }
172
173 TEST_F(Html5FsTest, Mkdir) {
174   StringMap_t map;
175   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
176
177   // mkdir at the root should return EEXIST, not EACCES.
178   EXPECT_EQ(EEXIST, fs->Mkdir(Path("/"), 0644));
179
180   Path path("/foo");
181   ASSERT_EQ(ENOENT, fs->Access(path, F_OK));
182   ASSERT_EQ(0, fs->Mkdir(path, 0644));
183
184   struct stat stat;
185   ScopedNode node;
186   ASSERT_EQ(0, fs->Open(path, O_RDONLY, &node));
187   EXPECT_EQ(0, node->GetStat(&stat));
188   EXPECT_EQ(S_IFDIR, stat.st_mode & S_IFDIR);
189 }
190
191 TEST_F(Html5FsTest, Remove) {
192   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL));
193
194   StringMap_t map;
195   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
196
197   Path path("/foo");
198   ASSERT_EQ(0, fs->Access(path, F_OK));
199   ASSERT_EQ(0, fs->Remove(path));
200   EXPECT_EQ(ENOENT, fs->Access(path, F_OK));
201 }
202
203 TEST_F(Html5FsTest, Unlink) {
204   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/file", NULL));
205   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
206
207   StringMap_t map;
208   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
209
210   ASSERT_EQ(EISDIR, fs->Unlink(Path("/dir")));
211   EXPECT_EQ(0, fs->Unlink(Path("/file")));
212   EXPECT_EQ(ENOENT, fs->Access(Path("/file"), F_OK));
213   EXPECT_EQ(0, fs->Access(Path("/dir"), F_OK));
214 }
215
216 TEST_F(Html5FsTest, Rmdir) {
217   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/file", NULL));
218   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
219
220   StringMap_t map;
221   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
222
223   ASSERT_EQ(ENOTDIR, fs->Rmdir(Path("/file")));
224   EXPECT_EQ(0, fs->Rmdir(Path("/dir")));
225   EXPECT_EQ(ENOENT, fs->Access(Path("/dir"), F_OK));
226   EXPECT_EQ(0, fs->Access(Path("/file"), F_OK));
227 }
228
229 TEST_F(Html5FsTest, Rename) {
230   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL));
231
232   StringMap_t map;
233   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
234
235   Path path("/foo");
236   Path newpath("/bar");
237   ASSERT_EQ(0, fs->Access(path, F_OK));
238   ASSERT_EQ(0, fs->Rename(path, newpath));
239   EXPECT_EQ(ENOENT, fs->Access(path, F_OK));
240   EXPECT_EQ(0, fs->Access(newpath, F_OK));
241 }
242
243 TEST_F(Html5FsTest, OpenForCreate) {
244   StringMap_t map;
245   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
246
247   Path path("/foo");
248   EXPECT_EQ(ENOENT, fs->Access(path, F_OK));
249
250   ScopedNode node;
251   ASSERT_EQ(0, fs->Open(path, O_CREAT | O_RDWR, &node));
252
253   // Write some data.
254   char contents[] = "contents";
255   int bytes_written = 0;
256   EXPECT_EQ(0, node->Write(HandleAttr(), &contents[0], strlen(contents),
257                            &bytes_written));
258   EXPECT_EQ(strlen(contents), bytes_written);
259
260   // Create again.
261   ASSERT_EQ(0, fs->Open(path, O_CREAT, &node));
262
263   // Check that the file still has data.
264   off_t size;
265   EXPECT_EQ(0, node->GetSize(&size));
266   EXPECT_EQ(strlen(contents), size);
267
268   // Open exclusively.
269   EXPECT_EQ(EEXIST, fs->Open(path, O_CREAT | O_EXCL, &node));
270
271   // Try to truncate without write access.
272   EXPECT_EQ(EINVAL, fs->Open(path, O_CREAT | O_TRUNC, &node));
273
274   // Open and truncate.
275   ASSERT_EQ(0, fs->Open(path, O_CREAT | O_TRUNC | O_WRONLY, &node));
276
277   // File should be empty.
278   EXPECT_EQ(0, node->GetSize(&size));
279   EXPECT_EQ(0, size);
280 }
281
282 TEST_F(Html5FsTest, Read) {
283   const char contents[] = "contents";
284   ASSERT_TRUE(
285       ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
286   ASSERT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
287   StringMap_t map;
288   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
289
290   ScopedNode node;
291   ASSERT_EQ(0, fs->Open(Path("/file"), O_RDONLY, &node));
292
293   char buffer[10] = {0};
294   int bytes_read = 0;
295   HandleAttr attr;
296   ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
297   ASSERT_EQ(strlen(contents), bytes_read);
298   ASSERT_STREQ(contents, buffer);
299
300   // Read nothing past the end of the file.
301   attr.offs = 100;
302   ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
303   ASSERT_EQ(0, bytes_read);
304
305   // Read part of the data.
306   attr.offs = 4;
307   ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
308   ASSERT_EQ(strlen(contents) - 4, bytes_read);
309   buffer[bytes_read] = 0;
310   ASSERT_STREQ("ents", buffer);
311
312   // Writing should fail.
313   int bytes_written = 1;  // Set to a non-zero value.
314   attr.offs = 0;
315   ASSERT_EQ(EACCES,
316             node->Write(attr, &buffer[0], sizeof(buffer), &bytes_written));
317   ASSERT_EQ(0, bytes_written);
318
319   // Reading from a directory should fail.
320   ASSERT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
321   ASSERT_EQ(EISDIR, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
322 }
323
324 TEST_F(Html5FsTest, Write) {
325   const char contents[] = "contents";
326   EXPECT_TRUE(
327       ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
328   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
329
330   StringMap_t map;
331   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
332
333   ScopedNode node;
334   ASSERT_EQ(0, fs->Open(Path("/file"), O_WRONLY, &node));
335
336   // Reading should fail.
337   char buffer[10];
338   int bytes_read = 1;  // Set to a non-zero value.
339   HandleAttr attr;
340   EXPECT_EQ(EACCES, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
341   EXPECT_EQ(0, bytes_read);
342
343   // Reopen as read-write.
344   ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
345
346   int bytes_written = 1;  // Set to a non-zero value.
347   attr.offs = 3;
348   EXPECT_EQ(0, node->Write(attr, "struct", 6, &bytes_written));
349   EXPECT_EQ(6, bytes_written);
350
351   attr.offs = 0;
352   EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
353   EXPECT_EQ(9, bytes_read);
354   buffer[bytes_read] = 0;
355   EXPECT_STREQ("construct", buffer);
356
357   // Writing to a directory should fail.
358   EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDWR, &node));
359   EXPECT_EQ(EISDIR, node->Write(attr, &buffer[0], sizeof(buffer), &bytes_read));
360 }
361
362 TEST_F(Html5FsTest, GetStat) {
363   const int creation_time = 1000;
364   const int access_time = 2000;
365   const int modified_time = 3000;
366   const char contents[] = "contents";
367
368   // Create fake file.
369   FakeHtml5FsNode* fake_node;
370   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddFile(
371       "/file", contents, &fake_node));
372   fake_node->set_creation_time(creation_time);
373   fake_node->set_last_access_time(access_time);
374   fake_node->set_last_modified_time(modified_time);
375
376   // Create fake directory.
377   EXPECT_TRUE(
378       ppapi_html5_.filesystem_template()->AddDirectory("/dir", &fake_node));
379   fake_node->set_creation_time(creation_time);
380   fake_node->set_last_access_time(access_time);
381   fake_node->set_last_modified_time(modified_time);
382
383   StringMap_t map;
384   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
385
386   ScopedNode node;
387   ASSERT_EQ(0, fs->Open(Path("/file"), O_RDONLY, &node));
388
389   struct stat statbuf;
390   EXPECT_EQ(0, node->GetStat(&statbuf));
391   EXPECT_EQ(S_IFREG, statbuf.st_mode & S_IFMT);
392   EXPECT_EQ(S_IRUSR | S_IRGRP | S_IROTH |
393             S_IWUSR | S_IWGRP | S_IWOTH, statbuf.st_mode & ~S_IFMT);
394   EXPECT_EQ(strlen(contents), statbuf.st_size);
395   EXPECT_EQ(access_time, statbuf.st_atime);
396   EXPECT_EQ(creation_time, statbuf.st_ctime);
397   EXPECT_EQ(modified_time, statbuf.st_mtime);
398
399   // Test Get* and Isa* methods.
400   off_t size;
401   EXPECT_EQ(0, node->GetSize(&size));
402   EXPECT_EQ(strlen(contents), size);
403   EXPECT_FALSE(node->IsaDir());
404   EXPECT_TRUE(node->IsaFile());
405   EXPECT_EQ(ENOTTY, node->Isatty());
406
407   // GetStat on a directory...
408   EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
409   EXPECT_EQ(0, node->GetStat(&statbuf));
410   EXPECT_EQ(S_IFDIR, statbuf.st_mode & S_IFMT);
411   EXPECT_EQ(S_IRUSR | S_IRGRP | S_IROTH |
412             S_IWUSR | S_IWGRP | S_IWOTH, statbuf.st_mode & ~S_IFMT);
413   EXPECT_EQ(0, statbuf.st_size);
414   EXPECT_EQ(access_time, statbuf.st_atime);
415   EXPECT_EQ(creation_time, statbuf.st_ctime);
416   EXPECT_EQ(modified_time, statbuf.st_mtime);
417
418   // Test Get* and Isa* methods.
419   EXPECT_EQ(0, node->GetSize(&size));
420   EXPECT_EQ(0, size);
421   EXPECT_TRUE(node->IsaDir());
422   EXPECT_FALSE(node->IsaFile());
423   EXPECT_EQ(ENOTTY, node->Isatty());
424 }
425
426 TEST_F(Html5FsTest, FTruncate) {
427   const char contents[] = "contents";
428   EXPECT_TRUE(
429       ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
430   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
431
432   StringMap_t map;
433   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
434
435   ScopedNode node;
436   ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
437
438   HandleAttr attr;
439   char buffer[10] = {0};
440   int bytes_read = 0;
441
442   // First make the file shorter...
443   EXPECT_EQ(0, node->FTruncate(4));
444   EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
445   EXPECT_EQ(4, bytes_read);
446   buffer[bytes_read] = 0;
447   EXPECT_STREQ("cont", buffer);
448
449   // Now make the file longer...
450   EXPECT_EQ(0, node->FTruncate(8));
451   EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
452   EXPECT_EQ(8, bytes_read);
453   buffer[bytes_read] = 0;
454   EXPECT_STREQ("cont\0\0\0\0", buffer);
455
456   // Ftruncate should fail for a directory.
457   EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
458   EXPECT_EQ(EISDIR, node->FTruncate(4));
459 }
460
461 TEST_F(Html5FsTest, GetDents) {
462   const char contents[] = "contents";
463   EXPECT_TRUE(
464       ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
465
466   StringMap_t map;
467   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
468
469   ScopedNode root;
470   ASSERT_EQ(0, fs->Open(Path("/"), O_RDONLY, &root));
471
472   ScopedNode node;
473   ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
474
475   // Should fail for regular files.
476   const size_t kMaxDirents = 5;
477   dirent dirents[kMaxDirents];
478   int bytes_read = 1;  // Set to a non-zero value.
479
480   memset(&dirents[0], 0, sizeof(dirents));
481   EXPECT_EQ(ENOTDIR,
482             node->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read));
483   EXPECT_EQ(0, bytes_read);
484
485   // Should work with root directory.
486   // +2 to test a size that is not a multiple of sizeof(dirent).
487   // Expect it to round down.
488   memset(&dirents[0], 0, sizeof(dirents));
489   EXPECT_EQ(
490       0, root->GetDents(0, &dirents[0], sizeof(dirent) * 3 + 2, &bytes_read));
491
492   {
493     size_t num_dirents = bytes_read / sizeof(dirent);
494     EXPECT_EQ(3, num_dirents);
495     EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read);
496
497     std::multiset<std::string> dirnames;
498     for (size_t i = 0; i < num_dirents; ++i) {
499       EXPECT_EQ(sizeof(dirent), dirents[i].d_off);
500       EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen);
501       dirnames.insert(dirents[i].d_name);
502     }
503
504     EXPECT_EQ(1, dirnames.count("file"));
505     EXPECT_EQ(1, dirnames.count("."));
506     EXPECT_EQ(1, dirnames.count(".."));
507   }
508
509   // Add another file...
510   ASSERT_EQ(0, fs->Open(Path("/file2"), O_CREAT, &node));
511
512   // Read the root directory again.
513   memset(&dirents[0], 0, sizeof(dirents));
514   EXPECT_EQ(0, root->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read));
515
516   {
517     size_t num_dirents = bytes_read / sizeof(dirent);
518     EXPECT_EQ(4, num_dirents);
519     EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read);
520
521     std::multiset<std::string> dirnames;
522     for (size_t i = 0; i < num_dirents; ++i) {
523       EXPECT_EQ(sizeof(dirent), dirents[i].d_off);
524       EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen);
525       dirnames.insert(dirents[i].d_name);
526     }
527
528     EXPECT_EQ(1, dirnames.count("file"));
529     EXPECT_EQ(1, dirnames.count("file2"));
530     EXPECT_EQ(1, dirnames.count("."));
531     EXPECT_EQ(1, dirnames.count(".."));
532   }
533 }