file: Add CHMOD and CHMOD_RECURSE subcommands

Fixes: #21057

Signed-off-by: Sibi Siddharthan <sibisiddharthan.github@gmail.com>
This commit is contained in:
Sibi Siddharthan 2020-08-12 00:03:59 +05:30
parent 675be013e9
commit 7de60beddf
26 changed files with 312 additions and 0 deletions

View File

@ -30,6 +30,8 @@ Synopsis
file(`SIZE`_ <filename> <out-var>)
file(`READ_SYMLINK`_ <linkname> <out-var>)
file(`CREATE_LINK`_ <original> <linkname> [...])
file(`CHMOD`_ <files>... <directories>... PERMISSIONS <permissions>... [...])
file(`CHMOD_RECURSE`_ <files>... <directories>... PERMISSIONS <permissions>... [...])
`Path Conversion`_
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,
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
^^^^^^^^^^^^^^^

View File

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

View File

@ -30,6 +30,7 @@
#include "cmArgumentParser.h"
#include "cmCryptoHash.h"
#include "cmExecutionStatus.h"
#include "cmFSPermissions.h"
#include "cmFileCopier.h"
#include "cmFileInstaller.h"
#include "cmFileLockPool.h"
@ -3160,6 +3161,163 @@ bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
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
bool cmFileCommand(std::vector<std::string> const& args,
@ -3216,6 +3374,8 @@ bool cmFileCommand(std::vector<std::string> const& args,
{ "CONFIGURE"_s, HandleConfigureCommand },
{ "ARCHIVE_CREATE"_s, HandleArchiveCreateCommand },
{ "ARCHIVE_EXTRACT"_s, HandleArchiveExtractCommand },
{ "CHMOD"_s, HandleChmodCommand },
{ "CHMOD_RECURSE"_s, HandleChmodRecurseCommand },
};
return subcommand(args[0], args, status);

View File

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

View File

@ -0,0 +1 @@
1

View File

@ -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\)

View File

@ -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)

View File

@ -0,0 +1 @@
1

View File

@ -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\)

View File

@ -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)

View File

@ -0,0 +1 @@
1

View File

@ -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\)

View File

@ -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)

View File

@ -0,0 +1 @@
1

View File

@ -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\)

View File

@ -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)

View File

@ -0,0 +1 @@
1

View File

@ -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\)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1 @@
1

View File

@ -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\)

View File

@ -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)

View File

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

View File

@ -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()