cmFileCommand.cxx 102 KB
Newer Older
1
2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
3
#include "cmFileCommand.h"
Brad King's avatar
Brad King committed
4

5
6
7
8
9
10
#include "cm_kwiml.h"
#include "cmsys/Directory.hxx"
#include "cmsys/FStream.hxx"
#include "cmsys/Glob.hxx"
#include "cmsys/RegularExpression.hxx"
#include "cmsys/String.hxx"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
11
12
#include <algorithm>
#include <assert.h>
13
#include <memory> // IWYU pragma: keep
Daniel Pfeifer's avatar
Daniel Pfeifer committed
14
15
16
17
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Ben Boeckel's avatar
Ben Boeckel committed
18
#include <vector>
Daniel Pfeifer's avatar
Daniel Pfeifer committed
19

20
#include "cmAlgorithms.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
21
#include "cmCommandArgumentsHelper.h"
22
#include "cmCryptoHash.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
23
#include "cmFileLockPool.h"
24
#include "cmFileTimeComparison.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
25
#include "cmGeneratorExpression.h"
Stephen Kelly's avatar
Stephen Kelly committed
26
#include "cmGlobalGenerator.h"
27
28
#include "cmHexFileConverter.h"
#include "cmInstallType.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
29
#include "cmListFileCache.h"
30
#include "cmMakefile.h"
Daniel Pfeifer's avatar
Daniel Pfeifer committed
31
32
#include "cmPolicies.h"
#include "cmSystemTools.h"
33
#include "cmTimestamp.h"
34
#include "cm_sys_stat.h"
35
#include "cmake.h"
36

37
#if defined(CMAKE_BUILD_WITH_CMAKE)
38
#include "cmCurl.h"
39
#include "cmFileLockResult.h"
40
#include "cm_curl.h"
41
42
#endif

43
44
45
46
#if defined(CMAKE_USE_ELF_PARSER)
#include "cmELF.h"
#endif

47
48
49
50
#if defined(_WIN32)
#include <windows.h>
#endif

Daniel Pfeifer's avatar
Daniel Pfeifer committed
51
class cmSystemToolsFileTime;
52
53
54
55
56
57

// Table of permissions flags.
#if defined(_WIN32) && !defined(__CYGWIN__)
static mode_t mode_owner_read = S_IREAD;
static mode_t mode_owner_write = S_IWRITE;
static mode_t mode_owner_execute = S_IEXEC;
58
59
60
61
62
63
64
65
static mode_t mode_group_read = 040;
static mode_t mode_group_write = 020;
static mode_t mode_group_execute = 010;
static mode_t mode_world_read = 04;
static mode_t mode_world_write = 02;
static mode_t mode_world_execute = 01;
static mode_t mode_setuid = 04000;
static mode_t mode_setgid = 02000;
66
67
68
69
70
71
72
73
74
75
76
77
78
#else
static mode_t mode_owner_read = S_IRUSR;
static mode_t mode_owner_write = S_IWUSR;
static mode_t mode_owner_execute = S_IXUSR;
static mode_t mode_group_read = S_IRGRP;
static mode_t mode_group_write = S_IWGRP;
static mode_t mode_group_execute = S_IXGRP;
static mode_t mode_world_read = S_IROTH;
static mode_t mode_world_write = S_IWOTH;
static mode_t mode_world_execute = S_IXOTH;
static mode_t mode_setuid = S_ISUID;
static mode_t mode_setgid = S_ISGID;
#endif
79

80
#if defined(_WIN32)
81
82
83
84
85
// libcurl doesn't support file:// urls for unicode filenames on Windows.
// Convert string from UTF-8 to ACP if this is a file:// URL.
static std::string fix_file_url_windows(const std::string& url)
{
  std::string ret = url;
86
  if (strncmp(url.c_str(), "file://", 7) == 0) {
Stephen Kelly's avatar
Stephen Kelly committed
87
    std::wstring wurl = cmsys::Encoding::ToWide(url);
88
89
90
91
    if (!wurl.empty()) {
      int mblen =
        WideCharToMultiByte(CP_ACP, 0, wurl.c_str(), -1, NULL, 0, NULL, NULL);
      if (mblen > 0) {
92
        std::vector<char> chars(mblen);
93
94
95
        mblen = WideCharToMultiByte(CP_ACP, 0, wurl.c_str(), -1, &chars[0],
                                    mblen, NULL, NULL);
        if (mblen > 0) {
96
97
98
99
          ret = &chars[0];
        }
      }
    }
100
  }
101
102
103
104
  return ret;
}
#endif

