Coverage Report

Created: 2026-06-15 07:03

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 += "/" + *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
0
  std::string err;
1473
0
  switch (cmSystemTools::RenameFile(oldname, newname,
1474
0
                                    arguments.NoReplace
1475
0
                                      ? cmSystemTools::Replace::No
1476
0
                                      : cmSystemTools::Replace::Yes,
1477
0
                                    &err)) {
1478
0
    case cmSystemTools::RenameResult::Success:
1479
0
      if (!arguments.Result.empty()) {
1480
0
        status.GetMakefile().AddDefinition(arguments.Result, "0");
1481
0
      }
1482
0
      return true;
1483
0
    case cmSystemTools::RenameResult::NoReplace:
1484
0
      if (!arguments.Result.empty()) {
1485
0
        err = "NO_REPLACE";
1486
0
      } else {
1487
0
        err = "path not replaced";
1488
0
      }
1489
0
      CM_FALLTHROUGH;
1490
0
    case cmSystemTools::RenameResult::Failure:
1491
0
      if (!arguments.Result.empty()) {
1492
0
        status.GetMakefile().AddDefinition(arguments.Result, err);
1493
0
        return true;
1494
0
      }
1495
0
      break;
1496
0
  }
1497
0
  status.SetError(cmStrCat("RENAME failed to rename\n  ", oldname, "\nto\n  ",
1498
0
                           newname, "\nbecause: ", err, '\n'));
1499
0
  return false;
1500
0
}
1501
1502
bool HandleCopyFile(std::vector<std::string> const& args,
1503
                    cmExecutionStatus& status)
1504
0
{
1505
0
  if (args.size() < 3) {
1506
0
    status.SetError("COPY_FILE must be called with at least two additional "
1507
0
                    "arguments");
1508
0
    return false;
1509
0
  }
1510
1511
  // Compute full path for old and new names.
1512
0
  std::string oldname = args[1];
1513
0
  if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
1514
0
    oldname =
1515
0
      cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
1516
0
  }
1517
0
  std::string newname = args[2];
1518
0
  if (!cmsys::SystemTools::FileIsFullPath(newname)) {
1519
0
    newname =
1520
0
      cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
1521
0
  }
1522
1523
0
  struct Arguments
1524
0
  {
1525
0
    bool InputMayBeRecent = false;
1526
0
    bool OnlyIfDifferent = false;
1527
0
    std::string Result;
1528
0
  };
1529
1530
0
  static auto const parser =
1531
0
    cmArgumentParser<Arguments>{}
1532
0
      .Bind("INPUT_MAY_BE_RECENT"_s, &Arguments::InputMayBeRecent)
1533
0
      .Bind("ONLY_IF_DIFFERENT"_s, &Arguments::OnlyIfDifferent)
1534
0
      .Bind("RESULT"_s, &Arguments::Result);
1535
1536
0
  std::vector<std::string> unconsumedArgs;
1537
0
  Arguments const arguments =
1538
0
    parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
1539
0
  if (!unconsumedArgs.empty()) {
1540
0
    status.SetError("COPY_FILE unknown argument:\n  " +
1541
0
                    unconsumedArgs.front());
1542
0
    return false;
1543
0
  }
1544
1545
0
  bool result = true;
1546
0
  if (cmsys::SystemTools::FileIsDirectory(oldname)) {
1547
0
    if (!arguments.Result.empty()) {
1548
0
      status.GetMakefile().AddDefinition(arguments.Result,
1549
0
                                         "cannot copy a directory");
1550
0
    } else {
1551
0
      status.SetError(
1552
0
        cmStrCat("COPY_FILE cannot copy a directory\n  ", oldname));
1553
0
      result = false;
1554
0
    }
1555
0
    return result;
1556
0
  }
1557
0
  if (cmsys::SystemTools::FileIsDirectory(newname)) {
1558
0
    if (!arguments.Result.empty()) {
1559
0
      status.GetMakefile().AddDefinition(arguments.Result,
1560
0
                                         "cannot copy to a directory");
1561
0
    } else {
1562
0
      status.SetError(
1563
0
        cmStrCat("COPY_FILE cannot copy to a directory\n  ", newname));
1564
0
      result = false;
1565
0
    }
1566
0
    return result;
1567
0
  }
1568
1569
0
  cmSystemTools::CopyWhen when;
1570
0
  if (arguments.OnlyIfDifferent) {
1571
0
    when = cmSystemTools::CopyWhen::OnlyIfDifferent;
1572
0
  } else {
1573
0
    when = cmSystemTools::CopyWhen::Always;
1574
0
  }
1575
0
  cmSystemTools::CopyInputRecent const inputRecent = arguments.InputMayBeRecent
1576
0
    ? cmSystemTools::CopyInputRecent::Yes
1577
0
    : cmSystemTools::CopyInputRecent::No;
1578
1579
0
  std::string err;
1580
0
  if (cmSystemTools::CopySingleFile(oldname, newname, when, inputRecent,
1581
0
                                    &err) ==
1582
0
      cmSystemTools::CopyResult::Success) {
1583
0
    if (!arguments.Result.empty()) {
1584
0
      status.GetMakefile().AddDefinition(arguments.Result, "0");
1585
0
    }
1586
0
  } else {
1587
0
    if (!arguments.Result.empty()) {
1588
0
      status.GetMakefile().AddDefinition(arguments.Result, err);
1589
0
    } else {
1590
0
      status.SetError(cmStrCat("COPY_FILE failed to copy\n  ", oldname,
1591
0
                               "\nto\n  ", newname, "\nbecause: ", err, '\n'));
1592
0
      result = false;
1593
0
    }
1594
0
  }
1595
1596
0
  return result;
1597
0
}
1598
1599
bool HandleRemoveImpl(std::vector<std::string> const& args, bool recurse,
1600
                      cmExecutionStatus& status)
1601
0
{
1602
0
  for (std::string const& arg :
1603
0
       cmMakeRange(args).advance(1)) // Get rid of subcommand
1604
0
  {
1605
0
    std::string fileName = arg;
1606
0
    if (fileName.empty()) {
1607
0
      std::string r = recurse ? "REMOVE_RECURSE" : "REMOVE";
1608
0
      status.GetMakefile().IssueDiagnostic(
1609
0
        cmDiagnostics::CMD_AUTHOR,
1610
0
        cmStrCat("Ignoring empty file name in ", std::move(r), '.'));
1611
0
      continue;
1612
0
    }
1613
0
    if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
1614
0
      fileName =
1615
0
        cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
1616
0
    }
1617
1618
0
    if (cmSystemTools::FileIsDirectory(fileName) &&
1619
0
        !cmSystemTools::FileIsSymlink(fileName) && recurse) {
1620
0
      cmSystemTools::RepeatedRemoveDirectory(fileName);
1621
0
    } else {
1622
0
      cmSystemTools::RemoveFile(fileName);
1623
0
    }
1624
0
  }
1625
0
  return true;
1626
0
}
1627
1628
bool HandleRemove(std::vector<std::string> const& args,
1629
                  cmExecutionStatus& status)
1630
0
{
1631
0
  return HandleRemoveImpl(args, false, status);
1632
0
}
1633
1634
bool HandleRemoveRecurse(std::vector<std::string> const& args,
1635
                         cmExecutionStatus& status)
1636
0
{
1637
0
  return HandleRemoveImpl(args, true, status);
1638
0
}
1639
1640
std::string ToNativePath(std::string const& path)
1641
0
{
1642
0
  auto const& outPath = cmSystemTools::ConvertToOutputPath(path);
1643
0
  if (outPath.size() > 1 && outPath.front() == '\"' &&
1644
0
      outPath.back() == '\"') {
1645
0
    return outPath.substr(1, outPath.size() - 2);
1646
0
  }
1647
0
  return outPath;
1648
0
}
1649
1650
std::string ToCMakePath(std::string const& path)
1651
0
{
1652
0
  auto temp = path;
1653
0
  cmSystemTools::ConvertToUnixSlashes(temp);
1654
0
  return temp;
1655
0
}
1656
1657
bool HandlePathCommand(std::vector<std::string> const& args,
1658
                       std::string (*convert)(std::string const&),
1659
                       cmExecutionStatus& status)
1660
0
{
1661
0
  if (args.size() != 3) {
1662
0
    status.SetError("FILE([TO_CMAKE_PATH|TO_NATIVE_PATH] path result) must be "
1663
0
                    "called with exactly three arguments.");
1664
0
    return false;
1665
0
  }
1666
#if defined(_WIN32) && !defined(__CYGWIN__)
1667
  char pathSep = ';';
1668
#else
1669
0
  char pathSep = ':';
1670
0
#endif
1671
0
  std::vector<std::string> path = cmSystemTools::SplitString(args[1], pathSep);
1672
1673
0
  std::string value = cmList::to_string(cmMakeRange(path).transform(convert));
1674
0
  status.GetMakefile().AddDefinition(args[2], value);
1675
0
  return true;
1676
0
}
1677
1678
bool HandleCMakePathCommand(std::vector<std::string> const& args,
1679
                            cmExecutionStatus& status)
1680
0
{
1681
0
  return HandlePathCommand(args, ToCMakePath, status);
1682
0
}
1683
1684
bool HandleNativePathCommand(std::vector<std::string> const& args,
1685
                             cmExecutionStatus& status)
1686
0
{
1687
0
  return HandlePathCommand(args, ToNativePath, status);
1688
0
}
1689
1690
#if !defined(CMAKE_BOOTSTRAP)
1691
1692
bool const TLS_VERIFY_DEFAULT = true;
1693
std::string const TLS_VERSION_DEFAULT = "1.2";
1694
1695
// Stuff for curl download/upload
1696
using cmFileCommandVectorOfChar = std::vector<char>;
1697
1698
std::string CurlCompatFileURL(std::string url)
1699
0
{
1700
0
  if (cmHasLiteralPrefix(url, "file://")) {
1701
    // libcurl 7.77 and below accidentally allowed spaces in file:// URLs,
1702
    // which CMake has long accepted as a result.  Explicitly encode spaces.
1703
0
    cmSystemTools::ReplaceString(url, " ", "%20");
1704
0
  }
1705
0
  return url;
1706
0
}
1707
1708
size_t cmWriteToFileCallback(void* ptr, size_t size, size_t nmemb, void* data)
1709
0
{
1710
0
  int realsize = static_cast<int>(size * nmemb);
1711
0
  cmsys::ofstream* fout = static_cast<cmsys::ofstream*>(data);
1712
0
  if (fout) {
1713
0
    char const* chPtr = static_cast<char*>(ptr);
1714
0
    if (!fout->write(chPtr, realsize)) {
1715
0
      return CURL_WRITEFUNC_ERROR;
1716
0
    }
1717
0
  }
1718
0
  return realsize;
1719
0
}
1720
1721
size_t cmWriteToMemoryCallback(void* ptr, size_t size, size_t nmemb,
1722
                               void* data)
1723
0
{
1724
0
  int realsize = static_cast<int>(size * nmemb);
1725
0
  char const* chPtr = static_cast<char*>(ptr);
1726
0
  cm::append(*static_cast<cmFileCommandVectorOfChar*>(data), chPtr,
1727
0
             chPtr + realsize);
1728
0
  return realsize;
1729
0
}
1730
1731
int cmFileCommandCurlDebugCallback(CURL*, curl_infotype type, char* chPtr,
1732
                                   size_t size, void* data)
1733
0
{
1734
0
  cmFileCommandVectorOfChar& vec =
1735
0
    *static_cast<cmFileCommandVectorOfChar*>(data);
1736
0
  switch (type) {
1737
0
    case CURLINFO_TEXT:
1738
0
    case CURLINFO_HEADER_IN:
1739
0
    case CURLINFO_HEADER_OUT:
1740
0
      cm::append(vec, chPtr, chPtr + size);
1741
0
      break;
1742
0
    case CURLINFO_DATA_IN:
1743
0
    case CURLINFO_DATA_OUT:
1744
0
    case CURLINFO_SSL_DATA_IN:
1745
0
    case CURLINFO_SSL_DATA_OUT: {
1746
0
      char buf[128];
1747
0
      int n =
1748
0
        snprintf(buf, sizeof(buf), "[%" KWIML_INT_PRIu64 " bytes data]\n",
1749
0
                 static_cast<KWIML_INT_uint64_t>(size));
1750
0
      if (n > 0) {
1751
0
        cm::append(vec, buf, buf + n);
1752
0
      }
1753
0
    } break;
1754
0
    default:
1755
0
      break;
1756
0
  }
1757
0
  return 0;
1758
0
}
1759
1760
#  if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x072000
1761
CURLoption const CM_CURLOPT_XFERINFOFUNCTION = CURLOPT_XFERINFOFUNCTION;
1762
using cm_curl_off_t = curl_off_t;
1763
#  else
1764
CURLoption const CM_CURLOPT_XFERINFOFUNCTION = CURLOPT_PROGRESSFUNCTION;
1765
using cm_curl_off_t = double;
1766
#  endif
1767
1768
class cURLProgressHelper
1769
{
1770
public:
1771
  cURLProgressHelper(cmMakefile* mf, char const* text)
1772
0
    : Makefile(mf)
1773
0
    , Text(text)
1774
0
  {
1775
0
  }
1776
1777
  bool UpdatePercentage(cm_curl_off_t value, cm_curl_off_t total,
1778
                        std::string& status)
1779
0
  {
1780
0
    long OldPercentage = this->CurrentPercentage;
1781
1782
0
    if (total > 0) {
1783
0
      this->CurrentPercentage = std::lround(
1784
0
        static_cast<double>(value) / static_cast<double>(total) * 100.0);
1785
0
      if (this->CurrentPercentage > 100) {
1786
        // Avoid extra progress reports for unexpected data beyond total.
1787
0
        this->CurrentPercentage = 100;
1788
0
      }
1789
0
    }
1790
1791
0
    bool updated = (OldPercentage != this->CurrentPercentage);
1792
1793
0
    if (updated) {
1794
0
      status =
1795
0
        cmStrCat('[', this->Text, ' ', this->CurrentPercentage, "% complete]");
1796
0
    }
1797
1798
0
    return updated;
1799
0
  }
1800
1801
0
  cmMakefile* GetMakefile() { return this->Makefile; }
1802
1803
private:
1804
  long CurrentPercentage = -1;
1805
  cmMakefile* Makefile;
1806
  std::string Text;
1807
};
1808
1809
int cmFileDownloadProgressCallback(void* clientp, cm_curl_off_t dltotal,
1810
                                   cm_curl_off_t dlnow, cm_curl_off_t ultotal,
1811
                                   cm_curl_off_t ulnow)
