summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update/common/commonupdatedir.cpp
blob: 0ba9fcef94173b24698a2ddee07a75d763f9b00e (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
/* 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/. */

/*
 * This file does not use many of the features Firefox provides such as
 * nsAString and nsIFile because code in this file will be included not only
 * in Firefox, but also in the Mozilla Maintenance Service, the Mozilla
 * Maintenance Service installer, and TestAUSHelper.
 */

#include <cinttypes>
#include <cwchar>
#include <string>
#include "city.h"
#include "commonupdatedir.h"
#include "updatedefines.h"

#ifdef XP_WIN
#  include <accctrl.h>
#  include <aclapi.h>
#  include <cstdarg>
#  include <errno.h>
#  include <objbase.h>
#  include <shellapi.h>
#  include <shlobj.h>
#  include <strsafe.h>
#  include <winerror.h>
#  include "nsWindowsHelpers.h"
#  include "updateutils_win.h"
#endif

#ifdef XP_WIN
// This is the name of the old update directory
// (i.e. C:\ProgramData\<OLD_ROOT_UPDATE_DIR_NAME>)
#  define OLD_ROOT_UPDATE_DIR_NAME "Mozilla"
// This is the name of the current update directory
// (i.e. C:\ProgramData\<ROOT_UPDATE_DIR_NAME>)
// It is really important that we properly set the permissions on this
// directory at creation time. Thus, it is really important that this code be
// the creator of this directory. We had many problems with the old update
// directory having been previously created by old versions of Firefox. To avoid
// this problem in the future, we are including a UUID in the root update
// directory name to attempt to ensure that it will be created by this code and
// won't already exist with the wrong permissions.
#  define ROOT_UPDATE_DIR_NAME "Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38"
// This describes the directory between the "Mozilla" directory and the install
// path hash (i.e. C:\ProgramData\Mozilla\<UPDATE_PATH_MID_DIR_NAME>\<hash>)
#  define UPDATE_PATH_MID_DIR_NAME "updates"

enum class WhichUpdateDir {
  CurrentUpdateDir,
  UnmigratedUpdateDir,
};

/**
 * This is a very simple string class.
 *
 * This class has some substantial limitations for the sake of simplicity. It
 * has no support whatsoever for modifying a string that already has data. There
 * is, therefore, no append function and no support for automatically resizing
 * strings.
 *
 * Error handling is also done in a slightly unusual manner. If there is ever
 * a failure allocating or assigning to a string, it will do the simplest
 * possible recovery: truncate itself to 0-length.
 * This coupled with the fact that the length is cached means that an effective
 * method of error checking is to attempt assignment and then check the length
 * of the result.
 */
class SimpleAutoString {
 private:
  size_t mLength;
  // Unique pointer frees the buffer when the class is deleted or goes out of
  // scope.
  mozilla::UniquePtr<wchar_t[]> mString;

  /**
   * Allocates enough space to store a string of the specified length.
   */
  bool AllocLen(size_t len) {
    mString = mozilla::MakeUnique<wchar_t[]>(len + 1);
    return mString.get() != nullptr;
  }

  /**
   * Allocates a buffer of the size given.
   */
  bool AllocSize(size_t size) {
    mString = mozilla::MakeUnique<wchar_t[]>(size);
    return mString.get() != nullptr;
  }

 public:
  SimpleAutoString() : mLength(0) {}

  /*
   * Allocates enough space for a string of the given length and formats it as
   * an empty string.
   */
  bool AllocEmpty(size_t len) {
    bool success = AllocLen(len);
    Truncate();
    return success;
  }

  /**
   * These functions can potentially return null if no buffer has yet been
   * allocated. After changing a string retrieved with MutableString, the Check
   * method should be called to synchronize other members (ex: mLength) with the
   * new buffer.
   */
  wchar_t* MutableString() { return mString.get(); }
  const wchar_t* String() const { return mString.get(); }

  size_t Length() const { return mLength; }

  /**
   * This method should be called after manually changing the string's buffer
   * via MutableString to synchronize other members (ex: mLength) with the
   * new buffer.
   * Returns true if the string is now in a valid state.
   */
  bool Check() {
    mLength = wcslen(mString.get());
    return true;
  }

  void SwapBufferWith(mozilla::UniquePtr<wchar_t[]>& other) {
    mString.swap(other);
    if (mString) {
      mLength = wcslen(mString.get());
    } else {
      mLength = 0;
    }
  }

  void Swap(SimpleAutoString& other) {
    mString.swap(other.mString);
    size_t newLength = other.mLength;
    other.mLength = mLength;
    mLength = newLength;
  }

  /**
   * Truncates the string to the length specified. This must not be greater than
   * or equal to the size of the string's buffer.
   */
  void Truncate(size_t len = 0) {
    if (len > mLength) {
      return;
    }
    mLength = len;
    if (mString) {
      mString.get()[len] = L'\0';
    }
  }

  /**
   * Assigns a string and ensures that the resulting string is valid and has its
   * length set properly.
   *
   * Note that although other similar functions in this class take length, this
   * function takes buffer size instead because it is intended to be follow the
   * input convention of sprintf.
   *
   * Returns the new length, which will be 0 if there was any failure.
   *
   * This function does no allocation or reallocation. If the buffer is not
   * large enough to hold the new string, the call will fail.
   */
  size_t AssignSprintf(size_t bufferSize, const wchar_t* format, ...) {
    va_list ap;
    va_start(ap, format);
    size_t returnValue = AssignVsprintf(bufferSize, format, ap);
    va_end(ap);
    return returnValue;
  }
  /**
   * Same as the above, but takes a va_list like vsprintf does.
   */
  size_t AssignVsprintf(size_t bufferSize, const wchar_t* format, va_list ap) {
    if (!mString) {
      Truncate();
      return 0;
    }

    int charsWritten = vswprintf(mString.get(), bufferSize, format, ap);
    if (charsWritten < 0 || static_cast<size_t>(charsWritten) >= bufferSize) {
      // charsWritten does not include the null terminator. If charsWritten is
      // equal to the buffer size, we do not have a null terminator nor do we
      // have room for one.
      Truncate();
      return 0;
    }
    mString.get()[charsWritten] = L'\0';

    mLength = charsWritten;
    return mLength;
  }

  /**
   * Allocates enough space for the string and assigns a value to it with
   * sprintf. Takes, as an argument, the maximum length that the string is
   * expected to use (which, after adding 1 for the null byte, is the amount of
   * space that will be allocated).
   *
   * Returns the new length, which will be 0 on any failure.
   */
  size_t AllocAndAssignSprintf(size_t maxLength, const wchar_t* format, ...) {
    if (!AllocLen(maxLength)) {
      Truncate();
      return 0;
    }
    va_list ap;
    va_start(ap, format);
    size_t charsWritten = AssignVsprintf(maxLength + 1, format, ap);
    va_end(ap);
    return charsWritten;
  }

  /*
   * Allocates enough for the formatted text desired. Returns maximum storable
   * length of a string in the allocated buffer on success, or 0 on failure.
   */
  size_t AllocFromScprintf(const wchar_t* format, ...) {
    va_list ap;
    va_start(ap, format);
    size_t returnValue = AllocFromVscprintf(format, ap);
    va_end(ap);
    return returnValue;
  }
  /**
   * Same as the above, but takes a va_list like vscprintf does.
   */
  size_t AllocFromVscprintf(const wchar_t* format, va_list ap) {
    int len = _vscwprintf(format, ap);
    if (len < 0) {
      Truncate();
      return 0;
    }
    if (!AllocEmpty(len)) {
      // AllocEmpty will Truncate, no need to call it here.
      return 0;
    }
    return len;
  }

  /**
   * Automatically determines how much space is necessary, allocates that much
   * for the string, and assigns the data using swprintf. Returns the resulting
   * length of the string, which will be 0 if the function fails.
   */
  size_t AutoAllocAndAssignSprintf(const wchar_t* format, ...) {
    va_list ap;
    va_start(ap, format);
    size_t len = AllocFromVscprintf(format, ap);
    va_end(ap);
    if (len == 0) {
      // AllocFromVscprintf will Truncate, no need to call it here.
      return 0;
    }

    va_start(ap, format);
    size_t charsWritten = AssignVsprintf(len + 1, format, ap);
    va_end(ap);

    if (len != charsWritten) {
      Truncate();
      return 0;
    }
    return charsWritten;
  }

  /**
   * The following CopyFrom functions take various types of strings, allocate
   * enough space to hold them, and then copy them into that space.
   *
   * They return an HRESULT that should be interpreted with the SUCCEEDED or
   * FAILED macro.
   */
  HRESULT CopyFrom(const wchar_t* src) {
    mLength = wcslen(src);
    if (!AllocLen(mLength)) {
      Truncate();
      return E_OUTOFMEMORY;
    }
    HRESULT hrv = StringCchCopyW(mString.get(), mLength + 1, src);
    if (FAILED(hrv)) {
      Truncate();
    }
    return hrv;
  }
  HRESULT CopyFrom(const SimpleAutoString& src) {
    if (!src.mString) {
      Truncate();
      return S_OK;
    }
    mLength = src.mLength;
    if (!AllocLen(mLength)) {
      Truncate();
      return E_OUTOFMEMORY;
    }
    HRESULT hrv = StringCchCopyW(mString.get(), mLength + 1, src.mString.get());
    if (FAILED(hrv)) {
      Truncate();
    }
    return hrv;
  }
  HRESULT CopyFrom(const char* src) {
    int bufferSize =
        MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, nullptr, 0);
    if (bufferSize == 0) {
      Truncate();
      return HRESULT_FROM_WIN32(GetLastError());
    }
    if (!AllocSize(bufferSize)) {
      Truncate();
      return E_OUTOFMEMORY;
    }
    int charsWritten = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src,
                                           -1, mString.get(), bufferSize);
    if (charsWritten == 0) {
      Truncate();
      return HRESULT_FROM_WIN32(GetLastError());
    } else if (charsWritten != bufferSize) {
      Truncate();
      return E_FAIL;
    }
    mLength = charsWritten - 1;
    return S_OK;
  }

  bool StartsWith(const SimpleAutoString& prefix) const {
    if (!mString) {
      return (prefix.mLength == 0);
    }
    if (!prefix.mString) {
      return true;
    }
    if (prefix.mLength > mLength) {
      return false;
    }
    return (wcsncmp(mString.get(), prefix.mString.get(), prefix.mLength) == 0);
  }
};

