1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #ifndef util_h__
8 #define util_h__
9
10 #include <cassert>
11 #include <cstdlib>
12 #include <iomanip>
13 #include <iostream>
14 #include <sstream>
15 #include <sys/stat.h>
16 #include <vector>
17 #if defined(_WIN32)
18 #include <windows.h>
19 #include <codecvt>
20 #include <direct.h>
21 #else
22 #include <unistd.h>
23 #endif
24
25 #include <nspr.h>
26
hex_string_to_bytes(std::string s)27 static inline std::vector<uint8_t> hex_string_to_bytes(std::string s) {
28 std::vector<uint8_t> bytes;
29 for (size_t i = 0; i < s.length(); i += 2) {
30 bytes.push_back(std::stoul(s.substr(i, 2), nullptr, 16));
31 }
32 return bytes;
33 }
34
35 // Given a prefix, attempts to create a unique directory that the user can do
36 // work in without impacting other tests. For example, if given the prefix
37 // "scratch", a directory like "scratch05c17b25" will be created in the current
38 // working directory (or the location specified by NSS_GTEST_WORKDIR, if
39 // defined).
40 // Upon destruction, the implementation will attempt to delete the directory.
41 // However, no attempt is made to first remove files in the directory - the
42 // user is responsible for this. If the directory is not empty, deleting it will
43 // fail.
44 // Statistically, it is technically possible to fail to create a unique
45 // directory name, but this is extremely unlikely given the expected workload of
46 // this implementation.
47 class ScopedUniqueDirectory {
48 public:
ScopedUniqueDirectory(const std::string & prefix)49 explicit ScopedUniqueDirectory(const std::string &prefix) {
50 std::string path;
51 const char *workingDirectory = PR_GetEnvSecure("NSS_GTEST_WORKDIR");
52 if (workingDirectory) {
53 path.assign(workingDirectory);
54 }
55 path.append(prefix);
56 for (int i = 0; i < RETRY_LIMIT; i++) {
57 std::string pathCopy(path);
58 // TryMakingDirectory will modify its input. If it fails, we want to throw
59 // away the modified result.
60 if (TryMakingDirectory(pathCopy)) {
61 mPath.assign(pathCopy);
62 break;
63 }
64 }
65 assert(mPath.length() > 0);
66 #if defined(_WIN32)
67 // sqldb always uses UTF-8 regardless of the current system locale.
68 DWORD len =
69 MultiByteToWideChar(CP_ACP, 0, mPath.data(), mPath.size(), nullptr, 0);
70 std::vector<wchar_t> buf(len, L'\0');
71 MultiByteToWideChar(CP_ACP, 0, mPath.data(), mPath.size(), buf.data(),
72 buf.size());
73 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
74 mUTF8Path = converter.to_bytes(std::wstring(buf.begin(), buf.end()));
75 #else
76 mUTF8Path = mPath;
77 #endif
78 }
79
80 // NB: the directory must be empty upon destruction
~ScopedUniqueDirectory()81 ~ScopedUniqueDirectory() { assert(rmdir(mPath.c_str()) == 0); }
82
GetPath()83 const std::string &GetPath() { return mPath; }
GetUTF8Path()84 const std::string &GetUTF8Path() { return mUTF8Path; }
85
86 private:
87 static const int RETRY_LIMIT = 5;
88
GenerateRandomName(std::string & prefix)89 static void GenerateRandomName(/*in/out*/ std::string &prefix) {
90 std::stringstream ss;
91 ss << prefix;
92 // RAND_MAX is at least 32767.
93 ss << std::setfill('0') << std::setw(4) << std::hex << rand() << rand();
94 // This will overwrite the value of prefix. This is a little inefficient,
95 // but at least it makes the code simple.
96 ss >> prefix;
97 }
98
TryMakingDirectory(std::string & prefix)99 static bool TryMakingDirectory(/*in/out*/ std::string &prefix) {
100 GenerateRandomName(prefix);
101 #if defined(_WIN32)
102 return _mkdir(prefix.c_str()) == 0;
103 #else
104 return mkdir(prefix.c_str(), 0777) == 0;
105 #endif
106 }
107
108 std::string mPath;
109 std::string mUTF8Path;
110 };
111
112 #endif // util_h__
113