Coverage Report

Created: 2026-02-09 06:05

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 <cctype>
8
#include <cmath>
9
#include <cstdio>
10
#include <cstdlib>
11
#include <iterator>
12
#include <map>
13
#include <set>
14
#include <sstream>
15
#include <utility>
16
#include <vector>
17
18
#include <cm/memory>
19
#include <cm/optional>
20
#include <cm/string_view>
21
#include <cmext/algorithm>
22
#include <cmext/string_view>
23
24
#include <cm3p/kwiml/int.h>
25
26
#include "cmsys/FStream.hxx"
27
#include "cmsys/Glob.hxx"
28
#include "cmsys/RegularExpression.hxx"
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 "cmELF.h"
37
#include "cmExecutionStatus.h"
38
#include "cmFSPermissions.h"
39
#include "cmFileCommand_ReadMacho.h"
40
#include "cmFileCopier.h"
41
#include "cmFileInstaller.h"
42
#include "cmFileLockPool.h"
43
#include "cmFileTimes.h"
44
#include "cmGeneratedFileStream.h"
45
#include "cmGeneratorExpression.h"
46
#include "cmGlobCacheEntry.h"
47
#include "cmGlobalGenerator.h"
48
#include "cmHexFileConverter.h"
49
#include "cmList.h"
50
#include "cmListFileCache.h"
51
#include "cmMakefile.h"
52
#include "cmMessageType.h"
53
#include "cmNewLineStyle.h"
54
#include "cmPolicies.h"
55
#include "cmRange.h"
56
#include "cmRuntimeDependencyArchive.h"
57
#include "cmState.h"
58
#include "cmStringAlgorithms.h"
59
#include "cmSubcommandTable.h"
60
#include "cmSystemTools.h"
61
#include "cmTimestamp.h"
62
#include "cmValue.h"
63
#include "cmWorkingDirectory.h"
64
#include "cmake.h"
65
66
#if !defined(CMAKE_BOOTSTRAP)
67
#  include <cm3p/curl/curl.h>
68
69
#  include "cmCurl.h"
70
#  include "cmFileLockResult.h"
71
#endif
72
73
namespace {
74
75
bool HandleWriteImpl(std::vector<std::string> const& args, bool append,
76
                     cmExecutionStatus& status)
77
0
{
78
0
  if (args.size() < 2) {
79
0
    status.SetError(cmStrCat(
80
0
      args[0], " must be called with at least one additional argument."));
81
0
    return false;
82
0
  }
83
0
  auto i = args.begin();
84
85
0
  i++; // Get rid of subcommand
86
87
0
  std::string fileName = *i;
88
0
  if (!cmsys::SystemTools::FileIsFullPath(*i)) {
89
0
    fileName =
90
0
      cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', *i);
91
0
  }
92
93
0
  i++;
94
95
0
  if (!status.GetMakefile().CanIWriteThisFile(fileName)) {
96
0
    std::string e = cmStrCat("attempted to write a file: ", fileName,
97
0
                             " into a source directory.");
98
0
    status.SetError(e);
99
0
    cmSystemTools::SetFatalErrorOccurred();
100
0
    return false;
101
0
  }
102
0
  std::string dir = cmSystemTools::GetFilenamePath(fileName);
103
0
  cmSystemTools::MakeDirectory(dir);
104
105
0
  mode_t mode = 0;
106
0
  bool writable = false;
107
108
  // Set permissions to writable
109
0
  if (cmSystemTools::GetPermissions(fileName, mode)) {
110
#if defined(_MSC_VER) || defined(__MINGW32__)
111
    writable = (mode & S_IWRITE) != 0;
112
    mode_t newMode = mode | S_IWRITE;
113
#else
114
0
    writable = mode & S_IWUSR;
115
0
    mode_t newMode = mode | S_IWUSR | S_IWGRP;
116
0
#endif
117
0
    if (!writable) {
118
0
      cmSystemTools::SetPermissions(fileName, newMode);
119
0
    }
120
0
  }
121
  // If GetPermissions fails, pretend like it is ok. File open will fail if
122
  // the file is not writable
123
0
  cmsys::ofstream file(fileName.c_str(),
124
0
                       append ? std::ios::app : std::ios::out);
125
0
  if (!file) {
126
0
    std::string error =
127
0
      cmStrCat("failed to open for writing (",
128
0
               cmSystemTools::GetLastSystemError(), "):\n  ", fileName);
129
0
    status.SetError(error);
130
0
    return false;
131
0
  }
132
0
  std::string message = cmJoin(cmMakeRange(i, args.end()), std::string());
133
0
  file << message;
134
0
  if (!file) {
135
0
    std::string error =
136
0
      cmStrCat("write failed (", cmSystemTools::GetLastSystemError(), "):\n  ",
137
0
               fileName);
138
0
    status.SetError(error);
139
0
    return false;
140
0
  }
141
0
  file.close();
142
0
  if (mode && !writable) {
143
0
    cmSystemTools::SetPermissions(fileName, mode);
144
0
  }
145
0
  return true;
146
0
}
147
148
bool HandleWriteCommand(std::vector<std::string> const& args,
149
                        cmExecutionStatus& status)
150
0
{
151
0
  return HandleWriteImpl(args, false, status);
152
0
}
153
154
bool HandleAppendCommand(std::vector<std::string> const& args,
155
                         cmExecutionStatus& status)
156
0
{
157
0
  return HandleWriteImpl(args, true, status);
158
0
}
159
160
bool HandleReadCommand(std::vector<std::string> const& args,
161
                       cmExecutionStatus& status)
162
0
{
163
0
  if (args.size() < 3) {
164
0
    status.SetError("READ must be called with at least two additional "
165
0
                    "arguments");
166
0
    return false;
167
0
  }
168
169
0
  std::string const& fileNameArg = args[1];
170
0
  std::string const& variable = args[2];
171
172
0
  struct Arguments
173
0
  {
174
0
    std::string Offset;
175
0
    std::string Limit;
176
0
    bool Hex = false;
177
0
  };
178
179
0
  static auto const parser = cmArgumentParser<Arguments>{}
180
0
                               .Bind("OFFSET"_s, &Arguments::Offset)
181
0
                               .Bind("LIMIT"_s, &Arguments::Limit)
182
0
                               .Bind("HEX"_s, &Arguments::Hex);
183
184
0
  Arguments const arguments = parser.Parse(cmMakeRange(args).advance(3),
185
0
                                           /*unparsedArguments=*/nullptr);
186
187
0
  std::string fileName = fileNameArg;
188
0
  if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
189
0
    fileName = cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/',
190
0
                        fileNameArg);
191
0
  }
192
193
// Open the specified file.
194
#if defined(_WIN32) || defined(__CYGWIN__)
195
  cmsys::ifstream file(fileName.c_str(),
196
                       arguments.Hex ? (std::ios::binary | std::ios::in)
197
                                     : std::ios::in);
198
#else
199
0
  cmsys::ifstream file(fileName.c_str());
200
0
#endif
201
202
0
  if (!file) {
203
0
    std::string error =
204
0
      cmStrCat("failed to open for reading (",
205
0
               cmSystemTools::GetLastSystemError(), "):\n  ", fileName);
206
0
    status.SetError(error);
207
0
    return false;
208
0
  }
209
210
  // is there a limit?
211
0
  std::string::size_type sizeLimit = std::string::npos;
212
0
  if (!arguments.Limit.empty()) {
213
0
    unsigned long long limit;
214
0
    if (cmStrToULongLong(arguments.Limit, &limit)) {
215
0
      sizeLimit = static_cast<std::string::size_type>(limit);
216
0
    }
217
0
  }
218
219
  // is there an offset?
220
0
  cmsys::ifstream::off_type offset = 0;
221
0
  if (!arguments.Offset.empty()) {
222
0
    long long off;
223
0
    if (cmStrToLongLong(arguments.Offset, &off)) {
224
0
      offset = static_cast<cmsys::ifstream::off_type>(off);
225
0
    }
226
0
  }
227
228
0
  file.seekg(offset, std::ios::beg); // explicit ios::beg for IBM VisualAge 6
229
230
0
  std::string output;
231
232
0
  if (arguments.Hex) {
233
    // Convert part of the file into hex code
234
0
    char c;
235
0
    while ((sizeLimit > 0) && (file.get(c))) {
236
0
      char hex[4];
237
0
      snprintf(hex, sizeof(hex), "%.2x", c & 0xFFu);
238
0
      output += hex;
239
0
      sizeLimit--;
240
0
    }
241
0
  } else {
242
0
    std::string line;
243
0
    bool has_newline = false;
244
0
    while (
245
0
      sizeLimit > 0 &&
246
0
      cmSystemTools::GetLineFromStream(file, line, &has_newline, sizeLimit)) {
247
0
      sizeLimit = sizeLimit - line.size();
248
0
      if (has_newline && sizeLimit > 0) {
249
0
        sizeLimit--;
250
0
      }
251
0
      output += line;
252
0
      if (has_newline) {
253
0
        output += "\n";
254
0
      }
255
0
    }
256
0
  }
257
0
  status.GetMakefile().AddDefinition(variable, output);
258
0
  return true;
259
0
}
260
261
bool HandleHashCommand(std::vector<std::string> const& args,
262
                       cmExecutionStatus& status)
263
0
{
264
0
#if !defined(CMAKE_BOOTSTRAP)
265
0
  if (args.size() != 3) {
266
0
    status.SetError(
267
0
      cmStrCat(args[0], " requires a file name and output variable"));
268
0
    return false;
269
0
  }
270
271
0
  std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0]));
272
0
  if (hash) {
273
0
    std::string out = hash->HashFile(args[1]);
274
0
    if (!out.empty()) {
275
0
      status.GetMakefile().AddDefinition(args[2], out);
276
0
      return true;
277
0
    }
278
0
    status.SetError(cmStrCat(args[0], " failed to read file \"", args[1],
279
0
                             "\": ", cmSystemTools::GetLastSystemError()));
280
0
  }
281
0
  return false;
282
#else
283
  status.SetError(cmStrCat(args[0], " not available during bootstrap"));
284
  return false;
285
#endif
286
0
}
287
288
bool HandleStringsCommand(std::vector<std::string> const& args,
289
                          cmExecutionStatus& status)
290
0
{
291
0
  if (args.size() < 3) {
292
0
    status.SetError("STRINGS requires a file name and output variable");
293
0
    return false;
294
0
  }
295
296
  // Get the file to read.
297
0
  std::string fileName = args[1];
298
0
  if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
299
0
    fileName =
300
0
      cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
301
0
  }
302
303
  // Get the variable in which to store the results.
304
0
  std::string const& outVar = args[2];
305
306
  // Parse the options.
307
0
  enum
308
0
  {
309
0
    arg_none,
310
0
    arg_limit_input,
311
0
    arg_limit_output,
312
0
    arg_limit_count,
313
0
    arg_length_minimum,
314
0
    arg_length_maximum,
315
0
    arg_maximum,
316
0
    arg_regex,
317
0
    arg_encoding
318
0
  };
319
0
  unsigned int minlen = 0;
320
0
  unsigned int maxlen = 0;
321
0
  int limit_input = -1;
322
0
  int limit_output = -1;
323
0
  unsigned int limit_count = 0;
324
0
  cmsys::RegularExpression regex;
325
0
  bool have_regex = false;
326
0
  bool store_regex = true;
327
0
  bool newline_consume = false;
328
0
  bool hex_conversion_enabled = true;
329
0
  enum
330
0
  {
331
0
    encoding_none = cmsys::FStream::BOM_None,
332
0
    encoding_utf8 = cmsys::FStream::BOM_UTF8,
333
0
    encoding_utf16le = cmsys::FStream::BOM_UTF16LE,
334
0
    encoding_utf16be = cmsys::FStream::BOM_UTF16BE,
335
0
    encoding_utf32le = cmsys::FStream::BOM_UTF32LE,
336
0
    encoding_utf32be = cmsys::FStream::BOM_UTF32BE
337
0
  };
338
0
  int encoding = encoding_none;
339
0
  int arg_mode = arg_none;
340
0
  for (unsigned int i = 3; i < args.size(); ++i) {
341
0
    if (args[i] == "LIMIT_INPUT") {
342
0
      arg_mode = arg_limit_input;
343
0
    } else if (args[i] == "LIMIT_OUTPUT") {
344
0
      arg_mode = arg_limit_output;
345
0
    } else if (args[i] == "LIMIT_COUNT") {
346
0
      arg_mode = arg_limit_count;
347
0
    } else if (args[i] == "LENGTH_MINIMUM") {
348
0
      arg_mode = arg_length_minimum;
349
0
    } else if (args[i] == "LENGTH_MAXIMUM") {
350
0
      arg_mode = arg_length_maximum;
351
0
    } else if (args[i] == "REGEX") {
352
0
      arg_mode = arg_regex;
353
0
    } else if (args[i] == "NEWLINE_CONSUME") {
354
0
      newline_consume = true;
355
0
      arg_mode = arg_none;
356
0
    } else if (args[i] == "NO_HEX_CONVERSION") {
357
0
      hex_conversion_enabled = false;
358
0
      arg_mode = arg_none;
359
0
    } else if (args[i] == "ENCODING") {
360
0
      arg_mode = arg_encoding;
361
0
    } else if (arg_mode == arg_limit_input) {
362
0
      if (sscanf(args[i].c_str(), "%d", &limit_input) != 1 ||
363
0
          limit_input < 0) {
364
0
        status.SetError(cmStrCat("STRINGS option LIMIT_INPUT value \"",
365
0
                                 args[i], "\" is not an unsigned integer."));
366
0
        return false;
367
0
      }
368
0
      arg_mode = arg_none;
369
0
    } else if (arg_mode == arg_limit_output) {
370
0
      if (sscanf(args[i].c_str(), "%d", &limit_output) != 1 ||
371
0
          limit_output < 0) {
372
0
        status.SetError(cmStrCat("STRINGS option LIMIT_OUTPUT value \"",
373
0
                                 args[i], "\" is not an unsigned integer."));
374
0
        return false;
375
0
      }
376
0
      arg_mode = arg_none;
377
0
    } else if (arg_mode == arg_limit_count) {
378
0
      int count;
379
0
      if (sscanf(args[i].c_str(), "%d", &count) != 1 || count < 0) {
380
0
        status.SetError(cmStrCat("STRINGS option LIMIT_COUNT value \"",
381
0
                                 args[i], "\" is not an unsigned integer."));
382
0
        return false;
383
0
      }
384
0
      limit_count = count;
385
0
      arg_mode = arg_none;
386
0
    } else if (arg_mode == arg_length_minimum) {
387
0
      int len;
388
0
      if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
389
0
        status.SetError(cmStrCat("STRINGS option LENGTH_MINIMUM value \"",
390
0
                                 args[i], "\" is not an unsigned integer."));
391
0
        return false;
392
0
      }
393
0
      minlen = len;
394
0
      arg_mode = arg_none;
395
0
    } else if (arg_mode == arg_length_maximum) {
396
0
      int len;
397
0
      if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
398
0
        status.SetError(cmStrCat("STRINGS option LENGTH_MAXIMUM value \"",
399
0
                                 args[i], "\" is not an unsigned integer."));
400
0
        return false;
401
0
      }
402
0
      maxlen = len;
403
0
      arg_mode = arg_none;
404
0
    } else if (arg_mode == arg_regex) {
405
0
      if (!regex.compile(args[i])) {
406
0
        status.SetError(cmStrCat("STRINGS option REGEX value \"", args[i],
407
0
                                 "\" could not be compiled."));
408
0
        return false;
409
0
      }
410
0
      have_regex = true;
411
0
      switch (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0159)) {
412
0
        case cmPolicies::NEW:
413
          // store_regex = true
414
0
          break;
415
0
        case cmPolicies::WARN:
416
0
          if (status.GetMakefile().PolicyOptionalWarningEnabled(
417
0
                "CMAKE_POLICY_WARNING_CMP0159")) {
418
0
            status.GetMakefile().IssueMessage(
419
0
              MessageType::AUTHOR_WARNING,
420
0
              cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0159),
421
0
                       "\n"
422
0
                       "For compatibility, CMake is leaving CMAKE_MATCH_<n> "
423
0
                       "unchanged."));
424
0
          }
425
0
          CM_FALLTHROUGH;
426
0
        case cmPolicies::OLD:
427
0
          store_regex = false;
428
0
          break;
429
0
      }
430
0
      arg_mode = arg_none;
431
0
    } else if (arg_mode == arg_encoding) {
432
0
      if (args[i] == "UTF-8") {
433
0
        encoding = encoding_utf8;
434
0
      } else if (args[i] == "UTF-16LE") {
435
0
        encoding = encoding_utf16le;
436
0
      } else if (args[i] == "UTF-16BE") {
437
0
        encoding = encoding_utf16be;
438
0
      } else if (args[i] == "UTF-32LE") {
439
0
        encoding = encoding_utf32le;
440
0
      } else if (args[i] == "UTF-32BE") {
441
0
        encoding = encoding_utf32be;
442
0
      } else {
443
0
        status.SetError(cmStrCat("STRINGS option ENCODING \"", args[i],
444
0
                                 "\" not recognized."));
445
0
        return false;
446
0
      }
447
0
      arg_mode = arg_none;
448
0
    } else {
449
0
      status.SetError(
450
0
        cmStrCat("STRINGS given unknown argument \"", args[i], '"'));
451
0
      return false;
452
0
    }
453
0
  }
454
455
0
  if (hex_conversion_enabled) {
456
    // TODO: should work without temp file, but just on a memory buffer
457
0
    std::string binaryFileName =
458
0
      cmStrCat(status.GetMakefile().GetCurrentBinaryDirectory(),
459
0
               "/CMakeFiles/FileCommandStringsBinaryFile");
460
0
    if (cmHexFileConverter::TryConvert(fileName, binaryFileName)) {
461
0
      fileName = binaryFileName;
462
0
    }
463
0
  }
