Coverage Report

Created: 2026-02-09 06:05

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