summaryrefslogtreecommitdiffstats
path: root/dom/system/IOUtils.h
blob: d723a2422ef90317a797ea37187d37262aef97d8 (plain)
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
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
/* -*- 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/StaticPtr.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/IOUtilsBinding.h"
#include "mozilla/dom/TypedArray.h"
#include "nsIAsyncShutdown.h"
#include "nsIFile.h"
#include "nsISerialEventTarget.h"
#include "nsPrintfCString.h"
#include "nsProxyRelease.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "prio.h"

class nsFileRandomAccessStream;

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;

  enum class ShutdownPhase : uint8_t {
    ProfileBeforeChange,
    SendTelemetry,
    XpcomWillShutdown,
    Count,
  };

  template <typename T>
  using PhaseArray =
      EnumeratedArray<IOUtils::ShutdownPhase, IOUtils::ShutdownPhase::Count, T>;

  static already_AddRefed<Promise> Read(GlobalObject& aGlobal,
                                        const nsAString& aPath,
                                        const ReadOptions& aOptions,
                                        ErrorResult& aError);

  static already_AddRefed<Promise> ReadUTF8(GlobalObject& aGlobal,
                                            const nsAString& aPath,
                                            const ReadUTF8Options& aOptions,
                                            ErrorResult& aError);

  static already_AddRefed<Promise> ReadJSON(GlobalObject& aGlobal,
                                            const nsAString& aPath,
                                            const ReadUTF8Options& aOptions,
                                            ErrorResult& aError);

  static already_AddRefed<Promise> Write(GlobalObject& aGlobal,
                                         const nsAString& aPath,
                                         const Uint8Array& aData,
                                         const WriteOptions& aOptions,
                                         ErrorResult& aError);

  static already_AddRefed<Promise> WriteUTF8(GlobalObject& aGlobal,
                                             const nsAString& aPath,
                                             const nsACString& aString,
                                             const WriteOptions& aOptions,
                                             ErrorResult& aError);

  static already_AddRefed<Promise> WriteJSON(GlobalObject& aGlobal,
                                             const nsAString& aPath,
                                             JS::Handle<JS::Value> aValue,
                                             const WriteOptions& aOptions,
                                             ErrorResult& aError);

  static already_AddRefed<Promise> Move(GlobalObject& aGlobal,
                                        const nsAString& aSourcePath,
                                        const nsAString& aDestPath,
                                        const MoveOptions& aOptions,
                                        ErrorResult& aError);

  static already_AddRefed<Promise> Remove(GlobalObject& aGlobal,
                                          const nsAString& aPath,
                                          const RemoveOptions& aOptions,
                                          ErrorResult& aError);

  static already_AddRefed<Promise> MakeDirectory(
      GlobalObject& aGlobal, const nsAString& aPath,
      const MakeDirectoryOptions& aOptions, ErrorResult& aError);

  static already_AddRefed<Promise> Stat(GlobalObject& aGlobal,
                                        const nsAString& aPath,
                                        ErrorResult& aError);

  static already_AddRefed<Promise> Copy(GlobalObject& aGlobal,
                                        const nsAString& aSourcePath,
                                        const nsAString& aDestPath,
                                        const CopyOptions& aOptions,
                                        ErrorResult& aError);

  static already_AddRefed<Promise> SetAccessTime(
      GlobalObject& aGlobal, const nsAString& aPath,
      const Optional<int64_t>& aAccess, ErrorResult& aError);

  static already_AddRefed<Promise> SetModificationTime(
      GlobalObject& aGlobal, const nsAString& aPath,
      const Optional<int64_t>& aModification, ErrorResult& aError);

 private:
  using SetTimeFn = decltype(&nsIFile::SetLastAccessedTime);

  static_assert(
      std::is_same_v<SetTimeFn, decltype(&nsIFile::SetLastModifiedTime)>);

  static already_AddRefed<Promise> SetTime(GlobalObject& aGlobal,
                                           const nsAString& aPath,
                                           const Optional<int64_t>& aNewTime,
                                           SetTimeFn aSetTimeFn,
                                           ErrorResult& aError);

 public:
  static already_AddRefed<Promise> GetChildren(
      GlobalObject& aGlobal, const nsAString& aPath,
      const GetChildrenOptions& aOptions, ErrorResult& aError);

  static already_AddRefed<Promise> SetPermissions(GlobalObject& aGlobal,
                                                  const nsAString& aPath,
                                                  uint32_t aPermissions,
                                                  const bool aHonorUmask,
                                                  ErrorResult& aError);

  static already_AddRefed<Promise> Exists(GlobalObject& aGlobal,
                                          const nsAString& aPath,
                                          ErrorResult& aError);

  static already_AddRefed<Promise> CreateUniqueFile(GlobalObject& aGlobal,
                                                    const nsAString& aParent,
                                                    const nsAString& aPrefix,
                                                    const uint32_t aPermissions,
                                                    ErrorResult& aError);
  static already_AddRefed<Promise> CreateUniqueDirectory(
      GlobalObject& aGlobal, const nsAString& aParent, const nsAString& aPrefix,
      const uint32_t aPermissions, ErrorResult& aError);

 private:
  /**
   * A helper method for CreateUniqueFile and CreateUniqueDirectory.
   */
  static already_AddRefed<Promise> CreateUnique(GlobalObject& aGlobal,
                                                const nsAString& aParent,
                                                const nsAString& aPrefix,
                                                const uint32_t aFileType,
                                                const uint32_t aPermissions,
                                                ErrorResult& aError);

 public:
  static already_AddRefed<Promise> ComputeHexDigest(
      GlobalObject& aGlobal, const nsAString& aPath,
      const HashAlgorithm aAlgorithm, ErrorResult& aError);