464
465
// Open the specified file.
466
#if defined(_WIN32) || defined(__CYGWIN__)
467
  cmsys::ifstream fin(fileName.c_str(), std::ios::in | std::ios::binary);
468
#else
469
0
  cmsys::ifstream fin(fileName.c_str());
470
0
#endif
471
0
  if (!fin) {
472
0
    status.SetError(
473
0
      cmStrCat("STRINGS file \"", fileName, "\" cannot be read."));
474
0
    return false;
475
0
  }
476
477
  // If BOM is found and encoding was not specified, use the BOM
478
0
  int bom_found = cmsys::FStream::ReadBOM(fin);
479
0
  if (encoding == encoding_none && bom_found != cmsys::FStream::BOM_None) {
480
0
    encoding = bom_found;
481
0
  }
482
483
0
  unsigned int bytes_rem = 0;
484
0
  if (encoding == encoding_utf16le || encoding == encoding_utf16be) {
485
0
    bytes_rem = 1;
486
0
  }
487
0
  if (encoding == encoding_utf32le || encoding == encoding_utf32be) {
488
0
    bytes_rem = 3;
489
0
  }
490
491
  // Parse strings out of the file.
492
0
  int output_size = 0;
493
0
  std::vector<std::string> strings;
494
0
  std::string s;
495
0
  while ((!limit_count || strings.size() < limit_count) &&
496
0
         (limit_input < 0 || static_cast<int>(fin.tellg()) < limit_input) &&
497
0
         fin) {
498
0
    std::string current_str;
499
500
0
    int c = fin.get();
501
0
    for (unsigned int i = 0; i < bytes_rem; ++i) {
502
0
      int c1 = fin.get();
503
0
      if (!fin) {
504
0
        fin.putback(static_cast<char>(c1));
505
0
        break;
506
0
      }
507
0
      c = (c << 8) | c1;
508
0
    }
509
0
    if (encoding == encoding_utf16le) {
510
0
      c = ((c & 0xFF) << 8) | ((c & 0xFF00) >> 8);
511
0
    } else if (encoding == encoding_utf32le) {
512
0
      c = (((c & 0xFF) << 24) | ((c & 0xFF00) << 8) | ((c & 0xFF0000) >> 8) |
513
0
           ((c & 0xFF000000) >> 24));
514
0
    }
515
516
0
    if (c == '\r') {
517
      // Ignore CR character to make output always have UNIX newlines.
518
0
      continue;
519
0
    }
520
521
0
    if (c >= 0 && c <= 0xFF &&
522
0
        (isprint(c) || c == '\t' || (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().IssueMessage(
735
0
          MessageType::AUTHOR_WARNING,
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().IssueMessage(
772
0
              MessageType::AUTHOR_WARNING,
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().IssueMessage(
1378
0
          MessageType::AUTHOR_WARNING,
1379
0
          cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0152),
1380
0
                   "\n"
1381
0
                   "From input path:\n  ",
1382
0
                   input, "\nthe policy OLD behavior produces path:\n  ",
1383
0
                   oldPolicyPath,
1384
0
                   "\nbut the policy NEW behavior produces path:\n  ",
1385
0
                   realPath,
1386
0
                   "\nSince the policy is not set, CMake is using the OLD "
1387
0
                   "behavior for compatibility."));
1388
0
      }
1389
0
    }
1390
0
    realPath = oldPolicyPath;
1391
0
  }
1392
1393
0
  if (!cmSystemTools::FileExists(realPath)) {
1394
0
    status.GetMakefile().IssueMessage(
1395
0
      MessageType::AUTHOR_WARNING,
1396
0
      cmStrCat("Given path:\n  ", input,
1397
0
               "\ndoes not refer to an existing path on disk."));
1398
0
  }
1399
1400
0
  status.GetMakefile().AddDefinition(args[2], realPath);
1401
1402
0
  return true;
1403
0
}
1404
1405
bool HandleRelativePathCommand(std::vector<std::string> const& args,
1406
                               cmExecutionStatus& status)
1407
0
{
1408
0
  if (args.size() != 4) {
1409
0
    status.SetError("RELATIVE_PATH called with incorrect number of arguments");
1410
0
    return false;
1411
0
  }
1412
1413
0
  std::string const& outVar = args[1];
1414
0
  std::string const& directoryName = args[2];
1415
0
  std::string const& fileName = args[3];
1416
1417
0
  if (!cmSystemTools::FileIsFullPath(directoryName)) {
1418
0
    std::string errstring =
1419
0
      "RELATIVE_PATH must be passed a full path to the directory: " +
1420
0
      directoryName;
1421
0
    status.SetError(errstring);
1422
0
    return false;
1423
0
  }
1424
0
  if (!cmSystemTools::FileIsFullPath(fileName)) {
1425
0
    std::string errstring =
1426
0
      "RELATIVE_PATH must be passed a full path to the file: " + fileName;
1427
0
    status.SetError(errstring);
1428
0
    return false;
1429
0
  }
1430
1431
0
  std::string res = cmSystemTools::RelativePath(directoryName, fileName);
1432
0
  status.GetMakefile().AddDefinition(outVar, res);
1433
0
  return true;
1434
0
}
1435
1436
bool HandleRename(std::vector<std::string> const& args,
1437
                  cmExecutionStatus& status)
1438
0
{
1439
0
  if (args.size() < 3) {
1440
0
    status.SetError("RENAME must be called with at least two additional "
1441
0
                    "arguments");
1442
0
    return false;
1443
0
  }
1444
1445
  // Compute full path for old and new names.
1446
0
  std::string oldname = args[1];
1447
0
  if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
1448
0
    oldname =
1449
0
      cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
1450
0
  }
1451
0
  std::string newname = args[2];
1452
0
  if (!cmsys::SystemTools::FileIsFullPath(newname)) {
1453
0
    newname =
1454
0
      cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
1455
0
  }
1456
1457
0
  struct Arguments
1458
0
  {
1459
0
    bool NoReplace = false;
1460
0
    std::string Result;
1461
0
  };
1462
1463
0
  static auto const parser = cmArgumentParser<Arguments>{}
1464
0
                               .Bind("NO_REPLACE"_s, &Arguments::NoReplace)
1465
0
                               .Bind("RESULT"_s, &Arguments::Result);
1466
1467
0
  std::vector<std::string> unconsumedArgs;
1468
0
  Arguments const arguments =
1469
0
    parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
1470
0
  if (!unconsumedArgs.empty()) {
1471
0
    status.SetError("RENAME unknown argument:\n  " + unconsumedArgs.front());
1472
0
    return false;
1473
0
  }
1474
1475
0
  std::string err;
1476
0
  switch (cmSystemTools::RenameFile(oldname, newname,
1477
0
                                    arguments.NoReplace
1478
0
                                      ? cmSystemTools::Replace::No
1479
0
                                      : cmSystemTools::Replace::Yes,
1480
0
                                    &err)) {
1481
0
    case cmSystemTools::RenameResult::Success:
1482
0
      if (!arguments.Result.empty()) {
1483
0
        status.GetMakefile().AddDefinition(arguments.Result, "0");
1484
0
      }
1485
0
      return true;
1486
0
    case cmSystemTools::RenameResult::NoReplace:
1487
0
      if (!arguments.Result.empty()) {
1488
0
        err = "NO_REPLACE";
1489
0
      } else {
1490
0
        err = "path not replaced";
1491
0
      }
1492
0
      CM_FALLTHROUGH;
1493
0
    case cmSystemTools::RenameResult::Failure:
1494
0
      if (!arguments.Result.empty()) {
1495
0
        status.GetMakefile().AddDefinition(arguments.Result, err);
1496
0
        return true;
1497
0
      }
1498
0
      break;
1499
0
  }
1500
0
  status.SetError(cmStrCat("RENAME failed to rename\n  ", oldname, "\nto\n  ",
1501
0
                           newname, "\nbecause: ", err, '\n'));
1502
0
  return false;
1503
0
}
1504
1505
bool HandleCopyFile(std::vector<std::string> const& args,
1506
                    cmExecutionStatus& status)
1507
0
{
1508
0
  if (args.size() < 3) {
1509
0
    status.SetError("COPY_FILE must be called with at least two additional "
1510
0
                    "arguments");
1511
0
    return false;
1512
0
  }
1513
1514
  // Compute full path for old and new names.
1515
0
  std::string oldname = args[1];
1516
0
  if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
1517
0
    oldname =
1518
0
      cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[1]);
1519
0
  }
1520
0
  std::string newname = args[2];
1521
0
  if (!cmsys::SystemTools::FileIsFullPath(newname)) {
1522
0
    newname =
1523
0
      cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', args[2]);
1524
0
  }
1525
1526
0
  struct Arguments
1527
0
  {
1528
0
    bool InputMayBeRecent = false;
1529
0
    bool OnlyIfDifferent = false;
1530
0
    std::string Result;
1531
0
  };
1532
1533
0
  static auto const parser =
1534
0
    cmArgumentParser<Arguments>{}
1535
0
      .Bind("INPUT_MAY_BE_RECENT"_s, &Arguments::InputMayBeRecent)
1536
0
      .Bind("ONLY_IF_DIFFERENT"_s, &Arguments::OnlyIfDifferent)
1537
0
      .Bind("RESULT"_s, &Arguments::Result);
1538
1539
0
  std::vector<std::string> unconsumedArgs;
1540
0
  Arguments const arguments =
1541
0
    parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
1542
0
  if (!unconsumedArgs.empty()) {
1543
0
    status.SetError("COPY_FILE unknown argument:\n  " +
1544
0
                    unconsumedArgs.front());
1545
0
    return false;
1546
0
  }
1547
1548
0
  bool result = true;
1549
0
  if (cmsys::SystemTools::FileIsDirectory(oldname)) {
1550
0
    if (!arguments.Result.empty()) {
1551
0
      status.GetMakefile().AddDefinition(arguments.Result,
1552
0
                                         "cannot copy a directory");
1553
0
    } else {
1554
0
      status.SetError(
1555
0
        cmStrCat("COPY_FILE cannot copy a directory\n  ", oldname));
1556
0
      result = false;
1557
0
    }
1558
0
    return result;
1559
0
  }
1560
0
  if (cmsys::SystemTools::FileIsDirectory(newname)) {
1561
0
    if (!arguments.Result.empty()) {
1562
0
      status.GetMakefile().AddDefinition(arguments.Result,
1563
0
                                         "cannot copy to a directory");
1564
0
    } else {
1565
0
      status.SetError(
1566
0
        cmStrCat("COPY_FILE cannot copy to a directory\n  ", newname));
1567
0
      result = false;
1568
0
    }
1569
0
    return result;
1570
0
  }
1571
1572
0
  cmSystemTools::CopyWhen when;
1573
0
  if (arguments.OnlyIfDifferent) {
1574
0
    when = cmSystemTools::CopyWhen::OnlyIfDifferent;
1575
0
  } else {
1576
0
    when = cmSystemTools::CopyWhen::Always;
1577
0
  }
1578
0
  cmSystemTools::CopyInputRecent const inputRecent = arguments.InputMayBeRecent
1579
0
    ? cmSystemTools::CopyInputRecent::Yes
1580
0
    : cmSystemTools::CopyInputRecent::No;
1581
1582
0
  std::string err;
1583
0
  if (cmSystemTools::CopySingleFile(oldname, newname, when, inputRecent,
1584
0
                                    &err) ==
1585
0
      cmSystemTools::CopyResult::Success) {
1586
0
    if (!arguments.Result.empty()) {
1587
0
      status.GetMakefile().AddDefinition(arguments.Result, "0");
1588
0
    }
1589
0
  } else {
1590
0
    if (!arguments.Result.empty()) {
1591
0
      status.GetMakefile().AddDefinition(arguments.Result, err);
1592
0
    } else {
1593
0
      status.SetError(cmStrCat("COPY_FILE failed to copy\n  ", oldname,
1594
0
                               "\nto\n  ", newname, "\nbecause: ", err, '\n'));
1595
0
      result = false;
1596
0
    }
1597
0
  }
1598
1599
0
  return result;
1600
0
}
1601
1602
bool HandleRemoveImpl(std::vector<std::string> const& args, bool recurse,
1603
                      cmExecutionStatus& status)
1604
0
{
1605
0
  for (std::string const& arg :
1606
0
       cmMakeRange(args).advance(1)) // Get rid of subcommand
1607
0
  {
1608
0
    std::string fileName = arg;
1609
0
    if (fileName.empty()) {
1610
0
      std::string r = recurse ? "REMOVE_RECURSE" : "REMOVE";
1611
0
      status.GetMakefile().IssueMessage(
1612
0
        MessageType::AUTHOR_WARNING,
1613
0
        cmStrCat("Ignoring empty file name in ", std::move(r), '.'));
1614
0
      continue;
1615
0
    }
1616
0
    if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
1617
0
      fileName =
1618
0
        cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/', arg);
1619
0
    }
1620
1621
0
    if (cmSystemTools::FileIsDirectory(fileName) &&
1622
0
        !cmSystemTools::FileIsSymlink(fileName) && recurse) {
1623
0
      cmSystemTools::RepeatedRemoveDirectory(fileName);
1624
0
    } else {
1625
0
      cmSystemTools::RemoveFile(fileName);
1626
0
    }
1627
0
  }
1628
0
  return true;
1629
0
}
1630
1631
bool HandleRemove(std::vector<std::string> const& args,
1632
                  cmExecutionStatus& status)
1633
0
{
1634
0
  return HandleRemoveImpl(args, false, status);
1635
0
}
1636
1637
bool HandleRemoveRecurse(std::vector<std::string> const& args,
1638
                         cmExecutionStatus& status)
1639
0
{
1640
0
  return HandleRemoveImpl(args, true, status);
1641
0
}
1642
1643
std::string ToNativePath(std::string const& path)
1644
0
{
1645
0
  auto const& outPath = cmSystemTools::ConvertToOutputPath(path);
1646
0
  if (outPath.size() > 1 && outPath.front() == '\"' &&
1647
0
      outPath.back() == '\"') {
1648
0
    return outPath.substr(1, outPath.size() - 2);
1649
0
  }
1650
0
  return outPath;
1651
0
}
1652
1653
std::string ToCMakePath(std::string const& path)
1654
0
{
1655
0
  auto temp = path;
1656
0
  cmSystemTools::ConvertToUnixSlashes(temp);
1657
0
  return temp;
1658
0
}
1659
1660
bool HandlePathCommand(std::vector<std::string> const& args,
1661
                       std::string (*convert)(std::string const&),
1662
                       cmExecutionStatus& status)
1663
0
{
1664
0
  if (args.size() != 3) {
1665
0
    status.SetError("FILE([TO_CMAKE_PATH|TO_NATIVE_PATH] path result) must be "
1666
0
                    "called with exactly three arguments.");
1667
0
    return false;
1668
0
  }
1669
#if defined(_WIN32) && !defined(__CYGWIN__)
1670
  char pathSep = ';';
1671
#else
1672
0
  char pathSep = ':';
1673
0
#endif
1674
0
  std::vector<std::string> path = cmSystemTools::SplitString(args[1], pathSep);
1675
1676
0
  std::string value = cmList::to_string(cmMakeRange(path).transform(convert));
1677
0
  status.GetMakefile().AddDefinition(args[2], value);
1678
0
  return true;
1679
0
}
1680
1681
bool HandleCMakePathCommand(std::vector<std::string> const& args,
1682
                            cmExecutionStatus& status)
1683
0
{
1684
0
  return HandlePathCommand(args, ToCMakePath, status);
1685
0
}
1686
1687
bool HandleNativePathCommand(std::vector<std::string> const& args,
1688
                             cmExecutionStatus& status)
1689
0
{
1690
0
  return HandlePathCommand(args, ToNativePath, status);
1691
0
}
1692
1693
#if !defined(CMAKE_BOOTSTRAP)
1694
1695
bool const TLS_VERIFY_DEFAULT = true;
1696
std::string const TLS_VERSION_DEFAULT = "1.2";
1697
1698
// Stuff for curl download/upload
1699
using cmFileCommandVectorOfChar = std::vector<char>;
1700
1701
size_t cmWriteToFileCallback(void* ptr, size_t size, size_t nmemb, void* data)
1702
0
{
1703
0
  int realsize = static_cast<int>(size * nmemb);
1704
0
  cmsys::ofstream* fout = static_cast<cmsys::ofstream*>(data);
1705
0
  if (fout) {
1706
0
    char const* chPtr = static_cast<char*>(ptr);
1707
0
    if (!fout->write(chPtr, realsize)) {
1708
0
      return CURL_WRITEFUNC_ERROR;
1709
0
    }
1710
0
  }
1711
0
  return realsize;
1712
0
}
1713
1714
size_t cmWriteToMemoryCallback(void* ptr, size_t size, size_t nmemb,
1715
                               void* data)
1716
0
{
1717
0
  int realsize = static_cast<int>(size * nmemb);
1718
0
  char const* chPtr = static_cast<char*>(ptr);
1719
0
  cm::append(*static_cast<cmFileCommandVectorOfChar*>(data), chPtr,
1720
0
             chPtr + realsize);
1721
0
  return realsize;
1722
0
}
1723
1724
int cmFileCommandCurlDebugCallback(CURL*, curl_infotype type, char* chPtr,
1725
                                   size_t size, void* data)