// Deleter for use with UniquePtr
struct CoTaskMemFreeDeleter {
  void operator()(void* aPtr) { ::CoTaskMemFree(aPtr); }
};

/**
 * A lot of data goes into constructing an ACL and security attributes, and the
 * Windows documentation does not make it very clear what can be safely freed
 * after these objects are constructed. This struct holds all of the
 * construction data in one place so that it can be passed around and freed
 * properly.
 */
struct AutoPerms {
  SID_IDENTIFIER_AUTHORITY sidIdentifierAuthority;
  UniqueSidPtr usersSID;
  UniqueSidPtr adminsSID;
  UniqueSidPtr systemSID;
  EXPLICIT_ACCESS_W ea[3];
  mozilla::UniquePtr<ACL, LocalFreeDeleter> acl;
  mozilla::UniquePtr<uint8_t[]> securityDescriptorBuffer;
  PSECURITY_DESCRIPTOR securityDescriptor;
  SECURITY_ATTRIBUTES securityAttributes;
};

static bool GetCachedHash(const char16_t* installPath, HKEY rootKey,
                          const SimpleAutoString& regPath,
                          mozilla::UniquePtr<NS_tchar[]>& result);
static HRESULT GetUpdateDirectory(const wchar_t* installPath,
                                  WhichUpdateDir whichDir,
                                  mozilla::UniquePtr<wchar_t[]>& result);
