Coverage Report

Created: 2026-10-28 06:38

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