1726
0
{
1727
0
  cmFileCommandVectorOfChar& vec =
1728
0
    *static_cast<cmFileCommandVectorOfChar*>(data);
1729
0
  switch (type) {
1730
0
    case CURLINFO_TEXT:
1731
0
    case CURLINFO_HEADER_IN:
1732
0
    case CURLINFO_HEADER_OUT:
1733
0
      cm::append(vec, chPtr, chPtr + size);
1734
0
      break;
1735
0
    case CURLINFO_DATA_IN:
1736
0
    case CURLINFO_DATA_OUT:
1737
0
    case CURLINFO_SSL_DATA_IN:
1738
0
    case CURLINFO_SSL_DATA_OUT: {
1739
0
      char buf[128];
1740
0
      int n =
1741
0
        snprintf(buf, sizeof(buf), "[%" KWIML_INT_PRIu64 " bytes data]\n",
1742
0
                 static_cast<KWIML_INT_uint64_t>(size));
1743
0
      if (n > 0) {
1744
0
        cm::append(vec, buf, buf + n);
1745
0
      }
1746
0
    } break;
1747
0
    default:
1748
0
      break;
1749
0
  }
1750
0
  return 0;
1751
0
}
1752
1753
#  if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x072000
1754
CURLoption const CM_CURLOPT_XFERINFOFUNCTION = CURLOPT_XFERINFOFUNCTION;
1755
using cm_curl_off_t = curl_off_t;
1756
#  else
1757
CURLoption const CM_CURLOPT_XFERINFOFUNCTION = CURLOPT_PROGRESSFUNCTION;
1758
using cm_curl_off_t = double;
1759
#  endif
1760
1761
class cURLProgressHelper
1762
{
1763
public:
1764
  cURLProgressHelper(cmMakefile* mf, char const* text)
1765
0
    : Makefile(mf)
1766
0
    , Text(text)
1767
0
  {
1768
0
  }
1769
1770
  bool UpdatePercentage(cm_curl_off_t value, cm_curl_off_t total,
1771
                        std::string& status)
1772
0
  {
1773
0
    long OldPercentage = this->CurrentPercentage;
1774
1775
0
    if (total > 0) {
1776
0
      this->CurrentPercentage = std::lround(
1777
0
        static_cast<double>(value) / static_cast<double>(total) * 100.0);
1778
0
      if (this->CurrentPercentage > 100) {
1779
        // Avoid extra progress reports for unexpected data beyond total.
1780
0
        this->CurrentPercentage = 100;
1781
0
      }
1782
0
    }
1783
1784
0
    bool updated = (OldPercentage != this->CurrentPercentage);
1785
1786
0
    if (updated) {
1787
0
      status =
1788
0
        cmStrCat('[', this->Text, ' ', this->CurrentPercentage, "% complete]");
1789
0
    }
1790
1791
0
    return updated;
1792
0
  }
1793
1794
0
  cmMakefile* GetMakefile() { return this->Makefile; }
1795
1796
private:
1797
  long CurrentPercentage = -1;
1798
  cmMakefile* Makefile;
1799
  std::string Text;
1800
};
1801
1802
int cmFileDownloadProgressCallback(void* clientp, cm_curl_off_t dltotal,
1803
                                   cm_curl_off_t dlnow, cm_curl_off_t ultotal,
1804
                                   cm_curl_off_t ulnow)
1805
0
{
1806
0
  cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1807
1808
0
  static_cast<void>(ultotal);
1809
0
  static_cast<void>(ulnow);
1810
1811
0
  std::string status;
1812
0
  if (helper->UpdatePercentage(dlnow, dltotal, status)) {
1813
0
    cmMakefile* mf = helper->GetMakefile();
1814
0
    mf->DisplayStatus(status, -1);
1815
0
  }
1816
1817
0
  return 0;
1818
0
}
1819
1820
int cmFileUploadProgressCallback(void* clientp, cm_curl_off_t dltotal,
1821
                                 cm_curl_off_t dlnow, cm_curl_off_t ultotal,
1822
                                 cm_curl_off_t ulnow)
1823
0
{
1824
0
  cURLProgressHelper* helper = reinterpret_cast<cURLProgressHelper*>(clientp);
1825
1826
0
  static_cast<void>(dltotal);
1827
0
  static_cast<void>(dlnow);
1828
1829
0
  std::string status;
1830
0
  if (helper->UpdatePercentage(ulnow, ultotal, status)) {
1831
0
    cmMakefile* mf = helper->GetMakefile();
1832
0
    mf->DisplayStatus(status, -1);
1833
0
  }
1834
1835
0
  return 0;
1836
0
}
1837
1838
class cURLEasyGuard
1839
{
1840
public:
1841
  cURLEasyGuard(CURL* easy)
1842
0
    : Easy(easy)
1843
0
  {
1844
0
  }
1845
1846
  ~cURLEasyGuard()
1847
0
  {
1848
0
    if (this->Easy) {
1849
0
      ::curl_easy_cleanup(this->Easy);
1850
0
    }
1851
0
  }
1852
1853
  cURLEasyGuard(cURLEasyGuard const&) = delete;
1854
  cURLEasyGuard& operator=(cURLEasyGuard const&) = delete;
1855
1856
0
  void release() { this->Easy = nullptr; }
1857
1858
private:
1859
  ::CURL* Easy;
1860
};
1861
1862
#endif
1863
1864
#define check_curl_result(result, errstr)                                     \
1865
0
  do {                                                                        \
1866
0
    if (result != CURLE_OK) {                                                 \
1867
0
      std::string e(errstr);                                                  \
1868
0
      e += ::curl_easy_strerror(result);                                      \
1869
0
      status.SetError(e);                                                     \
1870
0
      return false;                                                           \
1871
0
    }                                                                         \
1872
0
  } while (false)
1873
1874
bool HandleDownloadCommand(std::vector<std::string> const& args,
1875
                           cmExecutionStatus& status)
1876
0
{
1877
0
#if !defined(CMAKE_BOOTSTRAP)
1878
0
  auto i = args.begin();
1879
0
  if (args.size() < 2) {
1880
0
    status.SetError("DOWNLOAD must be called with at least two arguments.");
1881
0
    return false;
1882
0
  }
1883
0
  ++i; // Get rid of subcommand
1884
0
  std::string url = *i;
1885
0
  ++i;
1886
0
  std::string file;
1887
1888
0
  long timeout = 0;
1889
0
  long inactivity_timeout = 0;
1890
0
  std::string logVar;
1891
0
  std::string statusVar;
1892
0
  cm::optional<std::string> tlsVersionOpt;
1893
0
  cm::optional<bool> tlsVerifyOpt;
1894
0
  cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
1895
0
  std::string netrc_level =
1896
0
    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
1897
0
  std::string netrc_file =
1898
0
    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
1899
0
  std::string expectedHash;
1900
0
  std::string hashMatchMSG;
1901
0
  std::unique_ptr<cmCryptoHash> hash;
1902
0
  bool showProgress = false;
1903
0
  std::string userpwd;
1904
1905
0
  std::vector<std::string> curl_headers;
1906
0
  std::vector<std::pair<std::string, cm::optional<std::string>>> curl_ranges;
1907
1908
0
  while (i != args.end()) {
1909
0
    if (*i == "TIMEOUT") {
1910
0
      ++i;
1911
0
      if (i != args.end()) {
1912
0
        timeout = atol(i->c_str());
1913
0
      } else {
1914
0
        status.SetError("DOWNLOAD missing time for TIMEOUT.");
1915
0
        return false;
1916
0
      }
1917
0
    } else if (*i == "INACTIVITY_TIMEOUT") {
1918
0
      ++i;
1919
0
      if (i != args.end()) {
1920
0
        inactivity_timeout = atol(i->c_str());
1921
0
      } else {
1922
0
        status.SetError("DOWNLOAD missing time for INACTIVITY_TIMEOUT.");
1923
0
        return false;
1924
0
      }
1925
0
    } else if (*i == "LOG") {
1926
0
      ++i;
1927
0
      if (i == args.end()) {
1928
0
        status.SetError("DOWNLOAD missing VAR for LOG.");
1929
0
        return false;
1930
0
      }
1931
0
      logVar = *i;
1932
0
    } else if (*i == "STATUS") {
1933
0
      ++i;
1934
0
      if (i == args.end()) {
1935
0
        status.SetError("DOWNLOAD missing VAR for STATUS.");
1936
0
        return false;
1937
0
      }
1938
0
      statusVar = *i;
1939
0
    } else if (*i == "TLS_VERSION") {
1940
0
      ++i;
1941
0
      if (i != args.end()) {
1942
0
        tlsVersionOpt = *i;
1943
0
      } else {
1944
0
        status.SetError("DOWNLOAD missing value for TLS_VERSION.");
1945
0
        return false;
1946
0
      }
1947
0
    } else if (*i == "TLS_VERIFY") {
1948
0
      ++i;
1949
0
      if (i != args.end()) {
1950
0
        tlsVerifyOpt = cmIsOn(*i);
1951
0
      } else {
1952
0
        status.SetError("DOWNLOAD missing bool value for TLS_VERIFY.");
1953
0
        return false;
1954
0
      }
1955
0
    } else if (*i == "TLS_CAINFO") {
1956
0
      ++i;
1957
0
      if (i != args.end()) {
1958
0
        cainfo = cmValue(*i);
1959
0
      } else {
1960
0
        status.SetError("DOWNLOAD missing file value for TLS_CAINFO.");
1961
0
        return false;
1962
0
      }
1963
0
    } else if (*i == "NETRC_FILE") {
1964
0
      ++i;
1965
0
      if (i != args.end()) {
1966
0
        netrc_file = *i;
1967
0
      } else {
1968
0
        status.SetError("DOWNLOAD missing file value for NETRC_FILE.");
1969
0
        return false;
1970
0
      }
1971
0
    } else if (*i == "NETRC") {
1972
0
      ++i;
1973
0
      if (i != args.end()) {
1974
0
        netrc_level = *i;
1975
0
      } else {
1976
0
        status.SetError("DOWNLOAD missing level value for NETRC.");
1977
0
        return false;
1978
0
      }
1979
0
    } else if (*i == "EXPECTED_MD5") {
1980
0
      ++i;
1981
0
      if (i == args.end()) {
1982
0
        status.SetError("DOWNLOAD missing sum value for EXPECTED_MD5.");
1983
0
        return false;
1984
0
      }
1985
0
      hash = cm::make_unique<cmCryptoHash>(cmCryptoHash::AlgoMD5);
1986
0
      hashMatchMSG = "MD5 sum";
1987
0
      expectedHash = cmSystemTools::LowerCase(*i);
1988
0
    } else if (*i == "SHOW_PROGRESS") {
1989
0
      showProgress = true;
1990
0
    } else if (*i == "EXPECTED_HASH") {
1991
0
      ++i;
1992
0
      if (i == args.end()) {
1993
0
        status.SetError("DOWNLOAD missing ALGO=value for EXPECTED_HASH.");
1994
0
        return false;
1995
0
      }
1996
0
      std::string::size_type pos = i->find("=");
1997
0
      if (pos == std::string::npos) {
1998
0
        std::string err =
1999
0
          cmStrCat("DOWNLOAD EXPECTED_HASH expects ALGO=value but got: ", *i);
2000
0
        status.SetError(err);
2001
0
        return false;
2002
0
      }
2003
0
      std::string algo = i->substr(0, pos);
2004
0
      expectedHash = cmSystemTools::LowerCase(i->substr(pos + 1));
2005
0
      hash = cmCryptoHash::New(algo);
2006
0
      if (!hash) {
2007
0
        std::string err =
2008
0
          cmStrCat("DOWNLOAD EXPECTED_HASH given unknown ALGO: ", algo);
2009
0
        status.SetError(err);
2010
0
        return false;
2011
0
      }
2012
0
      hashMatchMSG = algo + " hash";
2013
0
    } else if (*i == "USERPWD") {
2014
0
      ++i;
2015
0
      if (i == args.end()) {
2016
0
        status.SetError("DOWNLOAD missing string for USERPWD.");
2017
0
        return false;
2018
0
      }
2019
0
      userpwd = *i;
2020
0
    } else if (*i == "HTTPHEADER") {
2021
0
      ++i;
2022
0
      if (i == args.end()) {
2023
0
        status.SetError("DOWNLOAD missing string for HTTPHEADER.");
2024
0
        return false;
2025
0
      }
2026
0
      curl_headers.push_back(*i);
2027
0
    } else if (*i == "RANGE_START") {
2028
0
      ++i;
2029
0
      if (i == args.end()) {
2030
0
        status.SetError("DOWNLOAD missing value for RANGE_START.");
2031
0
        return false;
2032
0
      }
2033
0
      curl_ranges.emplace_back(*i, cm::nullopt);
2034
0
    } else if (*i == "RANGE_END") {
2035
0
      ++i;
2036
0
      if (curl_ranges.empty()) {
2037
0
        curl_ranges.emplace_back("0", *i);
2038
0
      } else {
2039
0
        auto& last_range = curl_ranges.back();
2040
0
        if (!last_range.second.has_value()) {
2041
0
          last_range.second = *i;
2042
0
        } else {
2043
0
          status.SetError("Multiple RANGE_END values is provided without "
2044
0
                          "the corresponding RANGE_START.");
2045
0
          return false;
2046
0
        }
2047
0
      }
2048
0
    } else if (file.empty()) {
2049
0
      file = *i;
2050
0
    } else {
2051
      // Do not return error for compatibility reason.
2052
0
      std::string err = cmStrCat("Unexpected argument: ", *i);
2053
0
      status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
2054
0
    }
2055
0
    ++i;
2056
0
  }
2057
2058
0
  if (!tlsVerifyOpt.has_value()) {
2059
0
    if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERIFY")) {
2060
0
      tlsVerifyOpt = v.IsOn();
2061
0
    }
2062
0
  }
2063
0
  if (!tlsVerifyOpt.has_value()) {
2064
0
    if (cm::optional<std::string> v =
2065
0
          cmSystemTools::GetEnvVar("CMAKE_TLS_VERIFY")) {
2066
0
      tlsVerifyOpt = cmIsOn(*v);
2067
0
    }
2068
0
  }
2069
0
  bool tlsVerifyDefaulted = false;
2070
0
  if (!tlsVerifyOpt.has_value()) {
2071
0
    tlsVerifyOpt = TLS_VERIFY_DEFAULT;
2072
0
    tlsVerifyDefaulted = true;
2073
0
  }
2074
2075
0
  if (!tlsVersionOpt.has_value()) {
2076
0
    if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERSION")) {
2077
0
      tlsVersionOpt = *v;
2078
0
    }
2079
0
  }
2080
0
  if (!tlsVersionOpt.has_value()) {
2081
0
    if (cm::optional<std::string> v =
2082
0
          cmSystemTools::GetEnvVar("CMAKE_TLS_VERSION")) {
2083
0
      tlsVersionOpt = std::move(v);
2084
0
    }
2085
0
  }
2086
0
  bool tlsVersionDefaulted = false;
2087
0
  if (!tlsVersionOpt.has_value()) {
2088
0
    tlsVersionOpt = TLS_VERSION_DEFAULT;
2089
0
    tlsVersionDefaulted = true;
2090
0
  }
2091
2092
  // Can't calculate hash if we don't save the file.
2093
  // TODO Incrementally calculate hash in the write callback as the file is
2094
  // being downloaded so this check can be relaxed.
2095
0
  if (file.empty() && hash) {
2096
0
    status.SetError("DOWNLOAD cannot calculate hash if file is not saved.");
2097
0
    return false;
2098
0
  }
2099
  // If file exists already, and caller specified an expected md5 or sha,
2100
  // and the existing file already has the expected hash, then simply
2101
  // return.
2102
  //
2103
0
  if (!file.empty() && cmSystemTools::FileExists(file) && hash.get()) {
2104
0
    std::string msg;
2105
0
    std::string actualHash = hash->HashFile(file);
2106
0
    if (actualHash == expectedHash) {
2107
0
      msg = cmStrCat("skipping download as file already exists with expected ",
2108
0
                     hashMatchMSG, '"');
2109
0
      if (!statusVar.empty()) {
2110
0
        status.GetMakefile().AddDefinition(statusVar, cmStrCat(0, ";\"", msg));
2111
0
      }
2112
0
      return true;
2113
0
    }
2114
0
  }
2115
  // Make sure parent directory exists so we can write to the file
2116
  // as we receive downloaded bits from curl...
2117
  //
2118
0
  if (!file.empty()) {
2119
0
    std::string dir = cmSystemTools::GetFilenamePath(file);
2120
0
    if (!dir.empty() && !cmSystemTools::FileExists(dir) &&
2121
0
        !cmSystemTools::MakeDirectory(dir)) {
2122
0
      std::string errstring = "DOWNLOAD error: cannot create directory '" +
2123
0
        dir +
2124
0
        "' - Specify file by full path name and verify that you "
2125
0
        "have directory creation and file write privileges.";
2126
0
      status.SetError(errstring);
2127
0
      return false;
2128
0
    }
2129
0
  }
2130
2131
0
  cmsys::ofstream fout;
2132
0
  if (!file.empty()) {
2133
0
    fout.open(file.c_str(), std::ios::binary);
2134
0
    if (!fout) {
2135
0
      status.SetError(cmStrCat("DOWNLOAD cannot open file for write\n"
2136
0
                               "  file: \"",
2137
0
                               file, '"'));
2138
0
      return false;
2139
0
    }
2140
0
  }
2141
2142
0
  url = cmCurlFixFileURL(url);
2143
2144
0
  ::CURL* curl;
2145
0
  cm_curl_global_init(CURL_GLOBAL_DEFAULT);
2146
0
  curl = cm_curl_easy_init();
2147
0
  if (!curl) {
2148
0
    status.SetError("DOWNLOAD error initializing curl.");
2149
0
    return false;
2150
0
  }
2151
2152
0
  cURLEasyGuard g_curl(curl);
2153
0
  ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