105
// cmLibraryCommand
106
107
bool cmFileCommand::InitialPass(std::vector<std::string> const& args,
                                cmExecutionStatus&)
108
{
109
  if (args.size() < 2) {
110
111
    this->SetError("must be called with at least two arguments.");
    return false;
112
  }
113
  std::string const& subCommand = args[0];
114
  if (subCommand == "WRITE") {
115
    return this->HandleWriteCommand(args, false);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
116
117
  }
  if (subCommand == "APPEND") {
118
    return this->HandleWriteCommand(args, true);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
119
120
  }
  if (subCommand == "DOWNLOAD") {
121
    return this->HandleDownloadCommand(args);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
122
123
  }
  if (subCommand == "UPLOAD") {
David Cole's avatar
David Cole committed
124
    return this->HandleUploadCommand(args);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
125
126
  }
  if (subCommand == "READ") {
127
    return this->HandleReadCommand(args);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
128
129
130
  }
  if (subCommand == "MD5" || subCommand == "SHA1" || subCommand == "SHA224" ||
      subCommand == "SHA256" || subCommand == "SHA384" ||
131
132
133
      subCommand == "SHA512" || subCommand == "SHA3_224" ||
      subCommand == "SHA3_256" || subCommand == "SHA3_384" ||
      subCommand == "SHA3_512") {
134
    return this->HandleHashCommand(args);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
135
136
  }
  if (subCommand == "STRINGS") {
Brad King's avatar
Brad King committed
137
    return this->HandleStringsCommand(args);
138
139
  }
  if (subCommand == "GLOB") {
Andy Cedilnik's avatar
Andy Cedilnik committed
140
    return this->HandleGlobCommand(args, false);
141
142
  }
  if (subCommand == "GLOB_RECURSE") {
Andy Cedilnik's avatar
Andy Cedilnik committed
143
    return this->HandleGlobCommand(args, true);
144
145
  }
  if (subCommand == "MAKE_DIRECTORY") {
146
    return this->HandleMakeDirectoryCommand(args);
147
148
  }
  if (subCommand == "RENAME") {
149
    return this->HandleRename(args);
150
151
  }
  if (subCommand == "REMOVE") {
152
    return this->HandleRemove(args, false);
153
154
  }
  if (subCommand == "REMOVE_RECURSE") {
155
    return this->HandleRemove(args, true);
156
157
  }
  if (subCommand == "COPY") {
158
    return this->HandleCopyCommand(args);
159
160
  }
  if (subCommand == "INSTALL") {
161
    return this->HandleInstallCommand(args);
162
163
  }
  if (subCommand == "DIFFERENT") {
164
    return this->HandleDifferentCommand(args);
165
166
  }
  if (subCommand == "RPATH_CHANGE" || subCommand == "CHRPATH") {
167
    return this->HandleRPathChangeCommand(args);
168
169
  }
  if (subCommand == "RPATH_CHECK") {
170
    return this->HandleRPathCheckCommand(args);
171
172
  }
  if (subCommand == "RPATH_REMOVE") {
173
    return this->HandleRPathRemoveCommand(args);
174
  }
175
176
177
  if (subCommand == "READ_ELF") {
    return this->HandleReadElfCommand(args);
  }
178
  if (subCommand == "RELATIVE_PATH") {
179
    return this->HandleRelativePathCommand(args);
180
181
  }
  if (subCommand == "TO_CMAKE_PATH") {
182
    return this->HandleCMakePathCommand(args, false);
183
184
  }
  if (subCommand == "TO_NATIVE_PATH") {
185
    return this->HandleCMakePathCommand(args, true);
186
187
  }
  if (subCommand == "TIMESTAMP") {
188
    return this->HandleTimestampCommand(args);
189
190
  }
  if (subCommand == "GENERATE") {
191
    return this->HandleGenerateCommand(args);
192
193
  }
  if (subCommand == "LOCK") {
194
    return this->HandleLockCommand(args);
195
  }
196

197
  std::string e = "does not recognize sub-command " + subCommand;
Stephen Kelly's avatar
Stephen Kelly committed
198
  this->SetError(e);
199
  return false;
200
201
}