static HRESULT GeneratePermissions(AutoPerms& result);
static HRESULT MakeDir(const SimpleAutoString& path, const AutoPerms& perms);
#endif  // XP_WIN

/**
 * Returns a hash of the install path, suitable for uniquely identifying the
 * particular Firefox installation that is running.
 *
 * This function includes a compatibility mode that should NOT be used except by
 * GetUserUpdateDirectory. Previous implementations of this function could
 * return a value inconsistent with what our installer would generate. When the
 * update directory was migrated, this function was re-implemented to return
 * values consistent with those generated by the installer. The compatibility
 * mode is retained only so that we can properly get the old update directory
 * when migrating it.
 *
 * @param   installPath
 *          The null-terminated path to the installation directory (i.e. the
 *          directory that contains the binary). Must not be null. The path must
 *          not include a trailing slash.
 * @param   result
 *          The out parameter that will be set to contain the resulting hash.
 *          The value is wrapped in a UniquePtr to make cleanup easier on the
 *          caller.
 * @return  true if successful and false otherwise.
 */
bool GetInstallHash(const char16_t* installPath,
                    mozilla::UniquePtr<NS_tchar[]>& result) {
  MOZ_ASSERT(installPath != nullptr,
             "Install path must not be null in GetInstallHash");

  size_t pathSize =
      std::char_traits<char16_t>::length(installPath) * sizeof(*installPath);
  uint64_t hash =
      CityHash64(reinterpret_cast<const char*>(installPath), pathSize);

  size_t hashStrSize = sizeof(hash) * 2 + 1;  // 2 hex digits per byte + null
  result = mozilla::MakeUnique<NS_tchar[]>(hashStrSize);
  int charsWritten =
      NS_tsnprintf(result.get(), hashStrSize, NS_T("%") NS_T(PRIX64), hash);
  return !(charsWritten < 1 ||
           static_cast<size_t>(charsWritten) > hashStrSize - 1);
}

