Bläddra i källkod

file: Add CHMOD and CHMOD_RECURSE subcommands

Fixes: #21057

Signed-off-by: Sibi Siddharthan <[email protected]>
Sibi Siddharthan 5 år sedan
förälder
incheckning
7de60beddf
26 ändrade filer med 312 tillägg och 0 borttagningar
  1. 47 0
      Help/command/file.rst
  2. 5 0
      Help/release/dev/file-CHMOD.rst
  3. 160 0
      Source/cmFileCommand.cxx
  4. 1 0
      Tests/RunCMake/CMakeLists.txt
  5. 1 0
      Tests/RunCMake/file-CHMOD/CHMOD-all-perms-result.txt
  6. 5 0
      Tests/RunCMake/file-CHMOD/CHMOD-all-perms-stderr.txt
  7. 6 0
      Tests/RunCMake/file-CHMOD/CHMOD-all-perms.cmake
  8. 1 0
      Tests/RunCMake/file-CHMOD/CHMOD-invalid-path-result.txt
  9. 6 0
      Tests/RunCMake/file-CHMOD/CHMOD-invalid-path-stderr.txt
  10. 4 0
      Tests/RunCMake/file-CHMOD/CHMOD-invalid-path.cmake
  11. 1 0
      Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms-result.txt
  12. 4 0
      Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms-stderr.txt
  13. 5 0
      Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms.cmake
  14. 1 0
      Tests/RunCMake/file-CHMOD/CHMOD-no-keyword-result.txt
  15. 4 0
      Tests/RunCMake/file-CHMOD/CHMOD-no-keyword-stderr.txt
  16. 5 0
      Tests/RunCMake/file-CHMOD/CHMOD-no-keyword.cmake
  17. 1 0
      Tests/RunCMake/file-CHMOD/CHMOD-no-perms-result.txt
  18. 4 0
      Tests/RunCMake/file-CHMOD/CHMOD-no-perms-stderr.txt
  19. 5 0
      Tests/RunCMake/file-CHMOD/CHMOD-no-perms.cmake
  20. 5 0
      Tests/RunCMake/file-CHMOD/CHMOD-ok.cmake
  21. 6 0
      Tests/RunCMake/file-CHMOD/CHMOD-override.cmake
  22. 1 0
      Tests/RunCMake/file-CHMOD/CHMOD-write-only-result.txt
  23. 6 0
      Tests/RunCMake/file-CHMOD/CHMOD-write-only-stderr.txt
  24. 6 0
      Tests/RunCMake/file-CHMOD/CHMOD-write-only.cmake
  25. 3 0
      Tests/RunCMake/file-CHMOD/CMakeLists.txt
  26. 19 0
      Tests/RunCMake/file-CHMOD/RunCMakeTest.cmake

+ 47 - 0
Help/command/file.rst

@@ -30,6 +30,8 @@ Synopsis
     file(`SIZE`_ <filename> <out-var>)
     file(`SIZE`_ <filename> <out-var>)
     file(`READ_SYMLINK`_ <linkname> <out-var>)
     file(`READ_SYMLINK`_ <linkname> <out-var>)
     file(`CREATE_LINK`_ <original> <linkname> [...])
     file(`CREATE_LINK`_ <original> <linkname> [...])
+    file(`CHMOD`_ <files>... <directories>... PERMISSIONS <permissions>... [...])
+    file(`CHMOD_RECURSE`_ <files>... <directories>... PERMISSIONS <permissions>... [...])
 
 
   `Path Conversion`_
   `Path Conversion`_
     file(`RELATIVE_PATH`_ <out-var> <directory> <file>)
     file(`RELATIVE_PATH`_ <out-var> <directory> <file>)
@@ -741,6 +743,51 @@ creating the link fails.  It can be useful for handling situations such as
 ``<original>`` and ``<linkname>`` being on different drives or mount points,
 ``<original>`` and ``<linkname>`` being on different drives or mount points,
 which would make them unable to support a hard link.
 which would make them unable to support a hard link.
 
 
