Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmFileInstaller.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
4
#include "cmFileInstaller.h"
5
6
#include <map>
7
#include <utility>
8
#include <vector>
9
10
#include <cm/string_view>
11
#include <cmext/string_view>
12
13
#include "cm_sys_stat.h"
14
15
#include "cmExecutionStatus.h"
16
#include "cmFSPermissions.h"
17
#include "cmMakefile.h"
18
#include "cmStringAlgorithms.h"
19
#include "cmSystemTools.h"
20
#include "cmValue.h"
21
22
using namespace cmFSPermissions;
23
24
cmFileInstaller::cmFileInstaller(cmExecutionStatus& status)
25
0
  : cmFileCopier(status, "INSTALL")
26
0
{
27
  // Installation does not use source permissions by default.
28
0
  this->UseSourcePermissions = false;
29
  // Check whether to copy files always or only if they have changed.
30
0
  std::string install_always;
31
0
  if (cmSystemTools::GetEnv("CMAKE_INSTALL_ALWAYS", install_always)) {
32
0
    this->Always = cmIsOn(install_always);
33
0
  }
34
  // Get the current manifest.
35
0
  this->Manifest =
36
0
    this->Makefile->GetSafeDefinition("CMAKE_INSTALL_MANIFEST_FILES");
37
0
}
38
cmFileInstaller::~cmFileInstaller()
39
0
{
40
  // Save the updated install manifest.
41
0
  this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES",
42
0
                                this->Manifest);
43
0
}
44
45
void cmFileInstaller::ManifestAppend(std::string const& file)
46
0
{
47
0
  if (!this->Manifest.empty()) {
48
0
    this->Manifest += ";";
49
0
  }
50
0
  this->Manifest += file.substr(this->DestDirLength);
51
0
}
52
53
std::string const& cmFileInstaller::ToName(std::string const& fromName)
54
0
{
55
0
  return this->Rename.empty() ? fromName : this->Rename;
56
0
}
57
58
void cmFileInstaller::ReportCopy(std::string const& toFile, Type type,
59
                                 bool copy)
60
0
{
61
0
  if (!this->MessageNever && (copy || !this->MessageLazy)) {
62
0
    std::string message =
63
0
      cmStrCat((copy ? "Installing: " : "Up-to-date: "), toFile);
64
0
    this->Makefile->DisplayStatus(message, -1);
65
0
  }
66
0
  if (type != TypeDir) {
67
    // Add the file to the manifest.
68
0
    this->ManifestAppend(toFile);
69
0
  }
70
0
}
71
bool cmFileInstaller::ReportMissing(std::string const& fromFile)
72
0
{
73
0
  return (this->Optional || this->cmFileCopier::ReportMissing(fromFile));
74
0
}
75
bool cmFileInstaller::Install(std::string const& fromFile,
76
                              std::string const& toFile)
77
0
{
78
  // Support installing from empty source to make a directory.
79
0
  if (this->InstallType == cmInstallType_DIRECTORY && fromFile.empty()) {
80
0
    return this->InstallDirectory(fromFile, toFile, MatchProperties());
81
0
  }
82
0
  return this->cmFileCopier::Install(fromFile, toFile);
83
0
}
84
85
bool cmFileInstaller::InstallFile(std::string const& fromFile,
86
                                  std::string const& toFile,
87
                                  MatchProperties match_properties)
