1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_IOUtils__
#define mozilla_dom_IOUtils__
#include "js/Utility.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Attributes.h"
#include "mozilla/Buffer.h"
#include "mozilla/DataMutex.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Result.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/IOUtilsBinding.h"
#include "mozilla/dom/TypedArray.h"
#include "nsIAsyncShutdown.h"
#include "nsISerialEventTarget.h"
#include "nsPrintfCString.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "prio.h"
namespace mozilla {
/**
* Utility class to be used with |UniquePtr| to automatically close NSPR file
* descriptors when they go out of scope.
*
* Example:
*
* UniquePtr<PRFileDesc, PR_CloseDelete> fd = PR_Open(path, flags, mode);
*/
class PR_CloseDelete {
public:
constexpr PR_CloseDelete() = default;
PR_CloseDelete(const PR_CloseDelete& aOther) = default;
PR_CloseDelete(PR_CloseDelete&& aOther) = default;
PR_CloseDelete& operator=(const PR_CloseDelete& aOther) = default;
PR_CloseDelete& operator=(PR_CloseDelete&& aOther) = default;
void operator()(PRFileDesc* aPtr) const { PR_Close(aPtr); }
};
namespace dom {
/**
* Implementation for the Web IDL interface at dom/chrome-webidl/IOUtils.webidl.
* Methods of this class must only be called from the parent process.
*/
class IOUtils final {
public:
class IOError;
static already_AddRefed<Promise> Read(GlobalObject& aGlobal,
const nsAString& aPath,
const ReadOptions& aOptions);
static already_AddRefed<Promise> ReadUTF8(GlobalObject& aGlobal,
const nsAString& aPath,
const ReadUTF8Options& aOptions);
static already_AddRefed<Promise> ReadJSON(GlobalObject& aGlobal,
const nsAString& aPath,
const ReadUTF8Options& aOptions);
static already_AddRefed<Promise> Write(GlobalObject& aGlobal,
const nsAString& aPath,
const Uint8Array& aData,
const WriteOptions& aOptions);
static already_AddRefed<Promise> WriteUTF8(GlobalObject& aGlobal,
const nsAString& aPath,
const nsACString& aString,
const WriteOptions& aOptions);
static already_AddRefed<Promise> WriteJSON(GlobalObject& aGlobal,
const nsAString& aPath,
JS::Handle<JS::Value> aValue,
const WriteOptions& aOptions);
static already_AddRefed<Promise> Move(GlobalObject& aGlobal,
const nsAString& aSourcePath,
const nsAString& aDestPath,
const MoveOptions& aOptions);
static already_AddRefed<Promise> Remove(GlobalObject& aGlobal,
const nsAString& aPath,
const RemoveOptions& aOptions);
static already_AddRefed<Promise> MakeDirectory(
GlobalObject& aGlobal, const nsAString& aPath,
const MakeDirectoryOptions& aOptions);
static already_AddRefed<Promise> Stat(GlobalObject& aGlobal,
const nsAString& aPath);
static already_AddRefed<Promise> Copy(GlobalObject& aGlobal,
const nsAString& aSourcePath,
const nsAString& aDestPath,
const CopyOptions& aOptions);
static already_AddRefed<Promise> Touch(
GlobalObject& aGlobal, const nsAString& aPath,
const Optional<int64_t>& aModification);
static already_AddRefed<Promise> GetChildren(GlobalObject& aGlobal,
const nsAString& aPath);
static already_AddRefed<Promise> SetPermissions(GlobalObject& aGlobal,
const nsAString& aPath,
const uint32_t aPermissions);
static already_AddRefed<Promise> Exists(GlobalObject& aGlobal,
const nsAString& aPath);
class JsBuffer;
/**
* The kind of buffer to allocate.
*
* This controls what kind of JS object (a JSString or a Uint8Array) is
* returned by |ToJSValue()|.
*/
enum class BufferKind {
String,
Uint8Array,
};
private:
~IOUtils() = default;
template <typename T>
using IOPromise = MozPromise<T, IOError, true>;
friend class IOUtilsShutdownBlocker;
struct InternalFileInfo;
struct InternalWriteOpts;
class MozLZ4;
static StaticDataMutex<StaticRefPtr<nsISerialEventTarget>>
sBackgroundEventTarget;
static StaticRefPtr<nsIAsyncShutdownClient> sBarrier;
static Atomic<bool> sShutdownStarted;
template <typename OkT, typename Fn, typename... Args>
static RefPtr<IOUtils::IOPromise<OkT>> InvokeToIOPromise(Fn aFunc,
Args... aArgs);
static already_AddRefed<nsIAsyncShutdownClient> GetShutdownBarrier();
static already_AddRefed<nsISerialEventTarget> GetBackgroundEventTarget();
static void SetShutdownHooks();
template <typename OkT, typename Fn>
static RefPtr<IOPromise<OkT>> RunOnBackgroundThread(Fn aFunc);
template <typename OkT, typename Fn>
static void RunOnBackgroundThreadAndResolve(Promise* aPromise, Fn aFunc);
/**
* Creates a new JS Promise.
*
* @return The new promise, or |nullptr| on failure.
*/
static already_AddRefed<Promise> CreateJSPromise(GlobalObject& aGlobal);
// Allow conversion of |InternalFileInfo| with |ToJSValue|.
friend MOZ_MUST_USE bool ToJSValue(JSContext* aCx,
const InternalFileInfo& aInternalFileInfo,
JS::MutableHandle<JS::Value> aValue);
/**
* Resolves |aPromise| with an appropriate JS value for |aValue|.
*/
template <typename T>
static void ResolveJSPromise(Promise* aPromise, T&& aValue);
/**
* Rejects |aPromise| with an appropriate |DOMException| describing |aError|.
*/
static void RejectJSPromise(Promise* aPromise, const IOError& aError);
/**
* Attempts to read the entire file at |aPath| into a buffer.
*
* @param aFile The location of the file.
* @param aMaxBytes If |Some|, then only read up this this number of bytes,
* otherwise attempt to read the whole file.
* @param aDecompress If true, decompress the bytes read from disk before
* returning the result to the caller.
* @param aBufferKind The kind of buffer to allocate.
*
* @return A buffer containing the entire (decompressed) file contents, or an
* error.
*/
static Result<JsBuffer, IOError> ReadSync(nsIFile* aFile,
const Maybe<uint32_t>& aMaxBytes,
const bool aDecompress,
BufferKind aBufferKind);
/*
* Attempts to read the entire file at |aPath| as a UTF-8 string.
*
* @param aFile The location of the file.
* @param aDecompress If true, decompress the bytes read from disk before
* returning the result to the caller.
*
* @return The (decompressed) contents of the file re-encoded as a UTF-16
* string.
*/
static Result<JsBuffer, IOError> ReadUTF8Sync(nsIFile* aFile,
const bool aDecompress);
/**
* Attempt to write the entirety of |aByteArray| to the file at |aPath|.
* This may occur by writing to an intermediate destination and performing a
* move, depending on |aOptions|.
*
* @param aFile The location of the file.
* @param aByteArray The data to write to the file.
* @param aOptions Options to modify the way the write is completed.
*
* @return The number of bytes written to the file, or an error if the write
* failed or was incomplete.
*/
static Result<uint32_t, IOError> WriteSync(
nsIFile* aFile, const Span<const uint8_t>& aByteArray,
const InternalWriteOpts& aOptions);
/**
* Attempts to move the file located at |aSourceFile| to |aDestFile|.
*
* @param aSourceFile The location of the file to move.
* @param aDestFile The destination for the file.
* @param noOverWrite If true, abort with an error if a file already exists at
* |aDestFile|. Otherwise, the file will be overwritten by
* the move.
*
* @return Ok if the file was moved successfully, or an error.
*/
static Result<Ok, IOError> MoveSync(nsIFile* aSourceFile, nsIFile* aDestFile,
bool aNoOverwrite);
/**
* Attempts to copy the file at |aSourceFile| to |aDestFile|.
*
* @param aSourceFile The location of the file to copy.
* @param aDestFile The destination that the file will be copied to.
*
* @return Ok if the operation was successful, or an error.
*/
static Result<Ok, IOError> CopySync(nsIFile* aSourceFile, nsIFile* aDestFile,
bool aNoOverWrite, bool aRecursive);
/**
* Provides the implementation for |CopySync| and |MoveSync|.
*
* @param aMethod A pointer to one of |nsIFile::MoveTo| or |CopyTo|
* instance methods.
* @param aMethodName The name of the method to the performed. Either "move"
* or "copy".
* @param aSource The source file to be copied or moved.
* @param aDest The destination file.
* @param aNoOverwrite If true, allow overwriting |aDest| during the copy or
* move. Otherwise, abort with an error if the file would
* be overwritten.
*
* @return Ok if the operation was successful, or an error.
*/
template <typename CopyOrMoveFn>
static Result<Ok, IOError> CopyOrMoveSync(CopyOrMoveFn aMethod,
const char* aMethodName,
nsIFile* aSource, nsIFile* aDest,
bool aNoOverwrite);
/**
* Attempts to remove the file located at |aFile|.
*
* @param aFile The location of the file.
* @param aIgnoreAbsent If true, suppress errors due to an absent target file.
* @param aRecursive If true, attempt to recursively remove descendant
* files. This option is safe to use even if the target
* is not a directory.
*
* @return Ok if the file was removed successfully, or an error.
*/
static Result<Ok, IOError> RemoveSync(nsIFile* aFile, bool aIgnoreAbsent,
bool aRecursive);
/**
* Attempts to create a new directory at |aFile|.
*
* @param aFile The location of the directory to create.
* @param aCreateAncestors If true, create missing ancestor directories as
* needed. Otherwise, report an error if the target
* has non-existing ancestor directories.
* @param aIgnoreExisting If true, suppress errors that occur if the target
* directory already exists. Otherwise, propagate the
* error if it occurs.
* @param aMode Optional file mode. Defaults to 0777 to allow the
* system umask to compute the best mode for the new
* directory.
*
* @return Ok if the directory was created successfully, or an error.
*/
static Result<Ok, IOError> MakeDirectorySync(nsIFile* aFile,
bool aCreateAncestors,
bool aIgnoreExisting,
int32_t aMode = 0777);
/**
* Attempts to stat a file at |aFile|.
*
* @param aFile The location of the file.
*
* @return An |InternalFileInfo| struct if successful, or an error.
*/
static Result<IOUtils::InternalFileInfo, IOError> StatSync(nsIFile* aFile);
/**
* Attempts to update the last modification time of the file at |aFile|.
*
* @param aFile The location of the file.
* @param aNewModTime Some value in milliseconds since Epoch. For the current
* system time, use |Nothing|.
*
* @return Timestamp of the file if the operation was successful, or an error.
*/
static Result<int64_t, IOError> TouchSync(nsIFile* aFile,
const Maybe<int64_t>& aNewModTime);
/**
* Returns the immediate children of the directory at |aFile|, if any.
*
* @param aFile The location of the directory.
*
* @return An array of absolute paths identifying the children of |aFile|.
* If there are no children, an empty array. Otherwise, an error.
*/
static Result<nsTArray<nsString>, IOError> GetChildrenSync(nsIFile* aFile);
/**
* Set the permissions of the given file.
*
* Windows does not make a distinction between user, group, and other
* permissions like UNICES do. If a permission flag is set for any of user,
* group, or other has a permission, then all users will have that
* permission.
*
* @param aFile The location of the file.
* @param aPermissions The permissions to set, as a UNIX file mode.
*
* @return |Ok| if the permissions were successfully set, or an error.
*/
static Result<Ok, IOError> SetPermissionsSync(nsIFile* aFile,
const uint32_t aPermissions);
/**
* Return whether or not the file exists.
*
* @param aFile The location of the file.
*
* @return Whether or not the file exists.
*/
static Result<bool, IOError> ExistsSync(nsIFile* aFile);
};
/**
* An error class used with the |Result| type returned by most private |IOUtils|
* methods.
*/
class IOUtils::IOError {
public:
MOZ_IMPLICIT IOError(nsresult aCode) : mCode(aCode), mMessage(Nothing()) {}
/**
* Replaces the message associated with this error.
*/
template <typename... Args>
IOError WithMessage(const char* const aMessage, Args... aArgs) {
mMessage.emplace(nsPrintfCString(aMessage, aArgs...));
return *this;
}
IOError WithMessage(const char* const aMessage) {
mMessage.emplace(nsCString(aMessage));
return *this;
}
IOError WithMessage(const nsCString& aMessage) {
mMessage.emplace(aMessage);
return *this;
}
/**
* Returns the |nsresult| associated with this error.
*/
nsresult Code() const { return mCode; }
/**
* Maybe returns a message associated with this error.
*/
const Maybe<nsCString>& Message() const { return mMessage; }
private:
nsresult mCode;
Maybe<nsCString> mMessage;
};
/**
* This is an easier to work with representation of a |mozilla::dom::FileInfo|
* for private use in the IOUtils implementation.
*
* Because web IDL dictionaries are not easily copy/moveable, this class is
* used instead, until converted to the proper |mozilla::dom::FileInfo| before
* returning any results to JavaScript.
*/
struct IOUtils::InternalFileInfo {
nsString mPath;
FileType mType = FileType::Other;
uint64_t mSize = 0;
uint64_t mLastModified = 0;
Maybe<uint64_t> mCreationTime;
uint32_t mPermissions = 0;
};
/**
* This is an easier to work with representation of a
* |mozilla::dom::WriteOptions| for private use in the |IOUtils|
* implementation.
*
* Because web IDL dictionaries are not easily copy/moveable, this class is
* used instead.
*/
struct IOUtils::InternalWriteOpts {
RefPtr<nsIFile> mBackupFile;
RefPtr<nsIFile> mTmpFile;
bool mFlush = false;
bool mNoOverwrite = false;
bool mCompress = false;
static Result<InternalWriteOpts, IOUtils::IOError> FromBinding(
const WriteOptions& aOptions);
};
/**
* Re-implements the file compression and decompression utilities found
* in toolkit/components/lz4/lz4.js
*
* This implementation uses the non-standard data layout:
*
* - MAGIC_NUMBER (8 bytes)
* - content size (uint32_t, little endian)
* - content, as obtained from mozilla::Compression::LZ4::compress
*
* See bug 1209390 for more info.
*/
class IOUtils::MozLZ4 {
public:
static constexpr std::array<uint8_t, 8> MAGIC_NUMBER{
{'m', 'o', 'z', 'L', 'z', '4', '0', '\0'}};
static const uint32_t HEADER_SIZE = 8 + sizeof(uint32_t);
/**
* Compresses |aUncompressed| byte array, and returns a byte array with the
* correct format whose contents may be written to disk.
*/
static Result<nsTArray<uint8_t>, IOError> Compress(
Span<const uint8_t> aUncompressed);
/**
* Checks |aFileContents| for the correct file header, and returns the
* decompressed content.
*/
static Result<IOUtils::JsBuffer, IOError> Decompress(
Span<const uint8_t> aFileContents, IOUtils::BufferKind);
};
class IOUtilsShutdownBlocker : public nsIAsyncShutdownBlocker {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIASYNCSHUTDOWNBLOCKER
private:
virtual ~IOUtilsShutdownBlocker() = default;
};
/**
* A buffer that is allocated inside one of JS heaps so that it can be converted
* to a JSString or Uint8Array object with at most one copy in the worst case.
*/
class IOUtils::JsBuffer final {
public:
/**
* Create a new buffer of the given kind with the requested capacity.
*
* @param aBufferKind The kind of buffer to create (either a string or an
* array).
* @param aCapacity The capacity of the buffer.
*
* @return Either a successfully created buffer or an error if it could not be
* allocated.
*/
static Result<JsBuffer, IOUtils::IOError> Create(
IOUtils::BufferKind aBufferKind, size_t aCapacity);
/**
* Create a new, empty buffer.
*
* This operation cannot fail.
*
* @param aBufferKind The kind of buffer to create (either a string or an
* array).
*
* @return An empty JsBuffer.
*/
static JsBuffer CreateEmpty(IOUtils::BufferKind aBufferKind);
JsBuffer(const JsBuffer&) = delete;
JsBuffer(JsBuffer&& aOther) noexcept;
JsBuffer& operator=(const JsBuffer&) = delete;
JsBuffer& operator=(JsBuffer&& aOther) noexcept;
size_t Length() { return mLength; }
char* Elements() { return mBuffer.get(); }
void SetLength(size_t aNewLength) {
MOZ_RELEASE_ASSERT(aNewLength <= mCapacity);
mLength = aNewLength;
}
/**
* Return a span for writing to the buffer.
*
* |SetLength| should be called after the buffer has been written to.
*
* @returns A span for writing to. The size of the span is the entire
* allocated capacity.
*/
Span<char> BeginWriting() {
MOZ_RELEASE_ASSERT(mBuffer.get());
return Span(mBuffer.get(), mCapacity);
}
/**
* Return a span for reading from.
*
* @returns A span for reading form. The size of the span is the set length
* of the buffer.
*/
Span<const char> BeginReading() const {
MOZ_RELEASE_ASSERT(mBuffer.get() || mLength == 0);
return Span(mBuffer.get(), mLength);
}
/**
* Consume the JsBuffer and convert it into a JSString.
*
* NOTE: This method asserts the buffer was allocated as a string buffer.
*
* @param aBuffer The buffer to convert to a string. After this call, the
* buffer will be invaldated and |IntoString| cannot be called
* again.
*
* @returns A JSString with the contents of |aBuffer|.
*/
static JSString* IntoString(JSContext* aCx, JsBuffer aBuffer);
/**
* Consume the JsBuffer and convert it into a Uint8Array.
*
* NOTE: This method asserts the buffer was allocated as an array buffer.
*
* @param aBuffer The buffer to convert to an array. After this call, the
* buffer will be invalidated and |IntoUint8Array| cannot be
* called again.
*
* @returns A JSBuffer
*/
static JSObject* IntoUint8Array(JSContext* aCx, JsBuffer aBuffer);
friend MOZ_MUST_USE bool ToJSValue(JSContext* aCx, JsBuffer&& aBuffer,
JS::MutableHandle<JS::Value> aValue);
private:
IOUtils::BufferKind mBufferKind;
size_t mCapacity;
size_t mLength;
JS::UniqueChars mBuffer;
JsBuffer(BufferKind aBufferKind, size_t aCapacity);
};
} // namespace dom
} // namespace mozilla
#endif
|