1 | //===- Support/FileUtilities.cpp - File System Utilities ------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file implements a family of utility functions which are useful for doing |
10 | // various things with files. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "llvm/Support/FileUtilities.h" |
15 | #include "llvm/ADT/SmallString.h" |
16 | #include "llvm/ADT/StringExtras.h" |
17 | #include "llvm/Support/Error.h" |
18 | #include "llvm/Support/ErrorOr.h" |
19 | #include "llvm/Support/MemoryBuffer.h" |
20 | #include "llvm/Support/Process.h" |
21 | #include "llvm/Support/raw_ostream.h" |
22 | #include <cmath> |
23 | #include <cstdint> |
24 | #include <cstdlib> |
25 | #include <cstring> |
26 | #include <memory> |
27 | #include <system_error> |
28 | |
29 | using namespace llvm; |
30 | |
31 | static bool isSignedChar(char C) { |
32 | return (C == '+' || C == '-'); |
33 | } |
34 | |
35 | static bool isExponentChar(char C) { |
36 | switch (C) { |
37 | case 'D': // Strange exponential notation. |
38 | case 'd': // Strange exponential notation. |
39 | case 'e': |
40 | case 'E': return true; |
41 | default: return false; |
42 | } |
43 | } |
44 | |
45 | static bool isNumberChar(char C) { |
46 | switch (C) { |
47 | case '0': case '1': case '2': case '3': case '4': |
48 | case '5': case '6': case '7': case '8': case '9': |
49 | case '.': return true; |
50 | default: return isSignedChar(C) || isExponentChar(C); |
51 | } |
52 | } |
53 | |
54 | static const char *BackupNumber(const char *Pos, const char *FirstChar) { |
55 | // If we didn't stop in the middle of a number, don't backup. |
56 | if (!isNumberChar(C: *Pos)) return Pos; |
57 | |
58 | // Otherwise, return to the start of the number. |
59 | bool HasPeriod = false; |
60 | while (Pos > FirstChar && isNumberChar(C: Pos[-1])) { |
61 | // Backup over at most one period. |
62 | if (Pos[-1] == '.') { |
63 | if (HasPeriod) |
64 | break; |
65 | HasPeriod = true; |
66 | } |
67 | |
68 | --Pos; |
69 | if (Pos > FirstChar && isSignedChar(C: Pos[0]) && !isExponentChar(C: Pos[-1])) |
70 | break; |
71 | } |
72 | return Pos; |
73 | } |
74 | |
75 | /// EndOfNumber - Return the first character that is not part of the specified |
76 | /// number. This assumes that the buffer is null terminated, so it won't fall |
77 | /// off the end. |
78 | static const char *EndOfNumber(const char *Pos) { |
79 | while (isNumberChar(C: *Pos)) |
80 | ++Pos; |
81 | return Pos; |
82 | } |
83 | |
84 | /// CompareNumbers - compare two numbers, returning true if they are different. |
85 | static bool CompareNumbers(const char *&F1P, const char *&F2P, |
86 | const char *F1End, const char *F2End, |
87 | double AbsTolerance, double RelTolerance, |
88 | std::string *ErrorMsg) { |
89 | const char *F1NumEnd, *F2NumEnd; |
90 | double V1 = 0.0, V2 = 0.0; |
91 | |
92 | // If one of the positions is at a space and the other isn't, chomp up 'til |
93 | // the end of the space. |
94 | while (isSpace(C: static_cast<unsigned char>(*F1P)) && F1P != F1End) |
95 | ++F1P; |
96 | while (isSpace(C: static_cast<unsigned char>(*F2P)) && F2P != F2End) |
97 | ++F2P; |
98 | |
99 | // If we stop on numbers, compare their difference. |
100 | if (!isNumberChar(C: *F1P) || !isNumberChar(C: *F2P)) { |
101 | // The diff failed. |
102 | F1NumEnd = F1P; |
103 | F2NumEnd = F2P; |
104 | } else { |
105 | // Note that some ugliness is built into this to permit support for numbers |
106 | // that use "D" or "d" as their exponential marker, e.g. "1.234D45". This |
107 | // occurs in 200.sixtrack in spec2k. |
108 | V1 = strtod(nptr: F1P, endptr: const_cast<char**>(&F1NumEnd)); |
109 | V2 = strtod(nptr: F2P, endptr: const_cast<char**>(&F2NumEnd)); |
110 | |
111 | if (*F1NumEnd == 'D' || *F1NumEnd == 'd') { |
112 | // Copy string into tmp buffer to replace the 'D' with an 'e'. |
113 | SmallString<200> StrTmp(F1P, EndOfNumber(Pos: F1NumEnd)+1); |
114 | // Strange exponential notation! |
115 | StrTmp[static_cast<unsigned>(F1NumEnd-F1P)] = 'e'; |
116 | |
117 | V1 = strtod(nptr: &StrTmp[0], endptr: const_cast<char**>(&F1NumEnd)); |
118 | F1NumEnd = F1P + (F1NumEnd-&StrTmp[0]); |
119 | } |
120 | |
121 | if (*F2NumEnd == 'D' || *F2NumEnd == 'd') { |
122 | // Copy string into tmp buffer to replace the 'D' with an 'e'. |
123 | SmallString<200> StrTmp(F2P, EndOfNumber(Pos: F2NumEnd)+1); |
124 | // Strange exponential notation! |
125 | StrTmp[static_cast<unsigned>(F2NumEnd-F2P)] = 'e'; |
126 | |
127 | V2 = strtod(nptr: &StrTmp[0], endptr: const_cast<char**>(&F2NumEnd)); |
128 | F2NumEnd = F2P + (F2NumEnd-&StrTmp[0]); |
129 | } |
130 | } |
131 | |
132 | if (F1NumEnd == F1P || F2NumEnd == F2P) { |
133 | if (ErrorMsg) { |
134 | *ErrorMsg = "FP Comparison failed, not a numeric difference between '" ; |
135 | *ErrorMsg += F1P[0]; |
136 | *ErrorMsg += "' and '" ; |
137 | *ErrorMsg += F2P[0]; |
138 | *ErrorMsg += "'" ; |
139 | } |
140 | return true; |
141 | } |
142 | |
143 | // Check to see if these are inside the absolute tolerance |
144 | if (AbsTolerance < std::abs(x: V1-V2)) { |
145 | // Nope, check the relative tolerance... |
146 | double Diff; |
147 | if (V2) |
148 | Diff = std::abs(x: V1/V2 - 1.0); |
149 | else if (V1) |
150 | Diff = std::abs(x: V2/V1 - 1.0); |
151 | else |
152 | Diff = 0; // Both zero. |
153 | if (Diff > RelTolerance) { |
154 | if (ErrorMsg) { |
155 | raw_string_ostream(*ErrorMsg) |
156 | << "Compared: " << V1 << " and " << V2 << '\n' |
157 | << "abs. diff = " << std::abs(x: V1-V2) << " rel.diff = " << Diff << '\n' |
158 | << "Out of tolerance: rel/abs: " << RelTolerance << '/' |
159 | << AbsTolerance; |
160 | } |
161 | return true; |
162 | } |
163 | } |
164 | |
165 | // Otherwise, advance our read pointers to the end of the numbers. |
166 | F1P = F1NumEnd; F2P = F2NumEnd; |
167 | return false; |
168 | } |
169 | |
170 | /// DiffFilesWithTolerance - Compare the two files specified, returning 0 if the |
171 | /// files match, 1 if they are different, and 2 if there is a file error. This |
172 | /// function differs from DiffFiles in that you can specify an absolute and |
173 | /// relative FP error that is allowed to exist. If you specify a string to fill |
174 | /// in for the error option, it will set the string to an error message if an |
175 | /// error occurs, allowing the caller to distinguish between a failed diff and a |
176 | /// file system error. |
177 | /// |
178 | int llvm::DiffFilesWithTolerance(StringRef NameA, |
179 | StringRef NameB, |
180 | double AbsTol, double RelTol, |
181 | std::string *Error) { |
182 | // Now its safe to mmap the files into memory because both files |
183 | // have a non-zero size. |
184 | ErrorOr<std::unique_ptr<MemoryBuffer>> F1OrErr = MemoryBuffer::getFile(Filename: NameA); |
185 | if (std::error_code EC = F1OrErr.getError()) { |
186 | if (Error) |
187 | *Error = EC.message(); |
188 | return 2; |
189 | } |
190 | MemoryBuffer &F1 = *F1OrErr.get(); |
191 | |
192 | ErrorOr<std::unique_ptr<MemoryBuffer>> F2OrErr = MemoryBuffer::getFile(Filename: NameB); |
193 | if (std::error_code EC = F2OrErr.getError()) { |
194 | if (Error) |
195 | *Error = EC.message(); |
196 | return 2; |
197 | } |
198 | MemoryBuffer &F2 = *F2OrErr.get(); |
199 | |
200 | // Okay, now that we opened the files, scan them for the first difference. |
201 | const char *File1Start = F1.getBufferStart(); |
202 | const char *File2Start = F2.getBufferStart(); |
203 | const char *File1End = F1.getBufferEnd(); |
204 | const char *File2End = F2.getBufferEnd(); |
205 | const char *F1P = File1Start; |
206 | const char *F2P = File2Start; |
207 | uint64_t A_size = F1.getBufferSize(); |
208 | uint64_t B_size = F2.getBufferSize(); |
209 | |
210 | // Are the buffers identical? Common case: Handle this efficiently. |
211 | if (A_size == B_size && |
212 | std::memcmp(s1: File1Start, s2: File2Start, n: A_size) == 0) |
213 | return 0; |
214 | |
215 | // Otherwise, we are done a tolerances are set. |
216 | if (AbsTol == 0 && RelTol == 0) { |
217 | if (Error) |
218 | *Error = "Files differ without tolerance allowance" ; |
219 | return 1; // Files different! |
220 | } |
221 | |
222 | bool CompareFailed = false; |
223 | while (true) { |
224 | // Scan for the end of file or next difference. |
225 | while (F1P < File1End && F2P < File2End && *F1P == *F2P) { |
226 | ++F1P; |
227 | ++F2P; |
228 | } |
229 | |
230 | if (F1P >= File1End || F2P >= File2End) break; |
231 | |
232 | // Okay, we must have found a difference. Backup to the start of the |
233 | // current number each stream is at so that we can compare from the |
234 | // beginning. |
235 | F1P = BackupNumber(Pos: F1P, FirstChar: File1Start); |
236 | F2P = BackupNumber(Pos: F2P, FirstChar: File2Start); |
237 | |
238 | // Now that we are at the start of the numbers, compare them, exiting if |
239 | // they don't match. |
240 | if (CompareNumbers(F1P, F2P, F1End: File1End, F2End: File2End, AbsTolerance: AbsTol, RelTolerance: RelTol, ErrorMsg: Error)) { |
241 | CompareFailed = true; |
242 | break; |
243 | } |
244 | } |
245 | |
246 | // Okay, we reached the end of file. If both files are at the end, we |
247 | // succeeded. |
248 | bool F1AtEnd = F1P >= File1End; |
249 | bool F2AtEnd = F2P >= File2End; |
250 | if (!CompareFailed && (!F1AtEnd || !F2AtEnd)) { |
251 | // Else, we might have run off the end due to a number: backup and retry. |
252 | if (F1AtEnd && isNumberChar(C: F1P[-1])) --F1P; |
253 | if (F2AtEnd && isNumberChar(C: F2P[-1])) --F2P; |
254 | F1P = BackupNumber(Pos: F1P, FirstChar: File1Start); |
255 | F2P = BackupNumber(Pos: F2P, FirstChar: File2Start); |
256 | |
257 | // Now that we are at the start of the numbers, compare them, exiting if |
258 | // they don't match. |
259 | if (CompareNumbers(F1P, F2P, F1End: File1End, F2End: File2End, AbsTolerance: AbsTol, RelTolerance: RelTol, ErrorMsg: Error)) |
260 | CompareFailed = true; |
261 | |
262 | // If we found the end, we succeeded. |
263 | if (F1P < File1End || F2P < File2End) |
264 | CompareFailed = true; |
265 | } |
266 | |
267 | return CompareFailed; |
268 | } |
269 | |
270 | Expected<FilePermissionsApplier> |
271 | FilePermissionsApplier::create(StringRef InputFilename) { |
272 | sys::fs::file_status Status; |
273 | |
274 | if (InputFilename != "-" ) { |
275 | if (auto EC = sys::fs::status(path: InputFilename, result&: Status)) |
276 | return createFileError(F: InputFilename, EC); |
277 | } else { |
278 | Status.permissions(p: static_cast<sys::fs::perms>(0777)); |
279 | } |
280 | |
281 | return FilePermissionsApplier(InputFilename, Status); |
282 | } |
283 | |
284 | Error FilePermissionsApplier::apply( |
285 | StringRef OutputFilename, bool CopyDates, |
286 | std::optional<sys::fs::perms> OverwritePermissions) { |
287 | sys::fs::file_status Status = InputStatus; |
288 | |
289 | if (OverwritePermissions) |
290 | Status.permissions(p: *OverwritePermissions); |
291 | |
292 | int FD = 0; |
293 | |
294 | // Writing to stdout should not be treated as an error here, just |
295 | // do not set access/modification times or permissions. |
296 | if (OutputFilename == "-" ) |
297 | return Error::success(); |
298 | |
299 | if (std::error_code EC = sys::fs::openFileForWrite(Name: OutputFilename, ResultFD&: FD, |
300 | Disp: sys::fs::CD_OpenExisting)) |
301 | return createFileError(F: OutputFilename, EC); |
302 | |
303 | if (CopyDates) |
304 | if (std::error_code EC = sys::fs::setLastAccessAndModificationTime( |
305 | FD, AccessTime: Status.getLastAccessedTime(), ModificationTime: Status.getLastModificationTime())) |
306 | return createFileError(F: OutputFilename, EC); |
307 | |
308 | sys::fs::file_status OStat; |
309 | if (std::error_code EC = sys::fs::status(FD, Result&: OStat)) |
310 | return createFileError(F: OutputFilename, EC); |
311 | if (OStat.type() == sys::fs::file_type::regular_file) { |
312 | #ifndef _WIN32 |
313 | // Keep ownership if llvm-objcopy is called under root. |
314 | if (OutputFilename == InputFilename && OStat.getUser() == 0) |
315 | sys::fs::changeFileOwnership(FD, Owner: Status.getUser(), Group: Status.getGroup()); |
316 | #endif |
317 | |
318 | sys::fs::perms Perm = Status.permissions(); |
319 | if (OutputFilename != InputFilename) |
320 | Perm = static_cast<sys::fs::perms>(Perm & ~sys::fs::getUmask() & ~06000); |
321 | #ifdef _WIN32 |
322 | if (std::error_code EC = sys::fs::setPermissions(OutputFilename, Perm)) |
323 | #else |
324 | if (std::error_code EC = sys::fs::setPermissions(FD, Permissions: Perm)) |
325 | #endif |
326 | return createFileError(F: OutputFilename, EC); |
327 | } |
328 | |
329 | if (std::error_code EC = sys::Process::SafelyCloseFileDescriptor(FD)) |
330 | return createFileError(F: OutputFilename, EC); |
331 | |
332 | return Error::success(); |
333 | } |
334 | |