Handling lack of permissions/groups with pathlib's rglob - python

I am attempting to use pathlib to recursively glob and/or find files. File permissions and groups are all over the place due to poor management of the filesystem which is out of my control.
The problem occurs when I lack both permissions and group membership to a directory that rglob attempts to descend into. Rglob throws a KeyError and then a PermissionError and finally stops entirely. I see no way to recover gracefully from this and continue globbing.
The behavior that I want is for rglob to skip directories that I don't have permissions on and to generate the list of everything that it saw/had permissions on. The all or nothing nature isn't going to get me very far in this particular case because I'm almost guaranteed to have bad permissions on some directory or another on every run.
More specifics:
Python: 3.4.1 compiled from source for linux
Filesystem I am globbing on: automounted nfs share
How to reproduce:
mkdir /tmp/path_test && cd /tmp/path_test && mkdir dir1 dir2 dir2/dir3 && touch dir1/file1 dir1/file2 dir2/file1 dir2/file2 dir2/dir3/file1
su
chmod 700 dir2/dir3/
chown root:root dir2/dir3/
exit
python3.4.1
from pathlib import Path
p = Path('/tmp/path_test')
for x in p.rglob('*') : print(x)

At first I tried to manually iterate though the results of rglob() like this:
from pathlib import Path
p = Path('/tmp/path_test')
files = p.rglob('*')
while True:
try:
f = next(files)
except (KeyError, PermissionError):
continue
except StopIteration:
break
print(f)
But it looks like next(files) throws a StopIteration after the first PermissionError, so I don't get any files after that.
You may be better off using os.walk().

Related

How do I create an automated test for my python script?

I am fairly new to programming and currently working on a python script. It is supposed to gather all the files and directories that are given as paths inside the program and copy them to a new location that the user can choose as an input.
import shutil
import os
from pathlib import Path
import argparse
src = [ [insert name of destination directory, insert path of file/directory that
should be copied ]
]
x = input("Please choose a destination path\n>>>")
if not os.path.exists(x):
os.makedirs(x)
print("Directory was created")
else:
print("Existing directory was chosen")
dest = Path(x.strip())
for pfad in src:
if os.path.isdir(pfad[1]):
shutil.copytree(pfad[1], dest / pfad[0])
elif os.path.isfile(pfad[1]):
pfad1 = Path(dest / pfad[0])
if not os.path.exists(pfad1):
os.makedirs(pfad1)
shutil.copy(pfad[1], dest / pfad[0])
else:
print("An error occured")
print(pfad)
print("All files and directories have been copied!")
input()
The script itself is working just fine. The problem is that I want write a test that automatically test the code each time I push it to my GitLab repository. I have been browsing through the web for quite some time now but wasnt able to find a good explanation on how to approach creating a test for a script like this.
I would be extremely thankful for any kind of feedback or hints to helpful resources.
First, you should write a test that you can run in command line.
I suggest you use the argparse module to pass source and destination directories, so that you can run thescript.py source_dir dest_dir without human interaction.
Then, as you have a test you can run, you need to add a .gitlab-ci.yml to the root of the project so that you can use the gitlab CI.
If you never used the gitlab CI, you need to start here: https://docs.gitlab.com/ee/ci/quick_start/
After that, you'll be able to add a job to your .gitlab-ci.yml, so that a runner with python installed will run the test. If you don't understad the bold terms of the previous sentence, you need to understant Gitlab CI first.

Deleting Folders on drive

I am trying to delete a collection of folders on my drive. These directories are not empty. I have come up with a solution as follows:
import shutil
import os
path = "main/"
folderList = ['Blah', 'Blah', 'Blah'];
print ("Cleaning Project at %s" % path)
for c in folderList:
strippedPath = (path + c).strip("\n")
print ("Cleaning path " + strippedPath)
if os.path.exists(strippedPath):
try:
shutil.rmtree(strippedPath)
except OSError as why:
pass
print ("Done Cleaning Project")
The problem is that without the try / catch I get a error that says
PermissionError: [WinError 5] Access is denied: 'PathToFileHere'
Pressing the delete key on windows will work fine. Can someone provide me a command that will remove this directory without errors?
First you should avoid to silently swallow an Exception, but at least print or log it. But many thing can happen to a file, they may have Hidden, System or ReadOnly attributes. The current user may not have permissions on files but only on the containing folder. As Python is multi-platform its high-level commands can be less optimized for a particular OS (Windows in your case) than native ones.
You should first try to confirm that in a cmd window, the command rd /s folder correctly remove the folder that shutil.rmtree fails to delete, and if yes ask python so execute it vie the subprocess module :
subprocess.call("rd /s/q " + strippedPath)

Python. Unchroot directory