1812
0
{
1813
0
  cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1814
1815
0
  static_cast<void>(ultotal);
1816
0
  static_cast<void>(ulnow);
1817
1818
0
  std::string status;
1819
0
  if (helper->UpdatePercentage(dlnow, dltotal, status)) {
1820
0
    cmMakefile* mf = helper->GetMakefile();
1821
0
    mf->DisplayStatus(status, -1);
1822
0
  }
1823
1824
0
  return 0;
1825
0
}
1826
1827
int cmFileUploadProgressCallback(void* clientp, cm_curl_off_t dltotal,
1828
                                 cm_curl_off_t dlnow, cm_curl_off_t ultotal,
1829
                                 cm_curl_off_t ulnow)
1830
0
{
1831
0
  cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1832
1833
0
  static_cast<void>(dltotal);
1834
0
  static_cast<void>(dlnow);
1835
1836
0
  std::string status;
1837
0
  if (helper->UpdatePercentage(ulnow, ultotal, status)) {
1838
0
    cmMakefile* mf = helper->GetMakefile();
1839
0
    mf->DisplayStatus(status, -1);
1840
0
  }
1841
1842
0
  return 0;
1843
0
}
1844
1845
class cURLEasyGuard
1846
{
1847
public:
1848
  cURLEasyGuard(CURL* easy)
1849
0
    : Easy(easy)
1850
0
  {
1851
0
  }
1852
1853
  ~cURLEasyGuard()
1854
0
  {
1855
0
    if (this->Easy) {
1856
0
      ::curl_easy_cleanup(this->Easy);
1857
0
    }
1858
0
  }
1859
1860
  cURLEasyGuard(cURLEasyGuard const&) = delete;
1861
  cURLEasyGuard& operator=(cURLEasyGuard const&) = delete;
1862
1863
0
  void release() { this->Easy = nullptr; }
1864
1865
private:
1866
  ::CURL* Easy;
1867
};
1868
1869
#endif
1870
1871
#define check_curl_result(result, errstr)                                     \
1872
0
  do {                                                                        \
1873
0
    if (result != CURLE_OK) {                                                 \
1874
0
      std::string e(errstr);                                                  \
1875
0
      e += ::curl_easy_strerror(result);                                      \
1876
0
      status.SetError(e);                                                     \
1877
0
      return false;                                                           \
1878
0
    }                                                                         \
1879
0
  } while (false)
1880
1881
bool HandleDownloadCommand(std::vector<std::string> const& args,
1882
                           cmExecutionStatus& status)
1883
0
{
1884
0
#if !defined(CMAKE_BOOTSTRAP)
1885
0
  auto i = args.begin();
1886
0
  if (args.size() < 2) {
1887
0
    status.SetError("DOWNLOAD must be called with at least two arguments.");
1888
0
    return false;
1889
0
  }
1890
0
  ++i; // Get rid of subcommand
1891
0
  std::string url = *i;
1892
0
  ++i;
1893
0
  std::string file;
1894
1895
0
  long timeout = 0;
1896
0
  long inactivity_timeout = 0;
1897
0
  std::string logVar;
1898
0
  std::string statusVar;
1899
0
  cm::optional<std::string> tlsVersionOpt;
1900
0
  cm::optional<bool> tlsVerifyOpt;
1901
0
  cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
1902
0
  std::string netrc_level =
1903
0
    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
1904
0
  std::string netrc_file =
1905
0
    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
1906
0
  std::string expectedHash;
1907
0
  std::string hashMatchMSG;
1908
0
  std::unique_ptr<cmCryptoHash> hash;
1909
0
  bool showProgress = false;
1910
0
  std::string userpwd;
1911
1912
0
  std::vector<std::string> curl_headers;
1913
0
  std::vector<std::pair<std::string, cm::optional<std::string>>> curl_ranges;
1914
1915
0
  while (i != args.end()) {
1916
0
    if (*i == "TIMEOUT") {
1917
0
      ++i;
1918
0
      if (i != args.end()) {
1919
0
        timeout = atol(i->c_str());
1920
0
      } else {
1921
0
        status.SetError("DOWNLOAD missing time for TIMEOUT.");
1922
0
        return false;
1923
0
      }
1924
0
    } else if (*i == "INACTIVITY_TIMEOUT") {
1925
0
      ++i;
1926
0
      if (i != args.end()) {
1927
0
        inactivity_timeout = atol(i->c_str());
1928
0
      } else {
1929
0
        status.SetError("DOWNLOAD missing time for INACTIVITY_TIMEOUT.");
1930
0
        return false;
1931
0
      }
1932
0
    } else if (*i == "LOG") {
1933
0
      ++i;
1934
0
      if (i == args.end()) {
1935
0
        status.SetError("DOWNLOAD missing VAR for LOG.");
1936
0
        return false;
1937
0
      }
1938
0
      logVar = *i;
1939
0
    } else if (*i == "STATUS") {
1940
0
      ++i;
1941
0
      if (i == args.end()) {
1942
0
        status.SetError("DOWNLOAD missing VAR for STATUS.");
1943
0
        return false;
1944
0
      }
1945
0
      statusVar = *i;
1946
0
    } else if (*i == "TLS_VERSION") {
1947
0
      ++i;
1948
0
      if (i != args.end()) {
1949
0
        tlsVersionOpt = *i;
1950
0
      } else {
1951
0
        status.SetError("DOWNLOAD missing value for TLS_VERSION.");
1952
0
        return false;
1953
0
      }
1954
0
    } else if (*i == "TLS_VERIFY") {
1955
0
      ++i;
1956
0
      if (i != args.end()) {
1957
0
        tlsVerifyOpt = cmIsOn(*i);
1958
0
      } else {
1959
0
        status.SetError("DOWNLOAD missing bool value for TLS_VERIFY.");
1960
0
        return false;
1961
0
      }
1962
0
    } else if (*i == "TLS_CAINFO") {
1963
0
      ++i;
1964
0
      if (i != args.end()) {
1965
0
        cainfo = cmValue(*i);
1966
0
      } else {
1967
0
        status.SetError("DOWNLOAD missing file value for TLS_CAINFO.");
1968
0
        return false;
1969
0
      }
1970
0
    } else if (*i == "NETRC_FILE") {
1971
0
      ++i;
1972
0
      if (i != args.end()) {
1973
0
        netrc_file = *i;
1974
0
      } else {
1975
0
        status.SetError("DOWNLOAD missing file value for NETRC_FILE.");
1976
0
        return false;
1977
0
      }
1978
0
    } else if (*i == "NETRC") {
1979
0
      ++i;
1980
0
      if (i != args.end()) {
1981
0
        netrc_level = *i;
1982
0
      } else {
1983
0
        status.SetError("DOWNLOAD missing level value for NETRC.");
1984
0
        return false;
1985
0
      }
1986
0
    } else if (*i == "EXPECTED_MD5") {
1987
0
      ++i;
1988
0
      if (i == args.end()) {
1989
0
        status.SetError("DOWNLOAD missing sum value for EXPECTED_MD5.");
1990
0
        return false;
1991
0
      }
1992
0
      hash = cm::make_unique<cmCryptoHash>(cmCryptoHash::AlgoMD5);
1993
0
      hashMatchMSG = "MD5 sum";
1994
0
      expectedHash = cmSystemTools::LowerCase(*i);
1995
0
    } else if (*i == "SHOW_PROGRESS") {
1996
0
      showProgress = true;
1997
0
    } else if (*i == "EXPECTED_HASH") {
1998
0
      ++i;
1999
0
      if (i == args.end()) {
2000
0
        status.SetError("DOWNLOAD missing ALGO=value for EXPECTED_HASH.");
2001
0
        return false;
2002
0
      }
2003
0
      std::string::size_type pos = i->find("=");
2004
0
      if (pos == std::string::npos) {
2005
0
        std::string err =
2006
0
          cmStrCat("DOWNLOAD EXPECTED_HASH expects ALGO=value but got: ", *i);
2007
0
        status.SetError(err);
2008
0
        return false;
2009
0
      }
2010
0
      std::string algo = i->substr(0, pos);
2011
0
      expectedHash = cmSystemTools::LowerCase(i->substr(pos + 1));
2012
0
      hash = cmCryptoHash::New(algo);
2013
0
      if (!hash) {
2014
0
        std::string err =
2015
0
          cmStrCat("DOWNLOAD EXPECTED_HASH given unknown ALGO: ", algo);
2016
0
        status.SetError(err);
2017
0
        return false;
2018
0
      }
2019
0
      hashMatchMSG = algo + " hash";
2020
0
    } else if (*i == "USERPWD") {
2021
0
      ++i;
2022
0
      if (i == args.end()) {
2023
0
        status.SetError("DOWNLOAD missing string for USERPWD.");
2024
0
        return false;
2025
0
      }
2026
0
      userpwd = *i;
2027
0
    } else if (*i == "HTTPHEADER") {
2028
0
      ++i;
2029
0
      if (i == args.end()) {
2030
0
        status.SetError("DOWNLOAD missing string for HTTPHEADER.");
2031
0
        return false;
2032
0
      }
2033
0
      curl_headers.push_back(*i);
2034
0
    } else if (*i == "RANGE_START") {
2035
0
      ++i;
2036
0
      if (i == args.end()) {
2037
0
        status.SetError("DOWNLOAD missing value for RANGE_START.");
2038
0
        return false;
2039
0
      }
2040
0
      curl_ranges.emplace_back(*i, cm::nullopt);
2041
0
    } else if (*i == "RANGE_END") {
2042
0
      ++i;
2043
0
      if (curl_ranges.empty()) {
2044
0
        curl_ranges.emplace_back("0", *i);
2045
0
      } else {
2046
0
        auto& last_range = curl_ranges.back();
2047
0
        if (!last_range.second.has_value()) {
2048
0
          last_range.second = *i;
2049
0
        } else {
2050
0
          status.SetError("Multiple RANGE_END values is provided without "
2051
0
                          "the corresponding RANGE_START.");
2052
0
          return false;
2053
0
        }
2054
0
      }
2055
0
    } else if (file.empty()) {
2056
0
      file = *i;
2057
0
    } else {
2058
      // Do not return error for compatibility reason.
2059
0
      status.GetMakefile().IssueDiagnostic(
2060
0
        cmDiagnostics::CMD_AUTHOR, cmStrCat("Unexpected argument: ", *i));
2061
0
    }
2062
0
    ++i;
2063
0
  }
2064
2065
0
  if (!tlsVerifyOpt.has_value()) {
2066
0
    if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERIFY")) {
2067
0
      tlsVerifyOpt = v.IsOn();
2068
0
    }
2069
0
  }
2070
0
  if (!tlsVerifyOpt.has_value()) {
2071
0
    if (cm::optional<std::string> v =
2072
0
          cmSystemTools::GetEnvVar("CMAKE_TLS_VERIFY")) {
2073
0
      tlsVerifyOpt = cmIsOn(*v);
2074
0
    }
2075
0
  }
2076
0
  bool tlsVerifyDefaulted = false;
2077
0
  if (!tlsVerifyOpt.has_value()) {
2078
0
    tlsVerifyOpt = TLS_VERIFY_DEFAULT;
2079
0
    tlsVerifyDefaulted = true;
2080
0
  }
2081
2082
0
  if (!tlsVersionOpt.has_value()) {
2083
0
    if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERSION")) {
2084
0
      tlsVersionOpt = *v;
2085
0
    }
2086
0
  }
2087
0
  if (!tlsVersionOpt.has_value()) {
2088
0
    if (cm::optional<std::string> v =
2089
0
          cmSystemTools::GetEnvVar("CMAKE_TLS_VERSION")) {
2090
0
      tlsVersionOpt = std::move(v);
2091
0
    }
2092
0
  }
2093
0
  bool tlsVersionDefaulted = false;
2094
0
  if (!tlsVersionOpt.has_value()) {
2095
0
    tlsVersionOpt = TLS_VERSION_DEFAULT;
2096
0
    tlsVersionDefaulted = true;
2097
0
  }
2098
2099
  // Can't calculate hash if we don't save the file.
2100
  // TODO Incrementally calculate hash in the write callback as the file is
2101
  // being downloaded so this check can be relaxed.
2102
0
  if (file.empty() && hash) {
2103
0
    status.SetError("DOWNLOAD cannot calculate hash if file is not saved.");
2104
0
    return false;
2105
0
  }
2106
  // If file exists already, and caller specified an expected md5 or sha,
2107
  // and the existing file already has the expected hash, then simply
2108
  // return.
2109
  //
2110
0
  if (!file.empty() && cmSystemTools::FileExists(file) && hash.get()) {
2111
0
    std::string msg;
2112
0
    std::string actualHash = hash->HashFile(file);
2113
0
    if (actualHash == expectedHash) {
2114
0
      msg = cmStrCat("skipping download as file already exists with expected ",
2115
0
                     hashMatchMSG, '"');
2116
0
      if (!statusVar.empty()) {
2117
0
        status.GetMakefile().AddDefinition(statusVar, cmStrCat(0, ";\"", msg));
2118
0
      }
2119
0
      return true;
2120
0
    }
2121
0
  }
2122
  // Make sure parent directory exists so we can write to the file
2123
  // as we receive downloaded bits from curl...
2124
  //
2125
0
  if (!file.empty()) {
2126
0
    std::string dir = cmSystemTools::GetFilenamePath(file);
2127
0
    if (!dir.empty() && !cmSystemTools::FileExists(dir) &&
2128
0
        !cmSystemTools::MakeDirectory(dir)) {
2129
0
      std::string errstring = "DOWNLOAD error: cannot create directory '" +
2130
0
        dir +
2131
0
        "' - Specify file by full path name and verify that you "
2132
0
        "have directory creation and file write privileges.";
2133
0
      status.SetError(errstring);
2134
0
      return false;
2135
0
    }
2136
0
  }
