On 12/24/2013 05:46 AM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>

First will use setfacl to set the path search permission for user or
group, if failed then set search permission for others on the path.

Usage:
check_path_permission("/tmp/need/to/fix/path", username)
fix_path_permission("/tmp/need/to/fix/path", username)

Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com>
---
 src/kimchi/utils.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 115 insertions(+), 1 deletion(-)

diff --git a/src/kimchi/utils.py b/src/kimchi/utils.py
index bf2a760..e94aeb1 100644
--- a/src/kimchi/utils.py
+++ b/src/kimchi/utils.py
@@ -18,11 +18,15 @@
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 #

 import cherrypy
 import os
+import pwd
+import re
+import stat
+import subprocess


 from cherrypy.lib.reprconf import Parser
@@ -33,6 +37,7 @@ from kimchi import config

 kimchi_log = cherrypy.log.error_log

+
 def is_digit(value):
     if isinstance(value, int):
         return True
@@ -130,3 +135,112 @@ class RollbackContext(object):

     def prependDefer(self, func, *args, **kwargs):
         self._finally.insert(0, (func, args, kwargs))
+
+
+def name_uid(user):
+    return pwd.getpwnam(user).pw_uid
+
+
+def run_command(cmd):
+    try:
+        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE)
+        out, error = proc.communicate()
+
+        kimchi_log.debug("Run command '%s'", " ".join(cmd))
+        if out or error:
+            kimchi_log.debug("out:\n %s\nerror:\n %s", out, error)
+    except OSError:

From subprocess.Popen documentation a ValueError exception can also be raised

A ValueError will be raised if Popen is called with invalid arguments.

+        kimchi_log.debug("Failed to run command %s", " ".join(cmd))
+        return (None, None, None)
+    if proc.returncode != 0:
+        kimchi_log.debug("Cmd '%s' failed, error code: %s", cmd, error)
+    return (out, error, proc.returncode)
+
+
+def run_setfacl_set_x(path, user="qemu", group=None):

From my system I have:

alinefm@alinefm:~/kimchi$ getfacl /home/alinefm/kimchi
getfacl: Removing leading '/' from absolute path names
# file: home/alinefm/kimchi
# owner: alinefm
# group: alinefm
user::rwx
group::r-x
other::r-x

So I think the default value for 'group' parameter should be '' (an empty string)

Otherwise we will set

group:None:x

+    set_user = ["setfacl", "--modify", "user:%s:x" % user, path]
+    set_group = ["setfacl", "--modify", "group:%s:x" % group, path]
+    out, error, ret = run_command(set_user)
+    if ret != 0 and group:
+        out, error, ret = run_command(set_group)
+    return ret == 0
+
+
+def run_getfacl(path, user="qemu", group=None):

Same as I commented above about default value for 'group' parameter

+    cmd = ["getfacl", path]
+    out, error, ret = run_command(cmd)
+    if out and ret == 0:
+        res = re.findall("user:%s:..x" % user, out)

+        res = re.findall("group:%s:..x" % group,
+                         out) if group and not res else res

Please, do it in a common if statement (it is easier to ready)

+        return res != []
+    return False
+
+
+def set_x_on_path(path, who="other"):
+    S_IXWHO = ((stat.S_IXUSR if who == "user" else None) or
+               (stat.S_IXGRP if who == "group" else None) or
+               stat.S_IXOTH)
+    mode = os.stat(path).st_mode | S_IXWHO
+    os.chmod(path, mode)
+    ret = os.stat(path).st_mode == mode
+    if not ret:
+        kimchi_log.debug("faild to set +x attribute on %s", path)
+    return ret
+
+
+def get_x_on_path(path, uid):
+    try:
+        info = os.stat(path)
+        mode = info.st_mode
+        return ((uid == info.st_uid and mode & stat.S_IXUSR == stat.S_IXUSR) or
+                (uid == info.st_gid and mode & stat.S_IXGRP == stat.S_IXGRP) or
+                mode & stat.S_IXGRP == stat.S_IXOTH)
+    except OSError:
+        kimchi_log.debug("faild to get attribute on %s as user id: %s",
+                         path, uid)
+        return False
+
+
+def check_path_permission(path, user='qemu', group=None):
+    return get_x_on_path(path,
+                         name_uid(user)) or run_getfacl(path, user, group)
+
+
+def fix_path_permission(path, user='qemu'):
+    paths = []
+    while path:
+        paths.append(path)
+        if path == "/":
+            break
+        path = os.path.dirname(path)
+
+    paths.reverse()
+    for path in paths:
+        if not (check_path_permission(path) or
+                run_setfacl_set_x(path) or
+                set_x_on_path(path)):
+            return False

We can do it in a single for

# get all paths relates to 'path'
paths = path.split("/")

# remove /
paths = paths[1:]

path = "/"
for p in paths:
    path = os.path.join(path, p)
    if not (check_path_permission(path) or
            run_setfacl_set_x(path) or
            set_x_on_path(path))
        return False

+    return True
+
+
+def check_path_acl_permission(path, user='qemu', group=None):
+    return run_getfacl(path, user, group)
+
+

+def fix_path_acl_permission(path, user='qemu'):
+    paths = []
+    while path:
+        paths.append(path)
+        if path == "/":
+            break
+        path = os.path.dirname(path)
+
+    paths.reverse()
+    for path in paths:
+        if not (run_getfacl(path, user) or run_setfacl_set_x(path, user)):
+            kimchi_log.debug("faild to set on researchable permission on %s "
+                             "for user: %s", path, user)
+            return False

As I commented above, we can do it in a single for statement.

+    return True

All functions related to file permission could be moved to filepermission.py