#if defined(XP_WIN)
  static already_AddRefed<Promise> GetWindowsAttributes(GlobalObject& aGlobal,
                                                        const nsAString& aPath,
                                                        ErrorResult& aError);

  static already_AddRefed<Promise> SetWindowsAttributes(
      GlobalObject& aGlobal, const nsAString& aPath,
      const mozilla::dom::WindowsFileAttributes& aAttrs, ErrorResult& aError);
#elif defined(XP_MACOSX)
  static already_AddRefed<Promise> HasMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr,
                                               ErrorResult& aError);
  static already_AddRefed<Promise> GetMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr,
                                               ErrorResult& aError);
  static already_AddRefed<Promise> SetMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr,
                                               const Uint8Array& aValue,
                                               ErrorResult& aError);
  static already_AddRefed<Promise> DelMacXAttr(GlobalObject& aGlobal,
                                               const nsAString& aPath,
                                               const nsACString& aAttr,
                                               ErrorResult& aError);
#endif

#ifdef XP_UNIX
  using UnixString = OwningUTF8StringOrUint8Array;
  static uint32_t LaunchProcess(GlobalObject& aGlobal,
                                const Sequence<UnixString>& aArgv,
                                const LaunchOptions& aOptions,
                                ErrorResult& aRv);
#endif

  static already_AddRefed<Promise> GetFile(
      GlobalObject& aGlobal, const Sequence<nsString>& aComponents,
      ErrorResult& aError);

  static already_AddRefed<Promise> GetDirectory(
      GlobalObject& aGlobal, const Sequence<nsString>& aComponents,
      ErrorResult& aError);

  static void GetProfileBeforeChange(GlobalObject& aGlobal,
                                     JS::MutableHandle<JS::Value>,
                                     ErrorResult& aRv);

  static void GetSendTelemetry(GlobalObject& aGlobal,
                               JS::MutableHandle<JS::Value>, ErrorResult& aRv);

  static RefPtr<SyncReadFile> OpenFileForSyncReading(GlobalObject& aGlobal,
                                                     const nsAString& aPath,
                                                     ErrorResult& aRv);

  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;
  class EventQueue;
  class State;

  template <typename Fn>
  static already_AddRefed<Promise> WithPromiseAndState(GlobalObject& aGlobal,
                                                       ErrorResult& aError,
                                                       Fn aFn);

  /**
   * Dispatch a task on the event queue and resolve or reject the associated
   * promise based on the result.
   *
   * NB: If the calling thread is a worker, this function takes care of keepting
   *     it alive until the |IOPromise| can complete.
   *
   * @param aPromise The promise corresponding to the task running on the event
   * queue.
   * @param aFunc The task to run.
   */
  template <typename OkT, typename Fn>
  static void DispatchAndResolve(EventQueue* aQueue, Promise* aPromise,
                                 Fn aFunc);

  /**
   * Creates a new JS Promise.
   *
   * @return The new promise, or |nullptr| on failure.
   */
  static already_AddRefed<Promise> CreateJSPromise(GlobalObject& aGlobal,
                                                   ErrorResult& aError);

  // Allow conversion of |InternalFileInfo| with |ToJSValue|.
  friend bool ToJSValue(JSContext* aCx,
                        const InternalFileInfo& aInternalFileInfo,
                        JS::MutableHandle<JS::Value> aValue);

  /**
   * Attempts to read the entire file at |aPath| into a buffer.
   *
   * @param aFile       The location of the file.
   * @param aOffset     The offset to start reading from.
   * @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 uint64_t aOffset,
                                            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 aNoOverWrite 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.
   * @param aRetryReadonly Retry a delete that failed with a NotAllowedError by
   *                       first removing the readonly attribute. Only has an
   *                       effect on Windows.
   *
   * @return Ok if the file was removed successfully, or an error.
   */
  static Result<Ok, IOError> RemoveSync(nsIFile* aFile, bool aIgnoreAbsent,
                                        bool aRecursive, bool aRetryReadonly);

  /**
   * 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 access or modification time of the file at
   * |aFile|.
   *
   * @param aFile     The location of the file.
   * @param SetTimeFn A member function pointer to either
   *                  nsIFile::SetLastAccessedTime or
   *                  nsIFile::SetLastModifiedTime.
   * @param aNewTime  Some value in milliseconds since Epoch.
   *
   * @return Timestamp of the file if the operation was successful, or an error.
   */
  static Result<int64_t, IOError> SetTimeSync(nsIFile* aFile,
                                              SetTimeFn aSetTimeFn,
                                              int64_t aNewTime);

  /**
   * 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, bool aIgnoreAbsent);

  /**
   * 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);

  /**
   * Create a file or directory with a unique path.
   *
   * @param aFile     The location of the file or directory (including prefix)
   * @param aFileType One of |nsIFile::NORMAL_FILE_TYPE| or
   *                  |nsIFile::DIRECTORY_TYPE|.
   * @param aperms    The permissions to create the file or directory with.
   *
   * @return A unique path.
   */
  static Result<nsString, IOError> CreateUniqueSync(
      nsIFile* aFile, const uint32_t aFileType, const uint32_t aPermissions);

  /**
   * Compute the hash of a file.
   *
   * @param aFile      The file to hash.
   * @param aAlgorithm The hashing algorithm to use.
   *
   * @return The hash of the file, as a hex digest.
   */
  static Result<nsCString, IOError> ComputeHexDigestSync(
      nsIFile* aFile, const HashAlgorithm aAlgorithm);