#ifdef XP_WIN
/**
 * Returns true if the registry key was successfully found and read into result.
 */
static bool GetCachedHash(const char16_t* installPath, HKEY rootKey,
                          const SimpleAutoString& regPath,
                          mozilla::UniquePtr<NS_tchar[]>& result) {
  // Find the size of the string we are reading before we read it so we can
  // allocate space.
  unsigned long bufferSize;
  LSTATUS lrv = RegGetValueW(rootKey, regPath.String(),
                             reinterpret_cast<const wchar_t*>(installPath),
                             RRF_RT_REG_SZ, nullptr, nullptr, &bufferSize);
  if (lrv != ERROR_SUCCESS) {
    return false;
  }
  result = mozilla::MakeUnique<NS_tchar[]>(bufferSize);
  // Now read the actual value from the registry.
  lrv = RegGetValueW(rootKey, regPath.String(),
                     reinterpret_cast<const wchar_t*>(installPath),
                     RRF_RT_REG_SZ, nullptr, result.get(), &bufferSize);
  return (lrv == ERROR_SUCCESS);
}

/**
 * Returns the update directory path. The update directory needs to have
 * different permissions from the default, so we don't really want anyone using
 * the path without the directory already being created with the correct
 * permissions. Therefore, this function also ensures that the base directory
 * that needs permissions set already exists. If it does not exist, it is
 * created with the needed permissions.
 * The desired permissions give Full Control to SYSTEM, Administrators, and
 * Users.
 *
 * @param   installPath
 *          Must be the null-terminated path to the installation directory (i.e.
 *          the directory that contains the binary). The path must not include a
 *          trailing slash.
 * @param   result
 *          The out parameter that will be set to contain the resulting path.
 *          The value is wrapped in a UniquePtr to make cleanup easier on the
 *          caller.
 *
 * @return  An HRESULT that should be tested with SUCCEEDED or FAILED.
 */
HRESULT
GetCommonUpdateDirectory(const wchar_t* installPath,
                         mozilla::UniquePtr<wchar_t[]>& result) {
  return GetUpdateDirectory(installPath, WhichUpdateDir::CurrentUpdateDir,
                            result);
}

/**
 * This function is identical to the function above except that it gets the
 * "old" (pre-migration) update directory.
 *
 * The other difference is that this function does not create the directory.
 */
HRESULT
GetOldUpdateDirectory(const wchar_t* installPath,
                      mozilla::UniquePtr<wchar_t[]>& result) {
  return GetUpdateDirectory(installPath, WhichUpdateDir::UnmigratedUpdateDir,
                            result);
}

/**
 * This is a version of the GetCommonUpdateDirectory that can be called from
 * Rust.
 * The result parameter must be a valid pointer to a buffer of length
 * MAX_PATH + 1
 */
extern "C" HRESULT get_common_update_directory(const wchar_t* installPath,
                                               wchar_t* result) {
  mozilla::UniquePtr<wchar_t[]> uniqueResult;
  HRESULT hr = GetCommonUpdateDirectory(installPath, uniqueResult);
  if (FAILED(hr)) {
    return hr;
  }
  return StringCchCopyW(result, MAX_PATH + 1, uniqueResult.get());
}

/**
 * This is a helper function that does all of the work for
 * GetCommonUpdateDirectory and GetUserUpdateDirectory.
 *
 * For information on the parameters and return value, see
 * GetCommonUpdateDirectory.
 */