2154
0
  check_curl_result(res, "DOWNLOAD cannot set url: ");
2155
2156
  // enable HTTP ERROR parsing
2157
0
  res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
2158
0
  check_curl_result(res, "DOWNLOAD cannot set http failure option: ");
2159
2160
0
  curl_version_info_data* cv = curl_version_info(CURLVERSION_FIRST);
2161
0
  res = ::curl_easy_setopt(
2162
0
    curl, CURLOPT_USERAGENT,
2163
0
    cmStrCat("curl/", cv ? cv->version : LIBCURL_VERSION).c_str());
2164
0
  check_curl_result(res, "DOWNLOAD cannot set user agent option: ");
2165
2166
0
  res = ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToFileCallback);
2167
0
  check_curl_result(res, "DOWNLOAD cannot set write function: ");
2168
2169
0
  res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
2170
0
                           cmFileCommandCurlDebugCallback);
2171
0
  check_curl_result(res, "DOWNLOAD cannot set debug function: ");
2172
2173
0
  if (tlsVersionOpt.has_value()) {
2174
0
    if (cm::optional<int> v = cmCurlParseTLSVersion(*tlsVersionOpt)) {
2175
0
      res = ::curl_easy_setopt(curl, CURLOPT_SSLVERSION, *v);
2176
0
      if (tlsVersionDefaulted && res == CURLE_NOT_BUILT_IN) {
2177
0
        res = CURLE_OK;
2178
0
      }
2179
0
      check_curl_result(res,
2180
0
                        cmStrCat("DOWNLOAD cannot set TLS/SSL version ",
2181
0
                                 *tlsVersionOpt, ": "));
2182
0
    } else {
2183
0
      status.SetError(
2184
0
        cmStrCat("DOWNLOAD given unknown TLS/SSL version ", *tlsVersionOpt));
2185
0
      return false;
2186
0
    }
2187
0
  }
2188
2189
  // check to see if TLS verification is requested
2190
0
  if (tlsVerifyOpt.has_value() && tlsVerifyOpt.value()) {
2191
0
    res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
2192
0
    check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify on: ");
2193
0
  } else {
2194
0
    res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
2195
0
    check_curl_result(res, "DOWNLOAD cannot set TLS/SSL Verify off: ");
2196
0
  }
2197
2198
0
  for (auto const& range : curl_ranges) {
2199
0
    std::string curl_range =
2200
0
      cmStrCat(range.first, '-',
2201
0
               (range.second.has_value() ? range.second.value() : ""));
2202
0
    res = ::curl_easy_setopt(curl, CURLOPT_RANGE, curl_range.c_str());
2203
0
    check_curl_result(res, "DOWNLOAD cannot set range: ");
2204
0
  }
2205
2206
  // check to see if a CAINFO file has been specified
2207
  // command arg comes first
2208
0
  std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
2209
0
  if (!cainfo_err.empty()) {
2210
0
    status.SetError(cainfo_err);
2211
0
    return false;
2212
0
  }
2213
2214
  // check to see if netrc parameters have been specified
2215
  // local command args takes precedence over CMAKE_NETRC*
2216
0
  netrc_level = cmSystemTools::UpperCase(netrc_level);
2217
0
  std::string const& netrc_option_err =
2218
0
    cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2219
0
  if (!netrc_option_err.empty()) {
2220
0
    status.SetError(netrc_option_err);
2221
0
    return false;
2222
0
  }
2223
2224
0
  cmFileCommandVectorOfChar chunkDebug;
2225
2226
0
  res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA,
2227
0
                           file.empty() ? nullptr : &fout);
2228
0
  check_curl_result(res, "DOWNLOAD cannot set write data: ");
2229
2230
0
  res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2231
0
  check_curl_result(res, "DOWNLOAD cannot set debug data: ");
2232
2233
0
  res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2234
0
  check_curl_result(res, "DOWNLOAD cannot set follow-redirect option: ");
2235
2236
0
  if (!logVar.empty()) {
2237
0
    res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2238
0
    check_curl_result(res, "DOWNLOAD cannot set verbose: ");
2239
0
  }
2240
2241
0
  if (timeout > 0) {
2242
0
    res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2243
0
    check_curl_result(res, "DOWNLOAD cannot set timeout: ");
2244
0
  }
2245
2246
0
  if (inactivity_timeout > 0) {
2247
    // Give up if there is no progress for a long time.
2248
0
    ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2249
0
    ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2250
0
  }
2251
2252
  // Need the progress helper's scope to last through the duration of
2253
  // the curl_easy_perform call... so this object is declared at function
2254
  // scope intentionally, rather than inside the "if(showProgress)"
2255
  // block...
2256
  //
2257
0
  cURLProgressHelper helper(&status.GetMakefile(), "download");
2258
2259
0
  if (showProgress) {
2260
0
    res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2261
0
    check_curl_result(res, "DOWNLOAD cannot set noprogress value: ");
2262
2263
0
    res = ::curl_easy_setopt(curl, CM_CURLOPT_XFERINFOFUNCTION,
2264
0
                             cmFileDownloadProgressCallback);
2265
0
    check_curl_result(res, "DOWNLOAD cannot set progress function: ");
2266
2267
0
    res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2268
0
                             reinterpret_cast<void*>(&helper));
2269
0
    check_curl_result(res, "DOWNLOAD cannot set progress data: ");
2270
0
  }
2271
2272
0
  if (!userpwd.empty()) {
2273
0
    res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2274
0
    check_curl_result(res, "DOWNLOAD cannot set user password: ");
2275
0
  }
2276
2277
0
  struct curl_slist* headers = nullptr;
2278
0
  for (std::string const& h : curl_headers) {
2279
0
    headers = ::curl_slist_append(headers, h.c_str());
2280
0
  }
2281
0
  ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2282
2283
0
  res = ::curl_easy_perform(curl);
2284
2285
0
  ::curl_slist_free_all(headers);
2286
2287
  /* always cleanup */
2288
0
  g_curl.release();
2289
0
  ::curl_easy_cleanup(curl);
2290
2291
  // Explicitly close the file so we can check for write errors.
2292
0
  if (!file.empty()) {
2293
0
    fout.close();
2294
0
    if (!fout) {
2295
0
      res = CURLE_WRITE_ERROR;
2296
0
    }
2297
0
  }
2298
2299
0
  if (!statusVar.empty()) {
2300
0
    std::string m = curl_easy_strerror(res);
2301
0
    if ((res == CURLE_SSL_CONNECT_ERROR ||
2302
0
         res == CURLE_PEER_FAILED_VERIFICATION) &&
2303
0
        tlsVerifyDefaulted) {
2304
0
      m = cmStrCat(
2305
0
        std::move(m),
2306
0
        ".  If this is due to https certificate verification failure, one may "
2307
0
        "set environment variable CMAKE_TLS_VERIFY=0 to suppress it.");
2308
0
    }
2309
0
    status.GetMakefile().AddDefinition(
2310
0
      statusVar, cmStrCat(static_cast<int>(res), ";\"", std::move(m), '"'));
2311
0
  }
2312
2313
0
  ::curl_global_cleanup();
2314
2315
  // Ensure requested curl logs are returned (especially in case of failure)
2316
  //
2317
0
  if (!logVar.empty()) {
2318
0
    chunkDebug.push_back(0);
2319
0
    status.GetMakefile().AddDefinition(logVar, chunkDebug.data());
2320
0
  }
2321
2322
  // Verify MD5 sum if requested:
2323
  //
2324
0
  if (hash) {
2325
0
    if (res != CURLE_OK) {
2326
0
      status.SetError(
2327
0
        cmStrCat("DOWNLOAD cannot compute hash on failed download\n"
2328
0
                 "  from url: \"",
2329
0
                 url, "\"\n  status: [", static_cast<int>(res), ";\"",
2330
0
                 ::curl_easy_strerror(res), "\"]"));
2331
0
      return false;
2332
0
    }
2333
2334
0
    std::string actualHash = hash->HashFile(file);
2335
0
    if (actualHash.empty()) {
2336
0
      status.SetError(cmStrCat("DOWNLOAD cannot compute hash on download\n"
2337
0
                               "  for file: \"",
2338
0
                               file, '"'));
2339
0
      return false;
2340
0
    }
2341
2342
0
    if (expectedHash != actualHash) {
2343
0
      if (!statusVar.empty() && res == 0) {
2344
0
        status.GetMakefile().AddDefinition(statusVar,
2345
0
                                           "1;HASH mismatch: "
2346
0
                                           "expected: " +
2347
0
                                             expectedHash +
2348
0
                                             " actual: " + actualHash);
2349
0
      }
2350
2351
0
      status.SetError(cmStrCat("DOWNLOAD HASH mismatch\n"
2352
0
                               "  for file: \"",
2353
0
                               file,
2354
0
                               "\"\n"
2355
0
                               "    expected hash: \"",
2356
0
                               expectedHash,
2357
0
                               "\"\n"
2358
0
                               "      actual hash: \"",
2359
0
                               actualHash, "\"\n"));
2360
0
      return false;
2361
0
    }
2362
0
  }
2363
2364
0
  return true;
2365
#else
2366
  status.SetError("DOWNLOAD not supported by bootstrap cmake.");
2367
  return false;
2368
#endif
2369
0
}
2370
2371
bool HandleUploadCommand(std::vector<std::string> const& args,
2372
                         cmExecutionStatus& status)
2373
0
{
2374
0
#if !defined(CMAKE_BOOTSTRAP)
2375
0
  if (args.size() < 3) {
2376
0
    status.SetError("UPLOAD must be called with at least three arguments.");
2377
0
    return false;
2378
0
  }
2379
0
  auto i = args.begin();
2380
0
  ++i;
2381
0
  std::string filename = *i;
2382
0
  ++i;
2383
0
  std::string url = *i;
2384
0
  ++i;
2385
2386
0
  long timeout = 0;
2387
0
  long inactivity_timeout = 0;
2388
0
  std::string logVar;
2389
0
  std::string statusVar;
2390
0
  bool showProgress = false;
2391
0
  cm::optional<std::string> tlsVersionOpt;
2392
0
  cm::optional<bool> tlsVerifyOpt;
2393
0
  cmValue cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
2394
0
  std::string userpwd;
2395
0
  std::string netrc_level =
2396
0
    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
2397
0
  std::string netrc_file =
2398
0
    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
2399
2400
0
  std::vector<std::string> curl_headers;
2401
2402
0
  while (i != args.end()) {
2403
0
    if (*i == "TIMEOUT") {
2404
0
      ++i;
2405
0
      if (i != args.end()) {
2406
0
        timeout = atol(i->c_str());
2407
0
      } else {
2408
0
        status.SetError("UPLOAD missing time for TIMEOUT.");
2409
0
        return false;
2410
0
      }
2411
0
    } else if (*i == "INACTIVITY_TIMEOUT") {
2412
0
      ++i;
2413
0
      if (i != args.end()) {
2414
0
        inactivity_timeout = atol(i->c_str());
2415
0
      } else {
2416
0
        status.SetError("UPLOAD missing time for INACTIVITY_TIMEOUT.");
2417
0
        return false;
2418
0
      }
2419
0
    } else if (*i == "LOG") {
2420
0
      ++i;
2421
0
      if (i == args.end()) {
2422
0
        status.SetError("UPLOAD missing VAR for LOG.");
2423
0
        return false;
2424
0
      }
2425
0
      logVar = *i;
2426
0
    } else if (*i == "STATUS") {
2427
0
      ++i;
2428
0
      if (i == args.end()) {
2429
0
        status.SetError("UPLOAD missing VAR for STATUS.");
2430
0
        return false;
2431
0
      }
2432
0
      statusVar = *i;
2433
0
    } else if (*i == "SHOW_PROGRESS") {
2434
0
      showProgress = true;
2435
0
    } else if (*i == "TLS_VERSION") {
2436
0
      ++i;
2437
0
      if (i != args.end()) {
2438
0
        tlsVersionOpt = *i;
2439
0
      } else {
2440
0
        status.SetError("UPLOAD missing value for TLS_VERSION.");
2441
0
        return false;
2442
0
      }
2443
0
    } else if (*i == "TLS_VERIFY") {
2444
0
      ++i;
2445
0
      if (i != args.end()) {
2446
0
        tlsVerifyOpt = cmIsOn(*i);
2447
0
      } else {
2448
0
        status.SetError("UPLOAD missing bool value for TLS_VERIFY.");
2449
0
        return false;
2450
0
      }
2451
0
    } else if (*i == "TLS_CAINFO") {
2452
0
      ++i;
2453
0
      if (i != args.end()) {
2454
0
        cainfo = cmValue(*i);
2455
0
      } else {
2456
0
        status.SetError("UPLOAD missing file value for TLS_CAINFO.");
2457
0
        return false;
2458
0
      }
2459
0
    } else if (*i == "NETRC_FILE") {
2460
0
      ++i;
2461
0
      if (i != args.end()) {
2462
0
        netrc_file = *i;
2463
0
      } else {
2464
0
        status.SetError("UPLOAD missing file value for NETRC_FILE.");
2465
0
        return false;
2466
0
      }
2467
0
    } else if (*i == "NETRC") {
2468
0
      ++i;
2469
0
      if (i != args.end()) {
2470
0
        netrc_level = *i;
2471
0
      } else {
2472
0
        status.SetError("UPLOAD missing level value for NETRC.");
2473
0
        return false;
2474
0
      }
2475
0
    } else if (*i == "USERPWD") {
2476
0
      ++i;
2477
0
      if (i == args.end()) {
2478
0
        status.SetError("UPLOAD missing string for USERPWD.");
2479
0
        return false;
2480
0
      }
2481
0
      userpwd = *i;
2482
0
    } else if (*i == "HTTPHEADER") {
2483
0
      ++i;
2484
0
      if (i == args.end()) {
2485
0
        status.SetError("UPLOAD missing string for HTTPHEADER.");
2486
0
        return false;
2487
0
      }
2488
0
      curl_headers.push_back(*i);
2489
0
    } else {
2490
      // Do not return error for compatibility reason.
2491
0
      std::string err = cmStrCat("Unexpected argument: ", *i);
2492
0
      status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
2493
0
    }
2494
2495
0
    ++i;
2496
0
  }
2497
2498
0
  if (!tlsVerifyOpt.has_value()) {
2499
0
    if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERIFY")) {
2500
0
      tlsVerifyOpt = v.IsOn();
2501
0
    }
2502
0
  }
2503
0
  if (!tlsVerifyOpt.has_value()) {
2504
0
    if (cm::optional<std::string> v =
2505
0
          cmSystemTools::GetEnvVar("CMAKE_TLS_VERIFY")) {
2506
0
      tlsVerifyOpt = cmIsOn(*v);
2507
0
    }
2508
0
  }
2509
0
  bool tlsVerifyDefaulted = false;
2510
0
  if (!tlsVerifyOpt.has_value()) {
2511
0
    tlsVerifyOpt = TLS_VERIFY_DEFAULT;
2512
0
    tlsVerifyDefaulted = true;
2513
0
  }
2514
2515
0
  if (!tlsVersionOpt.has_value()) {
2516
0
    if (cmValue v = status.GetMakefile().GetDefinition("CMAKE_TLS_VERSION")) {
2517
0
      tlsVersionOpt = *v;
2518
0
    }
2519
0
  }
2520
0
  if (!tlsVersionOpt.has_value()) {
2521
0
    if (cm::optional<std::string> v =
2522
0
          cmSystemTools::GetEnvVar("CMAKE_TLS_VERSION")) {
2523
0
      tlsVersionOpt = std::move(v);
2524
0
    }
2525
0
  }
2526
0
  bool tlsVersionDefaulted = false;
2527
0
  if (!tlsVersionOpt.has_value()) {
2528
0
    tlsVersionOpt = TLS_VERSION_DEFAULT;
2529
0
    tlsVersionDefaulted = true;
2530
0
  }
2531
2532
  // Open file for reading:
2533
  //
2534
0
  FILE* fin = cmsys::SystemTools::Fopen(filename, "rb");
2535
0
  if (!fin) {
2536
0
    std::string errStr =
2537
0
      cmStrCat("UPLOAD cannot open file '", filename, "' for reading.");
2538
0
    status.SetError(errStr);
2539
0
    return false;
2540
0
  }
2541
2542
0
  unsigned long file_size = cmsys::SystemTools::FileLength(filename);
2543
2544
0
  url = cmCurlFixFileURL(url);
2545
2546
0
  ::CURL* curl;
2547
0
  cm_curl_global_init(CURL_GLOBAL_DEFAULT);
2548
0
  curl = cm_curl_easy_init();
2549
0
  if (!curl) {
2550
0
    status.SetError("UPLOAD error initializing curl.");
2551
0
    fclose(fin);
2552
0
    return false;
2553
0
  }
2554
2555
0
  cURLEasyGuard g_curl(curl);
2556
2557
  // enable HTTP ERROR parsing
2558
0
  ::CURLcode res = ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
2559
0
  check_curl_result(res, "UPLOAD cannot set fail on error flag: ");
2560
2561
  // enable uploading
2562
0
  res = ::curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
2563
0
  check_curl_result(res, "UPLOAD cannot set upload flag: ");
2564
2565
0
  res = ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
2566
0
  check_curl_result(res, "UPLOAD cannot set url: ");
2567
2568
0
  res =
2569
0
    ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cmWriteToMemoryCallback);
2570
0
  check_curl_result(res, "UPLOAD cannot set write function: ");
2571
2572
0
  res = ::curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION,
2573
0
                           cmFileCommandCurlDebugCallback);
2574
0
  check_curl_result(res, "UPLOAD cannot set debug function: ");