202
bool cmFileCommand::HandleWriteCommand(std::vector<std::string> const& args,
203
                                       bool append)
204
205
206
207
208
209
{
  std::vector<std::string>::const_iterator i = args.begin();

  i++; // Get rid of subcommand

  std::string fileName = *i;
210
  if (!cmsys::SystemTools::FileIsFullPath(i->c_str())) {
211
    fileName = this->Makefile->GetCurrentSourceDirectory();
212
    fileName += "/" + *i;
213
  }
214

215
216
  i++;

217
218
219
  if (!this->Makefile->CanIWriteThisFile(fileName.c_str())) {
    std::string e =
      "attempted to write a file: " + fileName + " into a source directory.";
Stephen Kelly's avatar
Stephen Kelly committed
220
    this->SetError(e);
221
222
    cmSystemTools::SetFatalErrorOccured();
    return false;
223
  }
224
225
226
  std::string dir = cmSystemTools::GetFilenamePath(fileName);
  cmSystemTools::MakeDirectory(dir.c_str());

227
  mode_t mode = 0;
228
229

  // Set permissions to writable
230
  if (cmSystemTools::GetPermissions(fileName.c_str(), mode)) {
231
    cmSystemTools::SetPermissions(fileName.c_str(),
232
233
#if defined(_MSC_VER) || defined(__MINGW32__)
                                  mode | S_IWRITE
234
#else
235
                                  mode | S_IWUSR | S_IWGRP
236
#endif
237
238
                                  );
  }
239
240
  // If GetPermissions fails, pretend like it is ok. File open will fail if
  // the file is not writable
241
242
243
  cmsys::ofstream file(fileName.c_str(),
                       append ? std::ios::app : std::ios::out);
  if (!file) {
244
245
246
247
    std::string error = "failed to open for writing (";
    error += cmSystemTools::GetLastSystemError();
    error += "):\n  ";
    error += fileName;
Stephen Kelly's avatar
Stephen Kelly committed
248
    this->SetError(error);
249
    return false;
250
  }
251
  std::string message = cmJoin(cmMakeRange(i, args.end()), std::string());
252
  file << message;
253
  file.close();
254
  if (mode) {
255
    cmSystemTools::SetPermissions(fileName.c_str(), mode);
256
  }
257
258
259
260
261
  return true;
}

bool cmFileCommand::HandleReadCommand(std::vector<std::string> const& args)
{
262
  if (args.size() < 3) {
Ken Martin's avatar
Ken Martin committed
263
264
    this->SetError("READ must be called with at least two additional "
                   "arguments");
265
    return false;
266
  }
267

268
269
270
  cmCommandArgumentsHelper argHelper;
  cmCommandArgumentGroup group;

271
  cmCAString readArg(&argHelper, "READ");
Daniel Pfeifer's avatar
Daniel Pfeifer committed
272
273
  cmCAString fileNameArg(&argHelper, nullptr);
  cmCAString resultArg(&argHelper, nullptr);
274

275
276
277
  cmCAString offsetArg(&argHelper, "OFFSET", &group);
  cmCAString limitArg(&argHelper, "LIMIT", &group);
  cmCAEnabler hexOutputArg(&argHelper, "HEX", &group);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
278
  readArg.Follows(nullptr);
279
280
281
  fileNameArg.Follows(&readArg);
  resultArg.Follows(&fileNameArg);
  group.Follows(&resultArg);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
282
  argHelper.Parse(&args, nullptr);
283
284

  std::string fileName = fileNameArg.GetString();
285
  if (!cmsys::SystemTools::FileIsFullPath(fileName.c_str())) {
286
    fileName = this->Makefile->GetCurrentSourceDirectory();
287
    fileName += "/" + fileNameArg.GetString();
288
  }
289

290
291
  std::string variable = resultArg.GetString();

292
// Open the specified file.
293
#if defined(_WIN32) || defined(__CYGWIN__)
294
295
296
  cmsys::ifstream file(
    fileName.c_str(), std::ios::in |
      (hexOutputArg.IsEnabled() ? std::ios::binary : std::ios::in));
297
#else
298
  cmsys::ifstream file(fileName.c_str());
299
300
#endif

301
  if (!file) {
302
303
304
305
    std::string error = "failed to open for reading (";
    error += cmSystemTools::GetLastSystemError();
    error += "):\n  ";
    error += fileName;
Stephen Kelly's avatar
Stephen Kelly committed
306
    this->SetError(error);
307
    return false;
308
  }
309

310
  // is there a limit?
Ken Martin's avatar
Ken Martin committed
311
  long sizeLimit = -1;
312
  if (!limitArg.GetString().empty()) {
313
    sizeLimit = atoi(limitArg.GetCString());
314
  }
315
316
317

  // is there an offset?
  long offset = 0;
318
  if (!offsetArg.GetString().empty()) {
319
    offset = atoi(offsetArg.GetCString());
320
  }
Ken Martin's avatar
Ken Martin committed
321

322
  file.seekg(offset, std::ios::beg); // explicit ios::beg for IBM VisualAge 6
323

324
  std::string output;
325

326
  if (hexOutputArg.IsEnabled()) {
327
    // Convert part of the file into hex code
328
    char c;
329
    while ((sizeLimit != 0) && (file.get(c))) {
330
      char hex[4];
331
      sprintf(hex, "%.2x", c & 0xff);
332
      output += hex;
333
      if (sizeLimit > 0) {
Ken Martin's avatar
Ken Martin committed
334
335
        sizeLimit--;
      }
336
    }
337
  } else {
338
339
    std::string line;
    bool has_newline = false;
340
341
342
    while (sizeLimit != 0 && cmSystemTools::GetLineFromStream(
                               file, line, &has_newline, sizeLimit)) {
      if (sizeLimit > 0) {
343
        sizeLimit = sizeLimit - static_cast<long>(line.size());
344
        if (has_newline) {
345
          sizeLimit--;
346
347
        }
        if (sizeLimit < 0) {
348
349
          sizeLimit = 0;
        }
350
      }
351
      output += line;
352
      if (has_newline) {
353
        output += "\n";
354
355
      }
    }
356
  }
Stephen Kelly's avatar
Stephen Kelly committed
357
  this->Makefile->AddDefinition(variable, output.c_str());
358
359
360
  return true;
}