+.. _CHMOD:
+
+.. code-block:: cmake
+
+  file(CHMOD <files>... <directories>... [PERMISSIONS <permissions>...]
+      [FILE_PERMISSIONS <permissions>...]
+      [DIRECTORY_PERMISSIONS <permissions>...])
+
+Set the permissions for the ``<files>...`` and ``<directories>...`` specified.
+Valid permissions are  ``OWNER_READ``, ``OWNER_WRITE``, ``OWNER_EXECUTE``,
+``GROUP_READ``, ``GROUP_WRITE``, ``GROUP_EXECUTE``, ``WORLD_READ``,
+``WORLD_WRITE``, ``WORLD_EXECUTE``.
+
+Valid combination of keywords are:
+
+``PERMISSIONS``
+  all items are changed
+
+``FILE_PERMISSIONS``
+  only files are changed
+
+``DIRECTORY_PERMISSIONS``
+  only directories are changed
+
+``PERMISSIONS`` and ``FILE_PERMISSIONS``
+  ``FILE_PERMISSIONS`` overrides ``PERMISSIONS`` for files
+
+``PERMISSIONS`` and ``DIRECTORY_PERMISSIONS``
+  ``DIRECTORY_PERMISSIONS`` overrides ``PERMISSIONS`` for directories
+
+``FILE_PERMISSIONS`` and ``DIRECTORY_PERMISSIONS``
+  use ``FILE_PERMISSIONS`` for files and ``DIRECTORY_PERMISSIONS`` for
+  directories
+
+
+.. _CHMOD_RECURSE:
+
+.. code-block:: cmake
+
+  file(CHMOD_RECURSE <files>... <directories>... PERMISSIONS <permissions>...
+       FILE_PERMISSIONS <permissions>... DIRECTORY_PERMISSIONS <permissions>...)
+
+Same as `CHMOD`_, but change the permissions of files and directories present in
+the ``<directories>..`` recursively.
+
 Path Conversion
 Path Conversion
 ^^^^^^^^^^^^^^^
 ^^^^^^^^^^^^^^^
 
 

+ 5 - 0
Help/release/dev/file-CHMOD.rst

@@ -0,0 +1,5 @@
+file-CHMOD
+----------
+
+* Add :command:`file(CHMOD)` and :command:`file(CHMOD_RECURSE)` to
+  set permissions of files and directories.

+ 160 - 0
Source/cmFileCommand.cxx

@@ -30,6 +30,7 @@
 #include "cmArgumentParser.h"
 #include "cmArgumentParser.h"
 #include "cmCryptoHash.h"
 #include "cmCryptoHash.h"
 #include "cmExecutionStatus.h"
 #include "cmExecutionStatus.h"
+#include "cmFSPermissions.h"
 #include "cmFileCopier.h"
 #include "cmFileCopier.h"
 #include "cmFileInstaller.h"
 #include "cmFileInstaller.h"
 #include "cmFileLockPool.h"
 #include "cmFileLockPool.h"
@@ -3160,6 +3161,163 @@ bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
   return true;
   return true;
 }
 }
 
 