2575
2576
0
  if (tlsVersionOpt.has_value()) {
2577
0
    if (cm::optional<int> v = cmCurlParseTLSVersion(*tlsVersionOpt)) {
2578
0
      res = ::curl_easy_setopt(curl, CURLOPT_SSLVERSION, *v);
2579
0
      if (tlsVersionDefaulted && res == CURLE_NOT_BUILT_IN) {
2580
0
        res = CURLE_OK;
2581
0
      }
2582
0
      check_curl_result(
2583
0
        res,
2584
0
        cmStrCat("UPLOAD cannot set TLS/SSL version ", *tlsVersionOpt, ": "));
2585
0
    } else {
2586
0
      status.SetError(
2587
0
        cmStrCat("UPLOAD given unknown TLS/SSL version ", *tlsVersionOpt));
2588
0
      return false;
2589
0
    }
2590
0
  }
2591
2592
  // check to see if TLS verification is requested
2593
0
  if (tlsVerifyOpt.has_value() && tlsVerifyOpt.value()) {
2594
0
    res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
2595
0
    check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify on: ");
2596
0
  } else {
2597
0
    res = ::curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
2598
0
    check_curl_result(res, "UPLOAD cannot set TLS/SSL Verify off: ");
2599
0
  }
2600
2601
  // check to see if a CAINFO file has been specified
2602
  // command arg comes first
2603
0
  std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
2604
0
  if (!cainfo_err.empty()) {
2605
0
    status.SetError(cainfo_err);
2606
0
    return false;
2607
0
  }
2608
2609
0
  cmFileCommandVectorOfChar chunkResponse;
2610
0
  cmFileCommandVectorOfChar chunkDebug;
2611
2612
0
  res = ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunkResponse);
2613
0
  check_curl_result(res, "UPLOAD cannot set write data: ");
2614
2615
0
  res = ::curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &chunkDebug);
2616
0
  check_curl_result(res, "UPLOAD cannot set debug data: ");
2617
2618
0
  res = ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2619
0
  check_curl_result(res, "UPLOAD cannot set follow-redirect option: ");
2620
2621
0
  if (!logVar.empty()) {
2622
0
    res = ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2623
0
    check_curl_result(res, "UPLOAD cannot set verbose: ");
2624
0
  }
2625
2626
0
  if (timeout > 0) {
2627
0
    res = ::curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
2628
0
    check_curl_result(res, "UPLOAD cannot set timeout: ");
2629
0
  }
2630
2631
0
  if (inactivity_timeout > 0) {
2632
    // Give up if there is no progress for a long time.
2633
0
    ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
2634
0
    ::curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, inactivity_timeout);
2635
0
  }
2636
2637
  // Need the progress helper's scope to last through the duration of
2638
  // the curl_easy_perform call... so this object is declared at function
2639
  // scope intentionally, rather than inside the "if(showProgress)"
2640
  // block...
2641
  //
2642
0
  cURLProgressHelper helper(&status.GetMakefile(), "upload");
2643
2644
0
  if (showProgress) {
2645
0
    res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2646
0
    check_curl_result(res, "UPLOAD cannot set noprogress value: ");
2647
2648
0
    res = ::curl_easy_setopt(curl, CM_CURLOPT_XFERINFOFUNCTION,
2649
0
                             cmFileUploadProgressCallback);
2650
0
    check_curl_result(res, "UPLOAD cannot set progress function: ");
2651
2652
0
    res = ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
2653
0
                             reinterpret_cast<void*>(&helper));
2654
0
    check_curl_result(res, "UPLOAD cannot set progress data: ");
2655
0
  }
2656
2657
  // now specify which file to upload
2658
0
  res = ::curl_easy_setopt(curl, CURLOPT_INFILE, fin);
2659
0
  check_curl_result(res, "UPLOAD cannot set input file: ");
2660
2661
  // and give the size of the upload (optional)
2662
0
  res =
2663
0
    ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, static_cast<long>(file_size));
2664
0
  check_curl_result(res, "UPLOAD cannot set input file size: ");
2665
2666
0
  if (!userpwd.empty()) {
2667
0
    res = ::curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd.c_str());
2668
0
    check_curl_result(res, "UPLOAD cannot set user password: ");
2669
0
  }
2670
2671
  // check to see if netrc parameters have been specified
2672
  // local command args takes precedence over CMAKE_NETRC*
2673
0
  netrc_level = cmSystemTools::UpperCase(netrc_level);
2674
0
  std::string const& netrc_option_err =
2675
0
    cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
2676
0
  if (!netrc_option_err.empty()) {
2677
0
    status.SetError(netrc_option_err);
2678
0
    return false;
2679
0
  }
2680
2681
0
  struct curl_slist* headers = nullptr;
2682
0
  for (std::string const& h : curl_headers) {
2683
0
    headers = ::curl_slist_append(headers, h.c_str());
2684
0
  }
2685
0
  ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
2686
2687
0
  res = ::curl_easy_perform(curl);
2688
2689
0
  ::curl_slist_free_all(headers);
2690
2691
  /* always cleanup */
2692
0
  g_curl.release();
2693
0
  ::curl_easy_cleanup(curl);
2694
2695
0
  if (!statusVar.empty()) {
2696
0
    std::string m = curl_easy_strerror(res);
2697
0
    if ((res == CURLE_SSL_CONNECT_ERROR ||
2698
0
         res == CURLE_PEER_FAILED_VERIFICATION) &&
2699
0
        tlsVerifyDefaulted) {
2700
0
      m = cmStrCat(
2701
0
        std::move(m),
2702
0
        ".  If this is due to https certificate verification failure, one may "
2703
0
        "set environment variable CMAKE_TLS_VERIFY=0 to suppress it.");
2704
0
    }
2705
0
    status.GetMakefile().AddDefinition(
2706
0
      statusVar, cmStrCat(static_cast<int>(res), ";\"", std::move(m), '"'));
2707
0
  }
2708
2709
0
  ::curl_global_cleanup();
2710
2711
0
  fclose(fin);
2712
0
  fin = nullptr;
2713
2714
0
  if (!logVar.empty()) {
2715
0
    std::string log;
2716
2717
0
    if (!chunkResponse.empty()) {
2718
0
      chunkResponse.push_back(0);
2719
0
      log += "Response:\n";
2720
0
      log += chunkResponse.data();
2721
0
      log += "\n";
2722
0
    }
2723
2724
0
    if (!chunkDebug.empty()) {
2725
0
      chunkDebug.push_back(0);
2726
0
      log += "Debug:\n";
2727
0
      log += chunkDebug.data();
2728
0
      log += "\n";
2729
0
    }
2730
2731
0
    status.GetMakefile().AddDefinition(logVar, log);
2732
0
  }
2733
2734
0
  return true;
2735
#else
2736
  status.SetError("UPLOAD not supported by bootstrap cmake.");
2737
  return false;
2738
#endif
2739
0
}
2740
2741
void AddEvaluationFile(std::string const& inputName,
2742
                       std::string const& targetName,
2743
                       std::string const& outputExpr,
2744
                       std::string const& condition, bool inputIsContent,
2745
                       std::string const& newLineCharacter, mode_t permissions,
2746
                       cmExecutionStatus& status)
2747
0
{
2748
0
  cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace();
2749
2750
0
  cmGeneratorExpression outputGe(*status.GetMakefile().GetCMakeInstance(),
2751
0
                                 lfbt);
2752
0
  std::unique_ptr<cmCompiledGeneratorExpression> outputCge =
2753
0
    outputGe.Parse(outputExpr);
2754
2755
0
  cmGeneratorExpression conditionGe(*status.GetMakefile().GetCMakeInstance(),
2756
0
                                    lfbt);
2757
0
  std::unique_ptr<cmCompiledGeneratorExpression> conditionCge =
2758
0
    conditionGe.Parse(condition);
2759
2760
0
  status.GetMakefile().AddEvaluationFile(
2761
0
    inputName, targetName, std::move(outputCge), std::move(conditionCge),
2762
0
    newLineCharacter, permissions, inputIsContent);
2763
0
}
2764
2765
bool HandleGenerateCommand(std::vector<std::string> const& args,
2766
                           cmExecutionStatus& status)
2767
0
{
2768
0
  if (args.size() < 5) {
2769
0
    status.SetError("Incorrect arguments to GENERATE subcommand.");
2770
0
    return false;
2771
0
  }
2772
2773
0
  struct Arguments : public ArgumentParser::ParseResult
2774
0
  {
2775
0
    cm::optional<std::string> Output;
2776
0
    cm::optional<std::string> Input;
2777
0
    cm::optional<std::string> Content;
2778
0
    cm::optional<std::string> Condition;
2779
0
    cm::optional<std::string> Target;
2780
0
    cm::optional<std::string> NewLineStyle;
2781
0
    bool NoSourcePermissions = false;
2782
0
    bool UseSourcePermissions = false;
2783
0
    ArgumentParser::NonEmpty<std::vector<std::string>> FilePermissions;
2784
0
    std::vector<cm::string_view> ParsedKeywords;
2785
0
  };
2786
2787
0
  static auto const parser =
2788
0
    cmArgumentParser<Arguments>{}
2789
0
      .Bind("OUTPUT"_s, &Arguments::Output)
2790
0
      .Bind("INPUT"_s, &Arguments::Input)
2791
0
      .Bind("CONTENT"_s, &Arguments::Content)
2792
0
      .Bind("CONDITION"_s, &Arguments::Condition)
2793
0
      .Bind("TARGET"_s, &Arguments::Target)
2794
0
      .Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions)
2795
0
      .Bind("USE_SOURCE_PERMISSIONS"_s, &Arguments::UseSourcePermissions)
2796
0
      .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
2797
0
      .Bind("NEWLINE_STYLE"_s, &Arguments::NewLineStyle)
2798
0
      .BindParsedKeywords(&Arguments::ParsedKeywords);
2799
2800
0
  std::vector<std::string> unparsedArguments;
2801
0
  Arguments const arguments =
2802
0
    parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments);
2803
2804
0
  if (arguments.MaybeReportError(status.GetMakefile())) {
2805
0
    return true;
2806
0
  }
2807
2808
0
  if (!unparsedArguments.empty()) {
2809
0
    status.SetError("Unknown argument to GENERATE subcommand.");
2810
0
    return false;
2811
0
  }
2812
2813
0
  if (!arguments.Output || arguments.ParsedKeywords[0] != "OUTPUT"_s) {
2814
0
    status.SetError("GENERATE requires OUTPUT as first option.");
2815
0
    return false;
2816
0
  }
2817
0
  std::string const& output = *arguments.Output;
2818
2819
0
  if (!arguments.Input && !arguments.Content) {
2820
0
    status.SetError("GENERATE requires INPUT or CONTENT option.");
2821
0
    return false;
2822
0
  }
2823
0
  bool const inputIsContent = arguments.ParsedKeywords[1] == "CONTENT"_s;
2824
0
  if (!inputIsContent && arguments.ParsedKeywords[1] != "INPUT") {
2825
0
    status.SetError("Unknown argument to GENERATE subcommand.");
2826
0
    return false;
2827
0
  }
2828
0
  std::string const& input =
2829
0
    inputIsContent ? *arguments.Content : *arguments.Input;
2830
2831
0
  if (arguments.Condition && arguments.Condition->empty()) {
2832
0
    status.SetError("CONDITION of sub-command GENERATE must not be empty "
2833
0
                    "if specified.");
2834
0
    return false;
2835
0
  }
2836
0
  std::string const& condition =
2837
0
    arguments.Condition ? *arguments.Condition : std::string();
2838
2839
0
  if (arguments.Target && arguments.Target->empty()) {
2840
0
    status.SetError("TARGET of sub-command GENERATE must not be empty "
2841
0
                    "if specified.");
2842
0
    return false;
2843
0
  }
2844
0
  std::string const& target =
2845
0
    arguments.Target ? *arguments.Target : std::string();
2846
2847
0
  cmNewLineStyle newLineStyle;
2848
0
  if (arguments.NewLineStyle) {
2849
0
    std::string errorMessage;
2850
0
    if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
2851
0
      status.SetError(cmStrCat("GENERATE ", errorMessage));
2852
0
      return false;
2853
0
    }
2854
0
  }
2855
2856
0
  if (arguments.NoSourcePermissions && arguments.UseSourcePermissions) {
2857
0
    status.SetError("given both NO_SOURCE_PERMISSIONS and "
2858
0
                    "USE_SOURCE_PERMISSIONS. Only one option allowed.");
2859
0
    return false;
2860
0
  }
2861
2862
0
  if (!arguments.FilePermissions.empty()) {
2863
0
    if (arguments.NoSourcePermissions) {
2864
0
      status.SetError("given both NO_SOURCE_PERMISSIONS and "
2865
0
                      "FILE_PERMISSIONS. Only one option allowed.");
2866
0
      return false;
2867
0
    }
2868
0
    if (arguments.UseSourcePermissions) {
2869
0
      status.SetError("given both USE_SOURCE_PERMISSIONS and "
2870
0
                      "FILE_PERMISSIONS. Only one option allowed.");
2871
0
      return false;
2872
0
    }
2873
0
  }
2874
2875
0
  if (arguments.UseSourcePermissions) {
2876
0
    if (inputIsContent) {
2877
0
      status.SetError("given USE_SOURCE_PERMISSIONS without a file INPUT.");
2878
0
      return false;
2879
0
    }
2880
0
  }
2881
2882
0
  mode_t permissions = 0;
2883
0
  if (arguments.NoSourcePermissions) {
2884
0
    permissions |= cmFSPermissions::mode_owner_read;
2885
0
    permissions |= cmFSPermissions::mode_owner_write;
2886
0
    permissions |= cmFSPermissions::mode_group_read;
2887
0
    permissions |= cmFSPermissions::mode_world_read;
2888
0
  }
2889
2890
0
  if (!arguments.FilePermissions.empty()) {
2891
0
    std::vector<std::string> invalidOptions;
2892
0
    for (auto const& e : arguments.FilePermissions) {
2893
0
      if (!cmFSPermissions::stringToModeT(e, permissions)) {
2894
0
        invalidOptions.push_back(e);
2895
0
      }
2896
0
    }
2897
0
    if (!invalidOptions.empty()) {
2898
0
      std::ostringstream oss;
2899
0
      oss << "given invalid permission ";
2900
0
      for (auto i = 0u; i < invalidOptions.size(); i++) {
2901
0
        if (i == 0u) {
2902
0
          oss << "\"" << invalidOptions[i] << "\"";
2903
0
        } else {
2904
0
          oss << ",\"" << invalidOptions[i] << "\"";
2905
0
        }
2906
0
      }
2907
0
      oss << ".";
2908
0
      status.SetError(oss.str());
2909
0
      return false;
2910
0
    }
2911
0
  }
2912
2913
0
  AddEvaluationFile(input, target, output, condition, inputIsContent,
2914
0
                    newLineStyle.GetCharacters(), permissions, status);
2915
0
  return true;
2916
0
}
2917
2918
bool HandleLockCommand(std::vector<std::string> const& args,
2919
                       cmExecutionStatus& status)
2920
0
{
2921
0
#if !defined(CMAKE_BOOTSTRAP)
2922
  // Default values
2923
0
  bool directory = false;
2924
0
  bool release = false;
2925
0
  enum Guard
2926
0
  {
2927
0
    GUARD_FUNCTION,
2928
0
    GUARD_FILE,
2929
0
    GUARD_PROCESS
2930
0
  };
2931
0
  Guard guard = GUARD_PROCESS;
2932
0
  std::string resultVariable;
2933
0
  unsigned long timeout = static_cast<unsigned long>(-1);
2934
2935
  // Parse arguments
2936
0
  if (args.size() < 2) {
2937
0
    status.GetMakefile().IssueMessage(
2938
0
      MessageType::FATAL_ERROR,
2939
0
      "sub-command LOCK requires at least two arguments.");
2940
0
    return false;
2941
0
  }
2942
2943
0
  std::string path = args[1];
2944
0
  for (unsigned i = 2; i < args.size(); ++i) {
2945
0
    if (args[i] == "DIRECTORY") {
2946
0
      directory = true;
2947
0
    } else if (args[i] == "RELEASE") {
2948
0
      release = true;
2949
0
    } else if (args[i] == "GUARD") {
2950
0
      ++i;
2951
0
      char const* merr = "expected FUNCTION, FILE or PROCESS after GUARD";
2952
0
      if (i >= args.size()) {
2953
0
        status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, merr);
2954
0
        return false;
2955
0
      }
2956
0
      if (args[i] == "FUNCTION") {
2957
0
        guard = GUARD_FUNCTION;
2958
0
      } else if (args[i] == "FILE") {
2959
0
        guard = GUARD_FILE;
2960
0
      } else if (args[i] == "PROCESS") {
2961
0
        guard = GUARD_PROCESS;
2962
0
      } else {
2963
0
        status.GetMakefile().IssueMessage(
2964
0
          MessageType::FATAL_ERROR,
2965
0
          cmStrCat(merr, ", but got:\n  \"", args[i], "\"."));
2966
0
        return false;
2967
0
      }
2968
2969
0
    } else if (args[i] == "RESULT_VARIABLE") {
2970
0
      ++i;
2971
0
      if (i >= args.size()) {
2972
0
        status.GetMakefile().IssueMessage(
2973
0
          MessageType::FATAL_ERROR,
2974
0
          "expected variable name after RESULT_VARIABLE");
2975
0
        return false;
2976
0
      }
2977
0
      resultVariable = args[i];
2978
0
    } else if (args[i] == "TIMEOUT") {
2979
0
      ++i;
2980
0
      if (i >= args.size()) {
2981
0
        status.GetMakefile().IssueMessage(
2982
0
          MessageType::FATAL_ERROR, "expected timeout value after TIMEOUT");
2983
0
        return false;
2984
0
      }
2985
0
      long scanned;
2986
0
      if (!cmStrToLong(args[i], &scanned) || scanned < 0) {
2987
0
        status.GetMakefile().IssueMessage(
2988
0
          MessageType::FATAL_ERROR,
2989
0
          cmStrCat("TIMEOUT value \"", args[i],
2990
0
                   "\" is not an unsigned integer."));
2991
0
        return false;
2992
0
      }
2993
0
      timeout = static_cast<unsigned long>(scanned);
2994
0
    } else {
2995
0
      status.GetMakefile().IssueMessage(
2996
0
        MessageType::FATAL_ERROR,
2997
0
        cmStrCat("expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or "
2998
0
                 "TIMEOUT\nbut got: \"",
2999
0
                 args[i], "\"."));
3000
0
      return false;
3001
0
    }
3002
0
  }