I chrooted directory using following commands:
os.chroot("/mydir")
How to return to directory to previous - before chrooting?
Maybe it is possible to unchroot directory?
SOLUTION:
Thanks to Phihag. I found a solution. Simple example:
import os
os.mkdir('/tmp/new_dir')
dir1 = os.open('.', os.O_RDONLY)
dir2 = os.open('/tmp/new_dir', os.O_RDONLY)
os.getcwd() # we are in 'tmp'
os.chroot('/tmp/new_dir') # chrooting 'new_dir' directory
os.fchdir(dir2)
os.getcwd() # we are in chrooted directory, but path is '/'. It's OK.
os.fchdir(dir1)
os.getcwd() # we came back to not chrooted 'tmp' directory
os.close(dir1)
os.close(dir2)
More info
If you haven't changed your current working directory, you can simply call
os.chroot('../..') # Add '../' as needed
Of course, this requires the CAP_SYS_CHROOT capability (usually only given to root).
If you have changed your working directory, you can still escape, but it's harder:
os.mkdir('tmp')
os.chroot('tmp')
os.chdir('../../') # Add '../' as needed
os.chroot('.')
If chroot changes the current working directory, you can get around that by opening the directory, and using fchdir to go back.
Of course, if you intend to go out of a chroot in the course of a normal program (i.e. not a demonstration or security exploit), you should rethink your program. First of all, do you really need to escape the chroot? Why can't you just copy the required info into it beforehand?
Also, consider using a second process that stays outside of the chroot and answers to the requests of the chrooted one.

Getting admin password while copy file using shutil.copy?

I m using shutil.copy from python to copy a list of files. But when i copy the files to /usr/lib/ location, i m getting permission denied as i need to be an administrator to do that.
So How could i copy files with admin permission or
how could i get the admin password from the user to copy the files?
Ideas would be appreciated
Make the user run the script as an administrator:
sudo python-script.py
Unix already has authentication and password management. You don't need to write your own, and there will doubtless be security bugs if you try.
To add to what katrielalex said: you can make the script run itself via sudo if you want. Here's a proof of concept:
import sys, os, subprocess
def do_root_stuff():
print('Trying to list /root:')
for filename in os.listdir('/root'):
print(filename)
if __name__ == '__main__':
print('Running as UID %d:' % os.geteuid())
if os.geteuid() == 0:
do_root_stuff()
else:
subprocess.check_call(['sudo', sys.executable] + sys.argv)
Start your program with a user that is allowed to write there. For example login to root first (su) or run the script with sudo myscript.py.
I came her looking for an alternative way of doing things.
A quick and dirty hack I use, because I don't want my whole script to run as root:
try:
shutil.os.remove(file1)
except PermissionError:
shutil.os.system('sudo chown $USER "{}"'.format(file1))
# try again
try:
shutil.os.remove(file1)
except:
print('Giving up on'.format(file1))
Which is probably not completely error-prone, but they work for the quick scripts I hack together
Oops, I saw you were asking for copy permissions.
But you could apply the same logic
try:
shutil.os.copy(file1,destination)
except PermissionError:
shutil.os.system('sudo cp "{}" "{}"'.format(file1,destination))

mkdir -p functionality in Python [duplicate]