+bool ValidateAndConvertPermissions(const std::vector<std::string>& permissions,
+                                   mode_t& perms, cmExecutionStatus& status)
+{
+  for (const auto& i : permissions) {
+    if (!cmFSPermissions::stringToModeT(i, perms)) {
+      status.SetError(i + " is an invalid permission specifier");
+      cmSystemTools::SetFatalErrorOccured();
+      return false;
+    }
+  }
+  return true;
+}
+
+bool SetPermissions(const std::string& filename, const mode_t& perms,
+                    cmExecutionStatus& status)
+{
+  if (!cmSystemTools::SetPermissions(filename, perms)) {
+    status.SetError("Failed to set permissions for " + filename);
+    cmSystemTools::SetFatalErrorOccured();
+    return false;
+  }
+  return true;
+}
+
+bool HandleChmodCommandImpl(std::vector<std::string> const& args, bool recurse,
+                            cmExecutionStatus& status)
+{
+  mode_t perms = 0;
+  mode_t fperms = 0;
+  mode_t dperms = 0;
+  cmsys::Glob globber;
+
+  globber.SetRecurse(recurse);
+  globber.SetRecurseListDirs(recurse);
+
+  struct Arguments
+  {
+    std::vector<std::string> Permissions;
+    std::vector<std::string> FilePermissions;
+    std::vector<std::string> DirectoryPermissions;
+  };
+
+  static auto const parser =
+    cmArgumentParser<Arguments>{}
+      .Bind("PERMISSIONS"_s, &Arguments::Permissions)
+      .Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions)
+      .Bind("DIRECTORY_PERMISSIONS"_s, &Arguments::DirectoryPermissions);
+
+  std::vector<std::string> pathEntries;
+  std::vector<std::string> keywordsMissingValues;
+  Arguments parsedArgs = parser.Parse(cmMakeRange(args).advance(1),
+                                      &pathEntries, &keywordsMissingValues);
+
+  // check validity of arguments
+  if (parsedArgs.Permissions.empty() && parsedArgs.FilePermissions.empty() &&
+      parsedArgs.DirectoryPermissions.empty()) // no permissions given
+  {
+    status.SetError("No permissions given");
+    cmSystemTools::SetFatalErrorOccured();
+    return false;
+  }
+
+  if (!parsedArgs.Permissions.empty() && !parsedArgs.FilePermissions.empty() &&
+      !parsedArgs.DirectoryPermissions.empty()) // all keywords are used
+  {
+    status.SetError("Remove either PERMISSIONS or FILE_PERMISSIONS or "
+                    "DIRECTORY_PERMISSIONS from the invocation");
+    cmSystemTools::SetFatalErrorOccured();
+    return false;
+  }
+
+  if (!keywordsMissingValues.empty()) {
+    for (const auto& i : keywordsMissingValues) {
+      status.SetError(i + " is not given any arguments");
+      cmSystemTools::SetFatalErrorOccured();
+    }
+    return false;
+  }
+
+  // validate permissions
+  bool validatePermissions =
+    ValidateAndConvertPermissions(parsedArgs.Permissions, perms, status) &&
+    ValidateAndConvertPermissions(parsedArgs.FilePermissions, fperms,
+                                  status) &&
+    ValidateAndConvertPermissions(parsedArgs.DirectoryPermissions, dperms,
+                                  status);
+  if (!validatePermissions) {
+    return false;
+  }
+
+  std::vector<std::string> allPathEntries;
+
+  if (recurse) {
+    std::vector<std::string> tempPathEntries;
+    for (const auto& i : pathEntries) {
+      if (cmSystemTools::FileIsDirectory(i)) {
+        globber.FindFiles(i + "/*");
+        tempPathEntries = globber.GetFiles();
+        allPathEntries.insert(allPathEntries.end(), tempPathEntries.begin(),
+                              tempPathEntries.end());
+        allPathEntries.emplace_back(i);
+      } else {
+        allPathEntries.emplace_back(i); // We validate path entries below
+      }
+    }
+  } else {
+    allPathEntries = std::move(pathEntries);
+  }
+
+  // chmod
+  for (const auto& i : allPathEntries) {
+    if (!(cmSystemTools::FileExists(i) || cmSystemTools::FileIsDirectory(i))) {
+      status.SetError(cmStrCat("does not exist:\n  ", i));
+      cmSystemTools::SetFatalErrorOccured();
+      return false;
+    }
+
+    if (cmSystemTools::FileExists(i, true)) {
+      bool success = true;
+      const mode_t& filePermissions =
+        parsedArgs.FilePermissions.empty() ? perms : fperms;
+      if (filePermissions) {
+        success = SetPermissions(i, filePermissions, status);
+      }
+      if (!success) {
+        return false;
+      }
+    }
+
+    else if (cmSystemTools::FileIsDirectory(i)) {
+      bool success = true;
+      const mode_t& directoryPermissions =
+        parsedArgs.DirectoryPermissions.empty() ? perms : dperms;
+      if (directoryPermissions) {
+        success = SetPermissions(i, directoryPermissions, status);
+      }
+      if (!success) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+bool HandleChmodCommand(std::vector<std::string> const& args,
+                        cmExecutionStatus& status)
+{
+  return HandleChmodCommandImpl(args, false, status);
+}
+
+bool HandleChmodRecurseCommand(std::vector<std::string> const& args,
+                               cmExecutionStatus& status)
+{
+  return HandleChmodCommandImpl(args, true, status);
+}
+
 } // namespace
 } // namespace
 
 
 bool cmFileCommand(std::vector<std::string> const& args,
 bool cmFileCommand(std::vector<std::string> const& args,
@@ -3216,6 +3374,8 @@ bool cmFileCommand(std::vector<std::string> const& args,
     { "CONFIGURE"_s, HandleConfigureCommand },
     { "CONFIGURE"_s, HandleConfigureCommand },
     { "ARCHIVE_CREATE"_s, HandleArchiveCreateCommand },
     { "ARCHIVE_CREATE"_s, HandleArchiveCreateCommand },
     { "ARCHIVE_EXTRACT"_s, HandleArchiveExtractCommand },
     { "ARCHIVE_EXTRACT"_s, HandleArchiveExtractCommand },
+    { "CHMOD"_s, HandleChmodCommand },
+    { "CHMOD_RECURSE"_s, HandleChmodRecurseCommand },
   };
   };
 
 
   return subcommand(args[0], args, status);
   return subcommand(args[0], args, status);

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -322,6 +322,7 @@ add_RunCMake_test(ctest_update)
 add_RunCMake_test(ctest_upload)
 add_RunCMake_test(ctest_upload)
 add_RunCMake_test(ctest_fixtures)
 add_RunCMake_test(ctest_fixtures)
 add_RunCMake_test(file)
 add_RunCMake_test(file)
+add_RunCMake_test(file-CHMOD)
 add_RunCMake_test(find_file)
 add_RunCMake_test(find_file)
 add_RunCMake_test(find_library -DCYGWIN=${CYGWIN})
 add_RunCMake_test(find_library -DCYGWIN=${CYGWIN})
 add_RunCMake_test(find_package)
 add_RunCMake_test(find_package)

+ 1 - 0
Tests/RunCMake/file-CHMOD/CHMOD-all-perms-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
Tests/RunCMake/file-CHMOD/CHMOD-all-perms-stderr.txt

@@ -0,0 +1,5 @@
+CMake Error at CHMOD-all-perms\.cmake:[0-9]+ \(file\):
+  file Remove either PERMISSIONS or FILE_PERMISSIONS or DIRECTORY_PERMISSIONS
+  from the invocation
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 6 - 0
Tests/RunCMake/file-CHMOD/CHMOD-all-perms.cmake

@@ -0,0 +1,6 @@
+file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS OWNER_READ
+  FILE_PERMISSIONS OWNER_READ DIRECTORY_PERMISSIONS OWNER_READ)