3003
3004
0
  if (directory) {
3005
0
    path += "/cmake.lock";
3006
0
  }
3007
3008
  // Unify path (remove '//', '/../', ...)
3009
0
  path = cmSystemTools::CollapseFullPath(
3010
0
    path, status.GetMakefile().GetCurrentSourceDirectory());
3011
3012
  // Create file and directories if needed
3013
0
  std::string parentDir = cmSystemTools::GetParentDirectory(path);
3014
0
  if (!cmSystemTools::MakeDirectory(parentDir)) {
3015
0
    status.GetMakefile().IssueMessage(
3016
0
      MessageType::FATAL_ERROR,
3017
0
      cmStrCat("directory\n  \"", parentDir,
3018
0
               "\"\ncreation failed (check permissions)."));
3019
0
    cmSystemTools::SetFatalErrorOccurred();
3020
0
    return false;
3021
0
  }
3022
0
  FILE* file = cmsys::SystemTools::Fopen(path, "a");
3023
0
  if (!file) {
3024
0
    status.GetMakefile().IssueMessage(
3025
0
      MessageType::FATAL_ERROR,
3026
0
      cmStrCat("file\n  \"", path,
3027
0
               "\"\ncreation failed (check permissions)."));
3028
0
    cmSystemTools::SetFatalErrorOccurred();
3029
0
    return false;
3030
0
  }
3031
0
  fclose(file);
3032
3033
  // Actual lock/unlock
3034
0
  cmFileLockPool& lockPool =
3035
0
    status.GetMakefile().GetGlobalGenerator()->GetFileLockPool();
3036
3037
0
  cmFileLockResult fileLockResult(cmFileLockResult::MakeOk());
3038
0
  if (release) {
3039
0
    fileLockResult = lockPool.Release(path);
3040
0
  } else {
3041
0
    switch (guard) {
3042
0
      case GUARD_FUNCTION:
3043
0
        fileLockResult = lockPool.LockFunctionScope(path, timeout);
3044
0
        break;
3045
0
      case GUARD_FILE:
3046
0
        fileLockResult = lockPool.LockFileScope(path, timeout);
3047
0
        break;
3048
0
      case GUARD_PROCESS:
3049
0
        fileLockResult = lockPool.LockProcessScope(path, timeout);
3050
0
        break;
3051
0
      default:
3052
0
        cmSystemTools::SetFatalErrorOccurred();
3053
0
        return false;
3054
0
    }
3055
0
  }
3056
3057
0
  std::string const result = fileLockResult.GetOutputMessage();
3058
3059
0
  if (resultVariable.empty() && !fileLockResult.IsOk()) {
3060
0
    status.GetMakefile().IssueMessage(
3061
0
      MessageType::FATAL_ERROR,
3062
0
      cmStrCat("error locking file\n  \"", path, "\"\n", result, '.'));
3063
0
    cmSystemTools::SetFatalErrorOccurred();
3064
0
    return false;
3065
0
  }
3066
3067
0
  if (!resultVariable.empty()) {
3068
0
    status.GetMakefile().AddDefinition(resultVariable, result);
3069
0
  }
3070
3071
0
  return true;
3072
#else
3073
  static_cast<void>(args);
3074
  status.SetError("sub-command LOCK not implemented in bootstrap cmake");
3075
  return false;
3076
#endif
3077
0
}
3078
3079
bool HandleTimestampCommand(std::vector<std::string> const& args,
3080
                            cmExecutionStatus& status)
3081
0
{
3082
0
  if (args.size() < 3) {
3083
0
    status.SetError("sub-command TIMESTAMP requires at least two arguments.");
3084
0
    return false;
3085
0
  }
3086
0
  if (args.size() > 5) {
3087
0
    status.SetError("sub-command TIMESTAMP takes at most four arguments.");
3088
0
    return false;
3089
0
  }
3090
3091
0
  unsigned int argsIndex = 1;
3092
3093
0
  std::string filename = args[argsIndex++];
3094
0
  if (!cmsys::SystemTools::FileIsFullPath(filename)) {
3095
0
    filename = cmStrCat(status.GetMakefile().GetCurrentSourceDirectory(), '/',
3096
0
                        filename);
3097
0
  }
3098
3099
0
  std::string const& outputVariable = args[argsIndex++];
3100
3101
0
  std::string formatString;
3102
0
  if (args.size() > argsIndex && args[argsIndex] != "UTC") {
3103
0
    formatString = args[argsIndex++];
3104
0
  }
3105
3106
0
  bool utcFlag = false;
3107
0
  if (args.size() > argsIndex) {
3108
0
    if (args[argsIndex] == "UTC") {
3109
0
      utcFlag = true;
3110
0
    } else {
3111
0
      std::string e =
3112
0
        cmStrCat(" TIMESTAMP sub-command does not recognize option ",
3113
0
                 args[argsIndex], '.');
3114
0
      status.SetError(e);
3115
0
      return false;
3116
0
    }
3117
0
  }
3118
3119
0
  cmTimestamp timestamp;
3120
0
  std::string result =
3121
0
    timestamp.FileModificationTime(filename.c_str(), formatString, utcFlag);
3122
0
  status.GetMakefile().AddDefinition(outputVariable, result);
3123
3124
0
  return true;
3125
0
}
3126
3127
bool HandleSizeCommand(std::vector<std::string> const& args,
3128
                       cmExecutionStatus& status)
3129
0
{
3130
0
  if (args.size() != 3) {
3131
0
    status.SetError(
3132
0
      cmStrCat(args[0], " requires a file name and output variable"));
3133
0
    return false;
3134
0
  }
3135
3136
0
  unsigned int argsIndex = 1;
3137
3138
0
  std::string const& filename = args[argsIndex++];
3139
3140
0
  std::string const& outputVariable = args[argsIndex++];
3141
3142
0
  if (!cmSystemTools::FileExists(filename, true)) {
3143
0
    status.SetError(
3144
0
      cmStrCat("SIZE requested of path that is not readable:\n  ", filename));
3145
0
    return false;
3146
0
  }
3147
3148
0
  status.GetMakefile().AddDefinition(
3149
0
    outputVariable, std::to_string(cmSystemTools::FileLength(filename)));
3150
3151
0
  return true;
3152
0
}
3153
3154
bool HandleReadSymlinkCommand(std::vector<std::string> const& args,
3155
                              cmExecutionStatus& status)
3156
0
{
3157
0
  if (args.size() != 3) {
3158
0
    status.SetError(
3159
0
      cmStrCat(args[0], " requires a file name and output variable"));
3160
0
    return false;
3161
0
  }
3162
3163
0
  std::string const& filename = args[1];
3164
0
  std::string const& outputVariable = args[2];
3165
3166
0
  std::string result;
3167
0
  if (!cmSystemTools::ReadSymlink(filename, result)) {
3168
0
    status.SetError(cmStrCat(
3169
0
      "READ_SYMLINK requested of path that is not a symlink:\n  ", filename));
3170
0
    return false;
3171
0
  }
3172
3173
0
  status.GetMakefile().AddDefinition(outputVariable, result);
3174
3175
0
  return true;
3176
0
}
3177
3178
bool HandleCreateLinkCommand(std::vector<std::string> const& args,
3179
                             cmExecutionStatus& status)
3180
0
{
3181
0
  if (args.size() < 3) {
3182
0
    status.SetError("CREATE_LINK must be called with at least two additional "
3183
0
                    "arguments");
3184
0
    return false;
3185
0
  }
3186
3187
0
  std::string const& fileName = args[1];
3188
0
  std::string const& newFileName = args[2];
3189
3190
0
  struct Arguments
3191
0
  {
3192
0
    std::string Result;
3193
0
    bool CopyOnError = false;
3194
0
    bool Symbolic = false;
3195
0
  };
3196
3197
0
  static auto const parser =
3198
0
    cmArgumentParser<Arguments>{}
3199
0
      .Bind("RESULT"_s, &Arguments::Result)
3200
0
      .Bind("COPY_ON_ERROR"_s, &Arguments::CopyOnError)
3201
0
      .Bind("SYMBOLIC"_s, &Arguments::Symbolic);
3202
3203
0
  std::vector<std::string> unconsumedArgs;
3204
0
  Arguments const arguments =
3205
0
    parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
3206
3207
0
  if (!unconsumedArgs.empty()) {
3208
0
    status.SetError(cmStrCat("unknown argument: \"",
3209
0
                             std::move(unconsumedArgs.front()), '\"'));
3210
0
    return false;
3211
0
  }
3212
3213
  // The system error message generated in the operation.
3214
0
  std::string result;
3215
3216
  // Check if the paths are distinct.
3217
0
  if (fileName == newFileName) {
3218
0
    result = "CREATE_LINK cannot use same file and newfile";
3219
0
    if (!arguments.Result.empty()) {
3220
0
      status.GetMakefile().AddDefinition(arguments.Result, result);
3221
0
      return true;
3222
0
    }
3223
0
    status.SetError(result);
3224
0
    return false;
3225
0
  }
3226
3227
0
  cmPolicies::PolicyStatus const cmp0205 =
3228
0
    status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0205);
3229
3230
  // Hard link requires original file to exist.
3231
0
  if (!arguments.Symbolic &&
3232
0
      (!cmSystemTools::PathExists(fileName) ||
3233
0
       (cmp0205 != cmPolicies::NEW && !cmSystemTools::FileExists(fileName)))) {
3234
0
    result =
3235
0
      cmStrCat("Cannot hard link \'", fileName, "\' as it does not exist.");
3236
0
    if (!arguments.Result.empty()) {
3237
0
      status.GetMakefile().AddDefinition(arguments.Result, result);
3238
0
      return true;
3239
0
    }
3240
0
    status.SetError(result);
3241
0
    return false;
3242
0
  }
3243
3244
  // Check if the new file already exists and remove it.
3245
0
  if (cmSystemTools::PathExists(newFileName)) {
3246
0
    cmsys::Status rmStatus;
3247
0
    if (cmp0205 == cmPolicies::NEW &&
3248
0
        cmSystemTools::FileIsDirectory(newFileName)) {
3249
0
      rmStatus = cmSystemTools::RepeatedRemoveDirectory(newFileName);
3250
0
    } else {
3251
0
      rmStatus = cmSystemTools::RemoveFile(newFileName);
3252
0
    }
3253
0
    if (!rmStatus) {
3254
0
      std::string err = cmStrCat("Failed to create link '", newFileName,
3255
0
                                 "' because existing path cannot be removed: ",
3256
0
                                 rmStatus.GetString(), '\n');
3257
3258
0
      if (!arguments.Result.empty()) {
3259
0
        status.GetMakefile().AddDefinition(arguments.Result, err);
3260
0
        return true;
3261
0
      }
3262
0
      status.SetError(err);
3263
0
      return false;
3264
0
    }
3265
0
  }
3266
3267
0
  bool const sourceIsDirectory = cmSystemTools::FileIsDirectory(fileName);
3268
3269
  // Whether the operation completed successfully.
3270
0
  bool completed = false;
3271
3272
  // Check if the command requires a symbolic link.
3273
0
  if (arguments.Symbolic) {
3274
0
    cmsys::Status linked =
3275
0
      cmSystemTools::CreateSymlinkQuietly(fileName, newFileName);
3276
0
    if (linked) {
3277
0
      completed = true;
3278
0
    } else {
3279
0
      result = cmStrCat("failed to create symbolic link '", newFileName,
3280
0
                        "': ", linked.GetString());
3281
0
    }
3282
0
  } else {
3283
0
    bool needToTry = true;
3284
0
    if (sourceIsDirectory) {
3285
0
      if (cmp0205 == cmPolicies::NEW) {
3286
0
        needToTry = false;
3287
0
      } else if (cmp0205 == cmPolicies::WARN) {
3288
0
        status.GetMakefile().IssueMessage(
3289
0
          MessageType::AUTHOR_WARNING,
3290
0
          cmStrCat("Path\n  ", fileName,
3291
0
                   "\nis directory. Hardlinks creation is not supported for "
3292
0
                   "directories.\n",
3293
0
                   cmPolicies::GetPolicyWarning(cmPolicies::CMP0205)));
3294
0
      }
3295
0
    }
3296
3297
0
    if (needToTry) {
3298
0
      cmsys::Status linked =
3299
0
        cmSystemTools::CreateLinkQuietly(fileName, newFileName);
3300
0
      if (linked) {
3301
0
        completed = true;
3302
0
      } else {
3303
0
        result = cmStrCat("failed to create link '", newFileName,
3304
0
                          "': ", linked.GetString());
3305
0
      }
3306
0
    } else {
3307
0
      result =
3308
0
        cmStrCat("failed to create link '", newFileName, "': not supported");
3309
0
    }
3310
0
  }
3311
3312
0
  if (arguments.CopyOnError && cmp0205 == cmPolicies::WARN &&
3313
0
      sourceIsDirectory) {
3314
0
    status.GetMakefile().IssueMessage(
3315
0
      MessageType::AUTHOR_WARNING,
3316
0
      cmStrCat("Path\n  ", fileName,
3317
0
               "\nis directory. It will be copied recursively when NEW policy "
3318
0
               "behavior applies for CMP0205.\n",
3319
0
               cmPolicies::GetPolicyWarning(cmPolicies::CMP0205)));
3320
0
  }
3321
3322
  // Check if copy-on-error is enabled in the arguments.
3323
0
  if (!completed && arguments.CopyOnError) {
3324
0
    cmsys::Status copied;
3325
0
    if (cmp0205 == cmPolicies::NEW && sourceIsDirectory) {
3326
0
      copied = cmsys::SystemTools::CopyADirectory(fileName, newFileName);
3327
0
    } else {
3328
0
      copied = cmsys::SystemTools::CopyFileAlways(fileName, newFileName);
3329
0
    }
3330
3331
0
    if (copied) {
3332
0
      completed = true;
3333
0
    } else {
3334
0
      result = "Copy failed: " + copied.GetString();
3335
0
    }
3336
0
  }
3337
3338
  // Check if the operation was successful.
3339
0
  if (completed) {
3340
0
    result = "0";
3341
0
  } else if (arguments.Result.empty()) {
3342
    // The operation failed and the result is not reported in a variable.
3343
0
    status.SetError(result);
3344
0
    return false;
3345
0
  }
3346
3347
0
  if (!arguments.Result.empty()) {
3348
0
    status.GetMakefile().AddDefinition(arguments.Result, result);
3349
0
  }
3350
3351
0
  return true;
3352
0
}
3353
3354
bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
3355
                                         cmExecutionStatus& status)
3356
0
{
3357
0
  std::string platform =
3358
0
    status.GetMakefile().GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
3359
0
  if (!cmRuntimeDependencyArchive::PlatformSupportsRuntimeDependencies(
3360
0
        platform)) {
3361
0
    status.SetError(
3362
0
      cmStrCat("GET_RUNTIME_DEPENDENCIES is not supported on system \"",
3363
0
               platform, '"'));
3364
0
    cmSystemTools::SetFatalErrorOccurred();
3365
0
    return false;
3366
0
  }
3367
3368
0
  if (status.GetMakefile().GetState()->GetRole() == cmState::Role::Project) {
3369
0
    status.GetMakefile().IssueMessage(
3370
0
      MessageType::AUTHOR_WARNING,
3371
0
      "You have used file(GET_RUNTIME_DEPENDENCIES)"
3372
0
      " in project mode. This is probably not what "
3373
0
      "you intended to do. Instead, please consider"
3374
0
      " using it in an install(CODE) or "
3375
0
      "install(SCRIPT) command. For example:"
3376
0
      "\n  install(CODE [["
3377
0
      "\n    file(GET_RUNTIME_DEPENDENCIES"
3378
0
      "\n      # ..."
3379
0
      "\n      )"
3380
0
      "\n    ]])");
3381
0
  }
3382
3383
0
  struct Arguments : public ArgumentParser::ParseResult
3384
0
  {
3385
0
    std::string ResolvedDependenciesVar;
3386
0
    std::string UnresolvedDependenciesVar;
3387
0
    std::string ConflictingDependenciesPrefix;
3388
0
    std::string RPathPrefix;
3389
0
    std::string BundleExecutable;
3390
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Executables;
3391
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Libraries;
3392
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories;
3393
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Modules;
3394
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PreIncludeRegexes;
3395
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PreExcludeRegexes;
3396
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeRegexes;
3397
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeRegexes;
3398
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PostIncludeFiles;
3399
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> PostExcludeFiles;
3400
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>>
3401
0
      PostExcludeFilesStrict;
3402
0
  };
3403
3404
0
  static auto const parser =
3405
0
    cmArgumentParser<Arguments>{}
3406
0
      .Bind("RESOLVED_DEPENDENCIES_VAR"_s, &Arguments::ResolvedDependenciesVar)
3407
0
      .Bind("UNRESOLVED_DEPENDENCIES_VAR"_s,
3408
0
            &Arguments::UnresolvedDependenciesVar)
3409
0
      .Bind("CONFLICTING_DEPENDENCIES_PREFIX"_s,
3410
0
            &Arguments::ConflictingDependenciesPrefix)
3411
0
      .Bind("RPATH_PREFIX"_s, &Arguments::RPathPrefix)
3412
0
      .Bind("BUNDLE_EXECUTABLE"_s, &Arguments::BundleExecutable)
3413
0
      .Bind("EXECUTABLES"_s, &Arguments::Executables)
3414
0
      .Bind("LIBRARIES"_s, &Arguments::Libraries)
3415
0
      .Bind("MODULES"_s, &Arguments::Modules)
3416
0
      .Bind("DIRECTORIES"_s, &Arguments::Directories)
3417
0
      .Bind("PRE_INCLUDE_REGEXES"_s, &Arguments::PreIncludeRegexes)
3418
0
      .Bind("PRE_EXCLUDE_REGEXES"_s, &Arguments::PreExcludeRegexes)
3419
0
      .Bind("POST_INCLUDE_REGEXES"_s, &Arguments::PostIncludeRegexes)
3420
0
      .Bind("POST_EXCLUDE_REGEXES"_s, &Arguments::PostExcludeRegexes)
3421
0
      .Bind("POST_INCLUDE_FILES"_s, &Arguments::PostIncludeFiles)
3422
0
      .Bind("POST_EXCLUDE_FILES"_s, &Arguments::PostExcludeFiles)
3423
0
      .Bind("POST_EXCLUDE_FILES_STRICT"_s, &Arguments::PostExcludeFilesStrict);
3424
3425
0
  std::vector<std::string> unrecognizedArguments;
3426
0
  auto parsedArgs =
3427
0
    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3428
0
  auto argIt = unrecognizedArguments.begin();
3429
0
  if (argIt != unrecognizedArguments.end()) {
3430
0
    status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, '"'));
