OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 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 "sql/mojo/enable_mojo_fs.h" | |
6 | |
7 #include "base/logging.h" | |
8 #include "components/filesystem/public/interfaces/file.mojom.h" | |
9 #include "components/filesystem/public/interfaces/file_system.mojom.h" | |
10 #include "components/filesystem/public/interfaces/types.mojom.h" | |
11 #include "mojo/public/cpp/bindings/lib/template_util.h" | |
12 #include "mojo/util/capture_util.h" | |
13 #include "third_party/sqlite/sqlite3.h" | |
14 | |
15 using mojo::Capture; | |
16 | |
17 namespace sql { | |
18 namespace { | |
19 | |
20 // Implementation of the sqlite3 Mojo proxying vfs. | |
sky
2015/06/10 21:26:27
I'm assuming shess is reviewing all this.
| |
21 // | |
22 // This is a bunch of C callback objects which transparently proxy sqlite3's | |
23 // filesystem reads/writes over the mojo:filesystem service. The main | |
24 // entrypoint is sqlite3_mojovfs(), which proxies all the file open/delete/etc | |
25 // operations. mojo:filesystem has support for passing a raw file descriptor | |
26 // over the IPC barrier, and most of the implementation of sqlite3_io_methods | |
27 // is derived from the default sqlite3 unix VFS and operates on the raw file | |
28 // descriptors. | |
29 | |
30 #if defined(OS_WIN) | |
31 const char kParentVFS[] = "win32"; | |
32 #elif defined(OS_POSIX) | |
33 const char kParentVFS[] = "unix"; | |
34 #endif | |
35 | |
36 const int kMaxPathName = 512; | |
37 | |
38 // Additional data that we store in the sqlite3_vfs's |pAppData| member. | |
39 struct MojoVFSData { | |
40 // The default vfs at the time MojoVFS was installed. We use the to pass | |
41 // through things like randomness requests and per-platform sleep calls. | |
42 sqlite3_vfs* parent; | |
43 | |
44 // When we initialize the subsystem, we are given a filesystem::Directory | |
45 // object, which is the root directory of a mojo:filesystem. All access to | |
46 // various files are specified from this root directory. | |
47 filesystem::DirectoryPtr root_directory; | |
Scott Hess - ex-Googler
2015/06/10 22:35:46
To make sure I'm clear on this - all databases for
Elliot Glaysher
2015/06/12 22:36:43
...within a service/origin combination.
The curre
Scott Hess - ex-Googler
2015/06/17 19:32:58
So all connections used by all clients in a single
Elliot Glaysher
2015/06/17 21:59:34
All sql::Connections in a process will use the sam
Scott Hess - ex-Googler
2015/06/19 20:48:01
Acknowledged.
| |
48 }; | |
49 | |
50 sqlite3_vfs* GetParentVFS(sqlite3_vfs* mojo_vfs) { | |
51 return reinterpret_cast<MojoVFSData*>(mojo_vfs->pAppData)->parent; | |
52 } | |
53 | |
54 filesystem::DirectoryPtr& GetRootDirectory(sqlite3_vfs* mojo_vfs) { | |
55 return reinterpret_cast<MojoVFSData*>(mojo_vfs->pAppData)->root_directory; | |
56 } | |
57 | |
58 // A struct which extends the base sqlite3_file to also hold on to a file | |
59 // pipe. We reinterpret_cast our sqlite3_file structs to this struct | |
60 // instead. This is "safe" because this struct is really just a slab of | |
61 // malloced memory, of which we tell sqlite how large we want with szOsFile. | |
62 struct MojoVFSFile { | |
63 // The "vtable" of our sqlite3_file "subclass". | |
64 sqlite3_file base; | |
65 | |
66 // We keep an open pipe to the File object to keep it from cleaning itself | |
67 // up. | |
68 filesystem::FilePtr file_ptr; | |
69 }; | |
70 | |
71 filesystem::FilePtr& GetFSFile(sqlite3_file* vfs_file) { | |
72 return reinterpret_cast<MojoVFSFile*>(vfs_file)->file_ptr; | |
73 } | |
74 | |
75 int MojoVFSClose(sqlite3_file* file) { | |
76 DVLOG(1) << "MojoVFSClose(*)"; | |
77 using mojo::InterfacePtr; | |
78 GetFSFile(file).~InterfacePtr<filesystem::File>(); | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
Symmetry-wise, why not:
delete (&GetFSFile(file
Elliot Glaysher
2015/06/12 22:36:44
Because there's no such thing as placement delete.
Scott Hess - ex-Googler
2015/06/17 19:32:58
But you need to call the destructor, and as a casu
Elliot Glaysher
2015/06/17 21:59:34
Ah. I see what you're saying. Calling ~FilePtr().
Scott Hess - ex-Googler
2015/06/19 20:48:01
Ewwww.
| |
79 return SQLITE_OK; | |
80 } | |
81 | |
82 int MojoVFSRead(sqlite3_file* sql_file, | |
83 void* buffer, | |
84 int size, | |
85 sqlite3_int64 offset) { | |
86 DVLOG(1) << "MojoVFSRead (" << size << " @ " << offset << ")"; | |
87 filesystem::FileError error = filesystem::FILE_ERROR_FAILED; | |
88 mojo::Array<uint8_t> mojo_data; | |
89 GetFSFile(sql_file)->Read(size, offset, filesystem::WHENCE_FROM_BEGIN, | |
90 Capture(&error, &mojo_data)); | |
91 GetFSFile(sql_file).WaitForIncomingResponse(); | |
92 | |
93 if (error != filesystem::FILE_ERROR_OK) { | |
94 // TODO(erg): Better implementation here. | |
95 NOTIMPLEMENTED(); | |
96 return SQLITE_IOERR_READ; | |
97 } | |
98 | |
99 if (mojo_data.size() == static_cast<size_t>(size)) { | |
100 memcpy(buffer, &mojo_data.front(), mojo_data.size()); | |
101 return SQLITE_OK; | |
102 } | |
103 | |
104 // We didn't read the entire buffer. We need to copy the data AND zero fill | |
105 // the rest of the buffer. | |
106 if (mojo_data.size()) | |
107 memcpy(buffer, &mojo_data.front(), mojo_data.size()); | |
108 memset(reinterpret_cast<char*>(buffer) + mojo_data.size(), 0, | |
109 size - mojo_data.size()); | |
110 | |
111 return SQLITE_IOERR_SHORT_READ; | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
Good to know about!
| |
112 } | |
113 | |
114 int MojoVFSWrite(sqlite3_file* sql_file, | |
115 const void* buffer, | |
116 int size, | |
117 sqlite_int64 offset) { | |
118 DVLOG(1) << "MojoVFSWrite(*, " << size << ", " << offset << ")"; | |
119 mojo::Array<uint8_t> mojo_data(size); | |
120 memcpy(&mojo_data.front(), buffer, size); | |
121 | |
122 filesystem::FileError error = filesystem::FILE_ERROR_FAILED; | |
123 uint32_t num_bytes_written = 0; | |
124 GetFSFile(sql_file)->Write(mojo_data.Pass(), offset, | |
125 filesystem::WHENCE_FROM_BEGIN, | |
126 Capture(&error, &num_bytes_written)); | |
127 GetFSFile(sql_file).WaitForIncomingResponse(); | |
128 if (error != filesystem::FILE_ERROR_OK) { | |
129 // TODO(erg): Better implementation here. | |
130 NOTIMPLEMENTED(); | |
131 return SQLITE_IOERR_WRITE; | |
132 } | |
133 if (num_bytes_written != static_cast<uint32_t>(size)) { | |
134 NOTIMPLEMENTED(); | |
135 return SQLITE_IOERR_WRITE; | |
136 } | |
137 | |
138 return SQLITE_OK; | |
139 } | |
140 | |
141 int MojoVFSTruncate(sqlite3_file* sql_file, sqlite_int64 size) { | |
142 DVLOG(1) << "MojoVFSTruncate(*, " << size << ")"; | |
143 filesystem::FileError error = filesystem::FILE_ERROR_FAILED; | |
144 GetFSFile(sql_file)->Truncate(size, Capture(&error)); | |
145 GetFSFile(sql_file).WaitForIncomingResponse(); | |
146 if (error != filesystem::FILE_ERROR_OK) { | |
147 // TODO(erg): Better implementation here. | |
148 NOTIMPLEMENTED(); | |
149 return SQLITE_IOERR_TRUNCATE; | |
150 } | |
151 | |
152 return SQLITE_OK; | |
153 } | |
154 | |
155 int MojoVFSSync(sqlite3_file* mojo_vfs, int flags) { | |
156 // TODO(erg): We need to flush data. This requires cooperation in | |
157 // mojo:filesystem. | |
158 DVLOG(1) << "MojoVFSSync(*, " << flags << ")"; | |
159 NOTIMPLEMENTED(); | |
160 | |
161 return SQLITE_OK; | |
162 } | |
163 | |
164 int MojoVFSFileSize(sqlite3_file* sql_file, sqlite_int64* size) { | |
165 DVLOG(1) << "MojoVFSFileSize(*)"; | |
166 | |
167 filesystem::FileError err = filesystem::FILE_ERROR_FAILED; | |
168 filesystem::FileInformationPtr file_info; | |
169 GetFSFile(sql_file)->Stat(Capture(&err, &file_info)); | |
170 GetFSFile(sql_file).WaitForIncomingResponse(); | |
171 | |
172 if (err != filesystem::FILE_ERROR_OK) { | |
173 // TODO(erg): Better implementation here. | |
174 NOTIMPLEMENTED(); | |
175 return SQLITE_IOERR_FSTAT; | |
176 } | |
177 | |
178 *size = file_info->size; | |
179 return SQLITE_OK; | |
180 } | |
181 | |
182 // TODO(erg): The current base::File interface isn't sufficient to handle | |
183 // sqlite's locking primitives, which are done on byte ranges in the file. (See | |
184 // "File Locking Notes" in sqlite3.c.) | |
185 int MojoVFSLock(sqlite3_file* pFile, int eLock) { | |
186 DVLOG(1) << "MojoVFSLock(*, " << eLock << ")"; | |
187 return SQLITE_OK; | |
188 } | |
189 int MojoVFSUnlock(sqlite3_file* pFile, int eLock) { | |
190 DVLOG(1) << "MojoVFSUnlock(*, " << eLock << ")"; | |
191 return SQLITE_OK; | |
192 } | |
193 int MojoVFSCheckReservedLock(sqlite3_file* pFile, int* pResOut) { | |
194 DVLOG(1) << "MojoVFSCheckReservedLock(*)"; | |
195 *pResOut = 0; | |
196 return SQLITE_OK; | |
197 } | |
198 | |
199 // TODO(erg): This is the minimal implementation to get a few tests passing; | |
200 // lots more needs to be done here. | |
201 int MojoVFSFileControl(sqlite3_file* pFile, int op, void* pArg) { | |
202 DVLOG(1) << "MojoVFSFileControl(*, " << op << ", *)"; | |
203 if (op == SQLITE_FCNTL_PRAGMA) { | |
204 // Returning NOTFOUND tells sqlite that we aren't doing any processing. | |
205 return SQLITE_NOTFOUND; | |
206 } | |
207 // TODO(erg): Unsure if we need to manually handle SQLITE_FCNTL_FILE_POINTER. | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
SQLITE_FCNTL_FILE_POINTER is magic, and is handled
Elliot Glaysher
2015/06/12 22:36:44
Done.
| |
208 | |
209 return SQLITE_OK; | |
210 } | |
211 | |
212 int MojoVFSSectorSize(sqlite3_file* pFile) { | |
213 DVLOG(1) << "MojoVFSSectorSize(*)"; | |
214 NOTIMPLEMENTED(); | |
215 return SQLITE_OK; | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
Returns a value, SQLITE_OK being 0 will just happe
Elliot Glaysher
2015/06/12 22:36:43
Done.
| |
216 } | |
217 | |
218 int MojoVFSDeviceCharacteristics(sqlite3_file* pFile) { | |
219 DVLOG(1) << "MojoVFSDeviceCharacteristics(*)"; | |
220 NOTIMPLEMENTED(); | |
221 return SQLITE_OK; | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
This returns a bitmask. Again, SQLITE_OK happens
Elliot Glaysher
2015/06/12 22:36:44
Done.
| |
222 } | |
223 | |
224 static sqlite3_io_methods mojo_vfs_io_methods = { | |
225 1, /* iVersion */ | |
226 MojoVFSClose, /* xClose */ | |
227 MojoVFSRead, /* xRead */ | |
228 MojoVFSWrite, /* xWrite */ | |
229 MojoVFSTruncate, /* xTruncate */ | |
230 MojoVFSSync, /* xSync */ | |
231 MojoVFSFileSize, /* xFileSize */ | |
232 MojoVFSLock, /* xLock */ | |
233 MojoVFSUnlock, /* xUnlock */ | |
234 MojoVFSCheckReservedLock, /* xCheckReservedLock */ | |
235 MojoVFSFileControl, /* xFileControl */ | |
236 MojoVFSSectorSize, /* xSectorSize */ | |
237 MojoVFSDeviceCharacteristics, /* xDeviceCharacteristics */ | |
238 0, /* xShmMap */ | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
You don't need to provide these for version 1. If
Elliot Glaysher
2015/06/12 22:36:44
Deleted. (I had put it for future reference, but w
| |
239 0, /* xShmLock */ | |
240 0, /* xShmBarrier */ | |
241 0 /* xShmUnmap */ | |
242 }; | |
243 | |
244 // who allocates sqlite3_file? | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
The caller provides szOsFile bytes as part of a gr
Elliot Glaysher
2015/06/12 22:36:43
(Comment was left over from a previous time when I
| |
245 int MojoVFSOpen(sqlite3_vfs* mojo_vfs, | |
246 const char* name, | |
247 sqlite3_file* file, | |
248 int flags, | |
249 int* pOutFlags) { | |
250 DVLOG(1) << "MojoVFSOpen(*, " << name << ", *, " << flags << ")"; | |
251 int open_flags = 0; | |
252 if (flags & SQLITE_OPEN_EXCLUSIVE) { | |
253 DCHECK(flags & SQLITE_OPEN_CREATE); | |
254 open_flags = filesystem::kFlagCreate; | |
255 } else if (flags & SQLITE_OPEN_CREATE) { | |
256 DCHECK(flags & SQLITE_OPEN_READWRITE); | |
257 open_flags = filesystem::kFlagOpenAlways; | |
258 } else { | |
259 open_flags = filesystem::kFlagOpen; | |
260 } | |
261 open_flags |= filesystem::kFlagRead; | |
262 if (flags & SQLITE_OPEN_READWRITE) | |
263 open_flags |= filesystem::kFlagWrite; | |
264 | |
265 // Grab the incoming file | |
266 filesystem::FilePtr file_ptr; | |
267 filesystem::FileError error = filesystem::FILE_ERROR_FAILED; | |
268 GetRootDirectory(mojo_vfs)->OpenFile(mojo::String(name), GetProxy(&file_ptr), | |
269 open_flags, Capture(&error)); | |
270 GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); | |
271 if (error != filesystem::FILE_ERROR_OK) { | |
272 // TODO(erg): Translate more of the mojo error codes into sqlite error | |
273 // codes. | |
274 return SQLITE_CANTOPEN; | |
275 } | |
276 | |
277 // Set the method table so we can be closed (and run the manual dtor call to | |
278 // match the following placement news). | |
279 file->pMethods = &mojo_vfs_io_methods; | |
280 | |
281 // |file| is actually a malloced buffer of size szOsFile. This means that we | |
282 // need to manually use placement new to construct the C++ object which owns | |
283 // the pipe to our file. | |
284 new (&GetFSFile(file)) filesystem::FilePtr(file_ptr.Pass()); | |
285 | |
286 return SQLITE_OK; | |
287 } | |
288 | |
289 int MojoVFSDelete(sqlite3_vfs* mojo_vfs, const char* filename, int sync_dir) { | |
290 DVLOG(1) << "MojoVFSDelete(*, " << filename << ", " << sync_dir << ")"; | |
291 filesystem::FileError error = filesystem::FILE_ERROR_FAILED; | |
292 GetRootDirectory(mojo_vfs)->Delete(filename, 0, Capture(&error)); | |
293 GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); | |
294 | |
295 // TODO(erg): sync_dir? | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
This may have per-platform warts, unfortunately.
Elliot Glaysher
2015/06/12 22:36:43
I have added a Flush() on sync_dir, which is what
| |
296 | |
297 return error == filesystem::FILE_ERROR_OK ? SQLITE_OK : SQLITE_IOERR_DELETE; | |
298 } | |
299 | |
300 int MojoVFSAccess(sqlite3_vfs* mojo_vfs, | |
301 const char* filename, | |
302 int flags, | |
303 int* result) { | |
304 DVLOG(1) << "MojoVFSAccess(*, " << filename << ", " << flags << ", *)"; | |
305 filesystem::FileError error = filesystem::FILE_ERROR_FAILED; | |
306 | |
307 if (flags == SQLITE_ACCESS_READWRITE || flags == SQLITE_ACCESS_READ) { | |
308 bool is_wriable = false; | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
is_writable.
Elliot Glaysher
2015/06/12 22:36:44
Done.
| |
309 GetRootDirectory(mojo_vfs) | |
310 ->IsWritable(filename, Capture(&error, &is_wriable)); | |
311 GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); | |
312 *result = is_wriable; | |
313 return SQLITE_OK; | |
314 } | |
315 | |
316 if (flags == SQLITE_ACCESS_EXISTS) { | |
317 bool exists = false; | |
318 GetRootDirectory(mojo_vfs)->Exists(filename, Capture(&error, &exists)); | |
319 GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); | |
320 *result = exists; | |
321 return SQLITE_OK; | |
322 } | |
323 | |
324 return SQLITE_IOERR; | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
So ... does someone guarantee that any file which
| |
325 } | |
326 | |
327 int MojoVFSFullPathname(sqlite3_vfs* mojo_vfs, | |
328 const char* relative_path, | |
329 int absolute_path_size, | |
330 char* absolute_path) { | |
331 // The sandboxed process doesn't need to know the absolute path of the file. | |
332 sqlite3_snprintf(absolute_path_size, absolute_path, "%s", relative_path); | |
333 return SQLITE_OK; | |
334 } | |
335 | |
336 // If we are using the mojo:filesystem proxying VFS, then it's highly likely | |
337 // that we are sandboxed and that any attempt to dlopen() a shared object is | |
338 // folly. | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
IMHO you should upgrade this to "Don't let SQLite
Elliot Glaysher
2015/06/12 22:36:44
Done.
| |
339 void* MojoVFSDlOpen(sqlite3_vfs*, const char*) { | |
340 return 0; | |
341 } | |
342 | |
343 void MojoVFSDlError(sqlite3_vfs*, int buf_size, char* error_msg) { | |
344 sqlite3_snprintf(buf_size, error_msg, "Dynamic loading not supported"); | |
345 } | |
346 | |
347 void (*MojoVFSDlSym(sqlite3_vfs*, void*, const char*))(void) { | |
348 return 0; | |
349 } | |
350 | |
351 void MojoVFSDlClose(sqlite3_vfs*, void*) { | |
352 return; | |
353 } | |
354 | |
355 // Proxy the rest of the calls down to the OS specific handler. | |
356 int MojoVFSRandomness(sqlite3_vfs* mojo_vfs, int byte, char* out) { | |
357 return GetParentVFS(mojo_vfs)->xRandomness(GetParentVFS(mojo_vfs), byte, out); | |
358 } | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
This one in particular might make more sense to im
Elliot Glaysher
2015/06/12 22:36:44
The timing stuff looks complicated enough that I'm
| |
359 | |
360 int MojoVFSSleep(sqlite3_vfs* mojo_vfs, int micro) { | |
361 return GetParentVFS(mojo_vfs)->xSleep(GetParentVFS(mojo_vfs), micro); | |
362 } | |
363 | |
364 int MojoVFSCurrentTime(sqlite3_vfs* mojo_vfs, double* time) { | |
365 return GetParentVFS(mojo_vfs)->xCurrentTime(GetParentVFS(mojo_vfs), time); | |
366 } | |
367 | |
368 int MojoVFSGetLastError(sqlite3_vfs* mojo_vfs, int a, char* b) { | |
369 return GetParentVFS(mojo_vfs)->xGetLastError(GetParentVFS(mojo_vfs), a, b); | |
370 } | |
371 | |
372 int MojoVFSCurrentTimeInt64(sqlite3_vfs* mojo_vfs, sqlite3_int64* out) { | |
373 return GetParentVFS(mojo_vfs)->xCurrentTimeInt64(GetParentVFS(mojo_vfs), out); | |
374 } | |
375 | |
376 static sqlite3_vfs mojo_vfs = { | |
377 1, /* iVersion */ | |
378 sizeof(MojoVFSFile), /* szOsFile */ | |
379 kMaxPathName, /* mxPathname */ | |
380 0, /* pNext */ | |
381 "mojo", /* zName */ | |
382 0, /* pAppData */ | |
383 MojoVFSOpen, /* xOpen */ | |
384 MojoVFSDelete, /* xDelete */ | |
385 MojoVFSAccess, /* xAccess */ | |
386 MojoVFSFullPathname, /* xFullPathname */ | |
387 MojoVFSDlOpen, /* xDlOpen */ | |
388 MojoVFSDlError, /* xDlError */ | |
389 MojoVFSDlSym, /* xDlSym */ | |
390 MojoVFSDlClose, /* xDlClose */ | |
391 MojoVFSRandomness, /* xRandomness */ | |
392 MojoVFSSleep, /* xSleep */ | |
393 MojoVFSCurrentTime, /* xCurrentTime */ | |
394 MojoVFSGetLastError, /* xGetLastError */ | |
395 MojoVFSCurrentTimeInt64 /* xCurrentTimeInt64 */ | |
396 }; | |
397 | |
398 } // namespace | |
399 | |
400 bool ScopedMojoFilesystemVFS::vfs_set_ = false; | |
401 | |
402 ScopedMojoFilesystemVFS::ScopedMojoFilesystemVFS( | |
403 filesystem::DirectoryPtr root_directory) { | |
404 CHECK(!vfs_set_); | |
405 vfs_set_ = true; | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
This could be implemented in terms of a check on t
| |
406 | |
407 MojoVFSData* data = new MojoVFSData; | |
408 data->parent = sqlite3_vfs_find(kParentVFS); | |
Scott Hess - ex-Googler
2015/06/10 22:35:46
Use NULL to say "The default VFS for this system",
| |
409 data->root_directory = root_directory.Pass(); | |
410 mojo_vfs.pAppData = data; | |
411 | |
412 mojo_vfs.mxPathname = data->parent->mxPathname; | |
413 | |
414 CHECK(sqlite3_vfs_register(&mojo_vfs, 1) == SQLITE_OK); | |
415 } | |
416 | |
417 ScopedMojoFilesystemVFS::~ScopedMojoFilesystemVFS() { | |
Scott Hess - ex-Googler
2015/06/10 22:35:45
Before clearing the pAppData, this needs to reset
Elliot Glaysher
2015/06/12 22:36:43
These three comments about how we deal with the de
| |
418 delete static_cast<MojoVFSData*>(mojo_vfs.pAppData); | |
419 | |
420 CHECK(vfs_set_); | |
421 vfs_set_ = false; | |
422 } | |
423 | |
424 filesystem::DirectoryPtr& ScopedMojoFilesystemVFS::GetDirectory() { | |
425 return static_cast<MojoVFSData*>(mojo_vfs.pAppData)->root_directory; | |
426 } | |
427 | |
428 } // namespace sql | |
OLD | NEW |