+ 1 - 0
Tests/RunCMake/file-CHMOD/CHMOD-invalid-path-result.txt

@@ -0,0 +1 @@
+1

+ 6 - 0
Tests/RunCMake/file-CHMOD/CHMOD-invalid-path-stderr.txt

@@ -0,0 +1,6 @@
+CMake Error at CHMOD-invalid-path\.cmake:[0-9]+ \(file\):
+  file does not exist:
+
+  .*/chmod-tests/I_dont_exist
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 4 - 0
Tests/RunCMake/file-CHMOD/CHMOD-invalid-path.cmake

@@ -0,0 +1,4 @@
+file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/I_dont_exist PERMISSIONS OWNER_READ)

+ 1 - 0
Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms-stderr.txt

@@ -0,0 +1,4 @@
+CMake Error at CHMOD-invalid-perms\.cmake:[0-9]+ \(file\):
+  file INVALID_PERMISSION is an invalid permission specifier
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 5 - 0
Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms.cmake

@@ -0,0 +1,5 @@
+file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS INVALID_PERMISSION)

+ 1 - 0
Tests/RunCMake/file-CHMOD/CHMOD-no-keyword-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/file-CHMOD/CHMOD-no-keyword-stderr.txt

@@ -0,0 +1,4 @@
+CMake Error at CHMOD-no-keyword\.cmake:[0-9]+ \(file\):
+  file No permissions given
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 5 - 0
Tests/RunCMake/file-CHMOD/CHMOD-no-keyword.cmake

@@ -0,0 +1,5 @@
+file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)

+ 1 - 0
Tests/RunCMake/file-CHMOD/CHMOD-no-perms-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
Tests/RunCMake/file-CHMOD/CHMOD-no-perms-stderr.txt

@@ -0,0 +1,4 @@
+CMake Error at CHMOD-no-perms\.cmake:[0-9]+ \(file\):
+  file No permissions given
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 5 - 0
Tests/RunCMake/file-CHMOD/CHMOD-no-perms.cmake

@@ -0,0 +1,5 @@
+file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS)

+ 5 - 0
Tests/RunCMake/file-CHMOD/CHMOD-ok.cmake

@@ -0,0 +1,5 @@
+file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS OWNER_READ)

+ 6 - 0
Tests/RunCMake/file-CHMOD/CHMOD-override.cmake

@@ -0,0 +1,6 @@
+file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS OWNER_READ
+    FILE_PERMISSIONS OWNER_READ OWNER_WRITE)

+ 1 - 0
Tests/RunCMake/file-CHMOD/CHMOD-write-only-result.txt

@@ -0,0 +1 @@
+1

+ 6 - 0
Tests/RunCMake/file-CHMOD/CHMOD-write-only-stderr.txt

@@ -0,0 +1,6 @@
+CMake Error at CHMOD-write-only\.cmake:[0-9]+ \(file\):
+  file failed to open for reading \(Permission denied\):
+
+    .*/chmod-tests/a
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 6 - 0
Tests/RunCMake/file-CHMOD/CHMOD-write-only.cmake

@@ -0,0 +1,6 @@
+file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
+
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a "CONTENT")
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS OWNER_WRITE)
+file(READ ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a content)

+ 3 - 0
Tests/RunCMake/file-CHMOD/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.0)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 19 - 0
Tests/RunCMake/file-CHMOD/RunCMakeTest.cmake

@@ -0,0 +1,19 @@
+include(RunCMake)
+
+run_cmake(CHMOD-no-perms)
+run_cmake(CHMOD-no-keyword)
+run_cmake(CHMOD-all-perms)
+run_cmake(CHMOD-invalid-perms)
+run_cmake(CHMOD-invalid-path)
+run_cmake(CHMOD-ok)
+run_cmake(CHMOD-override)
+
+if(UNIX)
+  execute_process(COMMAND id -u $ENV{USER}
+    OUTPUT_VARIABLE uid
+    OUTPUT_STRIP_TRAILING_WHITESPACE)
+endif()
+
+if(NOT WIN32 AND NOT "${uid}" STREQUAL "0")
+  run_cmake(CHMOD-write-only)
+endif()