3431
0
    cmSystemTools::SetFatalErrorOccurred();
3432
0
    return false;
3433
0
  }
3434
3435
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3436
0
    cmSystemTools::SetFatalErrorOccurred();
3437
0
    return true;
3438
0
  }
3439
3440
0
  cmRuntimeDependencyArchive archive(
3441
0
    status, parsedArgs.Directories, parsedArgs.BundleExecutable,
3442
0
    parsedArgs.PreIncludeRegexes, parsedArgs.PreExcludeRegexes,
3443
0
    parsedArgs.PostIncludeRegexes, parsedArgs.PostExcludeRegexes,
3444
0
    std::move(parsedArgs.PostIncludeFiles),
3445
0
    std::move(parsedArgs.PostExcludeFiles),
3446
0
    std::move(parsedArgs.PostExcludeFilesStrict));
3447
0
  if (!archive.Prepare()) {
3448
0
    cmSystemTools::SetFatalErrorOccurred();
3449
0
    return false;
3450
0
  }
3451
3452
0
  if (!archive.GetRuntimeDependencies(
3453
0
        parsedArgs.Executables, parsedArgs.Libraries, parsedArgs.Modules)) {
3454
0
    cmSystemTools::SetFatalErrorOccurred();
3455
0
    return false;
3456
0
  }
3457
3458
0
  std::vector<std::string> deps;
3459
0
  std::vector<std::string> unresolvedDeps;
3460
0
  std::vector<std::string> conflictingDeps;
3461
0
  for (auto const& val : archive.GetResolvedPaths()) {
3462
0
    bool unique = true;
3463
0
    auto it = val.second.begin();
3464
0
    assert(it != val.second.end());
3465
0
    auto const& firstPath = *it;
3466
0
    while (++it != val.second.end()) {
3467
0
      if (!cmSystemTools::SameFile(firstPath, *it)) {
3468
0
        unique = false;
3469
0
        break;
3470
0
      }
3471
0
    }
3472
3473
0
    if (unique) {
3474
0
      deps.push_back(firstPath);
3475
0
      if (!parsedArgs.RPathPrefix.empty()) {
3476
0
        status.GetMakefile().AddDefinition(
3477
0
          cmStrCat(parsedArgs.RPathPrefix, '_', firstPath),
3478
0
          cmList::to_string(archive.GetRPaths().at(firstPath)));
3479
0
      }
3480
0
    } else if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
3481
0
      conflictingDeps.push_back(val.first);
3482
0
      std::vector<std::string> paths;
3483
0
      paths.insert(paths.begin(), val.second.begin(), val.second.end());
3484
0
      std::string varName =
3485
0
        cmStrCat(parsedArgs.ConflictingDependenciesPrefix, '_', val.first);
3486
0
      std::string pathsStr = cmList::to_string(paths);
3487
0
      status.GetMakefile().AddDefinition(varName, pathsStr);
3488
0
    } else {
3489
0
      std::ostringstream e;
3490
0
      e << "Multiple conflicting paths found for " << val.first << ":";
3491
0
      for (auto const& path : val.second) {
3492
0
        e << "\n  " << path;
3493
0
      }
3494
0
      status.SetError(e.str());
3495
0
      cmSystemTools::SetFatalErrorOccurred();
3496
0
      return false;
3497
0
    }
3498
0
  }
3499
0
  if (!archive.GetUnresolvedPaths().empty()) {
3500
0
    if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
3501
0
      unresolvedDeps.insert(unresolvedDeps.begin(),
3502
0
                            archive.GetUnresolvedPaths().begin(),
3503
0
                            archive.GetUnresolvedPaths().end());
3504
0
    } else {
3505
0
      std::ostringstream e;
3506
0
      e << "Could not resolve runtime dependencies:";
3507
0
      for (auto const& path : archive.GetUnresolvedPaths()) {
3508
0
        e << "\n  " << path;
3509
0
      }
3510
0
      status.SetError(e.str());
3511
0
      cmSystemTools::SetFatalErrorOccurred();
3512
0
      return false;
3513
0
    }
3514
0
  }
3515
3516
0
  if (!parsedArgs.ResolvedDependenciesVar.empty()) {
3517
0
    std::string val = cmList::to_string(deps);
3518
0
    status.GetMakefile().AddDefinition(parsedArgs.ResolvedDependenciesVar,
3519
0
                                       val);
3520
0
  }
3521
0
  if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
3522
0
    std::string val = cmList::to_string(unresolvedDeps);
3523
0
    status.GetMakefile().AddDefinition(parsedArgs.UnresolvedDependenciesVar,
3524
0
                                       val);
3525
0
  }
3526
0
  if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
3527
0
    std::string val = cmList::to_string(conflictingDeps);
3528
0
    status.GetMakefile().AddDefinition(
3529
0
      parsedArgs.ConflictingDependenciesPrefix + "_FILENAMES", val);
3530
0
  }
3531
0
  return true;
3532
0
}
3533
3534
bool HandleConfigureCommand(std::vector<std::string> const& args,
3535
                            cmExecutionStatus& status)
3536
0
{
3537
0
  struct Arguments : public ArgumentParser::ParseResult
3538
0
  {
3539
0
    cm::optional<std::string> Output;
3540
0
    cm::optional<std::string> Content;
3541
0
    bool EscapeQuotes = false;
3542
0
    bool AtOnly = false;
3543
    // "NEWLINE_STYLE" requires one value, but we use a custom check below.
3544
0
    ArgumentParser::Maybe<std::string> NewlineStyle;
3545
0
  };
3546
3547
0
  static auto const parser =
3548
0
    cmArgumentParser<Arguments>{}
3549
0
      .Bind("OUTPUT"_s, &Arguments::Output)
3550
0
      .Bind("CONTENT"_s, &Arguments::Content)
3551
0
      .Bind("ESCAPE_QUOTES"_s, &Arguments::EscapeQuotes)
3552
0
      .Bind("@ONLY"_s, &Arguments::AtOnly)
3553
0
      .Bind("NEWLINE_STYLE"_s, &Arguments::NewlineStyle);
3554
3555
0
  std::vector<std::string> unrecognizedArguments;
3556
0
  auto parsedArgs =
3557
0
    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3558
3559
0
  auto argIt = unrecognizedArguments.begin();
3560
0
  if (argIt != unrecognizedArguments.end()) {
3561
0
    status.SetError(
3562
0
      cmStrCat("CONFIGURE Unrecognized argument: \"", *argIt, '"'));
3563
0
    cmSystemTools::SetFatalErrorOccurred();
3564
0
    return false;
3565
0
  }
3566
3567
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3568
0
    cmSystemTools::SetFatalErrorOccurred();
3569
0
    return true;
3570
0
  }
3571
3572
0
  if (!parsedArgs.Output) {
3573
0
    status.SetError("CONFIGURE OUTPUT option is mandatory.");
3574
0
    cmSystemTools::SetFatalErrorOccurred();
3575
0
    return false;
3576
0
  }
3577
0
  if (!parsedArgs.Content) {
3578
0
    status.SetError("CONFIGURE CONTENT option is mandatory.");
3579
0
    cmSystemTools::SetFatalErrorOccurred();
3580
0
    return false;
3581
0
  }
3582
3583
0
  std::string errorMessage;
3584
0
  cmNewLineStyle newLineStyle;
3585
0
  if (!newLineStyle.ReadFromArguments(args, errorMessage)) {
3586
0
    status.SetError(cmStrCat("CONFIGURE ", errorMessage));
3587
0
    return false;
3588
0
  }
3589
3590
  // Check for generator expressions
3591
0
  std::string outputFile = cmSystemTools::CollapseFullPath(
3592
0
    *parsedArgs.Output, status.GetMakefile().GetCurrentBinaryDirectory());
3593
3594
0
  std::string::size_type pos = outputFile.find_first_of("<>");
3595
0
  if (pos != std::string::npos) {
3596
0
    status.SetError(cmStrCat("CONFIGURE called with OUTPUT containing a \"",
3597
0
                             outputFile[pos],
3598
0
                             "\".  This character is not allowed."));
3599
0
    return false;
3600
0
  }
3601
3602
0
  cmMakefile& makeFile = status.GetMakefile();
3603
0
  if (!makeFile.CanIWriteThisFile(outputFile)) {
3604
0
    cmSystemTools::Error("Attempt to write file: " + outputFile +
3605
0
                         " into a source directory.");
3606
0
    return false;
3607
0
  }
3608
3609
0
  cmSystemTools::ConvertToUnixSlashes(outputFile);
3610
3611
  // Re-generate if non-temporary outputs are missing.
3612
  // when we finalize the configuration we will remove all
3613
  // output files that now don't exist.
3614
0
  makeFile.AddCMakeOutputFile(outputFile);
3615
3616
  // Create output directory
3617
0
  std::string::size_type const slashPos = outputFile.rfind('/');
3618
0
  if (slashPos != std::string::npos) {
3619
0
    std::string const path = outputFile.substr(0, slashPos);
3620
0
    cmSystemTools::MakeDirectory(path);
3621
0
  }
3622
3623
0
  std::string newLineCharacters = "\n";
3624
0
  bool open_with_binary_flag = false;
3625
0
  if (newLineStyle.IsValid()) {
3626
0
    newLineCharacters = newLineStyle.GetCharacters();
3627
0
    open_with_binary_flag = true;
3628
0
  }
3629
3630
0
  cmGeneratedFileStream fout;
3631
0
  fout.Open(outputFile, false, open_with_binary_flag);
3632
0
  if (!fout) {
3633
0
    cmSystemTools::Error("Could not open file for write in copy operation " +
3634
0
                         outputFile);
3635
0
    cmSystemTools::ReportLastSystemError("");
3636
0
    return false;
3637
0
  }
3638
0
  fout.SetCopyIfDifferent(true);
3639
3640
  // copy input to output and expand variables from input at the same time
3641
0
  std::stringstream sin(*parsedArgs.Content, std::ios::in);
3642
0
  std::string inLine;
3643
0
  std::string outLine;
3644
0
  bool hasNewLine = false;
3645
0
  while (cmSystemTools::GetLineFromStream(sin, inLine, &hasNewLine)) {
3646
0
    outLine.clear();
3647
0
    makeFile.ConfigureString(inLine, outLine, parsedArgs.AtOnly,
3648
0
                             parsedArgs.EscapeQuotes);
3649
0
    fout << outLine;
3650
0
    if (hasNewLine || newLineStyle.IsValid()) {
3651
0
      fout << newLineCharacters;
3652
0
    }
3653
0
  }
3654
3655
  // close file before attempting to copy
3656
0
  fout.close();
3657
3658
0
  return true;
3659
0
}
3660
3661
bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
3662
                                cmExecutionStatus& status)
3663
0
{
3664
0
  struct Arguments : public ArgumentParser::ParseResult
3665
0
  {
3666
0
    std::string Output;
3667
0
    std::string Format;
3668
0
    std::string Compression;
3669
0
    std::string CompressionLevel;
3670
    // "MTIME" should require one value, but it has long been accidentally
3671
    // accepted without one and treated as if an empty value were given.
3672
    // Fixing this would require a policy.
3673
0
    ArgumentParser::Maybe<std::string> MTime;
3674
0
    std::string Threads;
3675
0
    std::string WorkingDirectory;
3676
0
    bool Verbose = false;
3677
    // "PATHS" requires at least one value, but use a custom check below.
3678
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Paths;
3679
0
  };
3680
3681
0
  static auto const parser =
3682
0
    cmArgumentParser<Arguments>{}
3683
0
      .Bind("OUTPUT"_s, &Arguments::Output)
3684
0
      .Bind("FORMAT"_s, &Arguments::Format)
3685
0
      .Bind("COMPRESSION"_s, &Arguments::Compression)
3686
0
      .Bind("COMPRESSION_LEVEL"_s, &Arguments::CompressionLevel)
3687
0
      .Bind("MTIME"_s, &Arguments::MTime)
3688
0
      .Bind("THREADS"_s, &Arguments::Threads)
3689
0
      .Bind("WORKING_DIRECTORY"_s, &Arguments::WorkingDirectory)
3690
0
      .Bind("VERBOSE"_s, &Arguments::Verbose)
3691
0
      .Bind("PATHS"_s, &Arguments::Paths);
3692
3693
0
  std::vector<std::string> unrecognizedArguments;
3694
0
  auto parsedArgs =
3695
0
    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3696
0
  auto argIt = unrecognizedArguments.begin();
3697
0
  if (argIt != unrecognizedArguments.end()) {
3698
0
    status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, '"'));
3699
0
    cmSystemTools::SetFatalErrorOccurred();
3700
0
    return false;
3701
0
  }
3702
3703
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3704
0
    cmSystemTools::SetFatalErrorOccurred();
3705
0
    return true;
3706
0
  }
3707
3708
0
  char const* knownFormats[] = {
3709
0
    "7zip", "gnutar", "pax", "paxr", "raw", "zip"
3710
0
  };
3711
3712
0
  if (!parsedArgs.Format.empty() &&
3713
0
      !cm::contains(knownFormats, parsedArgs.Format)) {
3714
0
    status.SetError(
3715
0
      cmStrCat("archive format ", parsedArgs.Format, " not supported"));
3716
0
    cmSystemTools::SetFatalErrorOccurred();
3717
0
    return false;
3718
0
  }
3719
3720
0
  static std::map<std::string, cmSystemTools::cmTarCompression>
3721
0
    compressionTypeMap = { { "None", cmSystemTools::TarCompressNone },
3722
0
                           { "BZip2", cmSystemTools::TarCompressBZip2 },
3723
0
                           { "Deflate", cmSystemTools::TarCompressGZip },
3724
0
                           { "GZip", cmSystemTools::TarCompressGZip },
3725
0
                           { "LZMA", cmSystemTools::TarCompressLZMA },
3726
0
                           { "LZMA2", cmSystemTools::TarCompressXZ },
3727
0
                           { "PPMd", cmSystemTools::TarCompressPPMd },
3728
0
                           { "XZ", cmSystemTools::TarCompressXZ },
3729
0
                           { "Zstd", cmSystemTools::TarCompressZstd } };
3730
3731
0
  cmSystemTools::cmTarCompression compress = cmSystemTools::TarCompressAuto;
3732
0
  auto typeIt = compressionTypeMap.find(parsedArgs.Compression);
3733
0
  if (typeIt != compressionTypeMap.end()) {
3734
0
    compress = typeIt->second;
3735
0
  } else if (!parsedArgs.Compression.empty()) {
3736
0
    status.SetError(cmStrCat("compression type ", parsedArgs.Compression,
3737
0
                             " is not supported"));
3738
0
    cmSystemTools::SetFatalErrorOccurred();
3739
0
    return false;
3740
0
  }
3741
3742
0
  if (compress == cmSystemTools::TarCompressPPMd &&
3743
0
      parsedArgs.Format != "7zip") {
3744
0
    status.SetError(cmStrCat("PPMd compression is not supported for ",
3745
0
                             parsedArgs.Format, " format"));
3746
0
    cmSystemTools::SetFatalErrorOccurred();
3747
0
    return false;
3748
0
  }
3749
3750
0
  int compressionLevel = 0;
3751
0
  constexpr int minCompressionLevel = 0;
3752
0
  int maxCompressionLevel = 9;
3753
0
  if (compress == cmSystemTools::TarCompressZstd &&
3754
0
      parsedArgs.Format != "zip") {
3755
0
    maxCompressionLevel = 19;
3756
0
  }
