Automatically set execute permissions on generated scripts that contain a shebang [POLYGLOT]

This commit is contained in:
Tabulo 2023-04-15 01:47:11 +02:00
parent 26ebbd8688
commit 9b3505e8a1
3 changed files with 94 additions and 3 deletions

View file

@ -239,9 +239,9 @@ class CdistObject:
lambda obj: os.path.join(obj.absolute_path, "state"))
source = fsproperty.FileListProperty(
lambda obj: os.path.join(obj.absolute_path, "source"))
code_local = fsproperty.FileStringProperty(
code_local = fsproperty.FileScriptProperty(
lambda obj: os.path.join(obj.base_path, obj.code_local_path))
code_remote = fsproperty.FileStringProperty(
code_remote = fsproperty.FileScriptProperty(
lambda obj: os.path.join(obj.base_path, obj.code_remote_path))
typeorder = fsproperty.FileListProperty(
lambda obj: os.path.join(obj.absolute_path, 'typeorder'))

68
cdist/util/filesystem.py Normal file
View file

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
#
# 2023 Tabulon (dev-cdist at tabulon.net)
#
# This file is part of cdist.
#
# cdist is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cdist is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with cdist. If not, see <http://www.gnu.org/licenses/>.
#
#
import os
import stat
def ensure_file_is_executable_by_all(path):
"""Ensure (and if needed, add) execute permissions
for everyone (user, group, others) on the given file
Similar to : chmod a+x <path>
"""
ensure_file_permissions(path, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
def ensure_file_permissions(path, permissions):
"""Ensure (and if needed, add) the given permissions
for the given filesystem object.
Similar to using '+' with chmod
"""
perm = os.stat(path).st_mode & 0o777 # only the last 3 bits relate to permissions
# If desired permissions were already set, don't meddle.
if ( (perm & permissions ) != permissions ):
os.chmod(path, perm | permissions)
# return a mask of desired permissions that are/were actually set
return os.stat(path).st_mode & 0o777 & permissions
def file_has_shebang(path):
"""Does the given file start with a shebang ?
"""
return read_from_file(path, size=2) == '#!'
def read_from_file(path, size=-1, ignore=None):
"""Read and return a number of bytes from the given file.
If size is '-1' (the default) the entire contents are returned.
"""
value = ""
try:
with open(path, "r") as fd:
value = fd.read(size)
except ignore:
pass
finally:
fd.close()
return value
def slurp_file(path, ignore=None):
"""Read and return the entire contents of a given file
"""
return read_from_file(path, size=-1, ignore=ignore)

View file

@ -23,7 +23,7 @@ import os
import collections
import cdist
import cdist.util.filesystem as fs
class AbsolutePathRequiredError(cdist.Error):
def __init__(self, path):
@ -319,3 +319,26 @@ class FileStringProperty(FileBasedProperty):
os.remove(path)
except EnvironmentError:
pass
class FileScriptProperty(FileStringProperty):
"""A property specially tailored for script text,
which stores its value in a file.
"""
# Descriptor Protocol
def __set__(self, instance, value):
super().__set__(instance, value)
# -------------------------------------------------------------------
# NOTE [tabulon@2023-03-31]: If the file starts with a shebang (#!),
# mark it as an executable (chmod a+x), so that exec.(local|remote)
# can decide to invoke it directly (instead of feeding it to /bin/sh)
# -------------------------------------------------------------------
# NOTE that this enables cdist to become completely language-agnostic,
# even with regard to code generated via (gencode-*) that end up being
# stored as a `FileScriptProperty`; since most Unix/Linux systems are
# able to detect the **shebang**
# -------------------------------------------------------------------
if value:
path = self._get_path(instance)
if fs.file_has_shebang(path):
fs.ensure_file_is_executable_by_all(path)