361
362
bool cmFileCommand::HandleHashCommand(std::vector<std::string> const& args)
{
363
#if defined(CMAKE_BUILD_WITH_CMAKE)
364
  if (args.size() != 3) {
365
    std::ostringstream e;
366
    e << args[0] << " requires a file name and output variable";
Stephen Kelly's avatar
Stephen Kelly committed
367
    this->SetError(e.str());
368
    return false;
369
  }
370

371
372
  std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0].c_str()));
  if (hash) {
Stephen Kelly's avatar
Stephen Kelly committed
373
    std::string out = hash->HashFile(args[1]);
374
    if (!out.empty()) {
Stephen Kelly's avatar
Stephen Kelly committed
375
      this->Makefile->AddDefinition(args[2], out.c_str());
376
      return true;
377
    }
378
    std::ostringstream e;
379
380
    e << args[0] << " failed to read file \"" << args[1]
      << "\": " << cmSystemTools::GetLastSystemError();
Stephen Kelly's avatar
Stephen Kelly committed
381
    this->SetError(e.str());
382
  }
383
  return false;
384
#else
385
  std::ostringstream e;
386
387
388
389
  e << args[0] << " not available during bootstrap";
  this->SetError(e.str().c_str());
  return false;
#endif
390
391
}

Brad King's avatar
Brad King committed
392
393
bool cmFileCommand::HandleStringsCommand(std::vector<std::string> const& args)
{
394
  if (args.size() < 3) {
Brad King's avatar
Brad King committed
395
396
    this->SetError("STRINGS requires a file name and output variable");
    return false;
397
  }
Brad King's avatar
Brad King committed
398
399
400

  // Get the file to read.
  std::string fileName = args[1];
401
  if (!cmsys::SystemTools::FileIsFullPath(fileName.c_str())) {
402
    fileName = this->Makefile->GetCurrentSourceDirectory();
Brad King's avatar
Brad King committed
403
    fileName += "/" + args[1];
404
  }
Brad King's avatar
Brad King committed
405
406
407
408
409

  // Get the variable in which to store the results.
  std::string outVar = args[2];

  // Parse the options.
410
411
412
413
414
415
416
417
418
419
420
421
  enum
  {
    arg_none,
    arg_limit_input,
    arg_limit_output,
    arg_limit_count,
    arg_length_minimum,
    arg_length_maximum,
    arg__maximum,
    arg_regex,
    arg_encoding
  };
Brad King's avatar
Brad King committed
422
423
424
425
426
427
428
429
  unsigned int minlen = 0;
  unsigned int maxlen = 0;
  int limit_input = -1;
  int limit_output = -1;
  unsigned int limit_count = 0;
  cmsys::RegularExpression regex;
  bool have_regex = false;
  bool newline_consume = false;
Alexander Neundorf's avatar
   
Alexander Neundorf committed
430
  bool hex_conversion_enabled = true;
431
432
433
434
435
436
437
438
439
  enum
  {
    encoding_none = cmsys::FStream::BOM_None,
    encoding_utf8 = cmsys::FStream::BOM_UTF8,
    encoding_utf16le = cmsys::FStream::BOM_UTF16LE,
    encoding_utf16be = cmsys::FStream::BOM_UTF16BE,
    encoding_utf32le = cmsys::FStream::BOM_UTF32LE,
    encoding_utf32be = cmsys::FStream::BOM_UTF32BE
  };
440
  int encoding = encoding_none;
Brad King's avatar
Brad King committed
441
  int arg_mode = arg_none;
442
443
  for (unsigned int i = 3; i < args.size(); ++i) {
    if (args[i] == "LIMIT_INPUT") {
Brad King's avatar
Brad King committed
444
      arg_mode = arg_limit_input;
445
    } else if (args[i] == "LIMIT_OUTPUT") {
Brad King's avatar
Brad King committed
446
      arg_mode = arg_limit_output;
447
    } else if (args[i] == "LIMIT_COUNT") {
Brad King's avatar
Brad King committed
448
      arg_mode = arg_limit_count;
449
    } else if (args[i] == "LENGTH_MINIMUM") {
Brad King's avatar
Brad King committed
450
      arg_mode = arg_length_minimum;
451
    } else if (args[i] == "LENGTH_MAXIMUM") {
Brad King's avatar
Brad King committed
452
      arg_mode = arg_length_maximum;
453
    } else if (args[i] == "REGEX") {
Brad King's avatar
Brad King committed
454
      arg_mode = arg_regex;
455
    } else if (args[i] == "NEWLINE_CONSUME") {
Brad King's avatar
Brad King committed
456
457
      newline_consume = true;
      arg_mode = arg_none;
458
    } else if (args[i] == "NO_HEX_CONVERSION") {
Alexander Neundorf's avatar
   
Alexander Neundorf committed
459
460
      hex_conversion_enabled = false;
      arg_mode = arg_none;
461
    } else if (args[i] == "ENCODING") {
462
      arg_mode = arg_encoding;
463
464
465
    } else if (arg_mode == arg_limit_input) {
      if (sscanf(args[i].c_str(), "%d", &limit_input) != 1 ||
          limit_input < 0) {
466
        std::ostringstream e;
467
468
        e << "STRINGS option LIMIT_INPUT value \"" << args[i]
          << "\" is not an unsigned integer.";
Stephen Kelly's avatar
Stephen Kelly committed
469
        this->SetError(e.str());
Brad King's avatar
Brad King committed
470
471
        return false;
      }
472
473
474
475
      arg_mode = arg_none;
    } else if (arg_mode == arg_limit_output) {
      if (sscanf(args[i].c_str(), "%d", &limit_output) != 1 ||
          limit_output < 0) {
476
        std::ostringstream e;
477
478
        e << "STRINGS option LIMIT_OUTPUT value \"" << args[i]
          << "\" is not an unsigned integer.";
Stephen Kelly's avatar
Stephen Kelly committed
479
        this->SetError(e.str());
Brad King's avatar
Brad King committed
480
481
        return false;
      }
482
483
      arg_mode = arg_none;
    } else if (arg_mode == arg_limit_count) {
Brad King's avatar
Brad King committed
484
      int count;
485
      if (sscanf(args[i].c_str(), "%d", &count) != 1 || count < 0) {
486
        std::ostringstream e;
487
488
        e << "STRINGS option LIMIT_COUNT value \"" << args[i]
          << "\" is not an unsigned integer.";
Stephen Kelly's avatar
Stephen Kelly committed
489
        this->SetError(e.str());
Brad King's avatar
Brad King committed
490
        return false;
491
      }
Brad King's avatar
Brad King committed
492
493
      limit_count = count;
      arg_mode = arg_none;
494
    } else if (arg_mode == arg_length_minimum) {
Brad King's avatar
Brad King committed
495
      int len;
496
      if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
497
        std::ostringstream e;
498
499
        e << "STRINGS option LENGTH_MINIMUM value \"" << args[i]
          << "\" is not an unsigned integer.";
Stephen Kelly's avatar
Stephen Kelly committed
500
        this->SetError(e.str());
Brad King's avatar
Brad King committed
501
        return false;
502
      }
Brad King's avatar
Brad King committed
503
504
      minlen = len;
      arg_mode = arg_none;
505
    } else if (arg_mode == arg_length_maximum) {
Brad King's avatar
Brad King committed
506
      int len;
507
      if (sscanf(args[i].c_str(), "%d", &len) != 1 || len < 0) {
508
        std::ostringstream e;
509
510
        e << "STRINGS option LENGTH_MAXIMUM value \"" << args[i]
          << "\" is not an unsigned integer.";
Stephen Kelly's avatar
Stephen Kelly committed
511
        this->SetError(e.str());
Brad King's avatar
Brad King committed
512
        return false;
513
      }
Brad King's avatar
Brad King committed
514
515
      maxlen = len;
      arg_mode = arg_none;
516
517
    } else if (arg_mode == arg_regex) {
      if (!regex.compile(args[i].c_str())) {
518
        std::ostringstream e;
519
520
        e << "STRINGS option REGEX value \"" << args[i]
          << "\" could not be compiled.";
Stephen Kelly's avatar
Stephen Kelly committed
521
        this->SetError(e.str());
Brad King's avatar
Brad King committed
522
        return false;
523
      }
Brad King's avatar
Brad King committed
524
525
      have_regex = true;
      arg_mode = arg_none;
526
527
    } else if (arg_mode == arg_encoding) {
      if (args[i] == "UTF-8") {
528
        encoding = encoding_utf8;
529
      } else if (args[i] == "UTF-16LE") {
530
        encoding = encoding_utf16le;
531
      } else if (args[i] == "UTF-16BE") {
532
        encoding = encoding_utf16be;
533
      } else if (args[i] == "UTF-32LE") {
534
        encoding = encoding_utf32le;
535
      } else if (args[i] == "UTF-32BE") {
536
        encoding = encoding_utf32be;
537
      } else {
538
        std::ostringstream e;
539
        e << "STRINGS option ENCODING \"" << args[i] << "\" not recognized.";
540
541
542
        this->SetError(e.str());
        return false;
      }
543
544
      arg_mode = arg_none;
    } else {
545
      std::ostringstream e;
546
      e << "STRINGS given unknown argument \"" << args[i] << "\"";
Stephen Kelly's avatar
Stephen Kelly committed
547
      this->SetError(e.str());
Brad King's avatar
Brad King committed
548
549
      return false;
    }
550
  }