88
0
{
89
0
  if (this->InstallMode == cmInstallMode::COPY) {
90
0
    return this->cmFileCopier::InstallFile(fromFile, toFile, match_properties);
91
0
  }
92
93
0
  std::string newFromFile;
94
95
0
  if (this->InstallMode == cmInstallMode::REL_SYMLINK ||
96
0
      this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY ||
97
0
      this->InstallMode == cmInstallMode::SYMLINK ||
98
0
      this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
99
    // Try to get a relative path.
100
0
    std::string toDir = cmSystemTools::GetParentDirectory(toFile);
101
0
    newFromFile = cmSystemTools::ForceToRelativePath(toDir, fromFile);
102
103
    // Double check that we can restore the original path.
104
0
    std::string reassembled =
105
0
      cmSystemTools::CollapseFullPath(newFromFile, toDir);
106
0
    if (!cmSystemTools::ComparePath(reassembled, fromFile)) {
107
0
      if (this->InstallMode == cmInstallMode::SYMLINK ||
108
0
          this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
109
        // User does not mind, silently proceed with absolute path.
110
0
        newFromFile = fromFile;
111
0
      } else if (this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY) {
112
        // User expects a relative symbolic link or a copy.
113
        // Since an absolute symlink won't do, copy instead.
114
0
        return this->cmFileCopier::InstallFile(fromFile, toFile,
115
0
                                               match_properties);
116
0
      } else {
117
        // We cannot meet user's expectation (REL_SYMLINK)
118
0
        auto e = cmStrCat(this->Name,
119
0
                          " cannot determine relative path for symlink to \"",
120
0
                          newFromFile, "\" at \"", toFile, "\".");
121
0
        this->Status.SetError(e);
122
0
        return false;
123
0
      }
124
0
    }
125
0
  } else {
126
0
    newFromFile = fromFile; // stick with absolute path
127
0
  }
128
129
  // Compare the symlink value to that at the destination if not
130
  // always installing.
131
0
  bool copy = true;
132
0
  if (!this->Always) {
133
0
    std::string oldSymlinkTarget;
134
0
    if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
135
0
      if (newFromFile == oldSymlinkTarget) {
136
0
        copy = false;
137
0
      }
138
0
    }
139
0
  }
140
141
  // Inform the user about this file installation.
142
0
  this->ReportCopy(toFile, TypeLink, copy);
143
144
0
  if (copy) {
145
    // Remove the destination file so we can always create the symlink.
146
0
    cmSystemTools::RemoveFile(toFile);
147
148
    // Create destination directory if it doesn't exist
149
0
    cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
150
151
    // Create the symlink.
152
0
    if (!cmSystemTools::CreateSymlink(newFromFile, toFile)) {
153
0
      if (this->InstallMode == cmInstallMode::ABS_SYMLINK_OR_COPY ||
154
0
          this->InstallMode == cmInstallMode::REL_SYMLINK_OR_COPY ||
155
0
          this->InstallMode == cmInstallMode::SYMLINK_OR_COPY) {
156
        // Failed to create a symbolic link, fall back to copying.
157
0
        return this->cmFileCopier::InstallFile(newFromFile, toFile,
158
0
                                               match_properties);
159
0
      }
160
161
0
      auto e = cmStrCat(this->Name, " cannot create symlink to \"",
162
0
                        newFromFile, "\" at \"", toFile,
163
0
                        "\": ", cmSystemTools::GetLastSystemError(), "\".");
164
0
      this->Status.SetError(e);
165
0
      return false;
166
0
    }
167
0
  }
168
169
0
  return true;
