Imported Upstream version 3.25.0
[platform/upstream/cmake.git] / Source / cmFileCommand.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 "cmFileCommand.h"
4
5 #include <algorithm>
6 #include <cassert>
7 #include <cctype>
8 #include <cmath>
9 #include <cstdio>
10 #include <cstdlib>
11 #include <map>
12 #include <set>
13 #include <sstream>
14 #include <utility>
15 #include <vector>
16
17 #include <cm/memory>
18 #include <cm/optional>
19 #include <cm/string_view>
20 #include <cmext/algorithm>
21 #include <cmext/string_view>
22
23 #include <cm3p/kwiml/int.h>
24
25 #include "cmsys/FStream.hxx"
26 #include "cmsys/Glob.hxx"
27 #include "cmsys/RegularExpression.hxx"
28
29 #include "cm_sys_stat.h"
30
31 #include "cmArgumentParser.h"
32 #include "cmArgumentParserTypes.h"
33 #include "cmCMakePath.h"
34 #include "cmCryptoHash.h"
35 #include "cmELF.h"
36 #include "cmExecutionStatus.h"
37 #include "cmFSPermissions.h"
38 #include "cmFileCopier.h"
39 #include "cmFileInstaller.h"
40 #include "cmFileLockPool.h"
41 #include "cmFileTimes.h"
42 #include "cmGeneratedFileStream.h"
43 #include "cmGeneratorExpression.h"
44 #include "cmGlobalGenerator.h"
45 #include "cmHexFileConverter.h"
46 #include "cmListFileCache.h"
47 #include "cmMakefile.h"
48 #include "cmMessageType.h"
49 #include "cmNewLineStyle.h"
50 #include "cmPolicies.h"
51 #include "cmRange.h"
52 #include "cmRuntimeDependencyArchive.h"
53 #include "cmState.h"
54 #include "cmStringAlgorithms.h"
55 #include "cmSubcommandTable.h"
56 #include "cmSystemTools.h"
57 #include "cmTimestamp.h"
58 #include "cmValue.h"
59 #include "cmWorkingDirectory.h"
60 #include "cmake.h"
61
62 #if !defined(CMAKE_BOOTSTRAP)
63 #  include <cm3p/curl/curl.h>
64
65 #  include "cmCurl.h"
66 #  include "cmFileLockResult.h"
67 #endif
68
69 namespace {
70
71 bool HandleWriteImpl(std::vector<std::string> const& args, bool append,
72                      cmExecutionStatus& status)
73 {
74   auto i = args.begin();
75
76   i++; // Get rid of subcommand
77
78   std::string fileName = *i;
79   if (!cmsys::SystemTools::FileIsFullPath(*i)) {
80     fileName =
81       cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', *i);
82   }
83
84   i++;
85
86   if (!status.GetMakefile().CanIWriteThisFile(fileName)) {
87     std::string e =
88       "attempted to write a file: " + fileName + " into a source directory.";
89     status.SetError(e);
90     cmSystemTools::SetFatalErrorOccurred();
91     return false;
92   }
93   std::string dir = cmSystemTools::GetFilenamePath(fileName);
94   cmSystemTools::MakeDirectory(dir);
95
96   mode_t mode = 0;
97   bool writable = false;
98
99   // Set permissions to writable
100   if (cmSystemTools::GetPermissions(fileName, mode)) {
101 #if defined(_MSC_VER) || defined(__MINGW32__)
102     writable = (mode & S_IWRITE) != 0;
103     mode_t newMode = mode | S_IWRITE;
104 #else
105     writable = mode & S_IWUSR;
106     mode_t newMode = mode | S_IWUSR | S_IWGRP;
107 #endif
108     if (!writable) {
109       cmSystemTools::SetPermissions(fileName, newMode);
110     }
111   }
112   // If GetPermissions fails, pretend like it is ok. File open will fail if
113   // the file is not writable
114   cmsys::ofstream file(fileName.c_str(),
115                        append ? std::ios::app : std::ios::out);
116   if (!file) {
117     std::string error =
118       cmStrCat("failed to open for writing (",
119                cmSystemTools::GetLastSystemError(), "):\n  ", fileName);
120     status.SetError(error);
121     return false;
122   }
123   std::string message = cmJoin(cmMakeRange(i, args.end()), std::string());
124   file << message;
125   if (!file) {
126     std::string error =
127       cmStrCat("write failed (", cmSystemTools::GetLastSystemError(), "):\n  ",
128                fileName);
129     status.SetError(error);
130     return false;
131   }
132   file.close();
133   if (mode && !writable) {
134     cmSystemTools::SetPermissions(fileName, mode);
135   }
136   return true;
137 }
138
139 bool HandleWriteCommand(std::vector<std::string> const& args,
140                         cmExecutionStatus& status)
141 {
142   return HandleWriteImpl(args, false, status);
143 }
144
145 bool HandleAppendCommand(std::vector<std::string> const& args,
146                          cmExecutionStatus& status)
147 {
148   return HandleWriteImpl(args, true, status);
149 }
150
151 bool HandleReadCommand(std::vector<std::string> const& args,
152                        cmExecutionStatus& status)
153 {
154   if (args.size() < 3) {
155     status.SetError("READ must be called with at least two additional "
156                     "arguments");
157     return false;
158   }
159
160   std::string const& fileNameArg = args[1];
161   std::string const& variable = args[2];
162
163   struct Arguments
164   {
165     std::string Offset;
166     std::string Limit;
167     bool Hex = false;
168   };
169
170   static auto const parser = cmArgumentParser<Arguments>{}
171                                .Bind("OFFSET"_s, &Arguments::Offset)
172                                .Bind("LIMIT"_s, &Arguments::Limit)
173                                .Bind("HEX"_s, &Arguments::Hex);
174
175   Arguments const arguments = parser.Parse(cmMakeRange(args).advance(3),
176                                            /*unparsedArguments=*/nullptr);
177
178   std::string fileName = fileNameArg;
179   if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
180     fileName = cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/',
181                         fileNameArg);
182   }
183
184 // Open the specified file.
185 #if defined(_WIN32) || defined(__CYGWIN__)
186   cmsys::ifstream file(fileName.c_str(),
187                        arguments.Hex ? (std::ios::binary | std::ios::in)
188                                      : std::ios::in);
189 #else
190   cmsys::ifstream file(fileName.c_str());
191 #endif
192
193   if (!file) {
194     std::string error =
195       cmStrCat("failed to open for reading (",
196                cmSystemTools::GetLastSystemError(), "):\n  ", fileName);
197     status.SetError(error);
198     return false;
199   }
200
201   // is there a limit?
202   std::string::size_type sizeLimit = std::string::npos;
203   if (!arguments.Limit.empty()) {
204     unsigned long long limit;
205     if (cmStrToULongLong(arguments.Limit, &limit)) {
206       sizeLimit = static_cast<std::string::size_type>(limit);
207     }
208   }
209
210   // is there an offset?
211   cmsys::ifstream::off_type offset = 0;
212   if (!arguments.Offset.empty()) {
213     long long off;
214     if (cmStrToLongLong(arguments.Offset, &off)) {
215       offset = static_cast<cmsys::ifstream::off_type>(off);
216     }
217   }
218
219   file.seekg(offset, std::ios::beg); // explicit ios::beg for IBM VisualAge 6
220
221   std::string output;
222
223   if (arguments.Hex) {
224     // Convert part of the file into hex code
225     char c;
226     while ((sizeLimit > 0) && (file.get(c))) {
227       char hex[4];
228       snprintf(hex, sizeof(hex), "%.2x", c & 0xff);
229       output += hex;
230       sizeLimit--;
231     }
232   } else {
233     std::string line;
234     bool has_newline = false;
235     while (
236       sizeLimit > 0 &&
237       cmSystemTools::GetLineFromStream(file, line, &has_newline, sizeLimit)) {
238       sizeLimit = sizeLimit - line.size();
239       if (has_newline && sizeLimit > 0) {
240         sizeLimit--;
241       }
242       output += line;
243       if (has_newline) {
244         output += "\n";
245       }
246     }
247   }
248   status.GetMakefile().AddDefinition(variable, output);
249   return true;
250 }
251
252 bool HandleHashCommand(std::vector<std::string> const& args,
253                        cmExecutionStatus& status)
254 {
255 #if !defined(CMAKE_BOOTSTRAP)
256   if (args.size() != 3) {
257     status.SetError(
258       cmStrCat(args[0], " requires a file name and output variable"));
259     return false;
260   }
261
262   std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0]));
263   if (hash) {
264     std::string out = hash->HashFile(args[1]);
265     if (!out.empty()) {
266       status.GetMakefile().AddDefinition(args[2], out);
267       return true;
268     }
269     status.SetError(cmStrCat(args[0], " failed to read file \"", args[1],
270                              "\": ", cmSystemTools::GetLastSystemError()));
271   }
272   return false;
273 #else
274   status.SetError(cmStrCat(args[0], " not available during bootstrap"));
275   return false;
276 #endif
277 }
278
279 bool HandleStringsCommand(std::vector<std::string> const& args,
280                           cmExecutionStatus& status)
281 {
282   if (args.size() < 3) {
283     status.SetError("STRINGS requires a file name and output variable");
284     return false;
285   }
286
287   // Get the file to read.
288   std::string fileName = args[1];
289   if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
290     fileName =
291       cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
292   }
293
294   // Get the variable in which to store the results.
295   std::string const& outVar = args[2];
296
297   // Parse the options.
298   enum
299   {
300     arg_none,
301     arg_limit_input,
302     arg_limit_output,
303     arg_limit_count,
304     arg_length_minimum,
305     arg_length_maximum,
306     arg_maximum,
307     arg_regex,
308     arg_encoding
309   };
310   unsigned int minlen = 0;
311   unsigned int maxlen = 0;
312   int limit_input = -1;
313   int limit_output = -1;
314   unsigned int limit_count = 0;
315   cmsys::RegularExpression regex;
316   bool have_regex = false;
317   bool newline_consume = false;
318   bool hex_conversion_enabled = true;
319   enum
320   {
321     encoding_none = cmsys::FStream::BOM_None,
322     encoding_utf8 = cmsys::FStream::BOM_UTF8,
323     encoding_utf16le = cmsys::FStream::BOM_UTF16LE,
324     encoding_utf16be = cmsys::FStream::BOM_UTF16BE,
325     encoding_utf32le = cmsys::FStream::BOM_UTF32LE,
326     encoding_utf32be = cmsys::FStream::BOM_UTF32BE
327   };
328   int encoding = encoding_none;
329   int arg_mode = arg_none;
330   for (unsigned int i = 3; i < args.size(); ++i) {
331     if (args[i] == "LIMIT_INPUT") {
332       arg_mode = arg_limit_input;
333     } else if (args[i] == "LIMIT_OUTPUT") {
334       arg_mode = arg_limit_output;
335     } else if (args[i] == "LIMIT_COUNT") {
336       arg_mode = arg_limit_count;
337     } else if (args[i] == "LENGTH_MINIMUM") {
338       arg_mode = arg_length_minimum;
339     } else if (args[i] == "LENGTH_MAXIMUM") {
340       arg_mode = arg_length_maximum;
341     } else if (args[i] == "REGEX") {
342       arg_mode = arg_regex;
343     } else if (args[i] == "NEWLINE_CONSUME") {
344       newline_consume = true;
345       arg_mode = arg_none;
346     } else if (args[i] == "NO_HEX_CONVERSION") {
347       hex_conversion_enabled = false;
348       arg_mode = arg_none;
349     } else if (args[i] == "ENCODING") {
350       arg_mode = arg_encoding;
351     } else if (arg_mode == arg_limit_input) {
352       if (sscanf(args[i].c_str(), "%d", &limit_input) != 1 ||
353           limit_input < 0) {
354         status.SetError(cmStrCat("STRINGS option LIMIT_INPUT value \"",
355                                  args[i], "\" is not an unsigned integer."));
356         return false;
357       }
358       arg_mode = arg_none;
359     } else if (arg_mode == arg_limit_output) {
360       if (sscanf(args[i].c_str(), "%d", &limit_output) != 1 ||
361           limit_output < 0) {
362         status.SetError(cmStrCat("STRINGS option LIMIT_OUTPUT value \"",
363                                  args[i], "\" is not an unsigned integer."));
364         return false;
365       }
366       arg_mode = arg_none;
367     } else if (arg_mode == arg_limit_count) {
368       int count;
369       if (sscanf(args[i].c_str(), "%d", &count) != 1 || count < 0) {
370         status.SetError(cmStrCat("STRINGS option LIMIT_COUNT value \"",
371                                  args[i], "\" is not an unsigned integer."));
372         return false;
373       }
374       limit_count = count;
375       arg_mode = arg_none;
376     } else if (arg_mode == arg_length_minimum) {
377       int len;
378       if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
379         status.SetError(cmStrCat("STRINGS option LENGTH_MINIMUM value \"",
380                                  args[i], "\" is not an unsigned integer."));
381         return false;
382       }
383       minlen = len;
384       arg_mode = arg_none;
385     } else if (arg_mode == arg_length_maximum) {
386       int len;
387       if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
388         status.SetError(cmStrCat("STRINGS option LENGTH_MAXIMUM value \"",
389                                  args[i], "\" is not an unsigned integer."));
390         return false;
391       }
392       maxlen = len;
393       arg_mode = arg_none;
394     } else if (arg_mode == arg_regex) {
395       if (!regex.compile(args[i])) {
396         status.SetError(cmStrCat("STRINGS option REGEX value \"", args[i],
397                                  "\" could not be compiled."));
398         return false;
399       }
400       have_regex = true;
401       arg_mode = arg_none;
402     } else if (arg_mode == arg_encoding) {
403       if (args[i] == "UTF-8") {
404         encoding = encoding_utf8;
405       } else if (args[i] == "UTF-16LE") {
406         encoding = encoding_utf16le;
407       } else if (args[i] == "UTF-16BE") {
408         encoding = encoding_utf16be;
409       } else if (args[i] == "UTF-32LE") {
410         encoding = encoding_utf32le;
411       } else if (args[i] == "UTF-32BE") {
412         encoding = encoding_utf32be;
413       } else {
414         status.SetError(cmStrCat("STRINGS option ENCODING \"", args[i],
415                                  "\" not recognized."));
416         return false;
417       }
418       arg_mode = arg_none;
419     } else {
420       status.SetError(
421         cmStrCat("STRINGS given unknown argument \"", args[i], "\""));
422       return false;
423     }
424   }
425
426   if (hex_conversion_enabled) {
427     // TODO: should work without temp file, but just on a memory buffer
428     std::string binaryFileName =
429       cmStrCat(status.GetMakefile().GetCurrentBinaryDirectory(),
430                "/CMakeFiles/FileCommandStringsBinaryFile");
431     if (cmHexFileConverter::TryConvert(fileName, binaryFileName)) {
432       fileName = binaryFileName;
433     }
434   }
435
436 // Open the specified file.
437 #if defined(_WIN32) || defined(__CYGWIN__)
438   cmsys::ifstream fin(fileName.c_str(), std::ios::in | std::ios::binary);
439 #else
440   cmsys::ifstream fin(fileName.c_str());
441 #endif
442   if (!fin) {
443     status.SetError(
444       cmStrCat("STRINGS file \"", fileName, "\" cannot be read."));
445     return false;
446   }
447
448   // If BOM is found and encoding was not specified, use the BOM
449   int bom_found = cmsys::FStream::ReadBOM(fin);
450   if (encoding == encoding_none && bom_found != cmsys::FStream::BOM_None) {
451     encoding = bom_found;
452   }
453
454   unsigned int bytes_rem = 0;
455   if (encoding == encoding_utf16le || encoding == encoding_utf16be) {
456     bytes_rem = 1;
457   }
458   if (encoding == encoding_utf32le || encoding == encoding_utf32be) {
459     bytes_rem = 3;
460   }
461
462   // Parse strings out of the file.
463   int output_size = 0;
464   std::vector<std::string> strings;
465   std::string s;
466   while ((!limit_count || strings.size() < limit_count) &&
467          (limit_input < 0 || static_cast<int>(fin.tellg()) < limit_input) &&
468          fin) {
469     std::string current_str;
470
471     int c = fin.get();
472     for (unsigned int i = 0; i < bytes_rem; ++i) {
473       int c1 = fin.get();
474       if (!fin) {
475         fin.putback(static_cast<char>(c1));
476         break;
477       }
478       c = (c << 8) | c1;
479     }
480     if (encoding == encoding_utf16le) {
481       c = ((c & 0xFF) << 8) | ((c & 0xFF00) >> 8);
482     } else if (encoding == encoding_utf32le) {
483       c = (((c & 0xFF) << 24) | ((c & 0xFF00) << 8) | ((c & 0xFF0000) >> 8) |
484            ((c & 0xFF000000) >> 24));
485     }
486
487     if (c == '\r') {
488       // Ignore CR character to make output always have UNIX newlines.
489       continue;
490     }
491
492     if (c >= 0 && c <= 0xFF &&
493         (isprint(c) || c == '\t' || (c == '\n' && newline_consume))) {
494       // This is an ASCII character that may be part of a string.
495       // Cast added to avoid compiler warning. Cast is ok because
496       // c is guaranteed to fit in char by the above if...
497       current_str += static_cast<char>(c);
498     } else if (encoding == encoding_utf8) {
499       // Check for UTF-8 encoded string (up to 4 octets)
500       static const unsigned char utf8_check_table[3][2] = {
501         { 0xE0, 0xC0 },
502         { 0xF0, 0xE0 },
503         { 0xF8, 0xF0 },
504       };
505
506       // how many octets are there?
507       unsigned int num_utf8_bytes = 0;
508       for (unsigned int j = 0; num_utf8_bytes == 0 && j < 3; j++) {
509         if ((c & utf8_check_table[j][0]) == utf8_check_table[j][1]) {
510           num_utf8_bytes = j + 2;
511         }
512       }
513
514       // get subsequent octets and check that they are valid
515       for (unsigned int j = 0; j < num_utf8_bytes; j++) {
516         if (j != 0) {
517           c = fin.get();
518           if (!fin || (c & 0xC0) != 0x80) {
519             fin.putback(static_cast<char>(c));
520             break;
521           }
522         }
523         current_str += static_cast<char>(c);
524       }
525
526       // if this was an invalid utf8 sequence, discard the data, and put
527       // back subsequent characters
528       if ((current_str.length() != num_utf8_bytes)) {
529         for (unsigned int j = 0; j < current_str.size() - 1; j++) {
530           fin.putback(current_str[current_str.size() - 1 - j]);
531         }
532         current_str.clear();
533       }
534     }
535
536     if (c == '\n' && !newline_consume) {
537       // The current line has been terminated.  Check if the current
538       // string matches the requirements.  The length may now be as
539       // low as zero since blank lines are allowed.
540       if (s.length() >= minlen && (!have_regex || regex.find(s))) {
541         output_size += static_cast<int>(s.size()) + 1;
542         if (limit_output >= 0 && output_size >= limit_output) {
543           s.clear();
544           break;
545         }
546         strings.push_back(s);
547       }
548
549       // Reset the string to empty.
550       s.clear();
551     } else if (current_str.empty()) {
552       // A non-string character has been found.  Check if the current
553       // string matches the requirements.  We require that the length
554       // be at least one no matter what the user specified.
555       if (s.length() >= minlen && !s.empty() &&
556           (!have_regex || regex.find(s))) {
557         output_size += static_cast<int>(s.size()) + 1;
558         if (limit_output >= 0 && output_size >= limit_output) {
559           s.clear();
560           break;
561         }
562         strings.push_back(s);
563       }
564
565       // Reset the string to empty.
566       s.clear();
567     } else {
568       s += current_str;
569     }
570
571     if (maxlen > 0 && s.size() == maxlen) {
572       // Terminate a string if the maximum length is reached.
573       if (s.length() >= minlen && (!have_regex || regex.find(s))) {
574         output_size += static_cast<int>(s.size()) + 1;
575         if (limit_output >= 0 && output_size >= limit_output) {
576           s.clear();
577           break;
578         }
579         strings.push_back(s);
580       }
581       s.clear();
582     }
583   }
584
585   // If there is a non-empty current string we have hit the end of the
586   // input file or the input size limit.  Check if the current string
587   // matches the requirements.
588   if ((!limit_count || strings.size() < limit_count) && !s.empty() &&
589       s.length() >= minlen && (!have_regex || regex.find(s))) {
590     output_size += static_cast<int>(s.size()) + 1;
591     if (limit_output < 0 || output_size < limit_output) {
592       strings.push_back(s);
593     }
594   }
595
596   // Encode the result in a CMake list.
597   const char* sep = "";
598   std::string output;
599   for (std::string const& sr : strings) {
600     // Separate the strings in the output to make it a list.
601     output += sep;
602     sep = ";";
603
604     // Store the string in the output, but escape semicolons to
605     // make sure it is a list.
606     for (char i : sr) {
607       if (i == ';') {
608         output += '\\';
609       }
610       output += i;
611     }
612   }
613
614   // Save the output in a makefile variable.
615   status.GetMakefile().AddDefinition(outVar, output);
616   return true;
617 }
618
619 bool HandleGlobImpl(std::vector<std::string> const& args, bool recurse,
620                     cmExecutionStatus& status)
621 {
622   // File commands has at least one argument
623   assert(args.size() > 1);
624
625   auto i = args.begin();
626
627   i++; // Get rid of subcommand
628
629   std::string variable = *i;
630   i++;
631   cmsys::Glob g;
632   g.SetRecurse(recurse);
633
634   bool explicitFollowSymlinks = false;
635   cmPolicies::PolicyStatus policyStatus =
636     status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0009);
637   if (recurse) {
638     switch (policyStatus) {
639       case cmPolicies::REQUIRED_IF_USED:
640       case cmPolicies::REQUIRED_ALWAYS:
641       case cmPolicies::NEW:
642         g.RecurseThroughSymlinksOff();
643         break;
644       case cmPolicies::WARN:
645         CM_FALLTHROUGH;
646       case cmPolicies::OLD:
647         g.RecurseThroughSymlinksOn();
648         break;
649     }
650   }
651
652   cmake* cm = status.GetMakefile().GetCMakeInstance();
653   std::vector<std::string> files;
654   bool configureDepends = false;
655   bool warnConfigureLate = false;
656   bool warnFollowedSymlinks = false;
657   const cmake::WorkingMode workingMode = cm->GetWorkingMode();
658   while (i != args.end()) {
659     if (*i == "LIST_DIRECTORIES") {
660       ++i; // skip LIST_DIRECTORIES
661       if (i != args.end()) {
662         if (cmIsOn(*i)) {
663           g.SetListDirs(true);
664           g.SetRecurseListDirs(true);
665         } else if (cmIsOff(*i)) {
666           g.SetListDirs(false);
667           g.SetRecurseListDirs(false);
668         } else {
669           status.SetError("LIST_DIRECTORIES missing bool value.");
670           return false;
671         }
672         ++i;
673       } else {
674         status.SetError("LIST_DIRECTORIES missing bool value.");
675         return false;
676       }
677     } else if (*i == "FOLLOW_SYMLINKS") {
678       ++i; // skip FOLLOW_SYMLINKS
679       if (recurse) {
680         explicitFollowSymlinks = true;
681         g.RecurseThroughSymlinksOn();
682         if (i == args.end()) {
683           status.SetError(
684             "GLOB_RECURSE requires a glob expression after FOLLOW_SYMLINKS.");
685           return false;
686         }
687       }
688     } else if (*i == "RELATIVE") {
689       ++i; // skip RELATIVE
690       if (i == args.end()) {
691         status.SetError("GLOB requires a directory after the RELATIVE tag.");
692         return false;
693       }
694       g.SetRelative(i->c_str());
695       ++i;
696       if (i == args.end()) {
697         status.SetError(
698           "GLOB requires a glob expression after the directory.");
699         return false;
700       }
701     } else if (*i == "CONFIGURE_DEPENDS") {
702       // Generated build system depends on glob results
703       if (!configureDepends && warnConfigureLate) {
704         status.GetMakefile().IssueMessage(
705           MessageType::AUTHOR_WARNING,
706           "CONFIGURE_DEPENDS flag was given after a glob expression was "
707           "already evaluated.");
708       }
709       if (workingMode != cmake::NORMAL_MODE) {
710         status.GetMakefile().IssueMessage(
711           MessageType::FATAL_ERROR,
712           "CONFIGURE_DEPENDS is invalid for script and find package modes.");
713         return false;
714       }
715       configureDepends = true;
716       ++i;
717       if (i == args.end()) {
718         status.SetError(
719           "GLOB requires a glob expression after CONFIGURE_DEPENDS.");
720         return false;
721       }
722     } else {
723       std::string expr = *i;
724       if (!cmsys::SystemTools::FileIsFullPath(*i)) {
725         expr = status.GetMakefile().GetCurrentSourceDirectory();
726         // Handle script mode
727         if (!expr.empty()) {
728           expr += "/" + *i;
729         } else {
730           expr = *i;
731         }
732       }
733
734       cmsys::Glob::GlobMessages globMessages;
735       g.FindFiles(expr, &globMessages);
736
737       if (!globMessages.empty()) {
738         bool shouldExit = false;
739         for (cmsys::Glob::Message const& globMessage : globMessages) {
740           if (globMessage.type == cmsys::Glob::cyclicRecursion) {
741             status.GetMakefile().IssueMessage(
742               MessageType::AUTHOR_WARNING,
743               "Cyclic recursion detected while globbing for '" + *i + "':\n" +
744                 globMessage.content);
745           } else if (globMessage.type == cmsys::Glob::error) {
746             status.GetMakefile().IssueMessage(
747               MessageType::FATAL_ERROR,
748               "Error has occurred while globbing for '" + *i + "' - " +
749                 globMessage.content);
750             shouldExit = true;
751           } else if (cm->GetDebugOutput() || cm->GetTrace()) {
752             status.GetMakefile().IssueMessage(
753               MessageType::LOG,
754               cmStrCat("Globbing for\n  ", *i, "\nEncountered an error:\n ",
755                        globMessage.content));
756           }
757         }
758         if (shouldExit) {
759           return false;
760         }
761       }
762
763       if (recurse && !explicitFollowSymlinks &&
764           g.GetFollowedSymlinkCount() != 0) {
765         warnFollowedSymlinks = true;
766       }
767
768       std::vector<std::string>& foundFiles = g.GetFiles();
769       cm::append(files, foundFiles);
770
771       if (configureDepends) {
772         std::sort(foundFiles.begin(), foundFiles.end());
773         foundFiles.erase(std::unique(foundFiles.begin(), foundFiles.end()),
774                          foundFiles.end());
775         cm->AddGlobCacheEntry(
776           recurse, (recurse ? g.GetRecurseListDirs() : g.GetListDirs()),
777           (recurse ? g.GetRecurseThroughSymlinks() : false),
778           (g.GetRelative() ? g.GetRelative() : ""), expr, foundFiles, variable,
779           status.GetMakefile().GetBacktrace());
780       } else {
781         warnConfigureLate = true;
782       }
783       ++i;
784     }
785   }
786
787   switch (policyStatus) {
788     case cmPolicies::REQUIRED_IF_USED:
789     case cmPolicies::REQUIRED_ALWAYS:
790     case cmPolicies::NEW:
791       // Correct behavior, yay!
792       break;
793     case cmPolicies::OLD:
794     // Probably not really the expected behavior, but the author explicitly
795     // asked for the old behavior... no warning.
796     case cmPolicies::WARN:
797       // Possibly unexpected old behavior *and* we actually traversed
798       // symlinks without being explicitly asked to: warn the author.
799       if (warnFollowedSymlinks) {
800         status.GetMakefile().IssueMessage(
801           MessageType::AUTHOR_WARNING,
802           cmPolicies::GetPolicyWarning(cmPolicies::CMP0009));
803       }
804       break;
805   }
806
807   std::sort(files.begin(), files.end());
808   files.erase(std::unique(files.begin(), files.end()), files.end());
809   status.GetMakefile().AddDefinition(variable, cmJoin(files, ";"));
810   return true;
811 }
812
813 bool HandleGlobCommand(std::vector<std::string> const& args,
814                        cmExecutionStatus& status)
815 {
816   return HandleGlobImpl(args, false, status);
817 }
818
819 bool HandleGlobRecurseCommand(std::vector<std::string> const& args,
820                               cmExecutionStatus& status)
821 {
822   return HandleGlobImpl(args, true, status);
823 }
824
825 bool HandleMakeDirectoryCommand(std::vector<std::string> const& args,
826                                 cmExecutionStatus& status)
827 {
828   // File command has at least one argument
829   assert(args.size() > 1);
830
831   std::string expr;
832   for (std::string const& arg :
833        cmMakeRange(args).advance(1)) // Get rid of subcommand
834   {
835     const std::string* cdir = &arg;
836     if (!cmsys::SystemTools::FileIsFullPath(arg)) {
837       expr =
838         cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
839       cdir = &expr;
840     }
841     if (!status.GetMakefile().CanIWriteThisFile(*cdir)) {
842       std::string e = "attempted to create a directory: " + *cdir +
843         " into a source directory.";
844       status.SetError(e);
845       cmSystemTools::SetFatalErrorOccurred();
846       return false;
847     }
848     if (!cmSystemTools::MakeDirectory(*cdir)) {
849       std::string error = "problem creating directory: " + *cdir;
850       status.SetError(error);
851       return false;
852     }
853   }
854   return true;
855 }
856
857 bool HandleTouchImpl(std::vector<std::string> const& args, bool create,
858                      cmExecutionStatus& status)
859 {
860   // File command has at least one argument
861   assert(args.size() > 1);
862
863   for (std::string const& arg :
864        cmMakeRange(args).advance(1)) // Get rid of subcommand
865   {
866     std::string tfile = arg;
867     if (!cmsys::SystemTools::FileIsFullPath(tfile)) {
868       tfile =
869         cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
870     }
871     if (!status.GetMakefile().CanIWriteThisFile(tfile)) {
872       std::string e =
873         "attempted to touch a file: " + tfile + " in a source directory.";
874       status.SetError(e);
875       cmSystemTools::SetFatalErrorOccurred();
876       return false;
877     }
878     if (!cmSystemTools::Touch(tfile, create)) {
879       std::string error = "problem touching file: " + tfile;
880       status.SetError(error);
881       return false;
882     }
883   }
884   return true;
885 }
886
887 bool HandleTouchCommand(std::vector<std::string> const& args,
888                         cmExecutionStatus& status)
889 {
890   return HandleTouchImpl(args, true, status);
891 }
892
893 bool HandleTouchNocreateCommand(std::vector<std::string> const& args,
894                                 cmExecutionStatus& status)
895 {
896   return HandleTouchImpl(args, false, status);
897 }
898
899 bool HandleDifferentCommand(std::vector<std::string> const& args,
900                             cmExecutionStatus& status)
901 {
902   /*
903     FILE(DIFFERENT <variable> FILES <lhs> <rhs>)
904    */
905
906   // Evaluate arguments.
907   const char* file_lhs = nullptr;
908   const char* file_rhs = nullptr;
909   const char* var = nullptr;
910   enum Doing
911   {
912     DoingNone,
913     DoingVar,
914     DoingFileLHS,
915     DoingFileRHS
916   };
917   Doing doing = DoingVar;
918   for (unsigned int i = 1; i < args.size(); ++i) {
919     if (args[i] == "FILES") {
920       doing = DoingFileLHS;
921     } else if (doing == DoingVar) {
922       var = args[i].c_str();
923       doing = DoingNone;
924     } else if (doing == DoingFileLHS) {
925       file_lhs = args[i].c_str();
926       doing = DoingFileRHS;
927     } else if (doing == DoingFileRHS) {
928       file_rhs = args[i].c_str();
929       doing = DoingNone;
930     } else {
931       status.SetError(cmStrCat("DIFFERENT given unknown argument ", args[i]));
932       return false;
933     }
934   }
935   if (!var) {
936     status.SetError("DIFFERENT not given result variable name.");
937     return false;
938   }
939   if (!file_lhs || !file_rhs) {
940     status.SetError("DIFFERENT not given FILES option with two file names.");
941     return false;
942   }
943
944   // Compare the files.
945   const char* result =
946     cmSystemTools::FilesDiffer(file_lhs, file_rhs) ? "1" : "0";
947   status.GetMakefile().AddDefinition(var, result);
948   return true;
949 }
950
951 bool HandleCopyCommand(std::vector<std::string> const& args,
952                        cmExecutionStatus& status)
953 {
954   cmFileCopier copier(status);
955   return copier.Run(args);
956 }
957
958 bool HandleRPathChangeCommand(std::vector<std::string> const& args,
959                               cmExecutionStatus& status)
960 {
961   // Evaluate arguments.
962   std::string file;
963   cm::optional<std::string> oldRPath;
964   cm::optional<std::string> newRPath;
965   bool removeEnvironmentRPath = false;
966   cmArgumentParser<void> parser;
967   std::vector<std::string> unknownArgs;
968   parser.Bind("FILE"_s, file)
969     .Bind("OLD_RPATH"_s, oldRPath)
970     .Bind("NEW_RPATH"_s, newRPath)
971     .Bind("INSTALL_REMOVE_ENVIRONMENT_RPATH"_s, removeEnvironmentRPath);
972   ArgumentParser::ParseResult parseResult =
973     parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
974   if (!unknownArgs.empty()) {
975     status.SetError(
976       cmStrCat("RPATH_CHANGE given unknown argument ", unknownArgs.front()));
977     return false;
978   }
979   if (parseResult.MaybeReportError(status.GetMakefile())) {
980     return true;
981   }
982   if (file.empty()) {
983     status.SetError("RPATH_CHANGE not given FILE option.");
984     return false;
985   }
986   if (!oldRPath) {
987     status.SetError("RPATH_CHANGE not given OLD_RPATH option.");
988     return false;
989   }
990   if (!newRPath) {
991     status.SetError("RPATH_CHANGE not given NEW_RPATH option.");
992     return false;
993   }
994   if (!cmSystemTools::FileExists(file, true)) {
995     status.SetError(
996       cmStrCat("RPATH_CHANGE given FILE \"", file, "\" that does not exist."));
997     return false;
998   }
999   bool success = true;
1000   cmFileTimes const ft(file);
1001   std::string emsg;
1002   bool changed;
1003
1004   if (!cmSystemTools::ChangeRPath(file, *oldRPath, *newRPath,
1005                                   removeEnvironmentRPath, &emsg, &changed)) {
1006     status.SetError(cmStrCat("RPATH_CHANGE could not write new RPATH:\n  ",
1007                              *newRPath, "\nto the file:\n  ", file, "\n",
1008                              emsg));
1009     success = false;
1010   }
1011   if (success) {
1012     if (changed) {
1013       std::string message =
1014         cmStrCat("Set runtime path of \"", file, "\" to \"", *newRPath, '"');
1015       status.GetMakefile().DisplayStatus(message, -1);
1016     }
1017     ft.Store(file);
1018   }
1019   return success;
1020 }
1021
1022 bool HandleRPathSetCommand(std::vector<std::string> const& args,
1023                            cmExecutionStatus& status)
1024 {
1025   // Evaluate arguments.
1026   std::string file;
1027   cm::optional<std::string> newRPath;
1028   cmArgumentParser<void> parser;
1029   std::vector<std::string> unknownArgs;
1030   parser.Bind("FILE"_s, file).Bind("NEW_RPATH"_s, newRPath);
1031   ArgumentParser::ParseResult parseResult =
1032     parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
1033   if (!unknownArgs.empty()) {
1034     status.SetError(cmStrCat("RPATH_SET given unrecognized argument \"",
1035                              unknownArgs.front(), "\"."));
1036     return false;
1037   }
1038   if (parseResult.MaybeReportError(status.GetMakefile())) {
1039     return true;
1040   }
1041   if (file.empty()) {
1042     status.SetError("RPATH_SET not given FILE option.");
1043     return false;
1044   }
1045   if (!newRPath) {
1046     status.SetError("RPATH_SET not given NEW_RPATH option.");
1047     return false;
1048   }
1049   if (!cmSystemTools::FileExists(file, true)) {
1050     status.SetError(
1051       cmStrCat("RPATH_SET given FILE \"", file, "\" that does not exist."));
1052     return false;
1053   }
1054   bool success = true;
1055   cmFileTimes const ft(file);
1056   std::string emsg;
1057   bool changed;
1058
1059   if (!cmSystemTools::SetRPath(file, *newRPath, &emsg, &changed)) {
1060     status.SetError(cmStrCat("RPATH_SET could not write new RPATH:\n  ",
1061                              *newRPath, "\nto the file:\n  ", file, "\n",
1062                              emsg));
1063     success = false;
1064   }
1065   if (success) {
1066     if (changed) {
1067       std::string message =
1068         cmStrCat("Set runtime path of \"", file, "\" to \"", *newRPath, '"');
1069       status.GetMakefile().DisplayStatus(message, -1);
1070     }
1071     ft.Store(file);
1072   }
1073   return success;
1074 }
1075
1076 bool HandleRPathRemoveCommand(std::vector<std::string> const& args,
1077                               cmExecutionStatus& status)
1078 {
1079   // Evaluate arguments.
1080   std::string file;
1081   cmArgumentParser<void> parser;
1082   std::vector<std::string> unknownArgs;
1083   parser.Bind("FILE"_s, file);
1084   ArgumentParser::ParseResult parseResult =
1085     parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
1086   if (!unknownArgs.empty()) {
1087     status.SetError(
1088       cmStrCat("RPATH_REMOVE given unknown argument ", unknownArgs.front()));
1089     return false;
1090   }
1091   if (parseResult.MaybeReportError(status.GetMakefile())) {
1092     return true;
1093   }
1094   if (file.empty()) {
1095     status.SetError("RPATH_REMOVE not given FILE option.");
1096     return false;
1097   }
1098   if (!cmSystemTools::FileExists(file, true)) {
1099     status.SetError(
1100       cmStrCat("RPATH_REMOVE given FILE \"", file, "\" that does not exist."));
1101     return false;
1102   }
1103   bool success = true;
1104   cmFileTimes const ft(file);
1105   std::string emsg;
1106   bool removed;
1107   if (!cmSystemTools::RemoveRPath(file, &emsg, &removed)) {
1108     status.SetError(
1109       cmStrCat("RPATH_REMOVE could not remove RPATH from file: \n  ", file,
1110                "\n", emsg));
1111     success = false;
1112   }
1113   if (success) {
1114     if (removed) {
1115       std::string message =
1116         cmStrCat("Removed runtime path from \"", file, '"');
1117       status.GetMakefile().DisplayStatus(message, -1);
1118     }
1119     ft.Store(file);
1120   }
1121   return success;
1122 }
1123
1124 bool HandleRPathCheckCommand(std::vector<std::string> const& args,
1125                              cmExecutionStatus& status)
1126 {
1127   // Evaluate arguments.
1128   std::string file;
1129   cm::optional<std::string> rpath;
1130   cmArgumentParser<void> parser;
1131   std::vector<std::string> unknownArgs;
1132   parser.Bind("FILE"_s, file).Bind("RPATH"_s, rpath);
1133   ArgumentParser::ParseResult parseResult =
1134     parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
1135   if (!unknownArgs.empty()) {
1136     status.SetError(
1137       cmStrCat("RPATH_CHECK given unknown argument ", unknownArgs.front()));
1138     return false;
1139   }
1140   if (parseResult.MaybeReportError(status.GetMakefile())) {
1141     return true;
1142   }
1143   if (file.empty()) {
1144     status.SetError("RPATH_CHECK not given FILE option.");
1145     return false;
1146   }
1147   if (!rpath) {
1148     status.SetError("RPATH_CHECK not given RPATH option.");
1149     return false;
1150   }
1151
1152   // If the file exists but does not have the desired RPath then
1153   // delete it.  This is used during installation to re-install a file
1154   // if its RPath will change.
1155   if (cmSystemTools::FileExists(file, true) &&
1156       !cmSystemTools::CheckRPath(file, *rpath)) {
1157     cmSystemTools::RemoveFile(file);
1158   }
1159
1160   return true;
1161 }
1162
1163 bool HandleReadElfCommand(std::vector<std::string> const& args,
1164                           cmExecutionStatus& status)
1165 {
1166   if (args.size() < 4) {
1167     status.SetError("READ_ELF must be called with at least three additional "
1168                     "arguments.");
1169     return false;
1170   }
1171
1172   std::string const& fileNameArg = args[1];
1173
1174   struct Arguments
1175   {
1176     std::string RPath;
1177     std::string RunPath;
1178     std::string Error;
1179   };
1180
1181   static auto const parser = cmArgumentParser<Arguments>{}
1182                                .Bind("RPATH"_s, &Arguments::RPath)
1183                                .Bind("RUNPATH"_s, &Arguments::RunPath)
1184                                .Bind("CAPTURE_ERROR"_s, &Arguments::Error);
1185   Arguments const arguments = parser.Parse(cmMakeRange(args).advance(2),
1186                                            /*unparsedArguments=*/nullptr);
1187
1188   if (!cmSystemTools::FileExists(fileNameArg, true)) {
1189     status.SetError(cmStrCat("READ_ELF given FILE \"", fileNameArg,
1190                              "\" that does not exist."));
1191     return false;
1192   }
1193
1194   cmELF elf(fileNameArg.c_str());
1195   if (!elf) {
1196     if (arguments.Error.empty()) {
1197       status.SetError(cmStrCat("READ_ELF given FILE:\n  ", fileNameArg,
1198                                "\nthat is not a valid ELF file."));
1199       return false;
1200     }
1201     status.GetMakefile().AddDefinition(arguments.Error,
1202                                        "not a valid ELF file");
1203     return true;
1204   }
1205
1206   if (!arguments.RPath.empty()) {
1207     if (cmELF::StringEntry const* se_rpath = elf.GetRPath()) {
1208       std::string rpath(se_rpath->Value);
1209       std::replace(rpath.begin(), rpath.end(), ':', ';');
1210       status.GetMakefile().AddDefinition(arguments.RPath, rpath);
1211     }
1212   }
1213   if (!arguments.RunPath.empty()) {
1214     if (cmELF::StringEntry const* se_runpath = elf.GetRunPath()) {
1215       std::string runpath(se_runpath->Value);
1216       std::replace(runpath.begin(), runpath.end(), ':', ';');
1217       status.GetMakefile().AddDefinition(arguments.RunPath, runpath);
1218     }
1219   }
1220
1221   return true;
1222 }
1223
1224 bool HandleInstallCommand(std::vector<std::string> const& args,
1225                           cmExecutionStatus& status)
1226 {
1227   cmFileInstaller installer(status);
1228   return installer.Run(args);
1229 }
1230
1231 bool HandleRealPathCommand(std::vector<std::string> const& args,
1232                            cmExecutionStatus& status)
1233 {
1234   if (args.size() < 3) {
1235     status.SetError("REAL_PATH requires a path and an output variable");
1236     return false;
1237   }
1238
1239   struct Arguments : public ArgumentParser::ParseResult
1240   {
1241     cm::optional<std::string> BaseDirectory;
1242     bool ExpandTilde = false;
1243   };
1244   static auto const parser =
1245     cmArgumentParser<Arguments>{}
1246       .Bind("BASE_DIRECTORY"_s, &Arguments::BaseDirectory)
1247       .Bind("EXPAND_TILDE"_s, &Arguments::ExpandTilde);
1248
1249   std::vector<std::string> unparsedArguments;
1250   auto arguments =
1251     parser.Parse(cmMakeRange(args).advance(3), &unparsedArguments);
1252
1253   if (!unparsedArguments.empty()) {
1254     status.SetError("REAL_PATH called with unexpected arguments");
1255     return false;
1256   }
1257   if (arguments.MaybeReportError(status.GetMakefile())) {
1258     return true;
1259   }
1260
1261   if (!arguments.BaseDirectory) {
1262     arguments.BaseDirectory = status.GetMakefile().GetCurrentSourceDirectory();
1263   }
1264
1265   auto input = args[1];
1266   if (arguments.ExpandTilde && !input.empty()) {
1267     if (input[0] == '~' && (input.length() == 1 || input[1] == '/')) {
1268       std::string home;
1269       if (
1270 #if defined(_WIN32) && !defined(__CYGWIN__)
1271         cmSystemTools::GetEnv("USERPROFILE", home) ||
1272 #endif
1273         cmSystemTools::GetEnv("HOME", home)) {
1274         input.replace(0, 1, home);
1275       }
1276     }
1277   }
1278
1279   cmCMakePath path(input, cmCMakePath::auto_format);
1280   path = path.Absolute(*arguments.BaseDirectory).Normal();
1281   auto realPath = cmSystemTools::GetRealPath(path.GenericString());
1282
1283   status.GetMakefile().AddDefinition(args[2], realPath);
1284
1285   return true;
1286 }
1287
1288 bool HandleRelativePathCommand(std::vector<std::string> const& args,
1289                                cmExecutionStatus& status)
1290 {
1291   if (args.size() != 4) {
1292     status.SetError("RELATIVE_PATH called with incorrect number of arguments");
1293     return false;
1294   }
1295
1296   const std::string& outVar = args[1];
1297   const std::string& directoryName = args[2];
1298   const std::string& fileName = args[3];
1299
1300   if (!cmSystemTools::FileIsFullPath(directoryName)) {
1301     std::string errstring =
1302       "RELATIVE_PATH must be passed a full path to the directory: " +
1303       directoryName;
1304     status.SetError(errstring);
1305     return false;
1306   }
1307   if (!cmSystemTools::FileIsFullPath(fileName)) {
1308     std::string errstring =
1309       "RELATIVE_PATH must be passed a full path to the file: " + fileName;
1310     status.SetError(errstring);
1311     return false;
1312   }
1313
1314   std::string res = cmSystemTools::RelativePath(directoryName, fileName);
1315   status.GetMakefile().AddDefinition(outVar, res);
1316   return true;
1317 }
1318
1319 bool HandleRename(std::vector<std::string> const& args,
1320                   cmExecutionStatus& status)
1321 {
1322   if (args.size() < 3) {
1323     status.SetError("RENAME must be called with at least two additional "
1324                     "arguments");
1325     return false;
1326   }
1327
1328   // Compute full path for old and new names.
1329   std::string oldname = args[1];
1330   if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
1331     oldname =
1332       cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
1333   }
1334   std::string newname = args[2];
1335   if (!cmsys::SystemTools::FileIsFullPath(newname)) {
1336     newname =
1337       cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
1338   }
1339
1340   struct Arguments
1341   {
1342     bool NoReplace = false;
1343     std::string Result;
1344   };
1345
1346   static auto const parser = cmArgumentParser<Arguments>{}
1347                                .Bind("NO_REPLACE"_s, &Arguments::NoReplace)
1348                                .Bind("RESULT"_s, &Arguments::Result);
1349
1350   std::vector<std::string> unconsumedArgs;
1351   Arguments const arguments =
1352     parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
1353   if (!unconsumedArgs.empty()) {
1354     status.SetError("RENAME unknown argument:\n  " + unconsumedArgs.front());
1355     return false;
1356   }
1357
1358   std::string err;
1359   switch (cmSystemTools::RenameFile(oldname, newname,
1360                                     arguments.NoReplace
1361                                       ? cmSystemTools::Replace::No
1362                                       : cmSystemTools::Replace::Yes,
1363                                     &err)) {
1364     case cmSystemTools::RenameResult::Success:
1365       if (!arguments.Result.empty()) {
1366         status.GetMakefile().AddDefinition(arguments.Result, "0");
1367       }
1368       return true;
1369     case cmSystemTools::RenameResult::NoReplace:
1370       if (!arguments.Result.empty()) {
1371         err = "NO_REPLACE";
1372       } else {
1373         err = "path not replaced";
1374       }
1375       CM_FALLTHROUGH;
1376     case cmSystemTools::RenameResult::Failure:
1377       if (!arguments.Result.empty()) {
1378         status.GetMakefile().AddDefinition(arguments.Result, err);
1379         return true;
1380       }
1381       break;
1382   }
1383   status.SetError(cmStrCat("RENAME failed to rename\n  ", oldname, "\nto\n  ",
1384                            newname, "\nbecause: ", err, "\n"));
1385   return false;
1386 }
1387
1388 bool HandleCopyFile(std::vector<std::string> const& args,
1389                     cmExecutionStatus& status)
1390 {
1391   if (args.size() < 3) {
1392     status.SetError("COPY_FILE must be called with at least two additional "
1393                     "arguments");
1394     return false;
1395   }
1396
1397   // Compute full path for old and new names.
1398   std::string oldname = args[1];
1399   if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
1400     oldname =
1401       cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
1402   }
1403   std::string newname = args[2];
1404   if (!cmsys::SystemTools::FileIsFullPath(newname)) {
1405     newname =
1406       cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
1407   }
1408
1409   struct Arguments
1410   {
1411     bool OnlyIfDifferent = false;
1412     std::string Result;
1413   };
1414
1415   static auto const parser =
1416     cmArgumentParser<Arguments>{}
1417       .Bind("ONLY_IF_DIFFERENT"_s, &Arguments::OnlyIfDifferent)
1418       .Bind("RESULT"_s, &Arguments::Result);
1419
1420   std::vector<std::string> unconsumedArgs;
1421   Arguments const arguments =
1422     parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
1423   if (!unconsumedArgs.empty()) {
1424     status.SetError("COPY_FILE unknown argument:\n  " +
1425                     unconsumedArgs.front());
1426     return false;
1427   }
1428
1429   bool result = true;
1430   if (cmsys::SystemTools::FileIsDirectory(oldname)) {
1431     if (!arguments.Result.empty()) {
1432       status.GetMakefile().AddDefinition(arguments.Result,
1433                                          "cannot copy a directory");
1434     } else {
1435       status.SetError(
1436         cmStrCat("COPY_FILE cannot copy a directory\n  ", oldname));
1437       result = false;
1438     }
1439     return result;
1440   }
1441   if (cmsys::SystemTools::FileIsDirectory(newname)) {
1442     if (!arguments.Result.empty()) {
1443       status.GetMakefile().AddDefinition(arguments.Result,
1444                                          "cannot copy to a directory");
1445     } else {
1446       status.SetError(
1447         cmStrCat("COPY_FILE cannot copy to a directory\n  ", newname));
1448       result = false;
1449     }
1450     return result;
1451   }
1452
1453   cmSystemTools::CopyWhen when;
1454   if (arguments.OnlyIfDifferent) {
1455     when = cmSystemTools::CopyWhen::OnlyIfDifferent;
1456   } else {
1457     when = cmSystemTools::CopyWhen::Always;
1458   }
1459
1460   std::string err;
1461   if (cmSystemTools::CopySingleFile(oldname, newname, when, &err) ==
1462       cmSystemTools::CopyResult::Success) {
1463     if (!arguments.Result.empty()) {
1464       status.GetMakefile().AddDefinition(arguments.Result, "0");
1465     }
1466   } else {
1467     if (!arguments.Result.empty()) {
1468       status.GetMakefile().AddDefinition(arguments.Result, err);
1469     } else {
1470       status.SetError(cmStrCat("COPY_FILE failed to copy\n  ", oldname,
1471                                "\nto\n  ", newname, "\nbecause: ", err, "\n"));
1472       result = false;
1473     }
1474   }
1475
1476   return result;
1477 }
1478
1479 bool HandleRemoveImpl(std::vector<std::string> const& args, bool recurse,
1480                       cmExecutionStatus& status)
1481 {
1482   for (std::string const& arg :
1483        cmMakeRange(args).advance(1)) // Get rid of subcommand
1484   {
1485     std::string fileName = arg;
1486     if (fileName.empty()) {
1487       std::string const r = recurse ? "REMOVE_RECURSE" : "REMOVE";
1488       status.GetMakefile().IssueMessage(
1489         MessageType::AUTHOR_WARNING, "Ignoring empty file name in " + r + ".");
1490       continue;
1491     }
1492     if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
1493       fileName =
1494         cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
1495     }
1496
1497     if (cmSystemTools::FileIsDirectory(fileName) &&
1498         !cmSystemTools::FileIsSymlink(fileName) && recurse) {
1499       cmSystemTools::RepeatedRemoveDirectory(fileName);
1500     } else {
1501       cmSystemTools::RemoveFile(fileName);
1502     }
1503   }
1504   return true;
1505 }
1506
1507 bool HandleRemove(std::vector<std::string> const& args,
1508                   cmExecutionStatus& status)
1509 {
1510   return HandleRemoveImpl(args, false, status);
1511 }
1512
1513 bool HandleRemoveRecurse(std::vector<std::string> const& args,
1514                          cmExecutionStatus& status)
1515 {
1516   return HandleRemoveImpl(args, true, status);
1517 }
1518
1519 std::string ToNativePath(const std::string& path)
1520 {
1521   const auto& outPath = cmSystemTools::ConvertToOutputPath(path);
1522   if (outPath.size() > 1 && outPath.front() == '\"' &&
1523       outPath.back() == '\"') {
1524     return outPath.substr(1, outPath.size() - 2);
1525   }
1526   return outPath;
1527 }
1528
1529 std::string ToCMakePath(const std::string& path)
1530 {
1531   auto temp = path;
1532   cmSystemTools::ConvertToUnixSlashes(temp);
1533   return temp;
1534 }
1535
1536 bool HandlePathCommand(std::vector<std::string> const& args,
1537                        std::string (*convert)(std::string const&),
1538                        cmExecutionStatus& status)
1539 {
1540   if (args.size() != 3) {
1541     status.SetError("FILE([TO_CMAKE_PATH|TO_NATIVE_PATH] path result) must be "
1542                     "called with exactly three arguments.");
1543     return false;
1544   }
1545 #if defined(_WIN32) && !defined(__CYGWIN__)
1546   char pathSep = ';';
1547 #else
1548   char pathSep = ':';
1549 #endif
1550   std::vector<std::string> path = cmSystemTools::SplitString(args[1], pathSep);
1551
1552   std::string value = cmJoin(cmMakeRange(path).transform(convert), ";");
1553   status.GetMakefile().AddDefinition(args[2], value);
1554   return true;
1555 }
1556
1557 bool HandleCMakePathCommand(std::vector<std::string> const& args,
1558                             cmExecutionStatus& status)
1559 {
1560   return HandlePathCommand(args, ToCMakePath, status);
1561 }
1562
1563 bool HandleNativePathCommand(std::vector<std::string> const& args,
1564                              cmExecutionStatus& status)
1565 {
1566   return HandlePathCommand(args, ToNativePath, status);
1567 }
1568
1569 #if !defined(CMAKE_BOOTSTRAP)
1570
1571 // Stuff for curl download/upload
1572 using cmFileCommandVectorOfChar = std::vector<char>;
1573
1574 size_t cmWriteToFileCallback(void* ptr, size_t size, size_t nmemb, void* data)
1575 {
1576   int realsize = static_cast<int>(size * nmemb);
1577   cmsys::ofstream* fout = static_cast<cmsys::ofstream*>(data);
1578   if (fout) {
1579     const char* chPtr = static_cast<char*>(ptr);
1580     fout->write(chPtr, realsize);
1581   }
1582   return realsize;
1583 }
1584
1585 size_t cmWriteToMemoryCallback(void* ptr, size_t size, size_t nmemb,
1586                                void* data)
1587 {
1588   int realsize = static_cast<int>(size * nmemb);
1589   const char* chPtr = static_cast<char*>(ptr);
1590   cm::append(*static_cast<cmFileCommandVectorOfChar*>(data), chPtr,
1591              chPtr + realsize);
1592   return realsize;
1593 }
1594
1595 int cmFileCommandCurlDebugCallback(CURL*, curl_infotype type, char* chPtr,
1596                                    size_t size, void* data)
1597 {
1598   cmFileCommandVectorOfChar& vec =
1599     *static_cast<cmFileCommandVectorOfChar*>(data);
1600   switch (type) {
1601     case CURLINFO_TEXT:
1602     case CURLINFO_HEADER_IN:
1603     case CURLINFO_HEADER_OUT:
1604       cm::append(vec, chPtr, chPtr + size);
1605       break;
1606     case CURLINFO_DATA_IN:
1607     case CURLINFO_DATA_OUT:
1608     case CURLINFO_SSL_DATA_IN:
1609     case CURLINFO_SSL_DATA_OUT: {
1610       char buf[128];
1611       int n =
1612         snprintf(buf, sizeof(buf), "[%" KWIML_INT_PRIu64 " bytes data]\n",
1613                  static_cast<KWIML_INT_uint64_t>(size));
1614       if (n > 0) {
1615         cm::append(vec, buf, buf + n);
1616       }
1617     } break;
1618     default:
1619       break;
1620   }
1621   return 0;
1622 }
1623
1624 class cURLProgressHelper
1625 {
1626 public:
1627   cURLProgressHelper(cmMakefile* mf, const char* text)
1628     : Makefile(mf)
1629     , Text(text)
1630   {
1631   }
1632
1633   bool UpdatePercentage(double value, double total, std::string& status)
1634   {
1635     long OldPercentage = this->CurrentPercentage;
1636
1637     if (total > 0.0) {
1638       this->CurrentPercentage = std::lround(value / total * 100.0);
1639       if (this->CurrentPercentage > 100) {
1640         // Avoid extra progress reports for unexpected data beyond total.
1641         this->CurrentPercentage = 100;
1642       }
1643     }
1644
1645     bool updated = (OldPercentage != this->CurrentPercentage);
1646
1647     if (updated) {
1648       status =
1649         cmStrCat("[", this->Text, " ", this->CurrentPercentage, "% complete]");
1650     }
1651
1652     return updated;
1653   }
1654
1655   cmMakefile* GetMakefile() { return this->Makefile; }
1656
1657 private:
1658   long CurrentPercentage = -1;
1659   cmMakefile* Makefile;
1660   std::string Text;
1661 };
1662
1663 int cmFileDownloadProgressCallback(void* clientp, double dltotal, double dlnow,
1664                                    double ultotal, double ulnow)
1665 {
1666   cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1667
1668   static_cast<void>(ultotal);
1669   static_cast<void>(ulnow);
1670
1671   std::string status;
1672   if (helper->UpdatePercentage(dlnow, dltotal, status)) {
1673     cmMakefile* mf = helper->GetMakefile();
1674     mf->DisplayStatus(status, -1);
1675   }
1676
1677   return 0;
1678 }
1679
1680 int cmFileUploadProgressCallback(void* clientp, double dltotal, double dlnow,
1681                                  double ultotal, double ulnow)
1682 {
1683   cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1684
1685   static_cast<void>(dltotal);
1686   static_cast<void>(dlnow);
1687
1688   std::string status;
1689   if (helper->UpdatePercentage(ulnow, ultotal, status)) {
1690     cmMakefile* mf = helper->GetMakefile();
1691     mf->DisplayStatus(status, -1);
1692   }
1693
1694   return 0;
1695 }
1696
1697 class cURLEasyGuard
1698 {
1699 public:
1700   cURLEasyGuard(CURL* easy)
1701     : Easy(easy)
1702   {
1703   }
1704
1705   ~cURLEasyGuard()
1706   {
1707     if (this->Easy) {
1708       ::curl_easy_cleanup(this->Easy);
1709     }
1710   }
1711
1712   cURLEasyGuard(const cURLEasyGuard&) = delete;
1713   cURLEasyGuard& operator=(const cURLEasyGuard&) = delete;
1714
1715   void release() { this->Easy = nullptr; }
1716
1717 private:
1718   ::CURL* Easy;
1719 };
1720
1721 #endif
1722
1723 #define check_curl_result(result, errstr)                                     \
1724   do {                                                                        \
1725     if (result != CURLE_OK) {                                                 \
1726       std::string e(errstr);                                                  \
1727       e += ::curl_easy_strerror(result);                                      \
1728       status.SetError(e);                                                     \
1729       return false;                                                           \
1730     }                                                                         \
1731   } while (false)
1732
1733 bool HandleDownloadCommand(std::vector<std::string> const& args,
1734                            cmExecutionStatus& status)
1735 {
1736 #if !defined(CMAKE_BOOTSTRAP)
1737   auto i = args.begin();
1738   if (args.size() < 2) {
1739     status.SetError("DOWNLOAD must be called with at least two arguments.");
1740     return false;
1741   }
1742   ++i; // Get rid of subcommand
1743   std::string url = *i;
1744   ++i;
1745   std::string file;
1746
1747   long timeout = 0;
1748   long inactivity_timeout = 0;
1749   std::string logVar;
1750   std::string statusVar;
1751   bool tls_verify = status.GetMakefile().IsOn("CMAKE_TLS_VERIFY");
1752   cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
1753   std::string netrc_level =
1754     status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
1755   std::string netrc_file =
1756     status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
1757   std::string expectedHash;
1758   std::string hashMatchMSG;
1759   std::unique_ptr<cmCryptoHash> hash;
1760   bool showProgress = false;
1761   std::string userpwd;
1762
1763   std::vector<std::string> curl_headers;
1764   std::vector<std::pair<std::string, cm::optional<std::string>>> curl_ranges;
1765
1766   while (i != args.end()) {
1767     if (*i == "TIMEOUT") {
1768       ++i;
1769       if (i != args.end()) {
1770         timeout = atol(i->c_str());
1771       } else {
1772         status.SetError("DOWNLOAD missing time for TIMEOUT.");
1773         return false;
1774       }
1775     } else if (*i == "INACTIVITY_TIMEOUT") {
1776       ++i;
1777       if (i != args.end()) {
1778         inactivity_timeout = atol(i->c_str());
1779       } else {
1780         status.SetError("DOWNLOAD missing time for INACTIVITY_TIMEOUT.");
1781         return false;
1782       }
1783     } else if (*i == "LOG") {
1784       ++i;
1785       if (i == args.end()) {
1786         status.SetError("DOWNLOAD missing VAR for LOG.");
1787         return false;
1788       }
1789       logVar = *i;
1790     } else if (*i == "STATUS") {
1791       ++i;
1792       if (i == args.end()) {
1793         status.SetError("DOWNLOAD missing VAR for STATUS.");
1794         return false;
1795       }
1796       statusVar = *i;
1797     } else if (*i == "TLS_VERIFY") {
1798       ++i;
1799       if (i != args.end()) {
1800         tls_verify = cmIsOn(*i);
1801       } else {
1802         status.SetError("DOWNLOAD missing bool value for TLS_VERIFY.");
1803         return false;
1804       }
1805     } else if (*i == "TLS_CAINFO") {
1806       ++i;
1807       if (i != args.end()) {
1808         cainfo = cmValue(*i);
1809       } else {
1810         status.SetError("DOWNLOAD missing file value for TLS_CAINFO.");
1811         return false;
1812       }
1813     } else if (*i == "NETRC_FILE") {
1814       ++i;
1815       if (i != args.end()) {
1816         netrc_file = *i;
1817       } else {
1818         status.SetError("DOWNLOAD missing file value for NETRC_FILE.");
1819         return false;
1820       }
1821     } else if (*i == "NETRC") {
1822       ++i;
1823       if (i != args.end()) {
1824         netrc_level = *i;
1825       } else {
1826         status.SetError("DOWNLOAD missing level value for NETRC.");
1827         return false;
1828       }
1829     } else if (*i == "EXPECTED_MD5") {
1830       ++i;
1831       if (i == args.end()) {
1832         status.SetError("DOWNLOAD missing sum value for EXPECTED_MD5.");
1833         return false;
1834       }
1835       hash = cm::make_unique<cmCryptoHash>(cmCryptoHash::AlgoMD5);
1836       hashMatchMSG = "MD5 sum";
1837       expectedHash = cmSystemTools::LowerCase(*i);
1838     } else if (*i == "SHOW_PROGRESS") {
1839       showProgress = true;
1840     } else if (*i == "EXPECTED_HASH") {
1841       ++i;
1842       if (i == args.end()) {
1843         status.SetError("DOWNLOAD missing ALGO=value for EXPECTED_HASH.");
1844         return false;
1845       }
1846       std::string::size_type pos = i->find("=");
1847       if (pos == std::string::npos) {
1848         std::string err =
1849           cmStrCat("DOWNLOAD EXPECTED_HASH expects ALGO=value but got: ", *i);
1850         status.SetError(err);
1851         return false;
1852       }
1853       std::string algo = i->substr(0, pos);
1854       expectedHash = cmSystemTools::LowerCase(i->substr(pos + 1));
1855       hash = cmCryptoHash::New(algo);
1856       if (!hash) {
1857         std::string err =
1858           cmStrCat("DOWNLOAD EXPECTED_HASH given unknown ALGO: ", algo);
1859         status.SetError(err);
1860         return false;
1861       }
1862       hashMatchMSG = algo + " hash";
1863     } else if (*i == "USERPWD") {
1864       ++i;
1865       if (i == args.end()) {
1866         status.SetError("DOWNLOAD missing string for USERPWD.");
1867         return false;
1868       }
1869       userpwd = *i;
1870     } else if (*i == "HTTPHEADER") {
1871       ++i;
1872       if (i == args.end()) {
1873         status.SetError("DOWNLOAD missing string for HTTPHEADER.");
1874         return false;
1875       }
1876       curl_headers.push_back(*i);
1877     } else if (*i == "RANGE_START") {
1878       ++i;
1879       if (i == args.end()) {
1880         status.SetError("DOWNLOAD missing value for RANGE_START.");
1881         return false;
1882       }
1883       curl_ranges.emplace_back(*i, cm::nullopt);
1884     } else if (*i == "RANGE_END") {
1885       ++i;
1886       if (curl_ranges.empty()) {
1887         curl_ranges.emplace_back("0", *i);
1888       } else {
1889         auto& last_range = curl_ranges.back();
1890         if (!last_range.second.has_value()) {
1891           last_range.second = *i;
1892         } else {
1893           status.SetError("Multiple RANGE_END values is provided without "
1894                           "the corresponding RANGE_START.");
1895           return false;
1896         }
1897       }
1898     } else if (file.empty()) {
1899       file = *i;
1900     } else {
1901       // Do not return error for compatibility reason.
1902       std::string err = cmStrCat("Unexpected argument: ", *i);
1903       status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
1904     }
1905     ++i;
1906   }
1907
1908   // Can't calculate hash if we don't save the file.
1909   // TODO Incrementally calculate hash in the write callback as the file is
1910   // being downloaded so this check can be relaxed.
1911   if (file.empty() && hash) {
1912     status.SetError("DOWNLOAD cannot calculate hash if file is not saved.");
1913     return false;
1914   }
1915   // If file exists already, and caller specified an expected md5 or sha,
1916   // and the existing file already has the expected hash, then simply
1917   // return.
1918   //
1919   if (!file.empty() && cmSystemTools::FileExists(file) && hash.get()) {
1920     std::string msg;
1921     std::string actualHash = hash->HashFile(file);
1922     if (actualHash == expectedHash) {
1923       msg = cmStrCat("skipping download as file already exists with expected ",
1924                      hashMatchMSG, '"');
1925       if (!statusVar.empty()) {
1926         status.GetMakefile().AddDefinition(statusVar, cmStrCat(0, ";\"", msg));
1927       }
1928       return true;
1929     }
1930   }
1931   // Make sure parent directory exists so we can write to the file
1932   // as we receive downloaded bits from curl...
1933   //
1934   if (!file.empty()) {
1935     std::string dir = cmSystemTools::GetFilenamePath(file);
1936     if (!dir.empty() && !cmSystemTools::FileExists(dir) &&
1937         !cmSystemTools::MakeDirectory(dir)) {
1938       std::string errstring = "DOWNLOAD error: cannot create directory '" +
1939         dir +
1940         "' - Specify file by full path name and verify that you "
1941         "have directory creation and file write privileges.";
1942       status.SetError(errstring);
1943       return false;
1944     }
1945   }
1946
1947   cmsys::ofstream fout;
1948   if (!file.empty()) {
1949     fout.open(file.c_str(), std::ios::binary);
1950     if (!fout) {
1951       status.SetError("DOWNLOAD cannot open file for write.");
1952       return false;
1953     }
1954   }
1955
1956   url = cmCurlFixFileURL(url);
1957
1958   ::CURL* curl;
1959   ::curl_global_init(CURL_GLOBAL_DEFAULT);
1960   curl = ::curl_easy_init();
1961   if (!curl) {
1962     status.SetError("DOWNLOAD error initializing curl.");
1963     return false;
1964   }
1965
1966   cURLEasyGuard g_curl(curl);
1967   ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
1968   check_curl_result(res, "DOWNLOAD cannot set url: ");
1969
1970   // enable HTTP ERROR parsing
1971   res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
1972   check_curl_result(res, "DOWNLOAD cannot set http failure option: ");
1973
1974   res = ::curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/" LIBCURL_VERSION);
1975   check_curl_result(res, "DOWNLOAD cannot set user agent option: ");
1976
1977   res = ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToFileCallback);
1978   check_curl_result(res, "DOWNLOAD cannot set write function: ");
1979
1980   res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
1981                            cmFileCommandCurlDebugCallback);
1982   check_curl_result(res, "DOWNLOAD cannot set debug function: ");
1983
1984   // check to see if TLS verification is requested
1985   if (tls_verify) {
1986     res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
1987     check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify on: ");
1988   } else {
1989     res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
1990     check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify off: ");
1991   }
1992
1993   for (const auto& range : curl_ranges) {
1994     std::string curl_range = range.first + '-' +
1995       (range.second.has_value() ? range.second.value() : "");
1996     res = ::curl_easy_setopt(curl, CURLOPT_RANGE, curl_range.c_str());
1997     check_curl_result(res, "DOWNLOAD cannot set range: ");
1998   }
1999
2000   // check to see if a CAINFO file has been specified
2001   // command arg comes first
2002   std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
2003   if (!cainfo_err.empty()) {
2004     status.SetError(cainfo_err);
2005     return false;
2006   }
2007
2008   // check to see if netrc parameters have been specified
2009   // local command args takes precedence over CMAKE_NETRC*
2010   netrc_level = cmSystemTools::UpperCase(netrc_level);
2011   std::string const& netrc_option_err =
2012     cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2013   if (!netrc_option_err.empty()) {
2014     status.SetError(netrc_option_err);
2015     return false;
2016   }
2017
2018   cmFileCommandVectorOfChar chunkDebug;
2019
2020   res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA,
2021                            file.empty() ? nullptr : &fout);
2022   check_curl_result(res, "DOWNLOAD cannot set write data: ");
2023
2024   res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2025   check_curl_result(res, "DOWNLOAD cannot set debug data: ");
2026
2027   res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2028   check_curl_result(res, "DOWNLOAD cannot set follow-redirect option: ");
2029
2030   if (!logVar.empty()) {
2031     res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2032     check_curl_result(res, "DOWNLOAD cannot set verbose: ");
2033   }
2034
2035   if (timeout > 0) {
2036     res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2037     check_curl_result(res, "DOWNLOAD cannot set timeout: ");
2038   }
2039
2040   if (inactivity_timeout > 0) {
2041     // Give up if there is no progress for a long time.
2042     ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2043     ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2044   }
2045
2046   // Need the progress helper's scope to last through the duration of
2047   // the curl_easy_perform call... so this object is declared at function
2048   // scope intentionally, rather than inside the "if(showProgress)"
2049   // block...
2050   //
2051   cURLProgressHelper helper(&status.GetMakefile(), "download");
2052
2053   if (showProgress) {
2054     res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2055     check_curl_result(res, "DOWNLOAD cannot set noprogress value: ");
2056
2057     res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
2058                              cmFileDownloadProgressCallback);
2059     check_curl_result(res, "DOWNLOAD cannot set progress function: ");
2060
2061     res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2062                              reinterpret_cast<void*>(&helper));
2063     check_curl_result(res, "DOWNLOAD cannot set progress data: ");
2064   }
2065
2066   if (!userpwd.empty()) {
2067     res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2068     check_curl_result(res, "DOWNLOAD cannot set user password: ");
2069   }
2070
2071   struct curl_slist* headers = nullptr;
2072   for (std::string const& h : curl_headers) {
2073     headers = ::curl_slist_append(headers, h.c_str());
2074   }
2075   ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2076
2077   res = ::curl_easy_perform(curl);
2078
2079   ::curl_slist_free_all(headers);
2080
2081   /* always cleanup */
2082   g_curl.release();
2083   ::curl_easy_cleanup(curl);
2084
2085   if (!statusVar.empty()) {
2086     status.GetMakefile().AddDefinition(
2087       statusVar,
2088       cmStrCat(static_cast<int>(res), ";\"", ::curl_easy_strerror(res), "\""));
2089   }
2090
2091   ::curl_global_cleanup();
2092
2093   // Ensure requested curl logs are returned (especially in case of failure)
2094   //
2095   if (!logVar.empty()) {
2096     chunkDebug.push_back(0);
2097     status.GetMakefile().AddDefinition(logVar, chunkDebug.data());
2098   }
2099
2100   // Explicitly flush/close so we can measure the md5 accurately.
2101   //
2102   if (!file.empty()) {
2103     fout.flush();
2104     fout.close();
2105   }
2106
2107   // Verify MD5 sum if requested:
2108   //
2109   if (hash) {
2110     std::string actualHash = hash->HashFile(file);
2111     if (actualHash.empty()) {
2112       status.SetError("DOWNLOAD cannot compute hash on downloaded file");
2113       return false;
2114     }
2115
2116     if (expectedHash != actualHash) {
2117       if (!statusVar.empty() && res == 0) {
2118         status.GetMakefile().AddDefinition(statusVar,
2119                                            "1;HASH mismatch: "
2120                                            "expected: " +
2121                                              expectedHash +
2122                                              " actual: " + actualHash);
2123       }
2124
2125       status.SetError(cmStrCat("DOWNLOAD HASH mismatch\n"
2126                                "  for file: [",
2127                                file,
2128                                "]\n"
2129                                "    expected hash: [",
2130                                expectedHash,
2131                                "]\n"
2132                                "      actual hash: [",
2133                                actualHash,
2134                                "]\n"
2135                                "           status: [",
2136                                static_cast<int>(res), ";\"",
2137                                ::curl_easy_strerror(res), "\"]\n"));
2138       return false;
2139     }
2140   }
2141
2142   return true;
2143 #else
2144   status.SetError("DOWNLOAD not supported by bootstrap cmake.");
2145   return false;
2146 #endif
2147 }
2148
2149 bool HandleUploadCommand(std::vector<std::string> const& args,
2150                          cmExecutionStatus& status)
2151 {
2152 #if !defined(CMAKE_BOOTSTRAP)
2153   if (args.size() < 3) {
2154     status.SetError("UPLOAD must be called with at least three arguments.");
2155     return false;
2156   }
2157   auto i = args.begin();
2158   ++i;
2159   std::string filename = *i;
2160   ++i;
2161   std::string url = *i;
2162   ++i;
2163
2164   long timeout = 0;
2165   long inactivity_timeout = 0;
2166   std::string logVar;
2167   std::string statusVar;
2168   bool showProgress = false;
2169   bool tls_verify = status.GetMakefile().IsOn("CMAKE_TLS_VERIFY");
2170   cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
2171   std::string userpwd;
2172   std::string netrc_level =
2173     status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
2174   std::string netrc_file =
2175     status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
2176
2177   std::vector<std::string> curl_headers;
2178
2179   while (i != args.end()) {
2180     if (*i == "TIMEOUT") {
2181       ++i;
2182       if (i != args.end()) {
2183         timeout = atol(i->c_str());
2184       } else {
2185         status.SetError("UPLOAD missing time for TIMEOUT.");
2186         return false;
2187       }
2188     } else if (*i == "INACTIVITY_TIMEOUT") {
2189       ++i;
2190       if (i != args.end()) {
2191         inactivity_timeout = atol(i->c_str());
2192       } else {
2193         status.SetError("UPLOAD missing time for INACTIVITY_TIMEOUT.");
2194         return false;
2195       }
2196     } else if (*i == "LOG") {
2197       ++i;
2198       if (i == args.end()) {
2199         status.SetError("UPLOAD missing VAR for LOG.");
2200         return false;
2201       }
2202       logVar = *i;
2203     } else if (*i == "STATUS") {
2204       ++i;
2205       if (i == args.end()) {
2206         status.SetError("UPLOAD missing VAR for STATUS.");
2207         return false;
2208       }
2209       statusVar = *i;
2210     } else if (*i == "SHOW_PROGRESS") {
2211       showProgress = true;
2212     } else if (*i == "TLS_VERIFY") {
2213       ++i;
2214       if (i != args.end()) {
2215         tls_verify = cmIsOn(*i);
2216       } else {
2217         status.SetError("UPLOAD missing bool value for TLS_VERIFY.");
2218         return false;
2219       }
2220     } else if (*i == "TLS_CAINFO") {
2221       ++i;
2222       if (i != args.end()) {
2223         cainfo = cmValue(*i);
2224       } else {
2225         status.SetError("UPLOAD missing file value for TLS_CAINFO.");
2226         return false;
2227       }
2228     } else if (*i == "NETRC_FILE") {
2229       ++i;
2230       if (i != args.end()) {
2231         netrc_file = *i;
2232       } else {
2233         status.SetError("UPLOAD missing file value for NETRC_FILE.");
2234         return false;
2235       }
2236     } else if (*i == "NETRC") {
2237       ++i;
2238       if (i != args.end()) {
2239         netrc_level = *i;
2240       } else {
2241         status.SetError("UPLOAD missing level value for NETRC.");
2242         return false;
2243       }
2244     } else if (*i == "USERPWD") {
2245       ++i;
2246       if (i == args.end()) {
2247         status.SetError("UPLOAD missing string for USERPWD.");
2248         return false;
2249       }
2250       userpwd = *i;
2251     } else if (*i == "HTTPHEADER") {
2252       ++i;
2253       if (i == args.end()) {
2254         status.SetError("UPLOAD missing string for HTTPHEADER.");
2255         return false;
2256       }
2257       curl_headers.push_back(*i);
2258     } else {
2259       // Do not return error for compatibility reason.
2260       std::string err = cmStrCat("Unexpected argument: ", *i);
2261       status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
2262     }
2263
2264     ++i;
2265   }
2266
2267   // Open file for reading:
2268   //
2269   FILE* fin = cmsys::SystemTools::Fopen(filename, "rb");
2270   if (!fin) {
2271     std::string errStr =
2272       cmStrCat("UPLOAD cannot open file '", filename, "' for reading.");
2273     status.SetError(errStr);
2274     return false;
2275   }
2276
2277   unsigned long file_size = cmsys::SystemTools::FileLength(filename);
2278
2279   url = cmCurlFixFileURL(url);
2280
2281   ::CURL* curl;
2282   ::curl_global_init(CURL_GLOBAL_DEFAULT);
2283   curl = ::curl_easy_init();
2284   if (!curl) {
2285     status.SetError("UPLOAD error initializing curl.");
2286     fclose(fin);
2287     return false;
2288   }
2289
2290   cURLEasyGuard g_curl(curl);
2291
2292   // enable HTTP ERROR parsing
2293   ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
2294   check_curl_result(res, "UPLOAD cannot set fail on error flag: ");
2295
2296   // enable uploading
2297   res = ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
2298   check_curl_result(res, "UPLOAD cannot set upload flag: ");
2299
2300   res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
2301   check_curl_result(res, "UPLOAD cannot set url: ");
2302
2303   res =
2304     ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToMemoryCallback);
2305   check_curl_result(res, "UPLOAD cannot set write function: ");
2306
2307   res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
2308                            cmFileCommandCurlDebugCallback);
2309   check_curl_result(res, "UPLOAD cannot set debug function: ");
2310
2311   // check to see if TLS verification is requested
2312   if (tls_verify) {
2313     res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
2314     check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify on: ");
2315   } else {
2316     res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
2317     check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify off: ");
2318   }
2319
2320   // check to see if a CAINFO file has been specified
2321   // command arg comes first
2322   std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
2323   if (!cainfo_err.empty()) {
2324     status.SetError(cainfo_err);
2325     return false;
2326   }
2327
2328   cmFileCommandVectorOfChar chunkResponse;
2329   cmFileCommandVectorOfChar chunkDebug;
2330
2331   res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunkResponse);
2332   check_curl_result(res, "UPLOAD cannot set write data: ");
2333
2334   res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2335   check_curl_result(res, "UPLOAD cannot set debug data: ");
2336
2337   res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2338   check_curl_result(res, "UPLOAD cannot set follow-redirect option: ");
2339
2340   if (!logVar.empty()) {
2341     res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2342     check_curl_result(res, "UPLOAD cannot set verbose: ");
2343   }
2344
2345   if (timeout > 0) {
2346     res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2347     check_curl_result(res, "UPLOAD cannot set timeout: ");
2348   }
2349
2350   if (inactivity_timeout > 0) {
2351     // Give up if there is no progress for a long time.
2352     ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2353     ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2354   }
2355
2356   // Need the progress helper's scope to last through the duration of
2357   // the curl_easy_perform call... so this object is declared at function
2358   // scope intentionally, rather than inside the "if(showProgress)"
2359   // block...
2360   //
2361   cURLProgressHelper helper(&status.GetMakefile(), "upload");
2362
2363   if (showProgress) {
2364     res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2365     check_curl_result(res, "UPLOAD cannot set noprogress value: ");
2366
2367     res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
2368                              cmFileUploadProgressCallback);
2369     check_curl_result(res, "UPLOAD cannot set progress function: ");
2370
2371     res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2372                              reinterpret_cast<void*>(&helper));
2373     check_curl_result(res, "UPLOAD cannot set progress data: ");
2374   }
2375
2376   // now specify which file to upload
2377   res = ::curl_easy_setopt(curl, CURLOPT_INFILE, fin);
2378   check_curl_result(res, "UPLOAD cannot set input file: ");
2379
2380   // and give the size of the upload (optional)
2381   res =
2382     ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast<long>(file_size));
2383   check_curl_result(res, "UPLOAD cannot set input file size: ");
2384
2385   if (!userpwd.empty()) {
2386     res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2387     check_curl_result(res, "UPLOAD cannot set user password: ");
2388   }
2389
2390   // check to see if netrc parameters have been specified
2391   // local command args takes precedence over CMAKE_NETRC*
2392   netrc_level = cmSystemTools::UpperCase(netrc_level);
2393   std::string const& netrc_option_err =
2394     cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2395   if (!netrc_option_err.empty()) {
2396     status.SetError(netrc_option_err);
2397     return false;
2398   }
2399
2400   struct curl_slist* headers = nullptr;
2401   for (std::string const& h : curl_headers) {
2402     headers = ::curl_slist_append(headers, h.c_str());
2403   }
2404   ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2405
2406   res = ::curl_easy_perform(curl);
2407
2408   ::curl_slist_free_all(headers);
2409
2410   /* always cleanup */
2411   g_curl.release();
2412   ::curl_easy_cleanup(curl);
2413
2414   if (!statusVar.empty()) {
2415     status.GetMakefile().AddDefinition(
2416       statusVar,
2417       cmStrCat(static_cast<int>(res), ";\"", ::curl_easy_strerror(res), "\""));
2418   }
2419
2420   ::curl_global_cleanup();
2421
2422   fclose(fin);
2423   fin = nullptr;
2424
2425   if (!logVar.empty()) {
2426     std::string log;
2427
2428     if (!chunkResponse.empty()) {
2429       chunkResponse.push_back(0);
2430       log += "Response:\n";
2431       log += chunkResponse.data();
2432       log += "\n";
2433     }
2434
2435     if (!chunkDebug.empty()) {
2436       chunkDebug.push_back(0);
2437       log += "Debug:\n";
2438       log += chunkDebug.data();
2439       log += "\n";
2440     }
2441
2442     status.GetMakefile().AddDefinition(logVar, log);
2443   }
2444
2445   return true;
2446 #else
2447   status.SetError("UPLOAD not supported by bootstrap cmake.");
2448   return false;
2449 #endif
2450 }
2451
2452 void AddEvaluationFile(const std::string& inputName,
2453                        const std::string& targetName,
2454                        const std::string& outputExpr,
2455                        const std::string& condition, bool inputIsContent,
2456                        const std::string& newLineCharacter, mode_t permissions,
2457                        cmExecutionStatus& status)
2458 {
2459   cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace();
2460
2461   cmGeneratorExpression outputGe(lfbt);
2462   std::unique_ptr<cmCompiledGeneratorExpression> outputCge =
2463     outputGe.Parse(outputExpr);
2464
2465   cmGeneratorExpression conditionGe(lfbt);
2466   std::unique_ptr<cmCompiledGeneratorExpression> conditionCge =
2467     conditionGe.Parse(condition);
2468
2469   status.GetMakefile().AddEvaluationFile(
2470     inputName, targetName, std::move(outputCge), std::move(conditionCge),
2471     newLineCharacter, permissions, inputIsContent);
2472 }
2473
2474 bool HandleGenerateCommand(std::vector<std::string> const& args,
2475                            cmExecutionStatus& status)
2476 {
2477   if (args.size() < 5) {
2478     status.SetError("Incorrect arguments to GENERATE subcommand.");
2479     return false;
2480   }
2481
2482   struct Arguments : public ArgumentParser::ParseResult
2483   {
2484     cm::optional<std::string> Output;
2485     cm::optional<std::string> Input;
2486     cm::optional<std::string> Content;
2487     cm::optional<std::string> Condition;
2488     cm::optional<std::string> Target;
2489     cm::optional<std::string> NewLineStyle;
2490     bool NoSourcePermissions = false;
2491     bool UseSourcePermissions = false;
2492     ArgumentParser::NonEmpty<std::vector<std::string>> FilePermissions;
2493     std::vector<cm::string_view> ParsedKeywords;
2494   };
2495
2496   static auto const parser =
2497     cmArgumentParser<Arguments>{}
2498       .Bind("OUTPUT"_s, &Arguments::Output)
2499       .Bind("INPUT"_s, &Arguments::Input)
2500       .Bind("CONTENT"_s, &Arguments::Content)
2501       .Bind("CONDITION"_s, &Arguments::Condition)
2502       .Bind("TARGET"_s, &Arguments::Target)
2503       .Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions)
2504       .Bind("USE_SOURCE_PERMISSIONS"_s, &Arguments::UseSourcePermissions)
2505       .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
2506       .Bind("NEWLINE_STYLE"_s, &Arguments::NewLineStyle)
2507       .BindParsedKeywords(&Arguments::ParsedKeywords);
2508
2509   std::vector<std::string> unparsedArguments;
2510   Arguments const arguments =
2511     parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments);
2512
2513   if (arguments.MaybeReportError(status.GetMakefile())) {
2514     return true;
2515   }
2516
2517   if (!unparsedArguments.empty()) {
2518     status.SetError("Unknown argument to GENERATE subcommand.");
2519     return false;
2520   }
2521
2522   if (!arguments.Output || arguments.ParsedKeywords[0] != "OUTPUT"_s) {
2523     status.SetError("GENERATE requires OUTPUT as first option.");
2524     return false;
2525   }
2526   std::string const& output = *arguments.Output;
2527
2528   if (!arguments.Input && !arguments.Content) {
2529     status.SetError("GENERATE requires INPUT or CONTENT option.");
2530     return false;
2531   }
2532   const bool inputIsContent = arguments.ParsedKeywords[1] == "CONTENT"_s;
2533   if (!inputIsContent && arguments.ParsedKeywords[1] == "INPUT") {
2534     status.SetError("Unknown argument to GENERATE subcommand.");
2535   }
2536   std::string const& input =
2537     inputIsContent ? *arguments.Content : *arguments.Input;
2538
2539   if (arguments.Condition && arguments.Condition->empty()) {
2540     status.SetError("CONDITION of sub-command GENERATE must not be empty "
2541                     "if specified.");
2542     return false;
2543   }
2544   std::string const& condition =
2545     arguments.Condition ? *arguments.Condition : std::string();
2546
2547   if (arguments.Target && arguments.Target->empty()) {
2548     status.SetError("TARGET of sub-command GENERATE must not be empty "
2549                     "if specified.");
2550     return false;
2551   }
2552   std::string const& target =
2553     arguments.Target ? *arguments.Target : std::string();
2554
2555   cmNewLineStyle newLineStyle;
2556   if (arguments.NewLineStyle) {
2557     std::string errorMessage;
2558     if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
2559       status.SetError(cmStrCat("GENERATE ", errorMessage));
2560       return false;
2561     }
2562   }
2563
2564   if (arguments.NoSourcePermissions && arguments.UseSourcePermissions) {
2565     status.SetError("given both NO_SOURCE_PERMISSIONS and "
2566                     "USE_SOURCE_PERMISSIONS. Only one option allowed.");
2567     return false;
2568   }
2569
2570   if (!arguments.FilePermissions.empty()) {
2571     if (arguments.NoSourcePermissions) {
2572       status.SetError("given both NO_SOURCE_PERMISSIONS and "
2573                       "FILE_PERMISSIONS. Only one option allowed.");
2574       return false;
2575     }
2576     if (arguments.UseSourcePermissions) {
2577       status.SetError("given both USE_SOURCE_PERMISSIONS and "
2578                       "FILE_PERMISSIONS. Only one option allowed.");
2579       return false;
2580     }
2581   }
2582
2583   if (arguments.UseSourcePermissions) {
2584     if (inputIsContent) {
2585       status.SetError("given USE_SOURCE_PERMISSIONS without a file INPUT.");
2586       return false;
2587     }
2588   }
2589
2590   mode_t permissions = 0;
2591   if (arguments.NoSourcePermissions) {
2592     permissions |= cmFSPermissions::mode_owner_read;
2593     permissions |= cmFSPermissions::mode_owner_write;
2594     permissions |= cmFSPermissions::mode_group_read;
2595     permissions |= cmFSPermissions::mode_world_read;
2596   }
2597
2598   if (!arguments.FilePermissions.empty()) {
2599     std::vector<std::string> invalidOptions;
2600     for (auto const& e : arguments.FilePermissions) {
2601       if (!cmFSPermissions::stringToModeT(e, permissions)) {
2602         invalidOptions.push_back(e);
2603       }
2604     }
2605     if (!invalidOptions.empty()) {
2606       std::ostringstream oss;
2607       oss << "given invalid permission ";
2608       for (auto i = 0u; i < invalidOptions.size(); i++) {
2609         if (i == 0u) {
2610           oss << "\"" << invalidOptions[i] << "\"";
2611         } else {
2612           oss << ",\"" << invalidOptions[i] << "\"";
2613         }
2614       }
2615       oss << ".";
2616       status.SetError(oss.str());
2617       return false;
2618     }
2619   }
2620
2621   AddEvaluationFile(input, target, output, condition, inputIsContent,
2622                     newLineStyle.GetCharacters(), permissions, status);
2623   return true;
2624 }
2625
2626 bool HandleLockCommand(std::vector<std::string> const& args,
2627                        cmExecutionStatus& status)
2628 {
2629 #if !defined(CMAKE_BOOTSTRAP)
2630   // Default values
2631   bool directory = false;
2632   bool release = false;
2633   enum Guard
2634   {
2635     GUARD_FUNCTION,
2636     GUARD_FILE,
2637     GUARD_PROCESS
2638   };
2639   Guard guard = GUARD_PROCESS;
2640   std::string resultVariable;
2641   unsigned long timeout = static_cast<unsigned long>(-1);
2642
2643   // Parse arguments
2644   if (args.size() < 2) {
2645     status.GetMakefile().IssueMessage(
2646       MessageType::FATAL_ERROR,
2647       "sub-command LOCK requires at least two arguments.");
2648     return false;
2649   }
2650
2651   std::string path = args[1];
2652   for (unsigned i = 2; i < args.size(); ++i) {
2653     if (args[i] == "DIRECTORY") {
2654       directory = true;
2655     } else if (args[i] == "RELEASE") {
2656       release = true;
2657     } else if (args[i] == "GUARD") {
2658       ++i;
2659       const char* merr = "expected FUNCTION, FILE or PROCESS after GUARD";
2660       if (i >= args.size()) {
2661         status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, merr);
2662         return false;
2663       }
2664       if (args[i] == "FUNCTION") {
2665         guard = GUARD_FUNCTION;
2666       } else if (args[i] == "FILE") {
2667         guard = GUARD_FILE;
2668       } else if (args[i] == "PROCESS") {
2669         guard = GUARD_PROCESS;
2670       } else {
2671         status.GetMakefile().IssueMessage(
2672           MessageType::FATAL_ERROR,
2673           cmStrCat(merr, ", but got:\n  \"", args[i], "\"."));
2674         return false;
2675       }
2676
2677     } else if (args[i] == "RESULT_VARIABLE") {
2678       ++i;
2679       if (i >= args.size()) {
2680         status.GetMakefile().IssueMessage(
2681           MessageType::FATAL_ERROR,
2682           "expected variable name after RESULT_VARIABLE");
2683         return false;
2684       }
2685       resultVariable = args[i];
2686     } else if (args[i] == "TIMEOUT") {
2687       ++i;
2688       if (i >= args.size()) {
2689         status.GetMakefile().IssueMessage(
2690           MessageType::FATAL_ERROR, "expected timeout value after TIMEOUT");
2691         return false;
2692       }
2693       long scanned;
2694       if (!cmStrToLong(args[i], &scanned) || scanned < 0) {
2695         status.GetMakefile().IssueMessage(
2696           MessageType::FATAL_ERROR,
2697           cmStrCat("TIMEOUT value \"", args[i],
2698                    "\" is not an unsigned integer."));
2699         return false;
2700       }
2701       timeout = static_cast<unsigned long>(scanned);
2702     } else {
2703       status.GetMakefile().IssueMessage(
2704         MessageType::FATAL_ERROR,
2705         cmStrCat("expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or ",
2706                  "TIMEOUT\nbut got: \"", args[i], "\"."));
2707       return false;
2708     }
2709   }
2710
2711   if (directory) {
2712     path += "/cmake.lock";
2713   }
2714
2715   // Unify path (remove '//', '/../', ...)
2716   path = cmSystemTools::CollapseFullPath(
2717     path, status.GetMakefile().GetCurrentSourceDirectory());
2718
2719   // Create file and directories if needed
2720   std::string parentDir = cmSystemTools::GetParentDirectory(path);
2721   if (!cmSystemTools::MakeDirectory(parentDir)) {
2722     status.GetMakefile().IssueMessage(
2723       MessageType::FATAL_ERROR,
2724       cmStrCat("directory\n  \"", parentDir,
2725                "\"\ncreation failed (check permissions)."));
2726     cmSystemTools::SetFatalErrorOccurred();
2727     return false;
2728   }
2729   FILE* file = cmsys::SystemTools::Fopen(path, "w");
2730   if (!file) {
2731     status.GetMakefile().IssueMessage(
2732       MessageType::FATAL_ERROR,
2733       cmStrCat("file\n  \"", path,
2734                "\"\ncreation failed (check permissions)."));
2735     cmSystemTools::SetFatalErrorOccurred();
2736     return false;
2737   }
2738   fclose(file);
2739
2740   // Actual lock/unlock
2741   cmFileLockPool& lockPool =
2742     status.GetMakefile().GetGlobalGenerator()->GetFileLockPool();
2743
2744   cmFileLockResult fileLockResult(cmFileLockResult::MakeOk());
2745   if (release) {
2746     fileLockResult = lockPool.Release(path);
2747   } else {
2748     switch (guard) {
2749       case GUARD_FUNCTION:
2750         fileLockResult = lockPool.LockFunctionScope(path, timeout);
2751         break;
2752       case GUARD_FILE:
2753         fileLockResult = lockPool.LockFileScope(path, timeout);
2754         break;
2755       case GUARD_PROCESS:
2756         fileLockResult = lockPool.LockProcessScope(path, timeout);
2757         break;
2758       default:
2759         cmSystemTools::SetFatalErrorOccurred();
2760         return false;
2761     }
2762   }
2763
2764   const std::string result = fileLockResult.GetOutputMessage();
2765
2766   if (resultVariable.empty() && !fileLockResult.IsOk()) {
2767     status.GetMakefile().IssueMessage(
2768       MessageType::FATAL_ERROR,
2769       cmStrCat("error locking file\n  \"", path, "\"\n", result, "."));
2770     cmSystemTools::SetFatalErrorOccurred();
2771     return false;
2772   }
2773
2774   if (!resultVariable.empty()) {
2775     status.GetMakefile().AddDefinition(resultVariable, result);
2776   }
2777
2778   return true;
2779 #else
2780   static_cast<void>(args);
2781   status.SetError("sub-command LOCK not implemented in bootstrap cmake");
2782   return false;
2783 #endif
2784 }
2785
2786 bool HandleTimestampCommand(std::vector<std::string> const& args,
2787                             cmExecutionStatus& status)
2788 {
2789   if (args.size() < 3) {
2790     status.SetError("sub-command TIMESTAMP requires at least two arguments.");
2791     return false;
2792   }
2793   if (args.size() > 5) {
2794     status.SetError("sub-command TIMESTAMP takes at most four arguments.");
2795     return false;
2796   }
2797
2798   unsigned int argsIndex = 1;
2799
2800   std::string filename = args[argsIndex++];
2801   if (!cmsys::SystemTools::FileIsFullPath(filename)) {
2802     filename = cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/',
2803                         filename);
2804   }
2805
2806   const std::string& outputVariable = args[argsIndex++];
2807
2808   std::string formatString;
2809   if (args.size() > argsIndex && args[argsIndex] != "UTC") {
2810     formatString = args[argsIndex++];
2811   }
2812
2813   bool utcFlag = false;
2814   if (args.size() > argsIndex) {
2815     if (args[argsIndex] == "UTC") {
2816       utcFlag = true;
2817     } else {
2818       std::string e = " TIMESTAMP sub-command does not recognize option " +
2819         args[argsIndex] + ".";
2820       status.SetError(e);
2821       return false;
2822     }
2823   }
2824
2825   cmTimestamp timestamp;
2826   std::string result =
2827     timestamp.FileModificationTime(filename.c_str(), formatString, utcFlag);
2828   status.GetMakefile().AddDefinition(outputVariable, result);
2829
2830   return true;
2831 }
2832
2833 bool HandleSizeCommand(std::vector<std::string> const& args,
2834                        cmExecutionStatus& status)
2835 {
2836   if (args.size() != 3) {
2837     status.SetError(
2838       cmStrCat(args[0], " requires a file name and output variable"));
2839     return false;
2840   }
2841
2842   unsigned int argsIndex = 1;
2843
2844   const std::string& filename = args[argsIndex++];
2845
2846   const std::string& outputVariable = args[argsIndex++];
2847
2848   if (!cmSystemTools::FileExists(filename, true)) {
2849     status.SetError(
2850       cmStrCat("SIZE requested of path that is not readable:\n  ", filename));
2851     return false;
2852   }
2853
2854   status.GetMakefile().AddDefinition(
2855     outputVariable, std::to_string(cmSystemTools::FileLength(filename)));
2856
2857   return true;
2858 }
2859
2860 bool HandleReadSymlinkCommand(std::vector<std::string> const& args,
2861                               cmExecutionStatus& status)
2862 {
2863   if (args.size() != 3) {
2864     status.SetError(
2865       cmStrCat(args[0], " requires a file name and output variable"));
2866     return false;
2867   }
2868
2869   const std::string& filename = args[1];
2870   const std::string& outputVariable = args[2];
2871
2872   std::string result;
2873   if (!cmSystemTools::ReadSymlink(filename, result)) {
2874     status.SetError(cmStrCat(
2875       "READ_SYMLINK requested of path that is not a symlink:\n  ", filename));
2876     return false;
2877   }
2878
2879   status.GetMakefile().AddDefinition(outputVariable, result);
2880
2881   return true;
2882 }
2883
2884 bool HandleCreateLinkCommand(std::vector<std::string> const& args,
2885                              cmExecutionStatus& status)
2886 {
2887   if (args.size() < 3) {
2888     status.SetError("CREATE_LINK must be called with at least two additional "
2889                     "arguments");
2890     return false;
2891   }
2892
2893   std::string const& fileName = args[1];
2894   std::string const& newFileName = args[2];
2895
2896   struct Arguments
2897   {
2898     std::string Result;
2899     bool CopyOnError = false;
2900     bool Symbolic = false;
2901   };
2902
2903   static auto const parser =
2904     cmArgumentParser<Arguments>{}
2905       .Bind("RESULT"_s, &Arguments::Result)
2906       .Bind("COPY_ON_ERROR"_s, &Arguments::CopyOnError)
2907       .Bind("SYMBOLIC"_s, &Arguments::Symbolic);
2908
2909   std::vector<std::string> unconsumedArgs;
2910   Arguments const arguments =
2911     parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
2912
2913   if (!unconsumedArgs.empty()) {
2914     status.SetError("unknown argument: \"" + unconsumedArgs.front() + '\"');
2915     return false;
2916   }
2917
2918   // The system error message generated in the operation.
2919   std::string result;
2920
2921   // Check if the paths are distinct.
2922   if (fileName == newFileName) {
2923     result = "CREATE_LINK cannot use same file and newfile";
2924     if (!arguments.Result.empty()) {
2925       status.GetMakefile().AddDefinition(arguments.Result, result);
2926       return true;
2927     }
2928     status.SetError(result);
2929     return false;
2930   }
2931
2932   // Hard link requires original file to exist.
2933   if (!arguments.Symbolic && !cmSystemTools::FileExists(fileName)) {
2934     result = "Cannot hard link \'" + fileName + "\' as it does not exist.";
2935     if (!arguments.Result.empty()) {
2936       status.GetMakefile().AddDefinition(arguments.Result, result);
2937       return true;
2938     }
2939     status.SetError(result);
2940     return false;
2941   }
2942
2943   // Check if the new file already exists and remove it.
2944   if ((cmSystemTools::FileExists(newFileName) ||
2945        cmSystemTools::FileIsSymlink(newFileName)) &&
2946       !cmSystemTools::RemoveFile(newFileName)) {
2947     std::ostringstream e;
2948     e << "Failed to create link '" << newFileName
2949       << "' because existing path cannot be removed: "
2950       << cmSystemTools::GetLastSystemError() << "\n";
2951
2952     if (!arguments.Result.empty()) {
2953       status.GetMakefile().AddDefinition(arguments.Result, e.str());
2954       return true;
2955     }
2956     status.SetError(e.str());
2957     return false;
2958   }
2959
2960   // Whether the operation completed successfully.
2961   bool completed = false;
2962
2963   // Check if the command requires a symbolic link.
2964   if (arguments.Symbolic) {
2965     cmsys::Status linked =
2966       cmSystemTools::CreateSymlinkQuietly(fileName, newFileName);
2967     if (linked) {
2968       completed = true;
2969     } else {
2970       result = cmStrCat("failed to create symbolic link '", newFileName,
2971                         "': ", linked.GetString());
2972     }
2973   } else {
2974     cmsys::Status linked =
2975       cmSystemTools::CreateLinkQuietly(fileName, newFileName);
2976     if (linked) {
2977       completed = true;
2978     } else {
2979       result = cmStrCat("failed to create link '", newFileName,
2980                         "': ", linked.GetString());
2981     }
2982   }
2983
2984   // Check if copy-on-error is enabled in the arguments.
2985   if (!completed && arguments.CopyOnError) {
2986     cmsys::Status copied =
2987       cmsys::SystemTools::CopyFileAlways(fileName, newFileName);
2988     if (copied) {
2989       completed = true;
2990     } else {
2991       result = "Copy failed: " + copied.GetString();
2992     }
2993   }
2994
2995   // Check if the operation was successful.
2996   if (completed) {
2997     result = "0";
2998   } else if (arguments.Result.empty()) {
2999     // The operation failed and the result is not reported in a variable.
3000     status.SetError(result);
3001     return false;
3002   }
3003
3004   if (!arguments.Result.empty()) {
3005     status.GetMakefile().AddDefinition(arguments.Result, result);
3006   }
3007
3008   return true;
3009 }
3010
3011 bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
3012                                          cmExecutionStatus& status)
3013 {
3014   std::string platform =
3015     status.GetMakefile().GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
3016   if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
3017         platform)) {
3018     status.SetError(
3019       cmStrCat("GET_RUNTIME_DEPENDENCIES is not supported on system \"",
3020                platform, "\""));
3021     cmSystemTools::SetFatalErrorOccurred();
3022     return false;
3023   }
3024
3025   if (status.GetMakefile().GetState()->GetMode() == cmState::Project) {
3026     status.GetMakefile().IssueMessage(
3027       MessageType::AUTHOR_WARNING,
3028       "You have used file(GET_RUNTIME_DEPENDENCIES)"
3029       " in project mode. This is probably not what "
3030       "you intended to do. Instead, please consider"
3031       " using it in an install(CODE) or "
3032       "install(SCRIPT) command. For example:"
3033       "\n  install(CODE [["
3034       "\n    file(GET_RUNTIME_DEPENDENCIES"
3035       "\n      # ..."
3036       "\n      )"
3037       "\n    ]])");
3038   }
3039
3040   struct Arguments : public ArgumentParser::ParseResult
3041   {
3042     std::string ResolvedDependenciesVar;
3043     std::string UnresolvedDependenciesVar;
3044     std::string ConflictingDependenciesPrefix;
3045     std::string RPathPrefix;
3046     std::string BundleExecutable;
3047     ArgumentParser::MaybeEmpty<std::vector<std::string>> Executables;
3048     ArgumentParser::MaybeEmpty<std::vector<std::string>> Libraries;
3049     ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories;
3050     ArgumentParser::MaybeEmpty<std::vector<std::string>> Modules;
3051     ArgumentParser::MaybeEmpty<std::vector<std::string>> PreIncludeRegexes;
3052     ArgumentParser::MaybeEmpty<std::vector<std::string>> PreExcludeRegexes;
3053     ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeRegexes;
3054     ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeRegexes;
3055     ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeFiles;
3056     ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeFiles;
3057     ArgumentParser::MaybeEmpty<std::vector<std::string>>
3058       PostExcludeFilesStrict;
3059   };
3060
3061   static auto const parser =
3062     cmArgumentParser<Arguments>{}
3063       .Bind("RESOLVED_DEPENDENCIES_VAR"_s, &Arguments::ResolvedDependenciesVar)
3064       .Bind("UNRESOLVED_DEPENDENCIES_VAR"_s,
3065             &Arguments::UnresolvedDependenciesVar)
3066       .Bind("CONFLICTING_DEPENDENCIES_PREFIX"_s,
3067             &Arguments::ConflictingDependenciesPrefix)
3068       .Bind("RPATH_PREFIX"_s, &Arguments::RPathPrefix)
3069       .Bind("BUNDLE_EXECUTABLE"_s, &Arguments::BundleExecutable)
3070       .Bind("EXECUTABLES"_s, &Arguments::Executables)
3071       .Bind("LIBRARIES"_s, &Arguments::Libraries)
3072       .Bind("MODULES"_s, &Arguments::Modules)
3073       .Bind("DIRECTORIES"_s, &Arguments::Directories)
3074       .Bind("PRE_INCLUDE_REGEXES"_s, &Arguments::PreIncludeRegexes)
3075       .Bind("PRE_EXCLUDE_REGEXES"_s, &Arguments::PreExcludeRegexes)
3076       .Bind("POST_INCLUDE_REGEXES"_s, &Arguments::PostIncludeRegexes)
3077       .Bind("POST_EXCLUDE_REGEXES"_s, &Arguments::PostExcludeRegexes)
3078       .Bind("POST_INCLUDE_FILES"_s, &Arguments::PostIncludeFiles)
3079       .Bind("POST_EXCLUDE_FILES"_s, &Arguments::PostExcludeFiles)
3080       .Bind("POST_EXCLUDE_FILES_STRICT"_s, &Arguments::PostExcludeFilesStrict);
3081
3082   std::vector<std::string> unrecognizedArguments;
3083   auto parsedArgs =
3084     parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3085   auto argIt = unrecognizedArguments.begin();
3086   if (argIt != unrecognizedArguments.end()) {
3087     status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3088     cmSystemTools::SetFatalErrorOccurred();
3089     return false;
3090   }
3091
3092   if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3093     cmSystemTools::SetFatalErrorOccurred();
3094     return true;
3095   }
3096
3097   cmRuntimeDependencyArchive archive(
3098     status, parsedArgs.Directories, parsedArgs.BundleExecutable,
3099     parsedArgs.PreIncludeRegexes, parsedArgs.PreExcludeRegexes,
3100     parsedArgs.PostIncludeRegexes, parsedArgs.PostExcludeRegexes,
3101     std::move(parsedArgs.PostIncludeFiles),
3102     std::move(parsedArgs.PostExcludeFiles),
3103     std::move(parsedArgs.PostExcludeFilesStrict));
3104   if (!archive.Prepare()) {
3105     cmSystemTools::SetFatalErrorOccurred();
3106     return false;
3107   }
3108
3109   if (!archive.GetRuntimeDependencies(
3110         parsedArgs.Executables, parsedArgs.Libraries, parsedArgs.Modules)) {
3111     cmSystemTools::SetFatalErrorOccurred();
3112     return false;
3113   }
3114
3115   std::vector<std::string> deps;
3116   std::vector<std::string> unresolvedDeps;
3117   std::vector<std::string> conflictingDeps;
3118   for (auto const& val : archive.GetResolvedPaths()) {
3119     bool unique = true;
3120     auto it = val.second.begin();
3121     assert(it != val.second.end());
3122     auto const& firstPath = *it;
3123     while (++it != val.second.end()) {
3124       if (!cmSystemTools::SameFile(firstPath, *it)) {
3125         unique = false;
3126         break;
3127       }
3128     }
3129
3130     if (unique) {
3131       deps.push_back(firstPath);
3132       if (!parsedArgs.RPathPrefix.empty()) {
3133         status.GetMakefile().AddDefinition(
3134           parsedArgs.RPathPrefix + "_" + firstPath,
3135           cmJoin(archive.GetRPaths().at(firstPath), ";"));
3136       }
3137     } else if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
3138       conflictingDeps.push_back(val.first);
3139       std::vector<std::string> paths;
3140       paths.insert(paths.begin(), val.second.begin(), val.second.end());
3141       std::string varName =
3142         parsedArgs.ConflictingDependenciesPrefix + "_" + val.first;
3143       std::string pathsStr = cmJoin(paths, ";");
3144       status.GetMakefile().AddDefinition(varName, pathsStr);
3145     } else {
3146       std::ostringstream e;
3147       e << "Multiple conflicting paths found for " << val.first << ":";
3148       for (auto const& path : val.second) {
3149         e << "\n  " << path;
3150       }
3151       status.SetError(e.str());
3152       cmSystemTools::SetFatalErrorOccurred();
3153       return false;
3154     }
3155   }
3156   if (!archive.GetUnresolvedPaths().empty()) {
3157     if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
3158       unresolvedDeps.insert(unresolvedDeps.begin(),
3159                             archive.GetUnresolvedPaths().begin(),
3160                             archive.GetUnresolvedPaths().end());
3161     } else {
3162       std::ostringstream e;
3163       e << "Could not resolve runtime dependencies:";
3164       for (auto const& path : archive.GetUnresolvedPaths()) {
3165         e << "\n  " << path;
3166       }
3167       status.SetError(e.str());
3168       cmSystemTools::SetFatalErrorOccurred();
3169       return false;
3170     }
3171   }
3172
3173   if (!parsedArgs.ResolvedDependenciesVar.empty()) {
3174     std::string val = cmJoin(deps, ";");
3175     status.GetMakefile().AddDefinition(parsedArgs.ResolvedDependenciesVar,
3176                                        val);
3177   }
3178   if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
3179     std::string val = cmJoin(unresolvedDeps, ";");
3180     status.GetMakefile().AddDefinition(parsedArgs.UnresolvedDependenciesVar,
3181                                        val);
3182   }
3183   if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
3184     std::string val = cmJoin(conflictingDeps, ";");
3185     status.GetMakefile().AddDefinition(
3186       parsedArgs.ConflictingDependenciesPrefix + "_FILENAMES", val);
3187   }
3188   return true;
3189 }
3190
3191 bool HandleConfigureCommand(std::vector<std::string> const& args,
3192                             cmExecutionStatus& status)
3193 {
3194   struct Arguments : public ArgumentParser::ParseResult
3195   {
3196     cm::optional<std::string> Output;
3197     cm::optional<std::string> Content;
3198     bool EscapeQuotes = false;
3199     bool AtOnly = false;
3200     // "NEWLINE_STYLE" requires one value, but we use a custom check below.
3201     ArgumentParser::Maybe<std::string> NewlineStyle;
3202   };
3203
3204   static auto const parser =
3205     cmArgumentParser<Arguments>{}
3206       .Bind("OUTPUT"_s, &Arguments::Output)
3207       .Bind("CONTENT"_s, &Arguments::Content)
3208       .Bind("ESCAPE_QUOTES"_s, &Arguments::EscapeQuotes)
3209       .Bind("@ONLY"_s, &Arguments::AtOnly)
3210       .Bind("NEWLINE_STYLE"_s, &Arguments::NewlineStyle);
3211
3212   std::vector<std::string> unrecognizedArguments;
3213   auto parsedArgs =
3214     parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3215
3216   auto argIt = unrecognizedArguments.begin();
3217   if (argIt != unrecognizedArguments.end()) {
3218     status.SetError(
3219       cmStrCat("CONFIGURE Unrecognized argument: \"", *argIt, "\""));
3220     cmSystemTools::SetFatalErrorOccurred();
3221     return false;
3222   }
3223
3224   if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3225     cmSystemTools::SetFatalErrorOccurred();
3226     return true;
3227   }
3228
3229   if (!parsedArgs.Output) {
3230     status.SetError("CONFIGURE OUTPUT option is mandatory.");
3231     cmSystemTools::SetFatalErrorOccurred();
3232     return false;
3233   }
3234   if (!parsedArgs.Content) {
3235     status.SetError("CONFIGURE CONTENT option is mandatory.");
3236     cmSystemTools::SetFatalErrorOccurred();
3237     return false;
3238   }
3239
3240   std::string errorMessage;
3241   cmNewLineStyle newLineStyle;
3242   if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
3243     status.SetError(cmStrCat("CONFIGURE ", errorMessage));
3244     return false;
3245   }
3246
3247   // Check for generator expressions
3248   std::string outputFile = cmSystemTools::CollapseFullPath(
3249     *parsedArgs.Output, status.GetMakefile().GetCurrentBinaryDirectory());
3250
3251   std::string::size_type pos = outputFile.find_first_of("<>");
3252   if (pos != std::string::npos) {
3253     status.SetError(cmStrCat("CONFIGURE called with OUTPUT containing a \"",
3254                              outputFile[pos],
3255                              "\".  This character is not allowed."));
3256     return false;
3257   }
3258
3259   cmMakefile& makeFile = status.GetMakefile();
3260   if (!makeFile.CanIWriteThisFile(outputFile)) {
3261     cmSystemTools::Error("Attempt to write file: " + outputFile +
3262                          " into a source directory.");
3263     return false;
3264   }
3265
3266   cmSystemTools::ConvertToUnixSlashes(outputFile);
3267
3268   // Re-generate if non-temporary outputs are missing.
3269   // when we finalize the configuration we will remove all
3270   // output files that now don't exist.
3271   makeFile.AddCMakeOutputFile(outputFile);
3272
3273   // Create output directory
3274   const std::string::size_type slashPos = outputFile.rfind('/');
3275   if (slashPos != std::string::npos) {
3276     const std::string path = outputFile.substr(0, slashPos);
3277     cmSystemTools::MakeDirectory(path);
3278   }
3279
3280   std::string newLineCharacters = "\n";
3281   bool open_with_binary_flag = false;
3282   if (newLineStyle.IsValid()) {
3283     newLineCharacters = newLineStyle.GetCharacters();
3284     open_with_binary_flag = true;
3285   }
3286
3287   cmGeneratedFileStream fout;
3288   fout.Open(outputFile, false, open_with_binary_flag);
3289   if (!fout) {
3290     cmSystemTools::Error("Could not open file for write in copy operation " +
3291                          outputFile);
3292     cmSystemTools::ReportLastSystemError("");
3293     return false;
3294   }
3295   fout.SetCopyIfDifferent(true);
3296
3297   // copy input to output and expand variables from input at the same time
3298   std::stringstream sin(*parsedArgs.Content, std::ios::in);
3299   std::string inLine;
3300   std::string outLine;
3301   bool hasNewLine = false;
3302   while (cmSystemTools::GetLineFromStream(sin, inLine, &hasNewLine)) {
3303     outLine.clear();
3304     makeFile.ConfigureString(inLine, outLine, parsedArgs.AtOnly,
3305                              parsedArgs.EscapeQuotes);
3306     fout << outLine;
3307     if (hasNewLine || newLineStyle.IsValid()) {
3308       fout << newLineCharacters;
3309     }
3310   }
3311
3312   // close file before attempting to copy
3313   fout.close();
3314
3315   return true;
3316 }
3317
3318 bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
3319                                 cmExecutionStatus& status)
3320 {
3321   struct Arguments : public ArgumentParser::ParseResult
3322   {
3323     std::string Output;
3324     std::string Format;
3325     std::string Compression;
3326     std::string CompressionLevel;
3327     // "MTIME" should require one value, but it has long been accidentally
3328     // accepted without one and treated as if an empty value were given.
3329     // Fixing this would require a policy.
3330     ArgumentParser::Maybe<std::string> MTime;
3331     bool Verbose = false;
3332     // "PATHS" requires at least one value, but use a custom check below.
3333     ArgumentParser::MaybeEmpty<std::vector<std::string>> Paths;
3334   };
3335
3336   static auto const parser =
3337     cmArgumentParser<Arguments>{}
3338       .Bind("OUTPUT"_s, &Arguments::Output)
3339       .Bind("FORMAT"_s, &Arguments::Format)
3340       .Bind("COMPRESSION"_s, &Arguments::Compression)
3341       .Bind("COMPRESSION_LEVEL"_s, &Arguments::CompressionLevel)
3342       .Bind("MTIME"_s, &Arguments::MTime)
3343       .Bind("VERBOSE"_s, &Arguments::Verbose)
3344       .Bind("PATHS"_s, &Arguments::Paths);
3345
3346   std::vector<std::string> unrecognizedArguments;
3347   auto parsedArgs =
3348     parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3349   auto argIt = unrecognizedArguments.begin();
3350   if (argIt != unrecognizedArguments.end()) {
3351     status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3352     cmSystemTools::SetFatalErrorOccurred();
3353     return false;
3354   }
3355
3356   if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3357     cmSystemTools::SetFatalErrorOccurred();
3358     return true;
3359   }
3360
3361   const char* knownFormats[] = {
3362     "7zip", "gnutar", "pax", "paxr", "raw", "zip"
3363   };
3364
3365   if (!parsedArgs.Format.empty() &&
3366       !cm::contains(knownFormats, parsedArgs.Format)) {
3367     status.SetError(
3368       cmStrCat("archive format ", parsedArgs.Format, " not supported"));
3369     cmSystemTools::SetFatalErrorOccurred();
3370     return false;
3371   }
3372
3373   const char* zipFileFormats[] = { "7zip", "zip" };
3374   if (!parsedArgs.Compression.empty() &&
3375       cm::contains(zipFileFormats, parsedArgs.Format)) {
3376     status.SetError(cmStrCat("archive format ", parsedArgs.Format,
3377                              " does not support COMPRESSION arguments"));
3378     cmSystemTools::SetFatalErrorOccurred();
3379     return false;
3380   }
3381
3382   static std::map<std::string, cmSystemTools::cmTarCompression>
3383     compressionTypeMap = { { "None", cmSystemTools::TarCompressNone },
3384                            { "BZip2", cmSystemTools::TarCompressBZip2 },
3385                            { "GZip", cmSystemTools::TarCompressGZip },
3386                            { "XZ", cmSystemTools::TarCompressXZ },
3387                            { "Zstd", cmSystemTools::TarCompressZstd } };
3388
3389   cmSystemTools::cmTarCompression compress = cmSystemTools::TarCompressNone;
3390   auto typeIt = compressionTypeMap.find(parsedArgs.Compression);
3391   if (typeIt != compressionTypeMap.end()) {
3392     compress = typeIt->second;
3393   } else if (!parsedArgs.Compression.empty()) {
3394     status.SetError(cmStrCat("compression type ", parsedArgs.Compression,
3395                              " is not supported"));
3396     cmSystemTools::SetFatalErrorOccurred();
3397     return false;
3398   }
3399
3400   int compressionLevel = 0;
3401   if (!parsedArgs.CompressionLevel.empty()) {
3402     if (parsedArgs.CompressionLevel.size() != 1 &&
3403         !std::isdigit(parsedArgs.CompressionLevel[0])) {
3404       status.SetError(cmStrCat("compression level ",
3405                                parsedArgs.CompressionLevel,
3406                                " should be in range 0 to 9"));
3407       cmSystemTools::SetFatalErrorOccurred();
3408       return false;
3409     }
3410     compressionLevel = std::stoi(parsedArgs.CompressionLevel);
3411     if (compressionLevel < 0 || compressionLevel > 9) {
3412       status.SetError(cmStrCat("compression level ",
3413                                parsedArgs.CompressionLevel,
3414                                " should be in range 0 to 9"));
3415       cmSystemTools::SetFatalErrorOccurred();
3416       return false;
3417     }
3418     if (compress == cmSystemTools::TarCompressNone) {
3419       status.SetError(cmStrCat("compression level is not supported for "
3420                                "compression \"None\"",
3421                                parsedArgs.Compression));
3422       cmSystemTools::SetFatalErrorOccurred();
3423       return false;
3424     }
3425   }
3426
3427   if (parsedArgs.Paths.empty()) {
3428     status.SetError("ARCHIVE_CREATE requires a non-empty list of PATHS");
3429     cmSystemTools::SetFatalErrorOccurred();
3430     return false;
3431   }
3432
3433   if (!cmSystemTools::CreateTar(parsedArgs.Output, parsedArgs.Paths, compress,
3434                                 parsedArgs.Verbose, parsedArgs.MTime,
3435                                 parsedArgs.Format, compressionLevel)) {
3436     status.SetError(cmStrCat("failed to compress: ", parsedArgs.Output));
3437     cmSystemTools::SetFatalErrorOccurred();
3438     return false;
3439   }
3440
3441   return true;
3442 }
3443
3444 bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
3445                                  cmExecutionStatus& status)
3446 {
3447   struct Arguments : public ArgumentParser::ParseResult
3448   {
3449     std::string Input;
3450     bool Verbose = false;
3451     bool ListOnly = false;
3452     std::string Destination;
3453     ArgumentParser::MaybeEmpty<std::vector<std::string>> Patterns;
3454     bool Touch = false;
3455   };
3456
3457   static auto const parser = cmArgumentParser<Arguments>{}
3458                                .Bind("INPUT"_s, &Arguments::Input)
3459                                .Bind("VERBOSE"_s, &Arguments::Verbose)
3460                                .Bind("LIST_ONLY"_s, &Arguments::ListOnly)
3461                                .Bind("DESTINATION"_s, &Arguments::Destination)
3462                                .Bind("PATTERNS"_s, &Arguments::Patterns)
3463                                .Bind("TOUCH"_s, &Arguments::Touch);
3464
3465   std::vector<std::string> unrecognizedArguments;
3466   auto parsedArgs =
3467     parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3468   auto argIt = unrecognizedArguments.begin();
3469   if (argIt != unrecognizedArguments.end()) {
3470     status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
3471     cmSystemTools::SetFatalErrorOccurred();
3472     return false;
3473   }
3474
3475   if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3476     cmSystemTools::SetFatalErrorOccurred();
3477     return true;
3478   }
3479
3480   std::string inFile = parsedArgs.Input;
3481
3482   if (parsedArgs.ListOnly) {
3483     if (!cmSystemTools::ListTar(inFile, parsedArgs.Patterns,
3484                                 parsedArgs.Verbose)) {
3485       status.SetError(cmStrCat("failed to list: ", inFile));
3486       cmSystemTools::SetFatalErrorOccurred();
3487       return false;
3488     }
3489   } else {
3490     std::string destDir = status.GetMakefile().GetCurrentBinaryDirectory();
3491     if (!parsedArgs.Destination.empty()) {
3492       if (cmSystemTools::FileIsFullPath(parsedArgs.Destination)) {
3493         destDir = parsedArgs.Destination;
3494       } else {
3495         destDir = cmStrCat(destDir, "/", parsedArgs.Destination);
3496       }
3497
3498       if (!cmSystemTools::MakeDirectory(destDir)) {
3499         status.SetError(cmStrCat("failed to create directory: ", destDir));
3500         cmSystemTools::SetFatalErrorOccurred();
3501         return false;
3502       }
3503
3504       if (!cmSystemTools::FileIsFullPath(inFile)) {
3505         inFile =
3506           cmStrCat(cmSystemTools::GetCurrentWorkingDirectory(), "/", inFile);
3507       }
3508     }
3509
3510     cmWorkingDirectory workdir(destDir);
3511     if (workdir.Failed()) {
3512       status.SetError(
3513         cmStrCat("failed to change working directory to: ", destDir));
3514       cmSystemTools::SetFatalErrorOccurred();
3515       return false;
3516     }
3517
3518     if (!cmSystemTools::ExtractTar(
3519           inFile, parsedArgs.Patterns,
3520           parsedArgs.Touch ? cmSystemTools::cmTarExtractTimestamps::No
3521                            : cmSystemTools::cmTarExtractTimestamps::Yes,
3522           parsedArgs.Verbose)) {
3523       status.SetError(cmStrCat("failed to extract: ", inFile));
3524       cmSystemTools::SetFatalErrorOccurred();
3525       return false;
3526     }
3527   }
3528
3529   return true;
3530 }
3531
3532 bool ValidateAndConvertPermissions(
3533   cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> const&
3534     permissions,
3535   mode_t& perms, cmExecutionStatus& status)
3536 {
3537   if (!permissions) {
3538     return true;
3539   }
3540   for (const auto& i : *permissions) {
3541     if (!cmFSPermissions::stringToModeT(i, perms)) {
3542       status.SetError(i + " is an invalid permission specifier");
3543       cmSystemTools::SetFatalErrorOccurred();
3544       return false;
3545     }
3546   }
3547   return true;
3548 }
3549
3550 bool SetPermissions(const std::string& filename, const mode_t& perms,
3551                     cmExecutionStatus& status)
3552 {
3553   if (!cmSystemTools::SetPermissions(filename, perms)) {
3554     status.SetError("Failed to set permissions for " + filename);
3555     cmSystemTools::SetFatalErrorOccurred();
3556     return false;
3557   }
3558   return true;
3559 }
3560
3561 bool HandleChmodCommandImpl(std::vector<std::string> const& args, bool recurse,
3562                             cmExecutionStatus& status)
3563 {
3564   mode_t perms = 0;
3565   mode_t fperms = 0;
3566   mode_t dperms = 0;
3567   cmsys::Glob globber;
3568
3569   globber.SetRecurse(recurse);
3570   globber.SetRecurseListDirs(recurse);
3571
3572   struct Arguments : public ArgumentParser::ParseResult
3573   {
3574     cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
3575       Permissions;
3576     cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
3577       FilePermissions;
3578     cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
3579       DirectoryPermissions;
3580   };
3581
3582   static auto const parser =
3583     cmArgumentParser<Arguments>{}
3584       .Bind("PERMISSIONS"_s, &Arguments::Permissions)
3585       .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
3586       .Bind("DIRECTORY_PERMISSIONS"_s, &Arguments::DirectoryPermissions);
3587
3588   std::vector<std::string> pathEntries;
3589   Arguments parsedArgs =
3590     parser.Parse(cmMakeRange(args).advance(1), &pathEntries);
3591
3592   // check validity of arguments
3593   if (!parsedArgs.Permissions && !parsedArgs.FilePermissions &&
3594       !parsedArgs.DirectoryPermissions) // no permissions given
3595   {
3596     status.SetError("No permissions given");
3597     cmSystemTools::SetFatalErrorOccurred();
3598     return false;
3599   }
3600
3601   if (parsedArgs.Permissions && parsedArgs.FilePermissions &&
3602       parsedArgs.DirectoryPermissions) // all keywords are used
3603   {
3604     status.SetError("Remove either PERMISSIONS or FILE_PERMISSIONS or "
3605                     "DIRECTORY_PERMISSIONS from the invocation");
3606     cmSystemTools::SetFatalErrorOccurred();
3607     return false;
3608   }
3609
3610   if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3611     cmSystemTools::SetFatalErrorOccurred();
3612     return true;
3613   }
3614
3615   // validate permissions
3616   bool validatePermissions =
3617     ValidateAndConvertPermissions(parsedArgs.Permissions, perms, status) &&
3618     ValidateAndConvertPermissions(parsedArgs.FilePermissions, fperms,
3619                                   status) &&
3620     ValidateAndConvertPermissions(parsedArgs.DirectoryPermissions, dperms,
3621                                   status);
3622   if (!validatePermissions) {
3623     return false;
3624   }
3625
3626   std::vector<std::string> allPathEntries;
3627
3628   if (recurse) {
3629     std::vector<std::string> tempPathEntries;
3630     for (const auto& i : pathEntries) {
3631       if (cmSystemTools::FileIsDirectory(i)) {
3632         globber.FindFiles(i + "/*");
3633         tempPathEntries = globber.GetFiles();
3634         allPathEntries.insert(allPathEntries.end(), tempPathEntries.begin(),
3635                               tempPathEntries.end());
3636         allPathEntries.emplace_back(i);
3637       } else {
3638         allPathEntries.emplace_back(i); // We validate path entries below
3639       }
3640     }
3641   } else {
3642     allPathEntries = std::move(pathEntries);
3643   }
3644
3645   // chmod
3646   for (const auto& i : allPathEntries) {
3647     if (!(cmSystemTools::FileExists(i) || cmSystemTools::FileIsDirectory(i))) {
3648       status.SetError(cmStrCat("does not exist:\n  ", i));
3649       cmSystemTools::SetFatalErrorOccurred();
3650       return false;
3651     }
3652
3653     if (cmSystemTools::FileExists(i, true)) {
3654       bool success = true;
3655       const mode_t& filePermissions =
3656         parsedArgs.FilePermissions ? fperms : perms;
3657       if (filePermissions) {
3658         success = SetPermissions(i, filePermissions, status);
3659       }
3660       if (!success) {
3661         return false;
3662       }
3663     }
3664
3665     else if (cmSystemTools::FileIsDirectory(i)) {
3666       bool success = true;
3667       const mode_t& directoryPermissions =
3668         parsedArgs.DirectoryPermissions ? dperms : perms;
3669       if (directoryPermissions) {
3670         success = SetPermissions(i, directoryPermissions, status);
3671       }
3672       if (!success) {
3673         return false;
3674       }
3675     }
3676   }
3677
3678   return true;
3679 }
3680
3681 bool HandleChmodCommand(std::vector<std::string> const& args,
3682                         cmExecutionStatus& status)
3683 {
3684   return HandleChmodCommandImpl(args, false, status);
3685 }
3686
3687 bool HandleChmodRecurseCommand(std::vector<std::string> const& args,
3688                                cmExecutionStatus& status)
3689 {
3690   return HandleChmodCommandImpl(args, true, status);
3691 }
3692
3693 } // namespace
3694
3695 bool cmFileCommand(std::vector<std::string> const& args,
3696                    cmExecutionStatus& status)
3697 {
3698   if (args.size() < 2) {
3699     status.SetError("must be called with at least two arguments.");
3700     return false;
3701   }
3702
3703   static cmSubcommandTable const subcommand{
3704     { "WRITE"_s, HandleWriteCommand },
3705     { "APPEND"_s, HandleAppendCommand },
3706     { "DOWNLOAD"_s, HandleDownloadCommand },
3707     { "UPLOAD"_s, HandleUploadCommand },
3708     { "READ"_s, HandleReadCommand },
3709     { "MD5"_s, HandleHashCommand },
3710     { "SHA1"_s, HandleHashCommand },
3711     { "SHA224"_s, HandleHashCommand },
3712     { "SHA256"_s, HandleHashCommand },
3713     { "SHA384"_s, HandleHashCommand },
3714     { "SHA512"_s, HandleHashCommand },
3715     { "SHA3_224"_s, HandleHashCommand },
3716     { "SHA3_256"_s, HandleHashCommand },
3717     { "SHA3_384"_s, HandleHashCommand },
3718     { "SHA3_512"_s, HandleHashCommand },
3719     { "STRINGS"_s, HandleStringsCommand },
3720     { "GLOB"_s, HandleGlobCommand },
3721     { "GLOB_RECURSE"_s, HandleGlobRecurseCommand },
3722     { "MAKE_DIRECTORY"_s, HandleMakeDirectoryCommand },
3723     { "RENAME"_s, HandleRename },
3724     { "COPY_FILE"_s, HandleCopyFile },
3725     { "REMOVE"_s, HandleRemove },
3726     { "REMOVE_RECURSE"_s, HandleRemoveRecurse },
3727     { "COPY"_s, HandleCopyCommand },
3728     { "INSTALL"_s, HandleInstallCommand },
3729     { "DIFFERENT"_s, HandleDifferentCommand },
3730     { "RPATH_CHANGE"_s, HandleRPathChangeCommand },
3731     { "CHRPATH"_s, HandleRPathChangeCommand },
3732     { "RPATH_SET"_s, HandleRPathSetCommand },
3733     { "RPATH_CHECK"_s, HandleRPathCheckCommand },
3734     { "RPATH_REMOVE"_s, HandleRPathRemoveCommand },
3735     { "READ_ELF"_s, HandleReadElfCommand },
3736     { "REAL_PATH"_s, HandleRealPathCommand },
3737     { "RELATIVE_PATH"_s, HandleRelativePathCommand },
3738     { "TO_CMAKE_PATH"_s, HandleCMakePathCommand },
3739     { "TO_NATIVE_PATH"_s, HandleNativePathCommand },
3740     { "TOUCH"_s, HandleTouchCommand },
3741     { "TOUCH_NOCREATE"_s, HandleTouchNocreateCommand },
3742     { "TIMESTAMP"_s, HandleTimestampCommand },
3743     { "GENERATE"_s, HandleGenerateCommand },
3744     { "LOCK"_s, HandleLockCommand },
3745     { "SIZE"_s, HandleSizeCommand },
3746     { "READ_SYMLINK"_s, HandleReadSymlinkCommand },
3747     { "CREATE_LINK"_s, HandleCreateLinkCommand },
3748     { "GET_RUNTIME_DEPENDENCIES"_s, HandleGetRuntimeDependenciesCommand },
3749     { "CONFIGURE"_s, HandleConfigureCommand },
3750     { "ARCHIVE_CREATE"_s, HandleArchiveCreateCommand },
3751     { "ARCHIVE_EXTRACT"_s, HandleArchiveExtractCommand },
3752     { "CHMOD"_s, HandleChmodCommand },
3753     { "CHMOD_RECURSE"_s, HandleChmodRecurseCommand },
3754   };
3755
3756   return subcommand(args[0], args, status);
3757 }