Alexander Neundorf's avatar
   
Alexander Neundorf committed
551

552
  if (hex_conversion_enabled) {
Alexander Neundorf's avatar
   
Alexander Neundorf committed
553
    // TODO: should work without temp file, but just on a memory buffer
554
    std::string binaryFileName = this->Makefile->GetCurrentBinaryDirectory();
Alexander Neundorf's avatar
   
Alexander Neundorf committed
555
556
    binaryFileName += cmake::GetCMakeFilesDirectory();
    binaryFileName += "/FileCommandStringsBinaryFile";
557
558
    if (cmHexFileConverter::TryConvert(fileName.c_str(),
                                       binaryFileName.c_str())) {
Alexander Neundorf's avatar
   
Alexander Neundorf committed
559
      fileName = binaryFileName;
Alexander Neundorf's avatar
   
Alexander Neundorf committed
560
    }
561
  }
Alexander Neundorf's avatar
   
Alexander Neundorf committed
562

563
// Open the specified file.
Brad King's avatar
Brad King committed
564
#if defined(_WIN32) || defined(__CYGWIN__)
565
  cmsys::ifstream fin(fileName.c_str(), std::ios::in | std::ios::binary);
Brad King's avatar
Brad King committed
566
#else
567
  cmsys::ifstream fin(fileName.c_str());