2137
2138
0
  cmsys::ofstream fout;
2139
0
  if (!file.empty()) {
2140
0
    fout.open(file.c_str(), std::ios::binary);
2141
0
    if (!fout) {
2142
0
      status.SetError(cmStrCat("DOWNLOAD cannot open file for write\n"
2143
0
                               "  file: \"",
2144
0
                               file, '"'));
2145
0
      return false;
2146
0
    }
2147
0
  }
2148
2149
0
  url = CurlCompatFileURL(url);
2150
2151
0
  ::CURL* curl;
2152
0
  cm_curl_global_init(CURL_GLOBAL_DEFAULT);
2153
0
  curl = cm_curl_easy_init();
2154
0
  if (!curl) {
2155
0
    status.SetError("DOWNLOAD error initializing curl.");
2156
0
    return false;
2157
0
  }
2158
2159
0
  cURLEasyGuard g_curl(curl);
2160
0
  ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
2161
0
  check_curl_result(res, "DOWNLOAD cannot set url: ");
2162
2163
  // enable HTTP ERROR parsing
2164
0
  res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
2165
0
  check_curl_result(res, "DOWNLOAD cannot set http failure option: ");
2166
2167
0
  curl_version_info_data* cv = curl_version_info(CURLVERSION_FIRST);
2168
0
  res = ::curl_easy_setopt(
2169
0
    curl, CURLOPT_USERAGENT,
2170
0
    cmStrCat("curl/", cv ? cv->version : LIBCURL_VERSION).c_str());
2171
0
  check_curl_result(res, "DOWNLOAD cannot set user agent option: ");
2172
2173
0
  res = ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToFileCallback);
2174
0
  check_curl_result(res, "DOWNLOAD cannot set write function: ");
2175
2176
0
  res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
2177
0
                           cmFileCommandCurlDebugCallback);
2178
0
  check_curl_result(res, "DOWNLOAD cannot set debug function: ");
2179
2180
0
  if (tlsVersionOpt.has_value()) {
2181
0
    if (cm::optional<int> v = cmCurlParseTLSVersion(*tlsVersionOpt)) {
2182
0
      res = ::curl_easy_setopt(curl, CURLOPT_SSLVERSION, *v);
2183
      // If the caller did not explicitly ask for TLS, and libcurl was
2184
      // built without any TLS backend, ignore failure to set our default.
2185
      // Note that libcurl reports the absence of any TLS backend with
2186
      // one of two distinct errors depending on how it was built:
2187
      // - CURLE_NOT_BUILT_IN from the Curl_setopt_SSLVERSION
2188
      //   stub macro in setopt.h, or
2189
      // - CURLE_UNKNOWN_OPTION from the function-level #else
2190
      //   arm of setopt_long_ssl() in setopt.c.
2191
0
      if (tlsVersionDefaulted &&
2192
0
          (res == CURLE_NOT_BUILT_IN || res == CURLE_UNKNOWN_OPTION)) {
2193
0
        res = CURLE_OK;
2194
0
      }
2195
0
      check_curl_result(res,
2196
0
                        cmStrCat("DOWNLOAD cannot set TLS/SSL version ",
2197
0
                                 *tlsVersionOpt, ": "));
2198
0
    } else {
2199
0
      status.SetError(
2200
0
        cmStrCat("DOWNLOAD given unknown TLS/SSL version ", *tlsVersionOpt));
2201
0
      return false;
2202
0
    }
2203
0
  }
2204
2205
  // check to see if TLS verification is requested
2206
0
  if (tlsVerifyOpt.has_value() && tlsVerifyOpt.value()) {
2207
0
    res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
2208
0
    check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify on: ");
2209
0
  } else {
2210
0
    res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
2211
0
    check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify off: ");
2212
0
  }
2213
2214
0
  for (auto const& range : curl_ranges) {
2215
0
    std::string curl_range =
2216
0
      cmStrCat(range.first, '-',
2217
0
               (range.second.has_value() ? range.second.value() : ""));
2218
0
    res = ::curl_easy_setopt(curl, CURLOPT_RANGE, curl_range.c_str());
2219
0
    check_curl_result(res, "DOWNLOAD cannot set range: ");
2220
0
  }
2221
2222
  // check to see if a CAINFO file has been specified
2223
  // command arg comes first
2224
0
  std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
2225
0
  if (!cainfo_err.empty()) {
2226
0
    status.SetError(cainfo_err);
2227
0
    return false;
2228
0
  }
2229
2230
  // check to see if netrc parameters have been specified
2231
  // local command args takes precedence over CMAKE_NETRC*
2232
0
  netrc_level = cmSystemTools::UpperCase(netrc_level);
2233
0
  std::string const& netrc_option_err =
2234
0
    cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2235
0
  if (!netrc_option_err.empty()) {
2236
0
    status.SetError(netrc_option_err);
2237
0
    return false;
2238
0
  }
2239
2240
0
  cmFileCommandVectorOfChar chunkDebug;
2241
2242
0
  res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA,
2243
0
                           file.empty() ? nullptr : &fout);
2244
0
  check_curl_result(res, "DOWNLOAD cannot set write data: ");
2245
2246
0
  res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2247
0
  check_curl_result(res, "DOWNLOAD cannot set debug data: ");
2248
2249
0
  res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2250
0
  check_curl_result(res, "DOWNLOAD cannot set follow-redirect option: ");
2251
2252
0
  if (!logVar.empty()) {
2253
0
    res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2254
0
    check_curl_result(res, "DOWNLOAD cannot set verbose: ");
2255
0
  }
2256
2257
0
  if (timeout > 0) {
2258
0
    res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2259
0
    check_curl_result(res, "DOWNLOAD cannot set timeout: ");
2260
0
  }
2261
2262
0
  if (inactivity_timeout > 0) {
2263
    // Give up if there is no progress for a long time.
2264
0
    ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2265
0
    ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2266
0
  }
2267
2268
  // Need the progress helper's scope to last through the duration of
2269
  // the curl_easy_perform call... so this object is declared at function
2270
  // scope intentionally, rather than inside the "if(showProgress)"
2271
  // block...
2272
  //
2273
0
  cURLProgressHelper helper(&status.GetMakefile(), "download");
2274
2275
0
  if (showProgress) {
2276
0
    res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2277
0
    check_curl_result(res, "DOWNLOAD cannot set noprogress value: ");
2278
2279
0
    res = ::curl_easy_setopt(curl, CM_CURLOPT_XFERINFOFUNCTION,
2280
0
                             cmFileDownloadProgressCallback);
2281
0
    check_curl_result(res, "DOWNLOAD cannot set progress function: ");
2282
2283
0
    res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2284
0
                             reinterpret_cast<void*>(&helper));
2285
0
    check_curl_result(res, "DOWNLOAD cannot set progress data: ");
2286
0
  }
2287
2288
0
  if (!userpwd.empty()) {
2289
0
    res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2290
0
    check_curl_result(res, "DOWNLOAD cannot set user password: ");
2291
0
  }
2292
2293
0
  struct curl_slist* headers = nullptr;
2294
0
  for (std::string const& h : curl_headers) {
2295
0
    headers = ::curl_slist_append(headers, h.c_str());
2296
0
  }
2297
0
  ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2298
2299
0
  res = ::curl_easy_perform(curl);
2300
2301
0
  ::curl_slist_free_all(headers);
2302
2303
  /* always cleanup */
2304
0
  g_curl.release();
2305
0
  ::curl_easy_cleanup(curl);
2306
2307
  // Explicitly close the file so we can check for write errors.
2308
0
  if (!file.empty()) {
2309
0
    fout.close();
2310
0
    if (!fout) {
2311
0
      res = CURLE_WRITE_ERROR;
2312
0
    }
2313
0
  }
2314
2315
0
  if (!statusVar.empty()) {
2316
0
    std::string m = curl_easy_strerror(res);
2317
0
    if ((res == CURLE_SSL_CONNECT_ERROR ||
2318
0
         res == CURLE_PEER_FAILED_VERIFICATION) &&
2319
0
        tlsVerifyDefaulted) {
2320
0
      m = cmStrCat(
2321
0
        std::move(m),
2322
0
        ".  If this is due to https certificate verification failure, one may "
2323
0
        "set environment variable CMAKE_TLS_VERIFY=0 to suppress it.");
2324
0
    }
2325
0
    status.GetMakefile().AddDefinition(
2326
0
      statusVar, cmStrCat(static_cast<int>(res), ";\"", std::move(m), '"'));
2327
0
  }
2328
2329
0
  ::curl_global_cleanup();
2330
2331
  // Ensure requested curl logs are returned (especially in case of failure)
2332
  //
2333
0
  if (!logVar.empty()) {
2334
0
    chunkDebug.push_back(0);
2335
0
    status.GetMakefile().AddDefinition(logVar, chunkDebug.data());
2336
0
  }
2337
2338
  // Verify MD5 sum if requested:
2339
  //
2340
0
  if (hash) {
2341
0
    if (res != CURLE_OK) {
2342
0
      status.SetError(
2343
0
        cmStrCat("DOWNLOAD cannot compute hash on failed download\n"
2344
0
                 "  from url: \"",
2345
0
                 url, "\"\n  status: [", static_cast<int>(res), ";\"",
2346
0
                 ::curl_easy_strerror(res), "\"]"));
2347
0
      return false;
2348
0
    }
2349
2350
0
    std::string actualHash = hash->HashFile(file);
2351
0
    if (actualHash.empty()) {
2352
0
      status.SetError(cmStrCat("DOWNLOAD cannot compute hash on download\n"
2353
0
                               "  for file: \"",
2354
0
                               file, '"'));
2355
0
      return false;
2356
0
    }
2357
2358
0
    if (expectedHash != actualHash) {
2359
0
      if (!statusVar.empty() && res == 0) {
2360
0
        status.GetMakefile().AddDefinition(statusVar,
2361
0
                                           "1;HASH mismatch: "
2362
0
                                           "expected: " +
2363
0
                                             expectedHash +
2364
0
                                             " actual: " + actualHash);
2365
0
      }
2366
2367
0
      status.SetError(cmStrCat("DOWNLOAD HASH mismatch\n"
2368
0
                               "  for file: \"",
2369
0
                               file,
2370
0
                               "\"\n"
2371
0
                               "    expected hash: \"",
2372
0
                               expectedHash,
2373
0
                               "\"\n"
2374
0
                               "      actual hash: \"",
2375
0
                               actualHash, "\"\n"));
2376
0
      return false;
2377
0
    }
2378
0
  }
2379
2380
0
  return true;
2381
#else
2382
  status.SetError("DOWNLOAD not supported by bootstrap cmake.");
2383
  return false;
2384
#endif
2385
0
}
2386
2387
bool HandleUploadCommand(std::vector<std::string> const& args,
2388
                         cmExecutionStatus& status)
2389
0
{
2390
0
#if !defined(CMAKE_BOOTSTRAP)
2391
0
  if (args.size() < 3) {
2392
0
    status.SetError("UPLOAD must be called with at least three arguments.");
2393
0
    return false;
2394
0
  }
2395
0
  auto i = args.begin();
2396
0
  ++i;
2397
0
  std::string filename = *i;
2398
0
  ++i;
2399
0
  std::string url = *i;
2400
0
  ++i;
2401
2402
0
  long timeout = 0;
2403
0
  long inactivity_timeout = 0;
2404
0
  std::string logVar;
2405
0
  std::string statusVar;
2406
0
  bool showProgress = false;
2407
0
  cm::optional<std::string> tlsVersionOpt;
2408
0
  cm::optional<bool> tlsVerifyOpt;
2409
0
  cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
2410
0
  std::string userpwd;
2411
0
  std::string netrc_level =
2412
0
    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
2413
0
  std::string netrc_file =
2414
0
    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
2415
2416
0
  std::vector<std::string> curl_headers;
2417
2418
0
  while (i != args.end()) {
2419
0
    if (*i == "TIMEOUT") {
2420
0
      ++i;
2421
0
      if (i != args.end()) {
2422
0
        timeout = atol(i->c_str());
2423
0
      } else {
2424
0
        status.SetError("UPLOAD missing time for TIMEOUT.");
2425
0
        return false;
2426
0
      }
2427
0
    } else if (*i == "INACTIVITY_TIMEOUT") {
2428
0
      ++i;
2429
0
      if (i != args.end()) {
2430
0
        inactivity_timeout = atol(i->c_str());
2431
0
      } else {
2432
0
        status.SetError("UPLOAD missing time for INACTIVITY_TIMEOUT.");
2433
0
        return false;
2434
0
      }
2435
0
    } else if (*i == "LOG") {
2436
0
      ++i;
2437
0
      if (i == args.end()) {
2438
0
        status.SetError("UPLOAD missing VAR for LOG.");
2439
0
        return false;
2440
0
      }
2441
0
      logVar = *i;
2442
0
    } else if (*i == "STATUS") {
2443
0
      ++i;
2444
0
      if (i == args.end()) {
2445
0
        status.SetError("UPLOAD missing VAR for STATUS.");
2446
0
        return false;
2447
0
      }
2448
0
      statusVar = *i;
2449
0
    } else if (*i == "SHOW_PROGRESS") {
2450
0
      showProgress = true;
2451
0
    } else if (*i == "TLS_VERSION") {
2452
0
      ++i;
2453
0
      if (i != args.end()) {
2454
0
        tlsVersionOpt = *i;
2455
0
      } else {
2456
0
        status.SetError("UPLOAD missing value for TLS_VERSION.");
2457
0
        return false;
2458
0
      }
2459
0
    } else if (*i == "TLS_VERIFY") {
2460
0
      ++i;
2461
0
      if (i != args.end()) {
2462
0
        tlsVerifyOpt = cmIsOn(*i);
2463
0
      } else {
2464
0
        status.SetError("UPLOAD missing bool value for TLS_VERIFY.");
2465
0
        return false;
2466
0
      }
2467
0
    } else if (*i == "TLS_CAINFO") {
2468
0
      ++i;
2469
0
      if (i != args.end()) {
2470
0
        cainfo = cmValue(*i);
2471
0
      } else {
2472
0
        status.SetError("UPLOAD missing file value for TLS_CAINFO.");
2473
0
        return false;
2474
0
      }
2475
0
    } else if (*i == "NETRC_FILE") {
2476
0
      ++i;
2477
0
      if (i != args.end()) {
2478
0
        netrc_file = *i;
2479
0
      } else {
2480
0
        status.SetError("UPLOAD missing file value for NETRC_FILE.");
2481
0
        return false;
2482
0
      }
2483
0
    } else if (*i == "NETRC") {
2484
0
      ++i;
2485
0
      if (i != args.end()) {
2486
0
        netrc_level = *i;
2487
0
      } else {
2488
0
        status.SetError("UPLOAD missing level value for NETRC.");
2489
0
        return false;
2490
0
      }
2491
0
    } else if (*i == "USERPWD") {
2492
0
      ++i;
2493
0
      if (i == args.end()) {
2494
0
        status.SetError("UPLOAD missing string for USERPWD.");
2495
0
        return false;
2496
0
      }
2497
0
      userpwd = *i;
2498
0
    } else if (*i == "HTTPHEADER") {
2499
0
      ++i;
2500
0
      if (i == args.end()) {
2501
0
        status.SetError("UPLOAD missing string for HTTPHEADER.");
2502
0
        return false;
2503
0
      }
2504
0
      curl_headers.push_back(*i);
2505
0
    } else {
2506
      // Do not return error for compatibility reason.
2507
0
      status.GetMakefile().IssueDiagnostic(
2508
0
        cmDiagnostics::CMD_AUTHOR, cmStrCat("Unexpected argument: ", *i));
2509
0
    }
2510
2511
0
    ++i;
2512
0
  }
2513
2514
0
  if (!tlsVerifyOpt.has_value()) {
2515
0
    if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERIFY")) {
2516
0
      tlsVerifyOpt = v.IsOn();
2517
0
    }
2518
0
  }