static HRESULT GetUpdateDirectory(const wchar_t* installPath,
                                  WhichUpdateDir whichDir,
                                  mozilla::UniquePtr<wchar_t[]>& result) {
  MOZ_ASSERT(installPath != nullptr,
             "Install path must not be null in GetUpdateDirectory");

  AutoPerms perms;
  HRESULT hrv = GeneratePermissions(perms);
  if (FAILED(hrv)) {
    return hrv;
  }

  PWSTR baseDirParentPath;
  hrv = SHGetKnownFolderPath(FOLDERID_ProgramData, KF_FLAG_CREATE, nullptr,
                             &baseDirParentPath);
  // Free baseDirParentPath when it goes out of scope.
  mozilla::UniquePtr<wchar_t, CoTaskMemFreeDeleter> baseDirParentPathUnique(
      baseDirParentPath);
  if (FAILED(hrv)) {
    return hrv;
  }

  SimpleAutoString baseDir;
  if (whichDir == WhichUpdateDir::UnmigratedUpdateDir) {
    const wchar_t baseDirLiteral[] = NS_T(OLD_ROOT_UPDATE_DIR_NAME);
    hrv = baseDir.CopyFrom(baseDirLiteral);
  } else {
    const wchar_t baseDirLiteral[] = NS_T(ROOT_UPDATE_DIR_NAME);
    hrv = baseDir.CopyFrom(baseDirLiteral);
  }
  if (FAILED(hrv)) {
    return hrv;
  }

  // Generate the base path
  // (C:\ProgramData\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38)
  SimpleAutoString basePath;
  size_t basePathLen =
      wcslen(baseDirParentPath) + 1 /* path separator */ + baseDir.Length();
  basePath.AllocAndAssignSprintf(basePathLen, L"%s\\%s", baseDirParentPath,
                                 baseDir.String());
  if (basePath.Length() != basePathLen) {
    return E_FAIL;
  }

  if (whichDir == WhichUpdateDir::CurrentUpdateDir) {
    hrv = MakeDir(basePath, perms);
    if (FAILED(hrv)) {
      return hrv;
    }
  }

  // Generate what we are going to call the mid-path
  // (C:\ProgramData\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38\updates)
  const wchar_t midPathDirName[] = NS_T(UPDATE_PATH_MID_DIR_NAME);
  size_t midPathLen =
      basePath.Length() + 1 /* path separator */ + wcslen(midPathDirName);
  SimpleAutoString midPath;
  midPath.AllocAndAssignSprintf(midPathLen, L"%s\\%s", basePath.String(),
                                midPathDirName);
  if (midPath.Length() != midPathLen) {
    return E_FAIL;
  }

  mozilla::UniquePtr<NS_tchar[]> hash;

  // The Windows installer caches this hash value in the registry
  bool gotHash = false;
  SimpleAutoString regPath;
  regPath.AutoAllocAndAssignSprintf(L"SOFTWARE\\Mozilla\\%S\\TaskBarIDs",
                                    MOZ_APP_BASENAME);
  if (regPath.Length() != 0) {
    gotHash = GetCachedHash(reinterpret_cast<const char16_t*>(installPath),
                            HKEY_LOCAL_MACHINE, regPath, hash);
    if (!gotHash) {
      gotHash = GetCachedHash(reinterpret_cast<const char16_t*>(installPath),
                              HKEY_CURRENT_USER, regPath, hash);
    }
  }
  // If we couldn't get it out of the registry, we'll just have to regenerate
  // it.
  if (!gotHash) {
    bool success =
        GetInstallHash(reinterpret_cast<const char16_t*>(installPath), hash);
    if (!success) {
      return E_FAIL;
    }
  }

  size_t updatePathLen =
      midPath.Length() + 1 /* path separator */ + wcslen(hash.get());
  SimpleAutoString updatePath;
  updatePath.AllocAndAssignSprintf(updatePathLen, L"%s\\%s", midPath.String(),
                                   hash.get());
  if (updatePath.Length() != updatePathLen) {
    return E_FAIL;
  }

  updatePath.SwapBufferWith(result);
  return S_OK;
}

/**
 * Generates the permission set that we want to be applied to the update
 * directory and its contents. Returns the permissions data via the result
 * outparam.
 *
 * These are also the permissions that will be used to check that file
 * permissions are correct.
 */