Brad King's avatar
Brad King committed
568
#endif
569
  if (!fin) {
570
    std::ostringstream e;
Brad King's avatar
Brad King committed
571
    e << "STRINGS file \"" << fileName << "\" cannot be read.";
Stephen Kelly's avatar
Stephen Kelly committed
572
    this->SetError(e.str());
Brad King's avatar
Brad King committed
573
    return false;
574
  }
Brad King's avatar
Brad King committed
575

576
  // If BOM is found and encoding was not specified, use the BOM
577
  int bom_found = cmsys::FStream::ReadBOM(fin);
578
  if (encoding == encoding_none && bom_found != cmsys::FStream::BOM_None) {
579
    encoding = bom_found;
580
  }
581
582

  unsigned int bytes_rem = 0;
583
  if (encoding == encoding_utf16le || encoding == encoding_utf16be) {
584
    bytes_rem = 1;
585
586
  }
  if (encoding == encoding_utf32le || encoding == encoding_utf32be) {
587
    bytes_rem = 3;
588
  }
589

Brad King's avatar
Brad King committed
590
591
592
593
  // Parse strings out of the file.
  int output_size = 0;
  std::vector<std::string> strings;
  std::string s;
594
595
596
  while ((!limit_count || strings.size() < limit_count) &&
         (limit_input < 0 || static_cast<int>(fin.tellg()) < limit_input) &&
         fin) {
597
598
599
    std::string current_str;

    int c = fin.get();
600
    for (unsigned int i = 0; i < bytes_rem; ++i) {
601
      int c1 = fin.get();
602
      if (!fin) {
603
604
605
        fin.putback(static_cast<char>(c1));
        break;
      }
606
607
608
      c = (c << 8) | c1;
    }
    if (encoding == encoding_utf16le) {
609
      c = ((c & 0xFF) << 8) | ((c & 0xFF00) >> 8);
610
611
612
613
    } else if (encoding == encoding_utf32le) {
      c = (((c & 0xFF) << 24) | ((c & 0xFF00) << 8) | ((c & 0xFF0000) >> 8) |
           ((c & 0xFF000000) >> 24));
    }
614

615
    if (c == '\r') {
616
617
      // Ignore CR character to make output always have UNIX newlines.
      continue;
618
    }
619

620
621
    if ((c >= 0x20 && c < 0x7F) || c == '\t' ||
        (c == '\n' && newline_consume)) {
622
623
624
625
      // This is an ASCII character that may be part of a string.
      // Cast added to avoid compiler warning. Cast is ok because
      // c is guaranteed to fit in char by the above if...
      current_str += static_cast<char>(c);
626
    } else if (encoding == encoding_utf8) {
627
      // Check for UTF-8 encoded string (up to 4 octets)
628
629
630
      static const unsigned char utf8_check_table[3][2] = {
        { 0xE0, 0xC0 }, { 0xF0, 0xE0 }, { 0xF8, 0xF0 },
      };
631
632
633

      // how many octets are there?
      unsigned int num_utf8_bytes = 0;
634
      for (unsigned int j = 0; num_utf8_bytes == 0 && j < 3; j++) {
Daniel Pfeifer's avatar
Daniel Pfeifer committed
635
        if ((c & utf8_check_table[j][0]) == utf8_check_table[j][1]) {
636
          num_utf8_bytes = j + 2;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
637
        }
638
      }
639
640

      // get subsequent octets and check that they are valid
641
642
      for (unsigned int j = 0; j < num_utf8_bytes; j++) {
        if (j != 0) {
643
          c = fin.get();
644
          if (!fin || (c & 0xC0) != 0x80) {
645
646
647
648
            fin.putback(static_cast<char>(c));
            break;
          }
        }
649
650
        current_str += static_cast<char>(c);
      }
651
652
653

      // if this was an invalid utf8 sequence, discard the data, and put
      // back subsequent characters
654
655
      if ((current_str.length() != num_utf8_bytes)) {
        for (unsigned int j = 0; j < current_str.size() - 1; j++) {
656
657
658
          c = current_str[current_str.size() - 1 - j];
          fin.putback(static_cast<char>(c));
        }
659
        current_str.clear();
660
      }
661
    }
662

663
    if (c == '\n' && !newline_consume) {
Brad King's avatar
Brad King committed
664
665
666
      // The current line has been terminated.  Check if the current
      // string matches the requirements.  The length may now be as
      // low as zero since blank lines are allowed.
667
      if (s.length() >= minlen && (!have_regex || regex.find(s.c_str()))) {
Brad King's avatar
Brad King committed
668
        output_size += static_cast<int>(s.size()) + 1;
669
        if (limit_output >= 0 && output_size >= limit_output) {
670
          s.clear();
Brad King's avatar
Brad King committed
671
672
          break;
        }
673
674
        strings.push_back(s);
      }
Brad King's avatar
Brad King committed
675
676

      // Reset the string to empty.
677
      s.clear();
678
    } else if (current_str.empty()) {
679
680
681
      // A non-string character has been found.  Check if the current
      // string matches the requirements.  We require that the length
      // be at least one no matter what the user specified.
682
683
      if (s.length() >= minlen && !s.empty() &&
          (!have_regex || regex.find(s.c_str()))) {
684
        output_size += static_cast<int>(s.size()) + 1;
685
        if (limit_output >= 0 && output_size >= limit_output) {
686
          s.clear();
687
688
          break;
        }
689
690
        strings.push_back(s);
      }
691
692

      // Reset the string to empty.
693
      s.clear();
694
    } else {
695
      s += current_str;
696
    }
Brad King's avatar
Brad King committed
697

698
    if (maxlen > 0 && s.size() == maxlen) {
699
      // Terminate a string if the maximum length is reached.
700
      if (s.length() >= minlen && (!have_regex || regex.find(s.c_str()))) {
Brad King's avatar
Brad King committed
701
        output_size += static_cast<int>(s.size()) + 1;
702
        if (limit_output >= 0 && output_size >= limit_output) {
703
          s.clear();
Brad King's avatar
Brad King committed
704
705
          break;
        }
706
        strings.push_back(s);
Brad King's avatar
Brad King committed
707
      }
708
      s.clear();
Brad King's avatar
Brad King committed
709
    }
710
  }