170
0
}
171
172
void cmFileInstaller::DefaultFilePermissions()
173
0
{
174
0
  this->cmFileCopier::DefaultFilePermissions();
175
  // Add execute permissions based on the target type.
176
0
  switch (this->InstallType) {
177
0
    case cmInstallType_SHARED_LIBRARY:
178
0
    case cmInstallType_MODULE_LIBRARY:
179
0
      if (this->Makefile->IsOn("CMAKE_INSTALL_SO_NO_EXE")) {
180
0
        break;
181
0
      }
182
0
      CM_FALLTHROUGH;
183
0
    case cmInstallType_EXECUTABLE:
184
0
    case cmInstallType_PROGRAMS:
185
0
      this->FilePermissions |= mode_owner_execute;
186
0
      this->FilePermissions |= mode_group_execute;
187
0
      this->FilePermissions |= mode_world_execute;
188
0
      break;
189
0
    default:
190
0
      break;
191
0
  }
192
0
}
193
194
bool cmFileInstaller::Parse(std::vector<std::string> const& args)
195
0
{
196
0
  if (!this->cmFileCopier::Parse(args)) {
197
0
    return false;
198
0
  }
199
200
0
  if (!this->Rename.empty()) {
201
0
    if (!this->FilesFromDir.empty()) {
202
0
      this->Status.SetError("INSTALL option RENAME may not be "
203
0
                            "combined with FILES_FROM_DIR.");
204
0
      return false;
205
0
    }
206
0
    if (this->InstallType != cmInstallType_FILES &&
207
0
        this->InstallType != cmInstallType_PROGRAMS) {
208
0
      this->Status.SetError("INSTALL option RENAME may be used "
209
0
                            "only with FILES or PROGRAMS.");
210
0
      return false;
211
0
    }
212
0
    if (this->Files.size() > 1) {
213
0
      this->Status.SetError("INSTALL option RENAME may be used "
214
0
                            "only with one file.");
215
0
      return false;
216
0
    }
217
0
  }
218
219
0
  if (!this->HandleInstallDestination()) {
220
0
    return false;
221
0
  }
222
223
0
  if (((this->MessageAlways ? 1 : 0) + (this->MessageLazy ? 1 : 0) +
224
0
       (this->MessageNever ? 1 : 0)) > 1) {
225
0
    this->Status.SetError("INSTALL options MESSAGE_ALWAYS, "
226
0
                          "MESSAGE_LAZY, and MESSAGE_NEVER "
227
0
                          "are mutually exclusive.");
228
0
    return false;
229
0
  }
230
231
0
  static std::map<cm::string_view, cmInstallMode> const install_mode_dict{
232
0
    { "ABS_SYMLINK"_s, cmInstallMode::ABS_SYMLINK },
233
0
    { "ABS_SYMLINK_OR_COPY"_s, cmInstallMode::ABS_SYMLINK_OR_COPY },
234
0
    { "REL_SYMLINK"_s, cmInstallMode::REL_SYMLINK },
235
0
    { "REL_SYMLINK_OR_COPY"_s, cmInstallMode::REL_SYMLINK_OR_COPY },
236
0
    { "SYMLINK"_s, cmInstallMode::SYMLINK },
237
0
    { "SYMLINK_OR_COPY"_s, cmInstallMode::SYMLINK_OR_COPY }
238
0
  };
239
240
0
  std::string install_mode;
241
0
  cmSystemTools::GetEnv("CMAKE_INSTALL_MODE", install_mode);
242
0
  if (install_mode.empty() || install_mode == "COPY"_s) {
243
0
    this->InstallMode = cmInstallMode::COPY;
244
0
  } else {
245
0
    auto it = install_mode_dict.find(install_mode);
246
0
    if (it != install_mode_dict.end()) {
247
0
      this->InstallMode = it->second;
248
0
    } else {
249
0
      auto e = cmStrCat("Unrecognized value '", install_mode,
250
0
                        "' for environment variable CMAKE_INSTALL_MODE");
251
0
      this->Status.SetError(e);
252
0
      return false;
253
0
    }
254
0
  }
255
256
0
  return true;
257
0
}
258
259
bool cmFileInstaller::CheckKeyword(std::string const& arg)
260
0
{
261
0
  if (arg == "TYPE") {
262
0
    if (this->CurrentMatchRule) {
263
0
      this->NotAfterMatch(arg);
264
0
    } else {
265
0
      this->Doing = DoingType;
266
0
    }
267
0
  } else if (arg == "FILES") {
268
0
    if (this->CurrentMatchRule) {
269
0
      this->NotAfterMatch(arg);
270
0
    } else {
271
0
      this->Doing = DoingFiles;
272
0
    }
273
0
  } else if (arg == "RENAME") {
274
0
    if (this->CurrentMatchRule) {
275
0
      this->NotAfterMatch(arg);
276
0
    } else {
277
0
      this->Doing = DoingRename;
278
0
    }
279
0
  } else if (arg == "OPTIONAL") {
280
0
    if (this->CurrentMatchRule) {
281
0
      this->NotAfterMatch(arg);
282
0
    } else {
283
0
      this->Doing = DoingNone;
284
0
      this->Optional = true;
285
0
    }
286
0
  } else if (arg == "MESSAGE_ALWAYS") {
287
0
    if (this->CurrentMatchRule) {
288
0
      this->NotAfterMatch(arg);
289
0
    } else {
290
0
      this->Doing = DoingNone;
291
0
      this->MessageAlways = true;
292
0
    }
293
0
  } else if (arg == "MESSAGE_LAZY") {
294
0
    if (this->CurrentMatchRule) {
295
0
      this->NotAfterMatch(arg);
296
0
    } else {
297
0
      this->Doing = DoingNone;
298
0
      this->MessageLazy = true;
299
0
    }
300
0
  } else if (arg == "MESSAGE_NEVER") {
301
0
    if (this->CurrentMatchRule) {
302
0
      this->NotAfterMatch(arg);
303
0
    } else {
304
0
      this->Doing = DoingNone;
305
0
      this->MessageNever = true;
306
0
    }
307
0
  } else if (arg == "PERMISSIONS") {
308
0
    if (this->CurrentMatchRule) {
309
0
      this->Doing = DoingPermissionsMatch;
310
0
    } else {
311
      // file(INSTALL) aliases PERMISSIONS to FILE_PERMISSIONS
312
0
      this->Doing = DoingPermissionsFile;
313
0
      this->UseGivenPermissionsFile = true;
314
0
    }
315
0
  } else if (arg == "DIR_PERMISSIONS") {
316
0
    if (this->CurrentMatchRule) {
317
0
      this->NotAfterMatch(arg);
318
0
    } else {
319
      // file(INSTALL) aliases DIR_PERMISSIONS to DIRECTORY_PERMISSIONS
320
0
      this->Doing = DoingPermissionsDir;
321
0
      this->UseGivenPermissionsDir = true;
322
0
    }
323
0
  } else if (arg == "COMPONENTS" || arg == "CONFIGURATIONS" ||
324
0
             arg == "PROPERTIES") {
325
0
    this->Status.SetError(
326
0
      cmStrCat("INSTALL called with old-style ", arg,
327
0
               " argument.  "
328
0
               "This script was generated with an older version of CMake.  "
329
0
               "Re-run this cmake version on your build tree."));
330
0
    this->Doing = DoingError;
331
0
  } else {
332
0
    return this->cmFileCopier::CheckKeyword(arg);
333
0
  }
334
0
  return true;
335
0
}
336
337
bool cmFileInstaller::CheckValue(std::string const& arg)
338
0
{
339
0
  switch (this->Doing) {
340
0
    case DoingType:
341
0
      if (!this->GetTargetTypeFromString(arg)) {
342
0
        this->Doing = DoingError;
343
0
      }
344
0
      break;
345
0
    case DoingRename:
346
0
      this->Rename = arg;
347
0
      break;
348
0
    default:
349
0
      return this->cmFileCopier::CheckValue(arg);
350
0
  }
351
0
  return true;
352
0
}
353
354
bool cmFileInstaller::GetTargetTypeFromString(std::string const& stype)
355
0
{
356
0
  if (stype == "EXECUTABLE") {
357
0
    this->InstallType = cmInstallType_EXECUTABLE;
358
0
  } else if (stype == "FILE") {
359
0
    this->InstallType = cmInstallType_FILES;
360
0
  } else if (stype == "PROGRAM") {
361
0
    this->InstallType = cmInstallType_PROGRAMS;
362
0
  } else if (stype == "STATIC_LIBRARY") {
363
0
    this->InstallType = cmInstallType_STATIC_LIBRARY;
364
0
  } else if (stype == "SHARED_LIBRARY") {
365
0
    this->InstallType = cmInstallType_SHARED_LIBRARY;
366
0
  } else if (stype == "MODULE") {
367
0
    this->InstallType = cmInstallType_MODULE_LIBRARY;
368
0
  } else if (stype == "DIRECTORY") {
369
0
    this->InstallType = cmInstallType_DIRECTORY;
370
0
  } else {
371
0
    this->Status.SetError(
372
0
      cmStrCat("Option TYPE given unknown value \"", stype, "\"."));
373
0
    return false;
374
0
  }
375
0
  return true;
376
0
}
377
378
bool cmFileInstaller::HandleInstallDestination()
379
0
{
380
0
  std::string& destination = this->Destination;
381
382
  // allow for / to be a valid destination
383
0
  if (destination.size() < 2 && destination != "/") {
384
0
    this->Status.SetError("called with inappropriate arguments. "
385
0
                          "No DESTINATION provided or .");
386
0
    return false;
387
0
  }
388
389
0
  std::string sdestdir;
390
0
  if (cmSystemTools::GetEnv("DESTDIR", sdestdir) && !sdestdir.empty()) {
391
0
    cmSystemTools::ConvertToUnixSlashes(sdestdir);
392
0
    char ch1 = destination[0];
393
0
    char ch2 = destination[1];
394
0
    char ch3 = 0;
395
0
    if (destination.size() > 2) {
396
0
      ch3 = destination[2];
397
0
    }
398
0
    int skip = 0;
399
0
    if (ch1 != '/') {
400
0
      int relative = 0;
401
0
      if (((ch1 >= 'a' && ch1 <= 'z') || (ch1 >= 'A' && ch1 <= 'Z')) &&
402
0
          ch2 == ':') {
403
        // Assume windows
404
        // let's do some destdir magic:
405
0
        skip = 2;
406
0
        if (ch3 != '/') {
407
0
          relative = 1;
408
0
        }
409
0
      } else {
410
0
        relative = 1;
411
0
      }
412
0
      if (relative) {
413
        // This is relative path on unix or windows. Since we are doing
414
        // destdir, this case does not make sense.
415
0
        this->Status.SetError(
416
0
          "called with relative DESTINATION. This "
417
0
          "does not make sense when using DESTDIR. Specify "
418
0
          "absolute path or remove DESTDIR environment variable.");
419
0
        return false;
420
0
      }
421
0
    } else {
422
0
      if (ch2 == '/') {
423
        // looks like a network path.
424
0
        std::string message =
425
0
          cmStrCat("called with network path DESTINATION. This "
426
0
                   "does not make sense when using DESTDIR. Specify local "
427
0
                   "absolute path or remove DESTDIR environment variable."
428
0
                   "\nDESTINATION=\n",
429
0
                   destination);
430
0
        this->Status.SetError(message);
431
0
        return false;
432
0
      }
433
0
    }
434
0
    destination = sdestdir + destination.substr(skip);
435
0
    this->DestDirLength = static_cast<int>(sdestdir.size());
436
0
  }
437
438
  // check if default dir creation permissions were set
439
0
  mode_t default_dir_mode_v = 0;
440
0
  mode_t* default_dir_mode = &default_dir_mode_v;
441
0
  if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
442
0
    return false;
443
0
  }
444
445
0
  if (this->InstallType != cmInstallType_DIRECTORY) {
446
0
    if (!cmSystemTools::FileExists(destination)) {
447
0
      if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
448
0
        std::string errstring =
449
0
          cmStrCat("cannot create directory: ", destination,
450
0
                   ". Maybe need administrative privileges.");
451
0
        this->Status.SetError(errstring);
452
0
        return false;
453
0
      }
454
0
    }
455
0
    if (!cmSystemTools::FileIsDirectory(destination)) {
456
0
      std::string errstring =
457
0
        cmStrCat("INSTALL destination: ", destination, " is not a directory.");
458
0
      this->Status.SetError(errstring);
459
0
      return false;
460
0
    }
461
0
  }
462
0
  return true;
463
0
}