3757
3758
0
  if (!parsedArgs.CompressionLevel.empty()) {
3759
0
    if (parsedArgs.CompressionLevel.size() != 1 &&
3760
0
        !std::isdigit(parsedArgs.CompressionLevel[0])) {
3761
0
      status.SetError(
3762
0
        cmStrCat("compression level ", parsedArgs.CompressionLevel, " for ",
3763
0
                 parsedArgs.Compression, " should be in range ",
3764
0
                 minCompressionLevel, " to ", maxCompressionLevel));
3765
0
      cmSystemTools::SetFatalErrorOccurred();
3766
0
      return false;
3767
0
    }
3768
0
    compressionLevel = std::stoi(parsedArgs.CompressionLevel);
3769
0
    if (compressionLevel < minCompressionLevel ||
3770
0
        compressionLevel > maxCompressionLevel) {
3771
0
      status.SetError(
3772
0
        cmStrCat("compression level ", parsedArgs.CompressionLevel, " for ",
3773
0
                 parsedArgs.Compression, " should be in range ",
3774
0
                 minCompressionLevel, " to ", maxCompressionLevel));
3775
0
      cmSystemTools::SetFatalErrorOccurred();
3776
0
      return false;
3777
0
    }
3778
0
    if (compress == cmSystemTools::TarCompressNone) {
3779
0
      status.SetError(
3780
0
        cmStrCat("compression level is not supported for "
3781
0
                 "compression \"None\". Provided compression level: ",
3782
0
                 parsedArgs.CompressionLevel));
3783
0
      cmSystemTools::SetFatalErrorOccurred();
3784
0
      return false;
3785
0
    }
3786
0
  }
3787
3788
  // Use the single thread by default for backward compatibility
3789
0
  int threads = 1;
3790
0
  constexpr int minThreads = 0;
3791
0
  if (!parsedArgs.Threads.empty()) {
3792
0
    if (parsedArgs.Threads.size() != 1 &&
3793
0
        !std::isdigit(parsedArgs.Threads[0])) {
3794
0
      status.SetError(cmStrCat("number of threads ", parsedArgs.Threads,
3795
0
                               " should be at least ", minThreads));
3796
0
      cmSystemTools::SetFatalErrorOccurred();
3797
0
      return false;
3798
0
    }
3799
0
    threads = std::stoi(parsedArgs.Threads);
3800
0
    if (threads < minThreads) {
3801
0
      status.SetError(cmStrCat("number of threads ", parsedArgs.Threads,
3802
0
                               " should be at least ", minThreads));
3803
0
      cmSystemTools::SetFatalErrorOccurred();
3804
0
      return false;
3805
0
    }
3806
0
  }
3807
3808
0
  if (parsedArgs.Paths.empty()) {
3809
0
    status.SetError("ARCHIVE_CREATE requires a non-empty list of PATHS");
3810
0
    cmSystemTools::SetFatalErrorOccurred();
3811
0
    return false;
3812
0
  }
3813
3814
0
  if (!cmSystemTools::CreateTar(
3815
0
        parsedArgs.Output, parsedArgs.Paths, parsedArgs.WorkingDirectory,
3816
0
        compress, parsedArgs.Verbose, parsedArgs.MTime, parsedArgs.Format,
3817
0
        compressionLevel, threads)) {
3818
0
    status.SetError(cmStrCat("failed to compress: ", parsedArgs.Output));
3819
0
    cmSystemTools::SetFatalErrorOccurred();
3820
0
    return false;
3821
0
  }
3822
3823
0
  return true;
3824
0
}
3825
3826
bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
3827
                                 cmExecutionStatus& status)
3828
0
{
3829
0
  struct Arguments : public ArgumentParser::ParseResult
3830
0
  {
3831
0
    std::string Input;
3832
0
    bool Verbose = false;
3833
0
    bool ListOnly = false;
3834
0
    std::string Destination;
3835
0
    ArgumentParser::MaybeEmpty<std::vector<std::string>> Patterns;
3836
0
    bool Touch = false;
3837
0
  };
3838
3839
0
  static auto const parser = cmArgumentParser<Arguments>{}
3840
0
                               .Bind("INPUT"_s, &Arguments::Input)
3841
0
                               .Bind("VERBOSE"_s, &Arguments::Verbose)
3842
0
                               .Bind("LIST_ONLY"_s, &Arguments::ListOnly)
3843
0
                               .Bind("DESTINATION"_s, &Arguments::Destination)
3844
0
                               .Bind("PATTERNS"_s, &Arguments::Patterns)
3845
0
                               .Bind("TOUCH"_s, &Arguments::Touch);
3846
3847
0
  std::vector<std::string> unrecognizedArguments;
3848
0
  auto parsedArgs =
3849
0
    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
3850
0
  auto argIt = unrecognizedArguments.begin();
3851
0
  if (argIt != unrecognizedArguments.end()) {
3852
0
    status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, '"'));
3853
0
    cmSystemTools::SetFatalErrorOccurred();
3854
0
    return false;
3855
0
  }
3856
3857
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3858
0
    cmSystemTools::SetFatalErrorOccurred();
3859
0
    return true;
3860
0
  }
3861
3862
0
  std::string inFile = parsedArgs.Input;
3863
3864
0
  if (parsedArgs.ListOnly) {
3865
0
    if (!cmSystemTools::ListTar(inFile, parsedArgs.Patterns,
3866
0
                                parsedArgs.Verbose)) {
3867
0
      status.SetError(cmStrCat("failed to list: ", inFile));
3868
0
      cmSystemTools::SetFatalErrorOccurred();
3869
0
      return false;
3870
0
    }
3871
0
  } else {
3872
0
    std::string destDir = status.GetMakefile().GetCurrentBinaryDirectory();
3873
0
    if (!parsedArgs.Destination.empty()) {
3874
0
      if (cmSystemTools::FileIsFullPath(parsedArgs.Destination)) {
3875
0
        destDir = parsedArgs.Destination;
3876
0
      } else {
3877
0
        destDir = cmStrCat(destDir, '/', parsedArgs.Destination);
3878
0
      }
3879
3880
0
      if (!cmSystemTools::MakeDirectory(destDir)) {
3881
0
        status.SetError(cmStrCat("failed to create directory: ", destDir));
3882
0
        cmSystemTools::SetFatalErrorOccurred();
3883
0
        return false;
3884
0
      }
3885
3886
0
      if (!cmSystemTools::FileIsFullPath(inFile)) {
3887
0
        inFile =
3888
0
          cmStrCat(cmSystemTools::GetLogicalWorkingDirectory(), '/', inFile);
3889
0
      }
3890
0
    }
3891
3892
0
    cmWorkingDirectory workdir(destDir);
3893
0
    if (workdir.Failed()) {
3894
0
      status.SetError(workdir.GetError());
3895
0
      cmSystemTools::SetFatalErrorOccurred();
3896
0
      return false;
3897
0
    }
3898
3899
0
    if (!cmSystemTools::ExtractTar(
3900
0
          inFile, parsedArgs.Patterns,
3901
0
          parsedArgs.Touch ? cmSystemTools::cmTarExtractTimestamps::No
3902
0
                           : cmSystemTools::cmTarExtractTimestamps::Yes,
3903
0
          parsedArgs.Verbose)) {
3904
0
      status.SetError(cmStrCat("failed to extract:\n  ", inFile));
3905
0
      cmSystemTools::SetFatalErrorOccurred();
3906
0
      return false;
3907
0
    }
3908
0
  }
3909
3910
0
  return true;
3911
0
}
3912
3913
bool ValidateAndConvertPermissions(
3914
  cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> const&
3915
    permissions,
3916
  mode_t& perms, cmExecutionStatus& status)
3917
0
{
3918
0
  if (!permissions) {
3919
0
    return true;
3920
0
  }
3921
0
  for (auto const& i : *permissions) {
3922
0
    if (!cmFSPermissions::stringToModeT(i, perms)) {
3923
0
      status.SetError(i + " is an invalid permission specifier");
3924
0
      cmSystemTools::SetFatalErrorOccurred();
3925
0
      return false;
3926
0
    }
3927
0
  }
3928
0
  return true;
3929
0
}
3930
3931
bool SetPermissions(std::string const& filename, mode_t perms,
3932
                    cmExecutionStatus& status)
3933
0
{
3934
0
  if (!cmSystemTools::SetPermissions(filename, perms)) {
3935
0
    status.SetError("Failed to set permissions for " + filename);
3936
0
    cmSystemTools::SetFatalErrorOccurred();
3937
0
    return false;
3938
0
  }
3939
0
  return true;
3940
0
}
3941
3942
bool HandleChmodCommandImpl(std::vector<std::string> const& args, bool recurse,
3943
                            cmExecutionStatus& status)
3944
0
{
3945
0
  mode_t perms = 0;
3946
0
  mode_t fperms = 0;
3947
0
  mode_t dperms = 0;
3948
0
  cmsys::Glob globber;
3949
3950
0
  globber.SetRecurse(recurse);
3951
0
  globber.SetRecurseListDirs(recurse);
3952
3953
0
  struct Arguments : public ArgumentParser::ParseResult
3954
0
  {
3955
0
    cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
3956
0
      Permissions;
3957
0
    cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
3958
0
      FilePermissions;
3959
0
    cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
3960
0
      DirectoryPermissions;
3961
0
  };
3962
3963
0
  static auto const parser =
3964
0
    cmArgumentParser<Arguments>{}
3965
0
      .Bind("PERMISSIONS"_s, &Arguments::Permissions)
3966
0
      .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
3967
0
      .Bind("DIRECTORY_PERMISSIONS"_s, &Arguments::DirectoryPermissions);
3968
3969
0
  std::vector<std::string> pathEntries;
3970
0
  Arguments parsedArgs =
3971
0
    parser.Parse(cmMakeRange(args).advance(1), &pathEntries);
3972
3973
  // check validity of arguments
3974
0
  if (!parsedArgs.Permissions && !parsedArgs.FilePermissions &&
3975
0
      !parsedArgs.DirectoryPermissions) // no permissions given
3976
0
  {
3977
0
    status.SetError("No permissions given");
3978
0
    cmSystemTools::SetFatalErrorOccurred();
3979
0
    return false;
3980
0
  }
3981
3982
0
  if (parsedArgs.Permissions && parsedArgs.FilePermissions &&
3983
0
      parsedArgs.DirectoryPermissions) // all keywords are used
3984
0
  {
3985
0
    status.SetError("Remove either PERMISSIONS or FILE_PERMISSIONS or "
3986
0
                    "DIRECTORY_PERMISSIONS from the invocation");
3987
0
    cmSystemTools::SetFatalErrorOccurred();
3988
0
    return false;
3989
0
  }
3990
3991
0
  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
3992
0
    cmSystemTools::SetFatalErrorOccurred();
3993
0
    return true;
3994
0
  }
3995
3996
  // validate permissions
3997
0
  bool validatePermissions =
3998
0
    ValidateAndConvertPermissions(parsedArgs.Permissions, perms, status) &&
3999
0
    ValidateAndConvertPermissions(parsedArgs.FilePermissions, fperms,
4000
0
                                  status) &&
4001
0
    ValidateAndConvertPermissions(parsedArgs.DirectoryPermissions, dperms,
4002
0
                                  status);
4003
0
  if (!validatePermissions) {
4004
0
    return false;
4005
0
  }
4006
4007
0
  std::vector<std::string> allPathEntries;
4008
4009
0
  if (recurse) {
4010
0
    std::vector<std::string> tempPathEntries;
4011
0
    for (auto const& i : pathEntries) {
4012
0
      if (cmSystemTools::FileIsDirectory(i)) {
4013
0
        globber.FindFiles(i + "/*");
4014
0
        tempPathEntries = globber.GetFiles();
4015
0
        allPathEntries.insert(allPathEntries.end(), tempPathEntries.begin(),
4016
0
                              tempPathEntries.end());
4017
0
        allPathEntries.emplace_back(i);
4018
0
      } else {
4019
0
        allPathEntries.emplace_back(i); // We validate path entries below
4020
0
      }
4021
0
    }
4022
0
  } else {
4023
0
    allPathEntries = std::move(pathEntries);
4024
0
  }
4025
4026
  // chmod
4027
0
  for (auto const& i : allPathEntries) {
4028
0
    if (!(cmSystemTools::FileExists(i) || cmSystemTools::FileIsDirectory(i))) {
4029
0
      status.SetError(cmStrCat("does not exist:\n  ", i));
4030
0
      cmSystemTools::SetFatalErrorOccurred();
4031
0
      return false;
4032
0
    }
4033
4034
0
    if (cmSystemTools::FileExists(i, true)) {
4035
0
      bool success = true;
4036
0
      mode_t filePermissions = parsedArgs.FilePermissions ? fperms : perms;
4037
0
      if (filePermissions) {
4038
0
        success = SetPermissions(i, filePermissions, status);
4039
0
      }
4040
0
      if (!success) {
4041
0
        return false;
4042
0
      }
4043
0
    }
4044
4045
0
    else if (cmSystemTools::FileIsDirectory(i)) {
4046
0
      bool success = true;
4047
0
      mode_t directoryPermissions =
4048
0
        parsedArgs.DirectoryPermissions ? dperms : perms;
4049
0
      if (directoryPermissions) {
4050
0
        success = SetPermissions(i, directoryPermissions, status);
4051
0
      }
4052
0
      if (!success) {
4053
0
        return false;
4054
0
      }
4055
0
    }
4056
0
  }
4057
4058
0
  return true;
4059
0
}
4060
4061
bool HandleChmodCommand(std::vector<std::string> const& args,
4062
                        cmExecutionStatus& status)
4063
0
{
4064
0
  return HandleChmodCommandImpl(args, false, status);
4065
0
}
4066
4067
bool HandleChmodRecurseCommand(std::vector<std::string> const& args,
4068
                               cmExecutionStatus& status)
4069
0
{
4070
0
  return HandleChmodCommandImpl(args, true, status);
4071
0
}
4072
4073
} // namespace
4074
4075
bool cmFileCommand(std::vector<std::string> const& args,
4076
                   cmExecutionStatus& status)
4077
0
{
4078
0
  if (args.empty()) {
4079
0
    status.SetError(
4080
0
      "given no arguments, but it requires at least a sub-command.");
4081
0
    return false;
4082
0
  }
4083
4084
0
  static cmSubcommandTable const subcommand{
4085
0
    { "WRITE"_s, HandleWriteCommand },
4086
0
    { "APPEND"_s, HandleAppendCommand },
4087
0
    { "DOWNLOAD"_s, HandleDownloadCommand },
4088
0
    { "UPLOAD"_s, HandleUploadCommand },
4089
0
    { "READ"_s, HandleReadCommand },
4090
0
    { "MD5"_s, HandleHashCommand },
4091
0
    { "SHA1"_s, HandleHashCommand },
4092
0
    { "SHA224"_s, HandleHashCommand },
4093
0
    { "SHA256"_s, HandleHashCommand },
4094
0
    { "SHA384"_s, HandleHashCommand },
4095
0
    { "SHA512"_s, HandleHashCommand },
4096
0
    { "SHA3_224"_s, HandleHashCommand },
4097
0
    { "SHA3_256"_s, HandleHashCommand },
4098
0
    { "SHA3_384"_s, HandleHashCommand },
4099
0
    { "SHA3_512"_s, HandleHashCommand },
4100
0
    { "STRINGS"_s, HandleStringsCommand },
4101
0
    { "GLOB"_s, HandleGlobCommand },
4102
0
    { "GLOB_RECURSE"_s, HandleGlobRecurseCommand },
4103
0
    { "MAKE_DIRECTORY"_s, HandleMakeDirectoryCommand },
4104
0
    { "RENAME"_s, HandleRename },
4105
0
    { "COPY_FILE"_s, HandleCopyFile },
4106
0
    { "REMOVE"_s, HandleRemove },
4107
0
    { "REMOVE_RECURSE"_s, HandleRemoveRecurse },
4108
0
    { "COPY"_s, HandleCopyCommand },
4109
0
    { "INSTALL"_s, HandleInstallCommand },
4110
0
    { "DIFFERENT"_s, HandleDifferentCommand },
4111
0
    { "RPATH_CHANGE"_s, HandleRPathChangeCommand },
4112
0
    { "CHRPATH"_s, HandleRPathChangeCommand },
4113
0
    { "RPATH_SET"_s, HandleRPathSetCommand },
4114
0
    { "RPATH_CHECK"_s, HandleRPathCheckCommand },
4115
0
    { "RPATH_REMOVE"_s, HandleRPathRemoveCommand },
4116
0
    { "READ_ELF"_s, HandleReadElfCommand },
4117
0
    { "READ_MACHO"_s, HandleReadMachoCommand },
4118
0
    { "REAL_PATH"_s, HandleRealPathCommand },
4119
0
    { "RELATIVE_PATH"_s, HandleRelativePathCommand },
4120
0
    { "TO_CMAKE_PATH"_s, HandleCMakePathCommand },
4121
0
    { "TO_NATIVE_PATH"_s, HandleNativePathCommand },
4122
0
    { "TOUCH"_s, HandleTouchCommand },
4123
0
    { "TOUCH_NOCREATE"_s, HandleTouchNocreateCommand },
4124
0
    { "TIMESTAMP"_s, HandleTimestampCommand },
4125
0
    { "GENERATE"_s, HandleGenerateCommand },
4126
0
    { "LOCK"_s, HandleLockCommand },
4127
0
    { "SIZE"_s, HandleSizeCommand },
4128
0
    { "READ_SYMLINK"_s, HandleReadSymlinkCommand },
4129
0
    { "CREATE_LINK"_s, HandleCreateLinkCommand },
4130
0
    { "GET_RUNTIME_DEPENDENCIES"_s, HandleGetRuntimeDependenciesCommand },
4131
0
    { "CONFIGURE"_s, HandleConfigureCommand },
4132
0
    { "ARCHIVE_CREATE"_s, HandleArchiveCreateCommand },
4133
0
    { "ARCHIVE_EXTRACT"_s, HandleArchiveExtractCommand },
4134
0
    { "CHMOD"_s, HandleChmodCommand },
4135
0
    { "CHMOD_RECURSE"_s, HandleChmodRecurseCommand },
4136
0
  };
4137
4138
0
  return subcommand(args[0], args, status);
4139
0
}