static HRESULT GeneratePermissions(AutoPerms& result) {
  result.sidIdentifierAuthority = SECURITY_NT_AUTHORITY;
  ZeroMemory(&result.ea, sizeof(result.ea));

  // Make Users group SID and add it to the Explicit Access List.
  PSID usersSID = nullptr;
  BOOL success = AllocateAndInitializeSid(
      &result.sidIdentifierAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
      DOMAIN_ALIAS_RID_USERS, 0, 0, 0, 0, 0, 0, &usersSID);
  result.usersSID.reset(usersSID);
  if (!success) {
    return HRESULT_FROM_WIN32(GetLastError());
  }
  result.ea[0].grfAccessPermissions = FILE_ALL_ACCESS;
  result.ea[0].grfAccessMode = SET_ACCESS;
  result.ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
  result.ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
  result.ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
  result.ea[0].Trustee.ptstrName = static_cast<LPWSTR>(usersSID);

  // Make Administrators group SID and add it to the Explicit Access List.
  PSID adminsSID = nullptr;
  success = AllocateAndInitializeSid(
      &result.sidIdentifierAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
      DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminsSID);
  result.adminsSID.reset(adminsSID);
  if (!success) {
    return HRESULT_FROM_WIN32(GetLastError());
  }
  result.ea[1].grfAccessPermissions = FILE_ALL_ACCESS;
  result.ea[1].grfAccessMode = SET_ACCESS;
  result.ea[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
  result.ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
  result.ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
  result.ea[1].Trustee.ptstrName = static_cast<LPWSTR>(adminsSID);

  // Make SYSTEM user SID and add it to the Explicit Access List.
  PSID systemSID = nullptr;
  success = AllocateAndInitializeSid(&result.sidIdentifierAuthority, 1,
                                     SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0,
                                     0, 0, &systemSID);
  result.systemSID.reset(systemSID);
  if (!success) {
    return HRESULT_FROM_WIN32(GetLastError());
  }
  result.ea[2].grfAccessPermissions = FILE_ALL_ACCESS;
  result.ea[2].grfAccessMode = SET_ACCESS;
  result.ea[2].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
  result.ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID;
  result.ea[2].Trustee.TrusteeType = TRUSTEE_IS_USER;
  result.ea[2].Trustee.ptstrName = static_cast<LPWSTR>(systemSID);

  PACL acl = nullptr;
  DWORD drv = SetEntriesInAclW(3, result.ea, nullptr, &acl);
  // Put the ACL in a unique pointer so that LocalFree is called when it goes
  // out of scope
  result.acl.reset(acl);
  if (drv != ERROR_SUCCESS) {
    return HRESULT_FROM_WIN32(drv);
  }

  result.securityDescriptorBuffer =
      mozilla::MakeUnique<uint8_t[]>(SECURITY_DESCRIPTOR_MIN_LENGTH);
  if (!result.securityDescriptorBuffer) {
    return E_OUTOFMEMORY;
  }
  result.securityDescriptor = reinterpret_cast<PSECURITY_DESCRIPTOR>(
      result.securityDescriptorBuffer.get());
  success = InitializeSecurityDescriptor(result.securityDescriptor,
                                         SECURITY_DESCRIPTOR_REVISION);
  if (!success) {
    return HRESULT_FROM_WIN32(GetLastError());
  }

  success =
      SetSecurityDescriptorDacl(result.securityDescriptor, TRUE, acl, FALSE);
  if (!success) {
    return HRESULT_FROM_WIN32(GetLastError());
  }

  result.securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
  result.securityAttributes.lpSecurityDescriptor = result.securityDescriptor;
  result.securityAttributes.bInheritHandle = FALSE;
  return S_OK;
}

/**
 * Creates a directory with the permissions specified. If the directory already
 * exists, this function will return success.
 */
static HRESULT MakeDir(const SimpleAutoString& path, const AutoPerms& perms) {
  BOOL success = CreateDirectoryW(
      path.String(),
      const_cast<LPSECURITY_ATTRIBUTES>(&perms.securityAttributes));
  if (success) {
    return S_OK;
  }
  DWORD error = GetLastError();
  if (error == ERROR_ALREADY_EXISTS) {
    return S_OK;
  }
  return HRESULT_FROM_WIN32(error);
}
#endif  // XP_WIN