Brad King's avatar
Brad King committed
711
712
713
714

  // If there is a non-empty current string we have hit the end of the
  // input file or the input size limit.  Check if the current string
  // matches the requirements.
715
716
  if ((!limit_count || strings.size() < limit_count) && !s.empty() &&
      s.length() >= minlen && (!have_regex || regex.find(s.c_str()))) {
Brad King's avatar
Brad King committed
717
    output_size += static_cast<int>(s.size()) + 1;
718
    if (limit_output < 0 || output_size < limit_output) {
Brad King's avatar
Brad King committed
719
720
      strings.push_back(s);
    }
721
  }
Brad King's avatar
Brad King committed
722
723
724
725

  // Encode the result in a CMake list.
  const char* sep = "";
  std::string output;
726
  for (std::string const& sr : strings) {
Brad King's avatar
Brad King committed
727
728
729
730
731
732
    // Separate the strings in the output to make it a list.
    output += sep;
    sep = ";";

    // Store the string in the output, but escape semicolons to
    // make sure it is a list.
733
734
    for (char i : sr) {
      if (i == ';') {
Brad King's avatar
Brad King committed
735
736
        output += '\\';
      }
737
      output += i;
Brad King's avatar
Brad King committed
738
    }
739
  }
Brad King's avatar
Brad King committed
740
741

  // Save the output in a makefile variable.
Stephen Kelly's avatar
Stephen Kelly committed
742
  this->Makefile->AddDefinition(outVar, output.c_str());
Brad King's avatar
Brad King committed
743
744
745
  return true;
}

Andy Cedilnik's avatar
Andy Cedilnik committed
746
bool cmFileCommand::HandleGlobCommand(std::vector<std::string> const& args,
747
                                      bool recurse)
748
{
Amine Chadly's avatar
Amine Chadly committed
749
750
  // File commands has at least one argument
  assert(args.size() > 1);
751
752
753
754
755
756
757

  std::vector<std::string>::const_iterator i = args.begin();

  i++; // Get rid of subcommand

  std::string variable = *i;
  i++;
758
  cmsys::Glob g;
Andy Cedilnik's avatar
Andy Cedilnik committed
759
  g.SetRecurse(recurse);
760
761
762
763

  bool explicitFollowSymlinks = false;
  cmPolicies::PolicyStatus status =
    this->Makefile->GetPolicyStatus(cmPolicies::CMP0009);
764
765
  if (recurse) {
    switch (status) {
766
767
      case cmPolicies::REQUIRED_IF_USED:
      case cmPolicies::REQUIRED_ALWAYS:
768
769
770
771
<