This question already has answers here:
How can I safely create a directory (possibly including intermediate directories)?
(28 answers)
Closed 5 years ago.
Is there a way to get functionality similar to mkdir -p on the shell from within Python. I am looking for a solution other than a system call. I am sure the code is less than 20 lines, and I am wondering if someone has already written it?
For Python ≥ 3.5, use pathlib.Path.mkdir:
import pathlib
pathlib.Path("/tmp/path/to/desired/directory").mkdir(parents=True, exist_ok=True)
The exist_ok parameter was added in Python 3.5.
For Python ≥ 3.2, os.makedirs has an optional third argument exist_ok that, when True, enables the mkdir -p functionality—unless mode is provided and the existing directory has different permissions than the intended ones; in that case, OSError is raised as previously:
import os
os.makedirs("/tmp/path/to/desired/directory", exist_ok=True)
For even older versions of Python, you can use os.makedirs and ignore the error:
import errno
import os
def mkdir_p(path):
try:
os.makedirs(path)
except OSError as exc: # Python ≥ 2.5
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
# possibly handle other errno cases here, otherwise finally:
else:
raise
In Python >=3.2, that's
os.makedirs(path, exist_ok=True)
In earlier versions, use #tzot's answer.
This is easier than trapping the exception:
import os
if not os.path.exists(...):
os.makedirs(...)
Disclaimer This approach requires two system calls which is more susceptible to race conditions under certain environments/conditions. If you're writing something more sophisticated than a simple throwaway script running in a controlled environment, you're better off going with the accepted answer that requires only one system call.
UPDATE 2012-07-27
I'm tempted to delete this answer, but I think there's value in the comment thread below. As such, I'm converting it to a wiki.
Recently, I found this distutils.dir_util.mkpath:
In [17]: from distutils.dir_util import mkpath
In [18]: mkpath('./foo/bar')
Out[18]: ['foo', 'foo/bar']
With Pathlib from python3 standard library:
Path(mypath).mkdir(parents=True, exist_ok=True)
If parents is true, any missing parents of this path are created as
needed; they are created with the default permissions without taking
mode into account (mimicking the POSIX mkdir -p command).
If exist_ok is false (the default), an FileExistsError is raised if
the target directory already exists.
If exist_ok is true, FileExistsError exceptions will be ignored (same
behavior as the POSIX mkdir -p command), but only if the last path
component is not an existing non-directory file.
Changed in version 3.5: The exist_ok parameter was added.
mkdir -p gives you an error if the file already exists:
$ touch /tmp/foo
$ mkdir -p /tmp/foo
mkdir: cannot create directory `/tmp/foo': File exists
So a refinement to the previous suggestions would be to re-raise the exception if os.path.isdir returns False (when checking for errno.EEXIST).
(Update) See also this highly similar question; I agree with the accepted answer (and caveats) except I would recommend os.path.isdir instead of os.path.exists.
(Update) Per a suggestion in the comments, the full function would look like:
import os
def mkdirp(directory):
if not os.path.isdir(directory):
os.makedirs(directory)
As mentioned in the other solutions, we want to be able to hit the file system once while mimicking the behaviour of mkdir -p. I don't think that this is possible to do, but we should get as close as possible.
Code first, explanation later:
import os
import errno
def mkdir_p(path):
""" 'mkdir -p' in Python """
try:
os.makedirs(path)
except OSError as exc: # Python >2.5
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
else:
raise
As the comments to #tzot's answer indicate there are problems with checking whether you can create a directory before you actually create it: you can't tell whether someone has changed the file system in the meantime. That also fits in with Python's style of asking for forgiveness, not permission.
So the first thing we should do is try to make the directory, then if it goes wrong, work out why.
As Jacob Gabrielson points out, one of the cases we must look for is the case where a file already exists where we are trying to put the directory.
With mkdir -p:
$ touch /tmp/foo
$ mkdir -p /tmp/foo
mkdir: cannot create directory '/tmp/foo': File exists
The analogous behaviour in Python would be to raise an exception.
So we have to work out if this was the case. Unfortunately, we can't. We get the same error message back from makedirs whether a directory exists (good) or a file exists preventing the creation of the directory (bad).
The only way to work out what happened is to inspect the file system again to see if there is a directory there. If there is, then return silently, otherwise raise the exception.
The only problem is that the file system may be in a different state now than when makedirs was called. eg: a file existed causing makedirs to fail, but now a directory is in its place. That doesn't really matter that much, because the the function will only exit silently without raising an exception when at the time of the last file system call the directory existed.
I think Asa's answer is essentially correct, but you could extend it a little to act more like mkdir -p, either:
import os
def mkdir_path(path):
if not os.access(path, os.F_OK):
os.mkdirs(path)
or
import os
import errno
def mkdir_path(path):
try:
os.mkdirs(path)
except os.error, e:
if e.errno != errno.EEXIST:
raise
These both handle the case where the path already exists silently but let other errors bubble up.
Function declaration;
import os
def mkdir_p(filename):
try:
folder=os.path.dirname(filename)
if not os.path.exists(folder):
os.makedirs(folder)
return True
except:
return False
usage :
filename = "./download/80c16ee665c8/upload/backup/mysql/2014-12-22/adclient_sql_2014-12-22-13-38.sql.gz"
if (mkdir_p(filename):
print "Created dir :%s" % (os.path.dirname(filename))
import os
import tempfile
path = tempfile.mktemp(dir=path)
os.makedirs(path)
os.rmdir(path)
I've had success with the following personally, but my function should probably be called something like 'ensure this directory exists':
def mkdirRecursive(dirpath):
import os
if os.path.isdir(dirpath): return
h,t = os.path.split(dirpath) # head/tail
if not os.path.isdir(h):
mkdirRecursive(h)
os.mkdir(join(h,t))
# end mkdirRecursive
import os
from os.path import join as join_paths
def mk_dir_recursive(dir_path):
if os.path.isdir(dir_path):
return
h, t = os.path.split(dir_path) # head/tail
if not os.path.isdir(h):
mk_dir_recursive(h)
new_path = join_paths(h, t)
if not os.path.isdir(new_path):
os.mkdir(new_path)
based on #Dave C's answer but with a bug fixed where part of the tree already exists

Categories

Resources