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