2519
0
  if (!tlsVerifyOpt.has_value()) {
2520
0
    if (cm::optional<std::string> v =
2521
0
          cmSystemTools::GetEnvVar("CMAKE_TLS_VERIFY")) {
2522
0
      tlsVerifyOpt = cmIsOn(*v);
2523
0
    }
2524
0
  }
2525
0
  bool tlsVerifyDefaulted = false;
2526
0
  if (!tlsVerifyOpt.has_value()) {
2527
0
    tlsVerifyOpt = TLS_VERIFY_DEFAULT;
2528
0
    tlsVerifyDefaulted = true;
2529
0
  }
2530
2531
0
  if (!tlsVersionOpt.has_value()) {
2532
0
    if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERSION")) {
2533
0
      tlsVersionOpt = *v;
2534
0
    }
2535
0
  }
2536
0
  if (!tlsVersionOpt.has_value()) {
2537
0
    if (cm::optional<std::string> v =
2538
0
          cmSystemTools::GetEnvVar("CMAKE_TLS_VERSION")) {
2539
0
      tlsVersionOpt = std::move(v);
2540
0
    }
2541
0
  }
2542
0
  bool tlsVersionDefaulted = false;
2543
0
  if (!tlsVersionOpt.has_value()) {
2544
0
    tlsVersionOpt = TLS_VERSION_DEFAULT;
2545
0
    tlsVersionDefaulted = true;
2546
0
  }
2547
2548
  // Open file for reading:
2549
  //
2550
0
  FILE* fin = cmsys::SystemTools::Fopen(filename, "rb");
2551
0
  if (!fin) {
2552
0
    std::string errStr =
2553
0
      cmStrCat("UPLOAD cannot open file '", filename, "' for reading.");
2554
0
    status.SetError(errStr);
2555
0
    return false;
2556
0
  }
2557
2558
0
  unsigned long file_size = cmsys::SystemTools::FileLength(filename);
2559
2560
0
  url = CurlCompatFileURL(url);
2561
2562
0
  ::CURL* curl;
2563
0
  cm_curl_global_init(CURL_GLOBAL_DEFAULT);
2564
0
  curl = cm_curl_easy_init();
2565
0
  if (!curl) {
2566
0
    status.SetError("UPLOAD error initializing curl.");
2567
0
    fclose(fin);
2568
0
    return false;
2569
0
  }
2570
2571
0
  cURLEasyGuard g_curl(curl);
2572
2573
  // enable HTTP ERROR parsing
2574
0
  ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
2575
0
  check_curl_result(res, "UPLOAD cannot set fail on error flag: ");
2576
2577
  // enable uploading
2578
0
  res = ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
2579
0
  check_curl_result(res, "UPLOAD cannot set upload flag: ");
2580
2581
0
  res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
2582
0
  check_curl_result(res, "UPLOAD cannot set url: ");
2583
2584
0
  res =
2585
0
    ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToMemoryCallback);
2586
0
  check_curl_result(res, "UPLOAD cannot set write function: ");
2587
2588
0
  res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
2589
0
                           cmFileCommandCurlDebugCallback);
2590
0
  check_curl_result(res, "UPLOAD cannot set debug function: ");
2591
2592
0
  if (tlsVersionOpt.has_value()) {
2593
0
    if (cm::optional<int> v = cmCurlParseTLSVersion(*tlsVersionOpt)) {
2594
0
      res = ::curl_easy_setopt(curl, CURLOPT_SSLVERSION, *v);
2595
      // See HandleDownloadCommand for the rationale behind accepting both
2596
      // CURLE_NOT_BUILT_IN and CURLE_UNKNOWN_OPTION here.
2597
0
      if (tlsVersionDefaulted &&
2598
0
          (res == CURLE_NOT_BUILT_IN || res == CURLE_UNKNOWN_OPTION)) {
2599
0
        res = CURLE_OK;
2600
0
      }
2601
0
      check_curl_result(
2602
0
        res,
2603
0
        cmStrCat("UPLOAD cannot set TLS/SSL version ", *tlsVersionOpt, ": "));
2604
0
    } else {
2605
0
      status.SetError(
2606
0
        cmStrCat("UPLOAD given unknown TLS/SSL version ", *tlsVersionOpt));
2607
0
      return false;
2608
0
    }
2609
0
  }
2610
2611
  // check to see if TLS verification is requested
2612
0
  if (tlsVerifyOpt.has_value() && tlsVerifyOpt.value()) {
2613
0
    res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
2614
0
    check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify on: ");
2615
0
  } else {
2616
0
    res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
2617
0
    check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify off: ");
2618
0
  }
2619
2620
  // check to see if a CAINFO file has been specified
2621
  // command arg comes first
2622
0
  std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
2623
0
  if (!cainfo_err.empty()) {
2624
0
    status.SetError(cainfo_err);
2625
0
    return false;
2626
0
  }
2627
2628
0
  cmFileCommandVectorOfChar chunkResponse;
2629
0
  cmFileCommandVectorOfChar chunkDebug;
2630
2631
0
  res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunkResponse);
2632
0
  check_curl_result(res, "UPLOAD cannot set write data: ");
2633
2634
0
  res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2635
0
  check_curl_result(res, "UPLOAD cannot set debug data: ");
2636
2637
0
  res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2638
0
  check_curl_result(res, "UPLOAD cannot set follow-redirect option: ");
2639
2640
0
  if (!logVar.empty()) {
2641
0
    res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2642
0
    check_curl_result(res, "UPLOAD cannot set verbose: ");
2643
0
  }
2644
2645
0
  if (timeout > 0) {
2646
0
    res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2647
0
    check_curl_result(res, "UPLOAD cannot set timeout: ");
2648
0
  }
2649
2650
0
  if (inactivity_timeout > 0) {
2651
    // Give up if there is no progress for a long time.
2652
0
    ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2653
0
    ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2654
0
  }
2655
2656
  // Need the progress helper's scope to last through the duration of
2657
  // the curl_easy_perform call... so this object is declared at function
2658
  // scope intentionally, rather than inside the "if(showProgress)"
2659
  // block...
2660
  //
2661
0
  cURLProgressHelper helper(&status.GetMakefile(), "upload");
2662
2663
0
  if (showProgress) {
2664
0
    res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2665
0
    check_curl_result(res, "UPLOAD cannot set noprogress value: ");
2666
2667
0
    res = ::curl_easy_setopt(curl, CM_CURLOPT_XFERINFOFUNCTION,
2668
0
                             cmFileUploadProgressCallback);
2669
0
    check_curl_result(res, "UPLOAD cannot set progress function: ");
2670
2671
0
    res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2672
0
                             reinterpret_cast<void*>(&helper));
2673
0
    check_curl_result(res, "UPLOAD cannot set progress data: ");
2674
0
  }
2675
2676
  // now specify which file to upload
2677
0
  res = ::curl_easy_setopt(curl, CURLOPT_INFILE, fin);
2678
0
  check_curl_result(res, "UPLOAD cannot set input file: ");
2679
2680
  // and give the size of the upload (optional)
2681
0
  res =
2682
0
    ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast<long>(file_size));
2683
0
  check_curl_result(res, "UPLOAD cannot set input file size: ");
2684
2685
0
  if (!userpwd.empty()) {
2686
0
    res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2687
0
    check_curl_result(res, "UPLOAD cannot set user password: ");
2688
0
  }
2689
2690
  // check to see if netrc parameters have been specified
2691
  // local command args takes precedence over CMAKE_NETRC*
2692
0
  netrc_level = cmSystemTools::UpperCase(netrc_level);
2693
0
  std::string const& netrc_option_err =
2694
0
    cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2695
0
  if (!netrc_option_err.empty()) {
2696
0
    status.SetError(netrc_option_err);
2697
0
    return false;
2698
0
  }
2699
2700
0
  struct curl_slist* headers = nullptr;
2701
0
  for (std::string const& h : curl_headers) {
2702
0
    headers = ::curl_slist_append(headers, h.c_str());
2703
0
  }
2704
0
  ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2705
2706
0
  res = ::curl_easy_perform(curl);
2707
2708
0
  ::curl_slist_free_all(headers);
2709
2710
  /* always cleanup */
2711
0
  g_curl.release();
2712
0
  ::curl_easy_cleanup(curl);
2713
2714
0
  if (!statusVar.empty()) {
2715
0
    std::string m = curl_easy_strerror(res);
2716
0
    if ((res == CURLE_SSL_CONNECT_ERROR ||
2717
0
         res == CURLE_PEER_FAILED_VERIFICATION) &&
2718
0
        tlsVerifyDefaulted) {
2719
0
      m = cmStrCat(
2720
0
        std::move(m),
2721
0
        ".  If this is due to https certificate verification failure, one may "
2722
0
        "set environment variable CMAKE_TLS_VERIFY=0 to suppress it.");
2723
0
    }
2724
0
    status.GetMakefile().AddDefinition(
2725
0
      statusVar, cmStrCat(static_cast<int>(res), ";\"", std::move(m), '"'));
2726
0
  }
2727
2728
0
  ::curl_global_cleanup();
2729
2730
0
  fclose(fin);
2731
0
  fin = nullptr;
2732
2733
0
  if (!logVar.empty()) {
2734
0
    std::string log;
2735
2736
0
    if (!chunkResponse.empty()) {
2737
0
      chunkResponse.push_back(0);
2738
0
      log += "Response:\n";
2739
0
      log += chunkResponse.data();
2740
0
      log += "\n";
2741
0
    }
2742
2743
0
    if (!chunkDebug.empty()) {
2744
0
      chunkDebug.push_back(0);
2745
0
      log += "Debug:\n";
2746
0
      log += chunkDebug.data();
2747
0
      log += "\n";
2748
0
    }
2749
2750
0
    status.GetMakefile().AddDefinition(logVar, log);
2751
0
  }
2752
2753
0
  return true;
2754
#else
2755
  status.SetError("UPLOAD not supported by bootstrap cmake.");
2756
  return false;
2757
#endif
2758
0
}
2759
2760
void AddEvaluationFile(std::string const& inputName,
2761
                       std::string const& targetName,
2762
                       std::string const& outputExpr,
2763
                       std::string const& condition, bool inputIsContent,
2764
                       std::string const& newLineCharacter, mode_t permissions,
2765
                       cmExecutionStatus& status)
2766
0
{
2767
0
  cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace();
2768
2769
0
  cmGeneratorExpression outputGe(*status.GetMakefile().GetCMakeInstance(),
2770
0
                                 lfbt);
2771
0
  std::unique_ptr<cmCompiledGeneratorExpression> outputCge =
2772
0
    outputGe.Parse(outputExpr);
2773
2774
0
  cmGeneratorExpression conditionGe(*status.GetMakefile().GetCMakeInstance(),
2775
0
                                    lfbt);
2776
0
  std::unique_ptr<cmCompiledGeneratorExpression> conditionCge =
2777
0
    conditionGe.Parse(condition);
2778
2779
0
  status.GetMakefile().AddEvaluationFile(
2780
0
    inputName, targetName, std::move(outputCge), std::move(conditionCge),
2781
0
    newLineCharacter, permissions, inputIsContent);
2782
0
}
2783
2784
bool HandleGenerateCommand(std::vector<std::string> const& args,
2785
                           cmExecutionStatus& status)
2786
0
{
2787
0
  if (args.size() < 5) {
2788
0
    status.SetError("Incorrect arguments to GENERATE subcommand.");
2789
0
    return false;
2790
0
  }
2791
2792
0
  struct Arguments : public ArgumentParser::ParseResult
2793
0
  {
2794
0
    cm::optional<std::string> Output;
2795
0
    cm::optional<std::string> Input;
2796
0
    cm::optional<std::string> Content;
2797
0
    cm::optional<std::string> Condition;
2798
0
    cm::optional<std::string> Target;
2799
0
    cm::optional<std::string> NewLineStyle;
2800
0
    bool NoSourcePermissions = false;
2801
0
    bool UseSourcePermissions = false;
2802
0
    ArgumentParser::NonEmpty<std::vector<std::string>> FilePermissions;
2803
0
    std::vector<cm::string_view> ParsedKeywords;
2804
0
  };
2805
2806
0
  static auto const parser =
2807
0
    cmArgumentParser<Arguments>{}
2808
0
      .Bind("OUTPUT"_s, &Arguments::Output)
2809
0
      .Bind("INPUT"_s, &Arguments::Input)
2810
0
      .Bind("CONTENT"_s, &Arguments::Content)
2811
0
      .Bind("CONDITION"_s, &Arguments::Condition)
2812
0
      .Bind("TARGET"_s, &Arguments::Target)
2813
0
      .Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions)
