b529b8f04ca4a1553bc894c5cf3095f857847f0a
[platform/upstream/cmake.git] / Source / cmGeneratedFileStream.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 #include "cmGeneratedFileStream.h"
4
5 #include <cstdio>
6
7 #include "cmStringAlgorithms.h"
8 #include "cmSystemTools.h"
9
10 #if !defined(CMAKE_BOOTSTRAP)
11 #  include <cm3p/zlib.h>
12
13 #  include "cm_codecvt.hxx"
14 #endif
15
16 cmGeneratedFileStream::cmGeneratedFileStream(Encoding encoding)
17   : OriginalLocale(this->getloc())
18 {
19 #ifndef CMAKE_BOOTSTRAP
20   if (encoding != codecvt::None) {
21     this->imbue(std::locale(this->OriginalLocale, new codecvt(encoding)));
22   }
23 #else
24   static_cast<void>(encoding);
25 #endif
26 }
27
28 cmGeneratedFileStream::cmGeneratedFileStream(std::string const& name,
29                                              bool quiet, Encoding encoding)
30   : cmGeneratedFileStreamBase(name)
31   , Stream(this->TempName.c_str())
32 {
33   // Check if the file opened.
34   if (!*this && !quiet) {
35     cmSystemTools::Error("Cannot open file for write: " + this->TempName);
36     cmSystemTools::ReportLastSystemError("");
37   }
38 #ifndef CMAKE_BOOTSTRAP
39   if (encoding != codecvt::None) {
40     this->imbue(std::locale(this->getloc(), new codecvt(encoding)));
41   }
42 #else
43   static_cast<void>(encoding);
44 #endif
45   if (encoding == codecvt::UTF8_WITH_BOM) {
46     // Write the BOM encoding header into the file
47     char magic[] = { static_cast<char>(0xEF), static_cast<char>(0xBB),
48                      static_cast<char>(0xBF) };
49     this->write(magic, 3);
50   }
51 }
52
53 cmGeneratedFileStream::~cmGeneratedFileStream()
54 {
55   // This is the first destructor called.  Check the status of the
56   // stream and give the information to the private base.  Next the
57   // stream will be destroyed which will close the temporary file.
58   // Finally the base destructor will be called to replace the
59   // destination file.
60   this->Okay = !this->fail();
61 }
62
63 cmGeneratedFileStream& cmGeneratedFileStream::Open(std::string const& name,
64                                                    bool quiet, bool binaryFlag)
65 {
66   // Store the file name and construct the temporary file name.
67   this->cmGeneratedFileStreamBase::Open(name);
68
69   // Open the temporary output file.
70   if (binaryFlag) {
71     this->Stream::open(this->TempName.c_str(),
72                        std::ios::out | std::ios::binary);
73   } else {
74     this->Stream::open(this->TempName.c_str());
75   }
76
77   // Check if the file opened.
78   if (!*this && !quiet) {
79     cmSystemTools::Error("Cannot open file for write: " + this->TempName);
80     cmSystemTools::ReportLastSystemError("");
81   }
82   return *this;
83 }
84
85 bool cmGeneratedFileStream::Close()
86 {
87   // Save whether the temporary output file is valid before closing.
88   this->Okay = !this->fail();
89
90   // Close the temporary output file.
91   this->Stream::close();
92
93   // Remove the temporary file (possibly by renaming to the real file).
94   return this->cmGeneratedFileStreamBase::Close();
95 }
96
97 void cmGeneratedFileStream::SetCopyIfDifferent(bool copy_if_different)
98 {
99   this->CopyIfDifferent = copy_if_different;
100 }
101
102 void cmGeneratedFileStream::SetCompression(bool compression)
103 {
104   this->Compress = compression;
105 }
106
107 void cmGeneratedFileStream::SetCompressionExtraExtension(bool ext)
108 {
109   this->CompressExtraExtension = ext;
110 }
111
112 cmGeneratedFileStreamBase::cmGeneratedFileStreamBase() = default;
113
114 cmGeneratedFileStreamBase::cmGeneratedFileStreamBase(std::string const& name)
115 {
116   this->Open(name);
117 }
118
119 cmGeneratedFileStreamBase::~cmGeneratedFileStreamBase()
120 {
121   this->Close();
122 }
123
124 void cmGeneratedFileStreamBase::Open(std::string const& name)
125 {
126   // Save the original name of the file.
127   this->Name = name;
128
129   // Create the name of the temporary file.
130   this->TempName = name;
131 #if defined(__VMS)
132   this->TempName += "_";
133 #else
134   this->TempName += ".";
135 #endif
136   if (!this->TempExt.empty()) {
137     this->TempName += this->TempExt;
138   } else {
139     char buf[64];
140     snprintf(buf, sizeof(buf), "tmp%05x",
141              cmSystemTools::RandomSeed() & 0xFFFFF);
142     this->TempName += buf;
143   }
144
145   // Make sure the temporary file that will be used is not present.
146   cmSystemTools::RemoveFile(this->TempName);
147
148   std::string dir = cmSystemTools::GetFilenamePath(this->TempName);
149   cmSystemTools::MakeDirectory(dir);
150 }
151
152 bool cmGeneratedFileStreamBase::Close()
153 {
154   bool replaced = false;
155
156   std::string resname = this->Name;
157   if (this->Compress && this->CompressExtraExtension) {
158     resname += ".gz";
159   }
160
161   // Only consider replacing the destination file if no error
162   // occurred.
163   if (!this->Name.empty() && this->Okay &&
164       (!this->CopyIfDifferent ||
165        cmSystemTools::FilesDiffer(this->TempName, resname))) {
166     // The destination is to be replaced.  Rename the temporary to the
167     // destination atomically.
168     if (this->Compress) {
169       std::string gzname = cmStrCat(this->TempName, ".temp.gz");
170       if (this->CompressFile(this->TempName, gzname)) {
171         this->RenameFile(gzname, resname);
172       }
173       cmSystemTools::RemoveFile(gzname);
174     } else {
175       this->RenameFile(this->TempName, resname);
176     }
177
178     replaced = true;
179   }
180
181   // Else, the destination was not replaced.
182   //
183   // Always delete the temporary file. We never want it to stay around.
184   if (!this->TempName.empty()) {
185     cmSystemTools::RemoveFile(this->TempName);
186   }
187
188   return replaced;
189 }
190
191 #ifndef CMAKE_BOOTSTRAP
192 int cmGeneratedFileStreamBase::CompressFile(std::string const& oldname,
193                                             std::string const& newname)
194 {
195   gzFile gf = gzopen(newname.c_str(), "w");
196   if (!gf) {
197     return 0;
198   }
199   FILE* ifs = cmsys::SystemTools::Fopen(oldname, "r");
200   if (!ifs) {
201     gzclose(gf);
202     return 0;
203   }
204   size_t res;
205   const size_t BUFFER_SIZE = 1024;
206   char buffer[BUFFER_SIZE];
207   while ((res = fread(buffer, 1, BUFFER_SIZE, ifs)) > 0) {
208     if (!gzwrite(gf, buffer, static_cast<int>(res))) {
209       fclose(ifs);
210       gzclose(gf);
211       return 0;
212     }
213   }
214   fclose(ifs);
215   gzclose(gf);
216   return 1;
217 }
218 #else
219 int cmGeneratedFileStreamBase::CompressFile(std::string const&,
220                                             std::string const&)
221 {
222   return 0;
223 }
224 #endif
225
226 int cmGeneratedFileStreamBase::RenameFile(std::string const& oldname,
227                                           std::string const& newname)
228 {
229   return cmSystemTools::RenameFile(oldname, newname);
230 }
231
232 void cmGeneratedFileStream::SetName(const std::string& fname)
233 {
234   this->Name = fname;
235 }
236
237 void cmGeneratedFileStream::SetTempExt(std::string const& ext)
238 {
239   this->TempExt = ext;
240 }
241
242 void cmGeneratedFileStream::WriteRaw(std::string const& data)
243 {
244 #ifndef CMAKE_BOOTSTRAP
245   std::locale activeLocale = this->imbue(this->OriginalLocale);
246   this->write(data.data(), data.size());
247   this->imbue(activeLocale);
248 #else
249   this->write(data.data(), data.size());
250 #endif
251 }