2
2
//
3
3
// This source file is part of the Swift.org open source project
4
4
//
5
- // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5
+ // Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
6
6
// Licensed under Apache License v2.0 with Runtime Library Exception
7
7
//
8
8
// See https://swift.org/LICENSE.txt for license information
11
11
// ===----------------------------------------------------------------------===//
12
12
13
13
#include " swift/Basic/FileSystem.h"
14
+
15
+ #include " swift/Basic/LLVM.h"
16
+ #include " clang/Basic/FileManager.h"
17
+ #include " llvm/ADT/Twine.h"
18
+ #include " llvm/Support/Errc.h"
14
19
#include " llvm/Support/FileSystem.h"
20
+ #include " llvm/Support/Path.h"
15
21
#include " llvm/Support/Process.h"
16
- #include " clang/Basic/FileManager .h"
22
+ #include " llvm/Support/Signals .h"
17
23
18
24
using namespace swift ;
19
25
@@ -30,6 +36,137 @@ namespace {
30
36
};
31
37
} // end anonymous namespace
32
38
39
+ // / Does some simple checking to see if a temporary file can be written next to
40
+ // / \p outputPath and then renamed into place.
41
+ // /
42
+ // / Helper for swift::atomicallyWritingToFile.
43
+ // /
44
+ // / If the result is an error, the write won't succeed at all, and the calling
45
+ // / operation should bail out early.
46
+ static llvm::ErrorOr<bool >
47
+ canUseTemporaryForWrite (const StringRef outputPath) {
48
+ namespace fs = llvm::sys::fs;
49
+
50
+ if (outputPath == " -" ) {
51
+ // Special case: "-" represents stdout, and LLVM's output stream APIs are
52
+ // aware of this. It doesn't make sense to use a temporary in this case.
53
+ return false ;
54
+ }
55
+
56
+ fs::file_status status;
57
+ (void )fs::status (outputPath, status);
58
+ if (!fs::exists (status)) {
59
+ // Assume we'll be able to write to both a temporary file and to the final
60
+ // destination if the final destination doesn't exist yet.
61
+ return true ;
62
+ }
63
+
64
+ // Fail early if we can't write to the final destination.
65
+ if (!fs::can_write (outputPath))
66
+ return llvm::make_error_code (llvm::errc::operation_not_permitted);
67
+
68
+ // Only use a temporary if the output is a regular file. This handles
69
+ // things like '-o /dev/null'
70
+ return fs::is_regular_file (status);
71
+ }
72
+
73
+ // / Attempts to open a temporary file next to \p outputPath, with the intent
74
+ // / that once the file has been written it will be renamed into place.
75
+ // /
76
+ // / Helper for swift::atomicallyWritingToFile.
77
+ // /
78
+ // / \param[out] openedStream On success, a stream opened for writing to the
79
+ // / temporary file that was just created.
80
+ // / \param outputPath The path to the final output file, which is used to decide
81
+ // / where to put the temporary.
82
+ // / \param openFlags Controls how the output stream will be opened.
83
+ // /
84
+ // / \returns The path to the temporary file that was opened, or \c None if the
85
+ // / file couldn't be created.
86
+ static Optional<std::string>
87
+ tryToOpenTemporaryFile (Optional<llvm::raw_fd_ostream> &openedStream,
88
+ const StringRef outputPath,
89
+ const llvm::sys::fs::OpenFlags openFlags) {
90
+ namespace fs = llvm::sys::fs;
91
+
92
+ // Create a temporary file path.
93
+ // Insert a placeholder for a random suffix before the extension (if any).
94
+ // Then because some tools glob for build artifacts (such as clang's own
95
+ // GlobalModuleIndex.cpp), also append .tmp.
96
+ SmallString<128 > tempPath;
97
+ const StringRef outputExtension = llvm::sys::path::extension (outputPath);
98
+ tempPath = outputPath.drop_back (outputExtension.size ());
99
+ tempPath += " -%%%%%%%%" ;
100
+ tempPath += outputExtension;
101
+ tempPath += " .tmp" ;
102
+
103
+ int fd;
104
+ const unsigned perms = fs::all_read | fs::all_write;
105
+ std::error_code EC = fs::createUniqueFile (tempPath, fd, tempPath, perms,
106
+ openFlags);
107
+
108
+ if (EC) {
109
+ // Ignore the specific error; the caller has to fall back to not using a
110
+ // temporary anyway.
111
+ return None;
112
+ }
113
+
114
+ openedStream.emplace (fd, /* shouldClose=*/ true );
115
+ // Make sure the temporary file gets removed if we crash.
116
+ llvm::sys::RemoveFileOnSignal (tempPath);
117
+ return tempPath.str ().str ();
118
+ }
119
+
120
+ std::error_code swift::atomicallyWritingToFile (
121
+ const StringRef outputPath, const bool binaryMode,
122
+ const llvm::function_ref<void (llvm::raw_pwrite_stream &)> action) {
123
+ namespace fs = llvm::sys::fs;
124
+
125
+ // FIXME: This is mostly a simplified version of
126
+ // clang::CompilerInstance::createOutputFile. It would be great to share the
127
+ // implementation.
128
+ assert (!outputPath.empty ());
129
+
130
+ llvm::ErrorOr<bool > canUseTemporary = canUseTemporaryForWrite (outputPath);
131
+ if (std::error_code error = canUseTemporary.getError ())
132
+ return error;
133
+
134
+ Optional<std::string> temporaryPath;
135
+ {
136
+ const fs::OpenFlags openFlags = (binaryMode ? fs::F_None : fs::F_Text);
137
+
138
+ Optional<llvm::raw_fd_ostream> OS;
139
+ if (canUseTemporary.get ()) {
140
+ temporaryPath = tryToOpenTemporaryFile (OS, outputPath, openFlags);
141
+
142
+ if (!temporaryPath) {
143
+ assert (!OS.hasValue ());
144
+ // If we failed to create the temporary, fall back to writing to the
145
+ // file directly. This handles the corner case where we cannot write to
146
+ // the directory, but can write to the file.
147
+ }
148
+ }
149
+
150
+ if (!OS.hasValue ()) {
151
+ std::error_code error;
152
+ OS.emplace (outputPath, error, openFlags);
153
+ if (error)
154
+ return error;
155
+ }
156
+
157
+ action (OS.getValue ());
158
+ // In addition to scoping the use of 'OS', ending the scope here also
159
+ // ensures that it's been flushed (by destroying it).
160
+ }
161
+
162
+ if (!temporaryPath.hasValue ()) {
163
+ // If we didn't use a temporary, we're done!
164
+ return std::error_code ();
165
+ }
166
+
167
+ return swift::moveFileIfDifferent (temporaryPath.getValue (), outputPath);
168
+ }
169
+
33
170
std::error_code swift::moveFileIfDifferent (const llvm::Twine &source,
34
171
const llvm::Twine &destination) {
35
172
namespace fs = llvm::sys::fs;
0 commit comments