2814
0
      .Bind("USE_SOURCE_PERMISSIONS"_s, &Arguments::UseSourcePermissions)
2815
0
      .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
2816
0
      .Bind("NEWLINE_STYLE"_s, &Arguments::NewLineStyle)
2817
0
      .BindParsedKeywords(&Arguments::ParsedKeywords);
2818
2819
0
  std::vector<std::string> unparsedArguments;
2820
0
  Arguments const arguments =
2821
0
    parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments);
2822
2823
0
  if (arguments.MaybeReportError(status.GetMakefile())) {
2824
0
    return true;
2825
0
  }
2826
2827
0
  if (!unparsedArguments.empty()) {
2828
0
    status.SetError("Unknown argument to GENERATE subcommand.");
2829
0
    return false;
2830
0
  }
2831
2832
0
  if (!arguments.Output || arguments.ParsedKeywords[0] != "OUTPUT"_s) {
2833
0
    status.SetError("GENERATE requires OUTPUT as first option.");
2834
0
    return false;
2835
0
  }
2836
0
  std::string const& output = *arguments.Output;
2837
2838
0
  if (!arguments.Input && !arguments.Content) {
2839
0
    status.SetError("GENERATE requires INPUT or CONTENT option.");
2840
0
    return false;
2841
0
  }
2842
0
  bool const inputIsContent = arguments.ParsedKeywords[1] == "CONTENT"_s;
2843
0
  if (!inputIsContent && arguments.ParsedKeywords[1] != "INPUT") {
2844
0
    status.SetError("Unknown argument to GENERATE subcommand.");
2845
0
    return false;
2846
0
  }
2847
0
  std::string const& input =
2848
0
    inputIsContent ? *arguments.Content : *arguments.Input;
2849
2850
0
  if (arguments.Condition && arguments.Condition->empty()) {
2851
0
    status.SetError("CONDITION of sub-command GENERATE must not be empty "
2852
0
                    "if specified.");
2853
0
    return false;
2854
0
  }
2855
0
  std::string const& condition =
2856
0
    arguments.Condition ? *arguments.Condition : std::string();
2857
2858
0
  if (arguments.Target && arguments.Target->empty()) {
2859
0
    status.SetError("TARGET of sub-command GENERATE must not be empty "
2860
0
                    "if specified.");
2861
0
    return false;
2862
0
  }
2863
0
  std::string const& target =
2864
0
    arguments.Target ? *arguments.Target : std::string();
2865
2866
0
  cmNewLineStyle newLineStyle;
2867
0
  if (arguments.NewLineStyle) {
2868
0
    std::string errorMessage;
2869
0
    if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
2870
0
      status.SetError(cmStrCat("GENERATE ", errorMessage));
2871
0
      return false;
2872
0
    }
2873
0
  }
2874
2875
0
  if (arguments.NoSourcePermissions && arguments.UseSourcePermissions) {
2876
0
    status.SetError("given both NO_SOURCE_PERMISSIONS and "
2877
0
                    "USE_SOURCE_PERMISSIONS. Only one option allowed.");
2878
0
    return false;
2879
0
  }
2880
2881
0
  if (!arguments.FilePermissions.empty()) {
2882
0
    if (arguments.NoSourcePermissions) {
2883
0
      status.SetError("given both NO_SOURCE_PERMISSIONS and "
2884
0
                      "FILE_PERMISSIONS. Only one option allowed.");
2885
0
      return false;
2886
0
    }
2887
0
    if (arguments.UseSourcePermissions) {
2888
0
      status.SetError("given both USE_SOURCE_PERMISSIONS and "
2889
0
                      "FILE_PERMISSIONS. Only one option allowed.");
2890
0
      return false;
2891
0
    }
2892
0
  }
2893
2894
0
  if (arguments.UseSourcePermissions) {
2895
0
    if (inputIsContent) {
2896
0
      status.SetError("given USE_SOURCE_PERMISSIONS without a file INPUT.");
2897
0
      return false;
2898
0
    }
2899
0
  }
2900
2901
0
  mode_t permissions = 0;
2902
0
  if (arguments.NoSourcePermissions) {
2903
0
    permissions |= cmFSPermissions::mode_owner_read;
2904
0
    permissions |= cmFSPermissions::mode_owner_write;
2905
0
    permissions |= cmFSPermissions::mode_group_read;
2906
0
    permissions |= cmFSPermissions::mode_world_read;
2907
0
  }
2908
2909
0
  if (!arguments.FilePermissions.empty()) {
2910
0
    std::vector<std::string> invalidOptions;
2911
0
    for (auto const& e : arguments.FilePermissions) {
2912
0
      if (!cmFSPermissions::stringToModeT(e, permissions)) {
2913
0
        invalidOptions.push_back(e);
2914
0
      }
2915
0
    }
2916
0
    if (!invalidOptions.empty()) {
2917
0
      std::ostringstream oss;
2918
0
      oss << "given invalid permission ";
2919
0
      for (auto i = 0u; i < invalidOptions.size(); i++) {
2920
0
        if (i == 0u) {
2921
0
          oss << "\"" << invalidOptions[i] << "\"";
2922
0
        } else {
2923
0
          oss << ",\"" << invalidOptions[i] << "\"";
2924
0
        }
2925
0
      }
2926
0
      oss << ".";
2927
0
      status.SetError(oss.str());
2928
0
      return false;
2929
0
    }
2930
0
  }
2931
2932
0
  AddEvaluationFile(input, target, output, condition, inputIsContent,
2933
0
                    newLineStyle.GetCharacters(), permissions, status);
2934
0
  return true;
2935
0
}
2936
2937
bool HandleLockCommand(std::vector<std::string> const& args,
2938
                       cmExecutionStatus& status)
2939
0
{
2940
0
#if !defined(CMAKE_BOOTSTRAP)
2941
  // Default values
2942
0
  bool directory = false;
2943
0
  bool release = false;
2944
0
  enum Guard
2945
0
  {
2946
0
    GUARD_FUNCTION,
2947
0
    GUARD_FILE,
2948
0
    GUARD_PROCESS
2949
0
  };
2950
0
  Guard guard = GUARD_PROCESS;
2951
0
  std::string resultVariable;
2952
0
  unsigned long timeout = static_cast<unsigned long>(-1);
2953
2954
  // Parse arguments
2955
0
  if (args.size() < 2) {
2956
0
    status.GetMakefile().IssueMessage(
2957
0
      MessageType::FATAL_ERROR,
2958
0
      "sub-command LOCK requires at least two arguments.");
2959
0
    return false;
2960
0
  }
2961
2962
0
  std::string path = args[1];
2963
0
  for (unsigned i = 2; i < args.size(); ++i) {
2964
0
    if (args[i] == "DIRECTORY") {
2965
0
      directory = true;
2966
0
    } else if (args[i] == "RELEASE") {
2967
0
      release = true;
2968
0
    } else if (args[i] == "GUARD") {
2969
0
      ++i;
2970
0
      char const* merr = "expected FUNCTION, FILE or PROCESS after GUARD";
2971
0
      if (i >= args.size()) {
2972
0
        status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, merr);
2973
0
        return false;
2974
0
      }
2975
0
      if (args[i] == "FUNCTION") {
2976
0
        guard = GUARD_FUNCTION;
2977
0
      } else if (args[i] == "FILE") {
2978
0
        guard = GUARD_FILE;
2979
0
      } else if (args[i] == "PROCESS") {
2980
0
        guard = GUARD_PROCESS;
2981
0
      } else {
2982
0
        status.GetMakefile().IssueMessage(
2983
0
          MessageType::FATAL_ERROR,
2984
0
          cmStrCat(merr, ", but got:\n  \"", args[i], "\"."));
2985
0
        return false;
2986
0
      }
2987
2988
0
    } else if (args[i] == "RESULT_VARIABLE") {
2989
0
      ++i;
2990
0
      if (i >= args.size()) {
2991
0
        status.GetMakefile().IssueMessage(
2992
0
          MessageType::FATAL_ERROR,
2993
0
          "expected variable name after RESULT_VARIABLE");
2994
0
        return false;
2995
0
      }
2996
0
      resultVariable = args[i];
2997
0
    } else if (args[i] == "TIMEOUT") {
2998
0
      ++i;
2999
0
      if (i >= args.size()) {
3000
0
        status.GetMakefile().IssueMessage(
3001
0
          MessageType::FATAL_ERROR, "expected timeout value after TIMEOUT");
3002
0
        return false;
3003
0
      }
3004
0
      long scanned;
3005
0
      if (!cmStrToLong(args[i], &scanned) || scanned < 0) {
3006
0
        status.GetMakefile().IssueMessage(
3007
0
          MessageType::FATAL_ERROR,
3008
0
          cmStrCat("TIMEOUT value \"", args[i],
3009
0
                   "\" is not an unsigned integer."));
3010
0
        return false;
3011
0
      }
3012
0
      timeout = static_cast<unsigned long>(scanned);
3013
0
    } else {
3014
0
      status.GetMakefile().IssueMessage(
3015
0
        MessageType::FATAL_ERROR,
3016
0
        cmStrCat("expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or "
3017
0
                 "TIMEOUT\nbut got: \"",
3018
0
                 args[i], "\"."));
3019
0
      return false;
3020
0
    }
3021
0
  }
3022
3023
0
  if (directory) {
3024
0
    path += "/cmake.lock";
3025
0
  }
3026
3027
  // Unify path (remove '//', '/../', ...)
3028
0
  path = cmSystemTools::CollapseFullPath(
3029
0
    path, status.GetMakefile().GetCurrentSourceDirectory());
3030
3031
  // Create file and directories if needed
3032
0
  std::string parentDir = cmSystemTools::GetParentDirectory(path);
3033
0
  if (!cmSystemTools::MakeDirectory(parentDir)) {
3034
0
    status.GetMakefile().IssueMessage(
3035
0
      MessageType::FATAL_ERROR,
3036
0
      cmStrCat("directory\n  \"", parentDir,
3037
0
               "\"\ncreation failed (check permissions)."));
3038
0
    cmSystemTools::SetFatalErrorOccurred();
3039
0
    return false;
3040
0
  }
3041
0
  FILE* file = cmsys::SystemTools::Fopen(path, "a");
3042
0
  if (!file) {
3043
0
    status.GetMakefile().IssueMessage(
3044
0
      MessageType::FATAL_ERROR,
3045
0
      cmStrCat("file\n  \"", path,
3046
0
               "\"\ncreation failed (check permissions)."));
3047
0
    cmSystemTools::SetFatalErrorOccurred();
3048
0
    return false;
3049
0
  }
3050
0
  fclose(file);
3051
3052
  // Actual lock/unlock
3053
0
  cmFileLockPool& lockPool =
3054
0
    status.GetMakefile().GetGlobalGenerator()->GetFileLockPool();
3055
3056
0
  cmFileLockResult fileLockResult(cmFileLockResult::MakeOk());
3057
0
  if (release) {
3058
0
    fileLockResult = lockPool.Release(path);
3059
0
  } else {
3060
0
    switch (guard) {
3061
0
      case GUARD_FUNCTION:
3062
0
        fileLockResult = lockPool.LockFunctionScope(path, timeout);
3063
0
        break;
3064
0
      case GUARD_FILE:
3065
0
        fileLockResult = lockPool.LockFileScope(path, timeout);
3066
0
        break;
3067
0
      case GUARD_PROCESS:
3068
0
        fileLockResult = lockPool.LockProcessScope(path, timeout);
3069
0
        break;
3070
0
      default:
3071
0
        cmSystemTools::SetFatalErrorOccurred();
3072
0
        return false;
3073
0
    }
3074
0
  }
3075
3076
0
  std::string const result = fileLockResult.GetOutputMessage();
3077
3078
0
  if (resultVariable.empty() && !fileLockResult.IsOk()) {
3079
0
    status.GetMakefile().IssueMessage(
3080
0
      MessageType::FATAL_ERROR,
3081
0
      cmStrCat("error locking file\n  \"", path, "\"\n", result, '.'));
3082
0
    cmSystemTools::SetFatalErrorOccurred();
3083
0
    return false;
3084
0
  }
3085
3086
0
  if (!resultVariable.empty()) {
3087
0
    status.GetMakefile().AddDefinition(resultVariable, result);
3088
0
  }
3089
3090
0
  return true;
3091
#else
3092
  static_cast<void>(args);
3093
  status.SetError("sub-command LOCK not implemented in bootstrap cmake");
3094
  return false;
3095
#endif
3096
0
}
3097
3098
bool HandleTimestampCommand(std::vector<std::string> const& args,
3099
                            cmExecutionStatus& status)
3100
0
{
3101
0
  if (args.size() < 3) {
3102
0
    status.SetError("sub-command TIMESTAMP requires at least two arguments.");
3103
0
    return false;
3104
0
  }
3105
0
  if (args.size() > 5) {
3106
0
    status.SetError("sub-command TIMESTAMP takes at most four arguments.");
3107
0
    return false;
3108
0
  }
3109
3110
0
  unsigned int argsIndex = 1;
3111
3112
0
  std::string filename = args[argsIndex++];
3113
0
  if (!cmsys::SystemTools::FileIsFullPath(filename)) {
3114
0
    filename = cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/',
3115
0
                        filename);
3116
0
  }
3117
3118
0
  std::string const& outputVariable = args[argsIndex++];
3119
3120
0
  std::string formatString;
3121
0
  if (args.size() > argsIndex && args[argsIndex] != "UTC") {
3122
0
    formatString = args[argsIndex++];
3123
0
  }
3124
3125
0
  bool utcFlag = false;