#if defined(XP_WIN)
  /**
   * Return the Windows-specific attributes of the file.
   *
   * @param aFile The location of the file.
   *
   * @return The Windows-specific attributes of the file.
   */
  static Result<uint32_t, IOError> GetWindowsAttributesSync(nsIFile* aFile);

  /**
   * Set the Windows-specific attributes of the file.
   *
   * @param aFile  The location of the file.
   * @param aAttrs The attributes to set on the file.
   *
   * @return |Ok| if the attributes were successfully set, or an error.
   */
  static Result<Ok, IOError> SetWindowsAttributesSync(
      nsIFile* aFile, const uint32_t aSetAttrs, const uint32_t aClearAttrs);
#elif defined(XP_MACOSX)
  static Result<bool, IOError> HasMacXAttrSync(nsIFile* aFile,
                                               const nsCString& aAttr);
  static Result<nsTArray<uint8_t>, IOError> GetMacXAttrSync(
      nsIFile* aFile, const nsCString& aAttr);
  static Result<Ok, IOError> SetMacXAttrSync(nsIFile* aFile,
                                             const nsCString& aAttr,
                                             const nsTArray<uint8_t>& aValue);
  static Result<Ok, IOError> DelMacXAttrSync(nsIFile* aFile,
                                             const nsCString& aAttr);
#endif

  static void GetShutdownClient(GlobalObject& aGlobal,
                                JS::MutableHandle<JS::Value> aClient,
                                ErrorResult& aRv, const ShutdownPhase aPhase);

  enum class EventQueueStatus {
    Uninitialized,
    Initialized,
    Shutdown,
  };

  enum class ShutdownBlockerStatus {
    Uninitialized,
    Initialized,
    Failed,
  };

  /**
   * Internal IOUtils state.
   */
  class State {
   public:
    StaticAutoPtr<EventQueue> mEventQueue;
    EventQueueStatus mQueueStatus = EventQueueStatus::Uninitialized;
    ShutdownBlockerStatus mBlockerStatus = ShutdownBlockerStatus::Uninitialized;

    /**
     * Set up shutdown hooks to free our internals at shutdown.
     *
     * NB: Must be called on main thread.
     */
    void SetShutdownHooks();
  };

  using StateMutex = StaticDataMutex<State>;

  /**
   * Lock the state mutex and return a handle. If shutdown has not yet
   * finished, the internals will be constructed if necessary.
   *
   * @returns A handle to the internal state, which can be used to retrieve the
   *          event queue.
   *          If |Some| is returned, |mEventQueue| is guaranteed to be
   * initialized. If shutdown has finished, |Nothing| is returned.
   */
  static Maybe<StateMutex::AutoLock> GetState();

  static StateMutex sState;
};