3126
0
  if (args.size() > argsIndex) {
3127
0
    if (args[argsIndex] == "UTC") {
3128
0
      utcFlag = true;
3129
0
    } else {
3130
0
      std::string e =
3131
0
        cmStrCat(" TIMESTAMP sub-command does not recognize option ",
3132
0
                 args[argsIndex], '.');
3133
0
      status.SetError(e);
3134
0
      return false;
3135
0
    }
3136
0
  }
3137
3138
0
  cmTimestamp timestamp;
3139
0
  std::string result =
3140
0
    timestamp.FileModificationTime(filename.c_str(), formatString, utcFlag);
3141
0
  status.GetMakefile().AddDefinition(outputVariable, result);
3142
3143
0
  return true;
3144
0
}
3145
3146
bool HandleSizeCommand(std::vector<std::string> const& args,
3147
                       cmExecutionStatus& status)
3148
0
{
3149
0
  if (args.size() != 3) {
3150
0
    status.SetError(
3151
0
      cmStrCat(args[0], " requires a file name and output variable"));
3152
0
    return false;
3153
0
  }
3154
3155
0
  unsigned int argsIndex = 1;
3156
3157
0
  std::string const& filename = args[argsIndex++];
3158
3159
0
  std::string const& outputVariable = args[argsIndex++];
3160
3161
0
  if (!cmSystemTools::FileExists(filename, true)) {
3162
0
    status.SetError(
3163
0
      cmStrCat("SIZE requested of path that is not readable:\n  ", filename));
3164
0
    return false;
3165
0
  }
3166
3167
0
  status.GetMakefile().AddDefinition(
3168
0
    outputVariable, std::to_string(cmSystemTools::FileLength(filename)));
3169
3170
0
  return true;
3171
0
}
3172
3173
bool HandleReadSymlinkCommand(std::vector<std::string> const& args,
3174
                              cmExecutionStatus& status)
3175
0
{
3176
0
  if (args.size() < 3) {
3177
0
    status.SetError(
3178
0
      cmStrCat(args[0], " requires a file name and output variable"));
3179
0
    return false;
3180
0
  }
3181
3182
0
  std::string const& filename = args[1];
3183
0
  std::string const& outputVariable = args[2];
3184
3185
0
  struct Arguments
3186
0
  {
3187
0
    std::string Result;
3188
0
  };
3189
3190
0
  static auto const parser =
3191
0
    cmArgumentParser<Arguments>{}.Bind("RESULT"_s, &Arguments::Result);
3192
3193
0
  std::vector<std::string> unconsumedArgs;
3194
0
  Arguments const arguments =
3195
0
    parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
3196
0
  if (!unconsumedArgs.empty()) {
3197
0
    status.SetError(
3198
0
      cmStrCat("READ_SYMLINK unknown argument:\n  ", unconsumedArgs.front()));
3199
0
    return false;
3200
0
  }
3201
3202
0
  std::string result;
3203
0
  if (!cmSystemTools::ReadSymlink(filename, result)) {
3204
0
    std::string const error = cmStrCat(
3205
0
      "READ_SYMLINK requested of path that is not a symlink:\n  ", filename);
3206
0
    if (!arguments.Result.empty()) {
3207
0
      status.GetMakefile().AddDefinition(arguments.Result, error);
3208
0
      return true;
3209
0
    }
3210
0
    status.SetError(error);
3211
0
    return false;
3212
0
  }
3213
3214
0
  status.GetMakefile().AddDefinition(outputVariable, result);
3215
0
  if (!arguments.Result.empty()) {
3216
0
    status.GetMakefile().AddDefinition(arguments.Result, "0");
3217
0
  }
3218
3219
0
  return true;
3220
0
}
3221
3222
bool HandleCreateLinkCommand(std::vector<std::string> const& args,
3223
                             cmExecutionStatus& status)
3224
0
{
3225
0
  if (args.size() < 3) {
3226
0
    status.SetError("CREATE_LINK must be called with at least two additional "
3227
0
                    "arguments");
3228
0
    return false;
3229
0
  }
3230
3231
0
  std::string const& fileName = args[1];
3232
0
  std::string const& newFileName = args[2];
3233
3234
0
  struct Arguments
3235
0
  {
3236
0
    std::string Result;
3237
0
    bool CopyOnError = false;
3238
0
    bool Symbolic = false;
3239
0
  };
3240
3241
0
  static auto const parser =
3242
0
    cmArgumentParser<Arguments>{}
3243
0
      .Bind("RESULT"_s, &Arguments::Result)
3244
0
      .Bind("COPY_ON_ERROR"_s, &Arguments::CopyOnError)
3245
0
      .Bind("SYMBOLIC"_s, &Arguments::Symbolic);
3246
3247
0
  std::vector<std::string> unconsumedArgs;
3248
0
  Arguments const arguments =
3249
0
    parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
3250
3251
0
  if (!unconsumedArgs.empty()) {
3252
0
    status.SetError(cmStrCat("unknown argument: \"",
3253
0
                             std::move(unconsumedArgs.front()), '\"'));
3254
0
    return false;
3255
0
  }
3256
3257
  // The system error message generated in the operation.
3258
0
  std::string result;
3259
3260
  // Check if the paths are distinct.
3261
0
  if (fileName == newFileName) {
3262
0
    result = "CREATE_LINK cannot use same file and newfile";
3263
0
    if (!arguments.Result.empty()) {
3264
0
      status.GetMakefile().AddDefinition(arguments.Result, result);
3265
0
      return true;
3266
0
    }
3267
0
    status.SetError(result);
3268
0
    return false;
3269
0
  }
3270
3271
0
  cmPolicies::PolicyStatus const cmp0205 =
3272
0
    status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0205);
3273
3274
  // Hard link requires original file to exist.
3275
0
  if (!arguments.Symbolic &&
3276
0
      (!cmSystemTools::PathExists(fileName) ||
3277
0
       (cmp0205 != cmPolicies::NEW && !cmSystemTools::FileExists(fileName)))) {
3278
0
    result =
3279
0
      cmStrCat("Cannot hard link \'", fileName, "\' as it does not exist.");
3280
0
    if (!arguments.Result.empty()) {
3281
0
      status.GetMakefile().AddDefinition(arguments.Result, result);
3282
0
      return true;
3283
0
    }
3284
0
    status.SetError(result);
3285
0
    return false;
3286
0
  }
3287
3288
  // Check if the new file already exists and remove it.
3289
0
  if (cmSystemTools::PathExists(newFileName)) {
3290
0
    cmsys::Status rmStatus;
3291
0
    if (cmp0205 == cmPolicies::NEW && arguments.CopyOnError &&
3292
0
        cmSystemTools::FileIsDirectory(newFileName) &&
3293
0
        !cmSystemTools::FileIsSymlink(newFileName)) {
3294
0
      rmStatus = cmSystemTools::RepeatedRemoveDirectory(newFileName);
3295
0
    } else {
3296
0
      rmStatus = cmSystemTools::RemoveFile(newFileName);
3297
0
    }
3298
0
    if (!rmStatus) {
3299
0
      std::string err = cmStrCat("Failed to create link '", newFileName,
3300
0
                                 "' because existing path cannot be removed: ",
3301
0
                                 rmStatus.GetString(), '\n');
3302
3303
0
      if (!arguments.Result.empty()) {
3304
0
        status.GetMakefile().AddDefinition(arguments.Result, err);
3305
0
        return true;
3306
0
      }
3307
0
      status.SetError(err);
3308
0
      return false;
3309
0
    }
3310
0
  }
3311
3312
0
  bool const sourceIsDirectory = cmSystemTools::FileIsDirectory(fileName);
3313
3314
  // Whether the operation completed successfully.
3315
0
  bool completed = false;
3316
3317
  // Check if the command requires a symbolic link.
3318
0
  if (arguments.Symbolic) {
3319
0
    cmsys::Status linked =
3320
0
      cmSystemTools::CreateSymlinkQuietly(fileName, newFileName);
3321
0
    if (linked) {
3322
0
      completed = true;
3323
0
    } else {
3324
0
      result = cmStrCat("failed to create symbolic link '", newFileName,
3325
0
                        "': ", linked.GetString());
3326
0
    }
3327
0
  } else {
3328
0
    bool needToTry = true;
3329
0
    if (sourceIsDirectory) {
3330
0
      if (cmp0205 == cmPolicies::NEW) {
3331
0
        needToTry = false;
3332
0
      } else if (cmp0205 == cmPolicies::WARN && arguments.CopyOnError) {
3333
0
        status.GetMakefile().IssuePolicyWarning(
3334
0
          cmPolicies::CMP0205,
3335
0
          cmStrCat("Path\n  ", fileName,
3336
0
                   "\nis a directory. Hard link creation is not supported "
3337
0
                   "for directories."));
3338
0
      }
3339
0
    }
3340
3341
0
    if (needToTry) {
3342
0
      cmsys::Status linked =
3343
0
        cmSystemTools::CreateLinkQuietly(fileName, newFileName);
3344
0
      if (linked) {
3345
0
        completed = true;
3346
0
      } else {
3347
0
        result = cmStrCat("failed to create link '", newFileName,
3348
0
                          "': ", linked.GetString());
3349
0
      }
3350
0
    } else {
3351
0
      result =
3352
0
        cmStrCat("failed to create link '", newFileName, "': not supported");
3353
0
    }
3354
0
  }
3355
3356
0
  if (cmp0205 == cmPolicies::WARN && arguments.CopyOnError &&
3357
0
      sourceIsDirectory) {
3358
0
    status.GetMakefile().IssuePolicyWarning(
3359
0
      cmPolicies::CMP0205,
3360
0
      cmStrCat("Path\n  ", fileName,
3361
0
               "\nis a directory. It will be copied "
3362
0
               "recursively when CMP0205 is set to NEW."));
3363
0
  }
3364
3365
  // Check if copy-on-error is enabled in the arguments.
3366
0
  if (!completed && arguments.CopyOnError) {
3367
0
    cmsys::Status copied;
3368
0
    if (cmp0205 == cmPolicies::NEW && sourceIsDirectory) {
3369
0
      copied = cmsys::SystemTools::CopyADirectory(fileName, newFileName);
3370
0
    } else {
3371
0
      copied = cmsys::SystemTools::CopyFileAlways(fileName, newFileName);
3372
0
    }
3373
3374
0
    if (copied) {
3375
0
      completed = true;
3376
0
    } else {
3377
0
      result = "Copy failed: " + copied.GetString();
3378
0
    }
3379
0
  }
3380
3381
  // Check if the operation was successful.
3382
0
  if (completed) {
3383
0
    result = "0";
3384
0
  } else if (arguments.Result.empty()) {
3385
    // The operation failed and the result is not reported in a variable.
3386
0
    status.SetError(result);
3387
0
    return false;
3388
0
  }
3389
3390
0
  if (!arguments.Result.empty()) {
3391
0
    status.GetMakefile().AddDefinition(arguments.Result, result);
3392
0
  }
3393
3394
0
  return true;
3395
0
}
3396
3397
bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
3398
                                         cmExecutionStatus& status)
3399
0
{
3400
0
  std::string platform =
3401
0
    status.GetMakefile().GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
3402
0
  if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
3403
0
        platform)) {
3404
0
    status.SetError(
3405
0
      cmStrCat("GET_RUNTIME_DEPENDENCIES is not supported on system \"",
3406
0
               platform, '"'));
3407
0
    cmSystemTools::SetFatalErrorOccurred();
3408
0
    return false;
3409
0
  }
3410
3411
0
  if (status.GetMakefile().GetState()->GetRole() == cmState::Role::Project) {
3412
0
    status.GetMakefile().IssueDiagnostic(
3413
0
      cmDiagnostics::CMD_AUTHOR,
3414
0
      "You have used file(GET_RUNTIME_DEPENDENCIES)"
3415
0
      " in project mode. This is probably not what "
3416
0
      "you intended to do. Instead, please consider"
3417
0
      " using it in an install(CODE) or "
3418
0
      "install(SCRIPT) command. For example:"
3419
0
      "\n  install(CODE [["
3420
0
      "\n    file(GET_RUNTIME_DEPENDENCIES"
3421
0
      "\n      # ..."
3422
0
      "\n      )"
3423
0
      "\n    ]])");
3424
0
  }
3425
3426
0
  struct Arguments : public ArgumentParser::ParseResult
3427
0
  {
3428
0
    std::string ResolvedDependenciesVar;
3429
0
    std::string UnresolvedDependenciesVar;
3430
0
    std::string ConflictingDependenciesPrefix;
3431
0
    std::string RPathPrefix;
3432
0
    std::string BundleExecutable;
3433
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Executables;
3434
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Libraries;
3435
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories;
3436
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Modules;
3437
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PreIncludeRegexes;
3438
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PreExcludeRegexes;
3439
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeRegexes;
3440
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeRegexes;
3441
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeFiles;
3442
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeFiles;
3443
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>>
3444
0
      PostExcludeFilesStrict;
3445
0
  };
3446
3447
0
  static auto const parser =
3448
0
    cmArgumentParser<Arguments>{}
3449
0
      .Bind("RESOLVED_DEPENDENCIES_VAR"_s, &Arguments::ResolvedDependenciesVar)
3450
0
      .Bind("UNRESOLVED_DEPENDENCIES_VAR"_s,
3451
0
            &Arguments::UnresolvedDependenciesVar)
3452
0
      .Bind("CONFLICTING_DEPENDENCIES_PREFIX"_s,
3453
0
            &Arguments::ConflictingDependenciesPrefix)
3454
0
      .Bind("RPATH_PREFIX"_s, &Arguments::RPathPrefix)
3455
0
      .Bind("BUNDLE_EXECUTABLE"_s, &Arguments::BundleExecutable)
3456
0
      .Bind("EXECUTABLES"_s, &Arguments::Executables)
3457
0
      .Bind("LIBRARIES"_s, &Arguments::Libraries)
3458
0
      .Bind("MODULES"_s, &Arguments::Modules)
3459
0
      .Bind("DIRECTORIES"_s, &Arguments::Directories)
3460
0
      .Bind("PRE_INCLUDE_REGEXES"_s, &Arguments::PreIncludeRegexes)
3461
0
      .Bind("PRE_EXCLUDE_REGEXES"_s, &Arguments::PreExcludeRegexes)
3462
0
      .Bind("POST_INCLUDE_REGEXES"_s, &Arguments::PostIncludeRegexes)
3463
0
      .Bind("POST_EXCLUDE_REGEXES"_s, &Arguments::PostExcludeRegexes)
3464
0
      .Bind("POST_INCLUDE_FILES"_s, &Arguments::PostIncludeFiles)
3465
0
      .Bind("POST_EXCLUDE_FILES"_s, &Arguments::PostExcludeFiles)
3466
0
      .Bind("POST_EXCLUDE_FILES_STRICT"_s, &Arguments::PostExcludeFilesStrict);
3467
3468
0
  std::vector<std::string> unrecognizedArguments;
3469
0
  auto parsedArgs =
3470
0
    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3471
0
  auto argIt = unrecognizedArguments.begin();
3472
0
  if (argIt != unrecognizedArguments.end()) {
3473
0
    status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, '"'));
3474
0
    cmSystemTools::SetFatalErrorOccurred();
3475
0
    return false;
3476
0
  }
3477
3478
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3479
0
    cmSystemTools::SetFatalErrorOccurred();
3480
0
    return true;
3481
0
  }
3482
3483
0
  cmRuntimeDependencyArchive archive(
3484
0
    status, parsedArgs.Directories, parsedArgs.BundleExecutable,
3485
0
    parsedArgs.PreIncludeRegexes, parsedArgs.PreExcludeRegexes,
3486
0
    parsedArgs.PostIncludeRegexes, parsedArgs.PostExcludeRegexes,
3487
0
    std::move(parsedArgs.PostIncludeFiles),
3488
0
    std::move(parsedArgs.PostExcludeFiles),
3489
0
    std::move(parsedArgs.PostExcludeFilesStrict));
3490
0
  if (!archive.Prepare()) {
3491
0
    cmSystemTools::SetFatalErrorOccurred();
3492
0
    return false;
3493
0
  }
3494
3495
0
  if (!archive.GetRuntimeDependencies(
3496
0
        parsedArgs.Executables, parsedArgs.Libraries, parsedArgs.Modules)) {
3497
0
    cmSystemTools::SetFatalErrorOccurred();
3498
0
    return false;
3499
0
  }
3500
3501
0
  std::vector<std::string> deps;
3502
0
  std::vector<std::string> unresolvedDeps;
3503
0
  std::vector<std::string> conflictingDeps;
3504
0
  for (auto const& val : archive.GetResolvedPaths()) {
3505
0
    bool unique = true;
3506
0
    auto it = val.second.begin();
3507
0
    assert(it != val.second.end());
3508
0
    auto const& firstPath = *it;
3509
0
    while (++it != val.second.end()) {
3510
0
      if (!cmSystemTools::SameFile(firstPath, *it)) {
3511
0
        unique = false;
3512
0
        break;
3513
0
      }
3514
0
    }
3515
3516
0
    if (unique) {
3517
0
      deps.push_back(firstPath);
3518
0
      if (!parsedArgs.RPathPrefix.empty()) {
3519
0
        status.GetMakefile().AddDefinition(
3520
0
          cmStrCat(parsedArgs.RPathPrefix, '_', firstPath),
3521
0
          cmList::to_string(archive.GetRPaths().at(firstPath)));
3522
0
      }
3523
0
    } else if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
3524
0
      conflictingDeps.push_back(val.first);
3525
0
      std::vector<std::string> paths;
3526
0
      paths.insert(paths.begin(), val.second.begin(), val.second.end());
3527
0
      std::string varName =
3528
0
        cmStrCat(parsedArgs.ConflictingDependenciesPrefix, '_', val.first);
3529
0
      std::string pathsStr = cmList::to_string(paths);
3530
0
      status.GetMakefile().AddDefinition(varName, pathsStr);
3531
0
    } else {
3532
0
      std::ostringstream e;
3533
0
      e << "Multiple conflicting paths found for " << val.first << ":";
3534
0
      for (auto const& path : val.second) {
3535
0
        e << "\n  " << path;
3536
0
      }
3537
0
      status.SetError(e.str());
3538
0
      cmSystemTools::SetFatalErrorOccurred();
3539
0
      return false;
3540
0
    }
3541
0
  }
3542
0
  if (!archive.GetUnresolvedPaths().empty()) {
3543
0
    if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
3544
0
      unresolvedDeps.insert(unresolvedDeps.begin(),
3545
0
                            archive.GetUnresolvedPaths().begin(),
3546
0
                            archive.GetUnresolvedPaths().end());
3547
0
    } else {
3548
0
      std::ostringstream e;
3549
0
      e << "Could not resolve runtime dependencies:";
3550
0
      for (auto const& path : archive.GetUnresolvedPaths()) {
3551
0
        e << "\n  " << path;
3552
0
      }
3553
0
      status.SetError(e.str());
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("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
  };
3724
3725
0
  static auto const parser =
3726
0
    cmArgumentParser<Arguments>{}
3727
0
      .Bind("OUTPUT"_s, &Arguments::Output)
3728
0
      .Bind("FORMAT"_s, &Arguments::Format)
3729
0
      .Bind("COMPRESSION"_s, &Arguments::Compression)
3730
0
      .Bind("COMPRESSION_LEVEL"_s, &Arguments::CompressionLevel)
3731
0
      .Bind("ENCODING"_s, &Arguments::Encoding)
3732
0
      .Bind("MTIME"_s, &Arguments::MTime)
3733
0
      .Bind("THREADS"_s, &Arguments::Threads)
3734
0
      .Bind("WORKING_DIRECTORY"_s, &Arguments::WorkingDirectory)
3735
0
      .Bind("VERBOSE"_s, &Arguments::Verbose)
3736
0
      .Bind("PATHS"_s, &Arguments::Paths);
3737
3738
0
  std::vector<std::string> unrecognizedArguments;
3739
0
  auto parsedArgs =
3740
0
    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3741
0
  auto argIt = unrecognizedArguments.begin();
3742
0
  if (argIt != unrecognizedArguments.end()) {
3743
0
    status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, '"'));
3744
0
    cmSystemTools::SetFatalErrorOccurred();
3745
0
    return false;
3746
0
  }
3747
3748
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3749
0
    cmSystemTools::SetFatalErrorOccurred();
3750
0
    return true;
3751
0
  }
3752
3753
0
  char const* knownFormats[] = {
3754
0
    "7zip", "gnutar", "pax", "paxr", "raw", "zip"
3755
0
  };
3756
3757
0
  if (!parsedArgs.Format.empty() &&
3758
0
      !cm::contains(knownFormats, parsedArgs.Format)) {
3759
0
    status.SetError(
3760
0
      cmStrCat("archive format ", parsedArgs.Format, " not supported"));
3761
0
    cmSystemTools::SetFatalErrorOccurred();
3762
0
    return false;
3763
0
  }
3764
3765
0
  static std::map<std::string, cmSystemTools::cmTarCompression>
3766
0
    compressionTypeMap = { { "None", cmSystemTools::TarCompressNone },
3767
0
                           { "BZip2", cmSystemTools::TarCompressBZip2 },
3768
0
                           { "Deflate", cmSystemTools::TarCompressGZip },
3769
0
                           { "GZip", cmSystemTools::TarCompressGZip },
3770
0
                           { "LZMA", cmSystemTools::TarCompressLZMA },
3771
0
                           { "LZMA2", cmSystemTools::TarCompressXZ },
3772
0
                           { "PPMd", cmSystemTools::TarCompressPPMd },
3773
0
                           { "XZ", cmSystemTools::TarCompressXZ },
3774
0
                           { "Zstd", cmSystemTools::TarCompressZstd } };
3775
3776
0
  cmSystemTools::cmTarCompression compress = cmSystemTools::TarCompressAuto;
3777
0
  auto typeIt = compressionTypeMap.find(parsedArgs.Compression);
3778
0
  if (typeIt != compressionTypeMap.end()) {
3779
0
    compress = typeIt->second;
3780
0
  } else if (!parsedArgs.Compression.empty()) {
3781
0
    status.SetError(cmStrCat("compression type ", parsedArgs.Compression,
3782
0
                             " is not supported"));
3783
0
    cmSystemTools::SetFatalErrorOccurred();
3784
0
    return false;
3785
0
  }
3786
3787
0
  if (compress == cmSystemTools::TarCompressPPMd &&
3788
0
      parsedArgs.Format != "7zip") {
3789
0
    status.SetError(cmStrCat("PPMd compression is not supported for ",
3790
0
                             parsedArgs.Format, " format"));
3791
0
    cmSystemTools::SetFatalErrorOccurred();
3792
0
    return false;
3793
0
  }
3794
3795
0
  int compressionLevel = 0;
3796
0
  constexpr int minCompressionLevel = 0;
3797
0
  int maxCompressionLevel = 9;
3798
0
  if (compress == cmSystemTools::TarCompressZstd &&
3799
0
      parsedArgs.Format != "zip") {
3800
0
    maxCompressionLevel = 19;
3801
0
  }
3802
3803
0
  if (!parsedArgs.CompressionLevel.empty()) {
3804
0
    if (parsedArgs.CompressionLevel.size() != 1 &&
3805
0
        !cmsysString_isdigit(parsedArgs.CompressionLevel[0])) {
3806
0
      status.SetError(
3807
0
        cmStrCat("compression level ", parsedArgs.CompressionLevel, " for ",
3808
0
                 parsedArgs.Compression, " should be in range ",
3809
0
                 minCompressionLevel, " to ", maxCompressionLevel));
3810
0
      cmSystemTools::SetFatalErrorOccurred();
3811
0
      return false;
3812
0
    }
3813
0
    compressionLevel = std::stoi(parsedArgs.CompressionLevel);
3814
0
    if (compressionLevel < minCompressionLevel ||
3815
0
        compressionLevel > maxCompressionLevel) {
3816
0
      status.SetError(
3817
0
        cmStrCat("compression level ", parsedArgs.CompressionLevel, " for ",
3818
0
                 parsedArgs.Compression, " should be in range ",
3819
0
                 minCompressionLevel, " to ", maxCompressionLevel));
3820
0
      cmSystemTools::SetFatalErrorOccurred();
3821
0
      return false;
3822
0
    }
3823
0
    if (compress == cmSystemTools::TarCompressNone) {
3824
0
      status.SetError(
3825
0
        cmStrCat("compression level is not supported for "
3826
0
                 "compression \"None\". Provided compression level: ",
3827
0
                 parsedArgs.CompressionLevel));
3828
0
      cmSystemTools::SetFatalErrorOccurred();
3829
0
      return false;
3830
0
    }
3831
0
  }
3832
3833
  // Use the single thread by default for backward compatibility
3834
0
  int threads = 1;
3835
0
  constexpr int minThreads = 0;
3836
0
  if (!parsedArgs.Threads.empty()) {
3837
0
    if (parsedArgs.Threads.size() != 1 &&
3838
0
        !cmsysString_isdigit(parsedArgs.Threads[0])) {
3839
0
      status.SetError(cmStrCat("number of threads ", parsedArgs.Threads,
3840
0
                               " should be at least ", minThreads));
3841
0
      cmSystemTools::SetFatalErrorOccurred();
3842
0
      return false;
3843
0
    }
3844
0
    threads = std::stoi(parsedArgs.Threads);
3845
0
    if (threads < minThreads) {
3846
0
      status.SetError(cmStrCat("number of threads ", parsedArgs.Threads,
3847
0
                               " should be at least ", minThreads));
3848
0
      cmSystemTools::SetFatalErrorOccurred();
3849
0
      return false;
3850
0
    }
3851
0
  }
3852
3853
0
  if (parsedArgs.Paths.empty()) {
3854
0
    status.SetError("ARCHIVE_CREATE requires a non-empty list of PATHS");
3855
0
    cmSystemTools::SetFatalErrorOccurred();
3856
0
    return false;
3857
0
  }
3858
3859
0
  if (parsedArgs.Encoding.empty()) {
3860
0
    if (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0213) ==
3861
0
        cmPolicies::NEW) {
3862
0
      parsedArgs.Encoding = "UTF-8";
3863
0
    } else {
3864
0
      parsedArgs.Encoding = "OEM";
3865
0
    }
3866
0
  }
3867
3868
0
  if (!cmSystemTools::CreateTar(
3869
0
        parsedArgs.Output, parsedArgs.Paths, parsedArgs.WorkingDirectory,
3870
0
        compress, parsedArgs.Encoding, parsedArgs.Verbose, parsedArgs.MTime,
3871
0
        parsedArgs.Format, compressionLevel, threads)) {
3872
0
    status.SetError(cmStrCat("failed to compress: ", parsedArgs.Output));
3873
0
    cmSystemTools::SetFatalErrorOccurred();
3874
0
    return false;
3875
0
  }
3876
3877
0
  return true;
3878
0
}
3879
3880
bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
3881
                                 cmExecutionStatus& status)
3882
0
{
3883
0
  struct Arguments : public ArgumentParser::ParseResult
3884
0
  {
3885
0
    std::string Input;
3886
0
    std::string Encoding;
3887
0
    bool Verbose = false;
3888
0
    bool ListOnly = false;
3889
0
    std::string Destination;
3890
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Patterns;
3891
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PatternsExclude;
3892
0
    bool Touch = false;
3893
0
  };
3894
3895
0
  static auto const parser =
3896
0
    cmArgumentParser<Arguments>{}
3897
0
      .Bind("INPUT"_s, &Arguments::Input)
3898
0
      .Bind("ENCODING"_s, &Arguments::Encoding)
3899
0
      .Bind("VERBOSE"_s, &Arguments::Verbose)
3900
0
      .Bind("LIST_ONLY"_s, &Arguments::ListOnly)
3901
0
      .Bind("DESTINATION"_s, &Arguments::Destination)
3902
0
      .Bind("PATTERNS"_s, &Arguments::Patterns)
3903
0
      .Bind("PATTERNS_EXCLUDE"_s, &Arguments::PatternsExclude)
3904
0
      .Bind("TOUCH"_s, &Arguments::Touch);
3905
3906
0
  std::vector<std::string> unrecognizedArguments;
3907
0
  auto parsedArgs =
3908
0
    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3909
0
  auto argIt = unrecognizedArguments.begin();
3910
0
  if (argIt != unrecognizedArguments.end()) {
3911
0
    status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, '"'));