/**
 * The IOUtils event queue.
 */
class IOUtils::EventQueue final {
  friend void IOUtils::State::SetShutdownHooks();

 public:
  EventQueue();

  EventQueue(const EventQueue&) = delete;
  EventQueue(EventQueue&&) = delete;
  EventQueue& operator=(const EventQueue&) = delete;
  EventQueue& operator=(EventQueue&&) = delete;

  /**
   * Dispatch a task on the event queue.
   *
   * NB: If using this directly from |IOUtils| instead of
   *     |IOUtils::DispatchAndResolve| *and* the calling thread is a worker, you
   *     *must* take care to keep the worker thread alive until the |IOPromise|
   *     resolves or rejects. See the implementation of
   *     |IOUtils::DispatchAndResolve| or |IOUtils::GetWindowsAttributes| for an
   *     example.
   *
   * @param aFunc The task to dispatch on the event queue.
   *
   * @return A promise that resolves to the task's return value or rejects with
   *         an error.
   */
  template <typename OkT, typename Fn>
  RefPtr<IOPromise<OkT>> Dispatch(Fn aFunc);

  Result<already_AddRefed<nsIAsyncShutdownBarrier>, nsresult>
  GetShutdownBarrier(const ShutdownPhase aPhase);
  Result<already_AddRefed<nsIAsyncShutdownClient>, nsresult> GetShutdownClient(
      const ShutdownPhase aPhase);

 private:
  nsresult SetShutdownHooks();

  nsCOMPtr<nsISerialEventTarget> mBackgroundEventTarget;
  IOUtils::PhaseArray<nsCOMPtr<nsIAsyncShutdownBarrier>> mBarriers;
};

/**
 * 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;
  Maybe<PRTime> mCreationTime;  // In ms since epoch.
  PRTime mLastAccessed = 0;     // In ms since epoch.
  PRTime mLastModified = 0;     // In ms since epoch.
  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;
  WriteMode mMode;
  bool mFlush = 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 nsIAsyncShutdownCompletionCallback {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIASYNCSHUTDOWNBLOCKER
  NS_DECL_NSIASYNCSHUTDOWNCOMPLETIONCALLBACK

  explicit IOUtilsShutdownBlocker(const IOUtils::ShutdownPhase aPhase)
      : mPhase(aPhase) {}

 private:
  virtual ~IOUtilsShutdownBlocker() = default;

  /**
   * Called on the main thread after the event queue has been flushed.
   */
  void OnFlush();

  static constexpr IOUtils::PhaseArray<const char16_t*> PHASE_NAMES{
      u"profile-before-change",
      u"profile-before-change-telemetry",
      u"xpcom-will-shutdown",
  };

  // The last shutdown phase before we should shut down the event loop.
  static constexpr auto LAST_IO_PHASE = IOUtils::ShutdownPhase::SendTelemetry;

  IOUtils::ShutdownPhase mPhase;
  nsCOMPtr<nsIAsyncShutdownClient> mParentClient;
};

/**
 * 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 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);
};

class SyncReadFile : public nsISupports, public nsWrapperCache {
 public:
  SyncReadFile(nsISupports* aParent, RefPtr<nsFileRandomAccessStream>&& aStream,
               int64_t aSize);

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(SyncReadFile)

  nsISupports* GetParentObject() const { return mParent; }

  virtual JSObject* WrapObject(JSContext* aCx,
                               JS::Handle<JSObject*> aGivenProto) override;

  int64_t Size() const { return mSize; }
  void ReadBytesInto(const Uint8Array&, const int64_t, ErrorResult& aRv);
  void Close();

 private:
  virtual ~SyncReadFile();

  nsCOMPtr<nsISupports> mParent;
  RefPtr<nsFileRandomAccessStream> mStream;
  int64_t mSize = 0;
};

}  // namespace dom
}  // namespace mozilla

#endif