3912
0
    cmSystemTools::SetFatalErrorOccurred();
3913
0
    return false;
3914
0
  }
3915
3916
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3917
0
    cmSystemTools::SetFatalErrorOccurred();
3918
0
    return true;
3919
0
  }
3920
3921
0
  std::string inFile = parsedArgs.Input;
3922
3923
0
  if (parsedArgs.Encoding.empty()) {
3924
0
    if (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0213) ==
3925
0
        cmPolicies::NEW) {
3926
0
      parsedArgs.Encoding = "UTF-8";
3927
0
    } else {
3928
0
      parsedArgs.Encoding = "OEM";
3929
0
    }
3930
0
  }
3931
3932
0
  if (parsedArgs.ListOnly) {
3933
0
    if (!cmSystemTools::ListTar(inFile, parsedArgs.Patterns,
3934
0
                                parsedArgs.PatternsExclude,
3935
0
                                parsedArgs.Encoding, parsedArgs.Verbose)) {
3936
0
      status.SetError(cmStrCat("failed to list: ", inFile));
3937
0
      cmSystemTools::SetFatalErrorOccurred();
3938
0
      return false;
3939
0
    }
3940
0
  } else {
3941
0
    std::string destDir = status.GetMakefile().GetCurrentBinaryDirectory();
3942
0
    if (!parsedArgs.Destination.empty()) {
3943
0
      if (cmSystemTools::FileIsFullPath(parsedArgs.Destination)) {
3944
0
        destDir = parsedArgs.Destination;
3945
0
      } else {
3946
0
        destDir = cmStrCat(destDir, '/', parsedArgs.Destination);
3947
0
      }
3948
3949
0
      if (!cmSystemTools::MakeDirectory(destDir)) {
3950
0
        status.SetError(cmStrCat("failed to create directory: ", destDir));
3951
0
        cmSystemTools::SetFatalErrorOccurred();
3952
0
        return false;
3953
0
      }
3954
3955
0
      if (!cmSystemTools::FileIsFullPath(inFile)) {
3956
0
        inFile =
3957
0
          cmStrCat(cmSystemTools::GetLogicalWorkingDirectory(), '/', inFile);
3958
0
      }
3959
0
    }
3960
3961
0
    cmWorkingDirectory workdir(destDir);
3962
0
    if (workdir.Failed()) {
3963
0
      status.SetError(workdir.GetError());
3964
0
      cmSystemTools::SetFatalErrorOccurred();
3965
0
      return false;
3966
0
    }
3967
3968
0
    if (!cmSystemTools::ExtractTar(
3969
0
          inFile, parsedArgs.Patterns, parsedArgs.PatternsExclude,
3970
0
          parsedArgs.Touch ? cmSystemTools::cmTarExtractTimestamps::No
3971
0
                           : cmSystemTools::cmTarExtractTimestamps::Yes,
3972
0
          parsedArgs.Encoding, parsedArgs.Verbose)) {
3973
0
      status.SetError(cmStrCat("failed to extract:\n  ", inFile));
3974
0
      cmSystemTools::SetFatalErrorOccurred();
3975
0
      return false;
3976
0
    }
3977
0
  }
3978
3979
0
  return true;
3980
0
}
3981
3982
bool ValidateAndConvertPermissions(
3983
  cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> const&
3984
    permissions,
3985
  mode_t& perms, cmExecutionStatus& status)
3986
0
{
3987
0
  if (!permissions) {
3988
0
    return true;
3989
0
  }
3990
0
  for (auto const& i : *permissions) {
3991
0
    if (!cmFSPermissions::stringToModeT(i, perms)) {
3992
0
      status.SetError(i + " is an invalid permission specifier");
3993
0
      cmSystemTools::SetFatalErrorOccurred();
3994
0
      return false;
3995
0
    }
3996
0
  }
3997
0
  return true;
3998
0
}
3999
4000
bool SetPermissions(std::string const& filename, mode_t perms,
4001
                    cmExecutionStatus& status)
4002
0
{
4003
0
  if (!cmSystemTools::SetPermissions(filename, perms)) {
4004
0
    status.SetError("Failed to set permissions for " + filename);
4005
0
    cmSystemTools::SetFatalErrorOccurred();
4006
0
    return false;
4007
0
  }
4008
0
  return true;
4009
0
}
4010
4011
bool HandleChmodCommandImpl(std::vector<std::string> const& args, bool recurse,
4012
                            cmExecutionStatus& status)
4013
0
{
4014
0
  mode_t perms = 0;
4015
0
  mode_t fperms = 0;
4016
0
  mode_t dperms = 0;
4017
0
  cmsys::Glob globber;
4018
4019
0
  globber.SetRecurse(recurse);
4020
0
  globber.SetRecurseListDirs(recurse);
4021
4022
0
  struct Arguments : public ArgumentParser::ParseResult
4023
0
  {
4024
0
    cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
4025
0
      Permissions;
4026
0
    cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
4027
0
      FilePermissions;
4028
0
    cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
4029
0
      DirectoryPermissions;
4030
0
  };
4031
4032
0
  static auto const parser =
4033
0
    cmArgumentParser<Arguments>{}
4034
0
      .Bind("PERMISSIONS"_s, &Arguments::Permissions)
4035
0
      .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
4036
0
      .Bind("DIRECTORY_PERMISSIONS"_s, &Arguments::DirectoryPermissions);
4037
4038
0
  std::vector<std::string> pathEntries;
4039
0
  Arguments parsedArgs =
4040
0
    parser.Parse(cmMakeRange(args).advance(1), &pathEntries);
4041
4042
  // check validity of arguments
4043
0
  if (!parsedArgs.Permissions && !parsedArgs.FilePermissions &&
4044
0
      !parsedArgs.DirectoryPermissions) // no permissions given
4045
0
  {
4046
0
    status.SetError("No permissions given");
4047
0
    cmSystemTools::SetFatalErrorOccurred();
4048
0
    return false;
4049
0
  }
4050
4051
0
  if (parsedArgs.Permissions && parsedArgs.FilePermissions &&
4052
0
      parsedArgs.DirectoryPermissions) // all keywords are used
4053
0
  {
4054
0
    status.SetError("Remove either PERMISSIONS or FILE_PERMISSIONS or "
4055
0
                    "DIRECTORY_PERMISSIONS from the invocation");
4056
0
    cmSystemTools::SetFatalErrorOccurred();
4057
0
    return false;
4058
0
  }
4059
4060
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
4061
0
    cmSystemTools::SetFatalErrorOccurred();
4062
0
    return true;
4063
0
  }
4064
4065
  // validate permissions
4066
0
  bool validatePermissions =
4067
0
    ValidateAndConvertPermissions(parsedArgs.Permissions, perms, status) &&
4068
0
    ValidateAndConvertPermissions(parsedArgs.FilePermissions, fperms,
4069
0
                                  status) &&
4070
0
    ValidateAndConvertPermissions(parsedArgs.DirectoryPermissions, dperms,
4071
0
                                  status);
4072
0
  if (!validatePermissions) {
4073
0
    return false;
4074
0
  }
4075
4076
0
  std::vector<std::string> allPathEntries;
4077
4078
0
  if (recurse) {
4079
0
    std::vector<std::string> tempPathEntries;
4080
0
    for (auto const& i : pathEntries) {
4081
0
      if (cmSystemTools::FileIsDirectory(i)) {
4082
0
        globber.FindFiles(i + "/*");
4083
0
        tempPathEntries = globber.GetFiles();
4084
0
        allPathEntries.insert(allPathEntries.end(), tempPathEntries.begin(),
4085
0
                              tempPathEntries.end());
4086
0
        allPathEntries.emplace_back(i);
4087
0
      } else {
4088
0
        allPathEntries.emplace_back(i); // We validate path entries below
4089
0
      }
4090
0
    }
4091
0
  } else {
4092
0
    allPathEntries = std::move(pathEntries);
4093
0
  }
4094
4095
  // chmod
4096
0
  for (auto const& i : allPathEntries) {
4097
0
    if (!(cmSystemTools::FileExists(i) || cmSystemTools::FileIsDirectory(i))) {
4098
0
      status.SetError(cmStrCat("does not exist:\n  ", i));
4099
0
      cmSystemTools::SetFatalErrorOccurred();
4100
0
      return false;
4101
0
    }
4102
4103
0
    if (cmSystemTools::FileExists(i, true)) {
4104
0
      bool success = true;
4105
0
      mode_t filePermissions = parsedArgs.FilePermissions ? fperms : perms;
4106
0
      if (filePermissions) {
4107
0
        success = SetPermissions(i, filePermissions, status);
4108
0
      }
4109
0
      if (!success) {
4110
0
        return false;
4111
0
      }
4112
0
    }
4113
4114
0
    else if (cmSystemTools::FileIsDirectory(i)) {
4115
0
      bool success = true;
4116
0
      mode_t directoryPermissions =
4117
0
        parsedArgs.DirectoryPermissions ? dperms : perms;
4118
0
      if (directoryPermissions) {
4119
0
        success = SetPermissions(i, directoryPermissions, status);
4120
0
      }
4121
0
      if (!success) {
4122
0
        return false;
4123
0
      }
4124
0
    }
4125
0
  }
4126
4127
0
  return true;
4128
0
}
4129
4130
bool HandleChmodCommand(std::vector<std::string> const& args,
4131
                        cmExecutionStatus& status)
4132
0
{
4133
0
  return HandleChmodCommandImpl(args, false, status);
4134
0
}
4135
4136
bool HandleChmodRecurseCommand(std::vector<std::string> const& args,
4137
                               cmExecutionStatus& status)
4138
0
{
4139
0
  return HandleChmodCommandImpl(args, true, status);
4140
0
}
4141
4142
} // namespace
4143
4144
bool cmFileCommand(std::vector<std::string> const& args,
4145
                   cmExecutionStatus& status)
4146
0
{
4147
0
  if (args.empty()) {
4148
0
    status.SetError(
4149
0
      "given no arguments, but it requires at least a sub-command.");
4150
0
    return false;
4151
0
  }
4152
4153
0
  static cmSubcommandTable const subcommand{
4154
0
    { "WRITE"_s, HandleWriteCommand },
4155
0
    { "APPEND"_s, HandleAppendCommand },
4156
0
    { "DOWNLOAD"_s, HandleDownloadCommand },
4157
0
    { "UPLOAD"_s, HandleUploadCommand },
4158
0
    { "READ"_s, HandleReadCommand },
4159
0
    { "MD5"_s, HandleHashCommand },
4160
0
    { "SHA1"_s, HandleHashCommand },
4161
0
    { "SHA224"_s, HandleHashCommand },
4162
0
    { "SHA256"_s, HandleHashCommand },
4163
0
    { "SHA384"_s, HandleHashCommand },
4164
0
    { "SHA512"_s, HandleHashCommand },
4165
0
    { "SHA3_224"_s, HandleHashCommand },
4166
0
    { "SHA3_256"_s, HandleHashCommand },
4167
0
    { "SHA3_384"_s, HandleHashCommand },
4168
0
    { "SHA3_512"_s, HandleHashCommand },
4169
0
    { "STRINGS"_s, HandleStringsCommand },
4170
0
    { "GLOB"_s, HandleGlobCommand },
4171
0
    { "GLOB_RECURSE"_s, HandleGlobRecurseCommand },
4172
0
    { "MAKE_DIRECTORY"_s, HandleMakeDirectoryCommand },
4173
0
    { "RENAME"_s, HandleRename },
4174
0
    { "COPY_FILE"_s, HandleCopyFile },
4175
0
    { "REMOVE"_s, HandleRemove },
4176
0
    { "REMOVE_RECURSE"_s, HandleRemoveRecurse },
4177
0
    { "COPY"_s, HandleCopyCommand },
4178
0
    { "INSTALL"_s, HandleInstallCommand },
4179
0
    { "DIFFERENT"_s, HandleDifferentCommand },
4180
0
    { "RPATH_CHANGE"_s, HandleRPathChangeCommand },
4181
0
    { "CHRPATH"_s, HandleRPathChangeCommand },
4182
0
    { "RPATH_SET"_s, HandleRPathSetCommand },
4183
0
    { "RPATH_CHECK"_s, HandleRPathCheckCommand },
4184
0
    { "RPATH_REMOVE"_s, HandleRPathRemoveCommand },
4185
0
    { "READ_ELF"_s, HandleReadElfCommand },
4186
0
    { "READ_MACHO"_s, HandleReadMachoCommand },
4187
0
    { "REAL_PATH"_s, HandleRealPathCommand },
4188
0
    { "RELATIVE_PATH"_s, HandleRelativePathCommand },
4189
0
    { "TO_CMAKE_PATH"_s, HandleCMakePathCommand },
4190
0
    { "TO_NATIVE_PATH"_s, HandleNativePathCommand },
4191
0
    { "TOUCH"_s, HandleTouchCommand },
4192
0
    { "TOUCH_NOCREATE"_s, HandleTouchNocreateCommand },
4193
0
    { "TIMESTAMP"_s, HandleTimestampCommand },
4194
0
    { "GENERATE"_s, HandleGenerateCommand },
4195
0
    { "LOCK"_s, HandleLockCommand },
4196
0
    { "SIZE"_s, HandleSizeCommand },
4197
0
    { "READ_SYMLINK"_s, HandleReadSymlinkCommand },
4198
0
    { "CREATE_LINK"_s, HandleCreateLinkCommand },
4199
0
    { "GET_RUNTIME_DEPENDENCIES"_s, HandleGetRuntimeDependenciesCommand },
4200
0
    { "CONFIGURE"_s, HandleConfigureCommand },
4201
0
    { "ARCHIVE_CREATE"_s, HandleArchiveCreateCommand },
4202
0
    { "ARCHIVE_EXTRACT"_s, HandleArchiveExtractCommand },
4203
0
    { "CHMOD"_s, HandleChmodCommand },
4204
0
    { "CHMOD_RECURSE"_s, HandleChmodRecurseCommand },
4205
0
  };
4206
4207
0
  return subcommand(args[0], args, status);
4208
0
}