copy file, keep permissions and owner - python

The docs of shutil tells me:
Even the higher-level file copying functions (shutil.copy(), shutil.copy2()) can’t copy all file metadata. On POSIX platforms, this means that file owner and group are lost as well as ACLs
How can I keep the file owner and group if I need to copy a file in python?
The process runs on linux as root.
Update: We don't use ACLs. We only need to keep the stuff which is preserved with tools like tar and rsync.

You perhaps could use os.stat to get the guid and uid like in this answer and then reset the uid and guid after coping using os.chown.

I did it this way:
import os
import stat
import shutil
def copyComplete(source, target):
# copy content, stat-info (mode too), timestamps...
shutil.copy2(source, target)
# copy owner and group
st = os.stat(source)
os.chown(target, st.st_uid, st.st_gid)

You can use the subprocess module:
from subprocess import Popen
p = Popen(['cp','-p','--preserve',src,dest])
p.wait()

See Keeping fileowner and permissions after copying file in C on how cp itself does this, then just replicate its logic. Python has all the system calls mentioned in its os module.

I suggest you use the OS and subprocess modules. This will only work in Unix, but it should work well. Use the os.fchown to change file ownership, and subprocess.Popen() to pipe ls -l into a variable to read ownership. For one file, the permissions reader would look like this:
import os
import subprocess
def readfile(filepath):
process = subprocess.Popen(["ls","-l"],stdout=subprocess.PIPE)
output = process.communicate()[0]
output = output.split()
return (output[0],output[1]) #insert into this tuple the indexing for the words you want from ls -l
and for the uid setter (just a wrapper for os function):
def setuid(filepath,uid,gid):
os.chown(filepath,uid,gid)

as #Thom Wiggers 's answer
os.stat get uid,gid
os.chmod set uid,gid
demo code:
import os
st = os.stat(src_path)
os.chown(dst_path, st.st_uid, st.st_gid)
check except:
import os
def copy_owner_group(src, dst):
try:
st = os.stat(src)
except Exception as e:
print 'stat except:', e
else:
try:
os.chown(dst, st.st_uid, st.st_gid)
except Exception as e:
print 'chmod except:', e

Related

Python: Paging of argparse help text?

For a Python script that uses argparse and has a very long argument list, is it possible to make argparse page what it prints to the terminal when calling the script with the -h option?
I could not find a quick answer, so I wrote a little something:
# hello.py
import argparse
import os
import shlex
import stat
import subprocess as sb
import tempfile
def get_pager():
"""
Get path to your pager of choice, or less, or more
"""
pagers = (os.getenv('PAGER'), 'less', 'more',)
for path in (os.getenv('PATH') or '').split(os.path.pathsep):
for pager in pagers:
if pager is None:
continue
pager = iter(pager.split(' ', 1))
prog = os.path.join(path, next(pager))
args = next(pager, None) or ''
try:
md = os.stat(prog).st_mode
if md & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
return '{p} {a}'.format(p=prog, a=args)
except OSError:
continue
class CustomArgParser(argparse.ArgumentParser):
"""
A custom ArgumentParser class that prints help messages
using either your pager, or less or more, if available.
Otherwise, it does what ArgumentParser would do.
Use the PAGER environment variable to force it to use your pager
of choice.
"""
def print_help(self, file=None):
text = self.format_help()
pager = get_pager()
if pager is None:
return super().print_help(file)
fd, fname = tempfile.mkstemp(prefix='simeon_help_', suffix='.txt')
with open(fd, 'w') as fh:
super().print_help(fh)
cmd = shlex.split('{p} {f}'.format(p=pager, f=fname))
with sb.Popen(cmd) as proc:
rc = proc.wait()
if rc != 0:
super().print_help(file)
try:
os.unlink(fname)
except:
pass
if __name__ == '__main__':
parser = CustomArgParser(description='Some little program')
parser.add_argument('--message', '-m', help='Your message', default='hello world')
args = parser.parse_args()
print(args.message)
This snippet does main things. First, it defines a function to get the absolute path to a pager. If you set the environment variable PAGER, it will try and use it to display the help messages. Second, it defines a custom class that inherits pretty much everything from argparse.ArgumentParser. The only method that gets overridden here is print_help. It implements print_help by defaulting to super().print_help() whenever a valid pager is not found. If a valid is found, then it writes the help message to a temporary file and then opens a child process that invokes the pager with the path to the temporary file. When the pager returns, the temporary file is deleted. That's pretty much it.
You are more than welcome to update get_pager to add as many pager programs as you see fit.
Call the script:
python3 hello.py --help ## Uses less
PAGER='nano --view' python3 hello.py --help ## Uses nano
PAGER=more python3 hello.py --help ## Uses more

Using python how to display dns

Using Python, is there a way using psutil or something else to return the output of "ipconfig /displaydns" or something similar?
Spawning a cmd process and running the command is not an option.
To execute a command, you can use the os library. Executing the command you've shown above is as simple as:
import os
os.system('ipconfig /displaydns')
If you want to store the output of a command in a variable, you can use:
x = os.popen('ipconfig /displaydns')
You can try using dnspython's resolver class, which has a nameservers list attached to it.
import dns.resolver
resolver = dns.resolver.Resolver()
resolver.nameservers
# Returns ['8.8.8.8', '8.8.4.4', '8.8.8.8'] on my pc
This python script I made creates a directory in widows user profile and copies windows dns information into a log.txt,So if directory does not exist it will create it. Hope it works for you.
#!/usr/bin/env python3
import time
import os
def dns():
path = os.system("ipconfig /displaydns >> %USERPROFILE%\DNS\logdns.txt")
if path == False:
os.system("ipconfig /displaydns >> %USERPROFILE%\DNS\logdns.txt")
print("Please wait copying information........")
time.sleep(5 )
os.system("cls")
print("Information copy complete.")
time.sleep(3)
else:
print("Making directory please wait a second....")
time.sleep(5)
os.system("cls")
os.system("mkdir %USERPROFILE%\DNS")
time.sleep(1)
dns()
dns()

Python Inotify for a list of folders

I am looking for a way to check a list of document roots of 5 domains to inotify directory watch.
For a single folder it is working like this
DIRECTORY_TO_WATCH = "/home/serveradmin/"
I have a list of folders which needs to be checked recursively for my server.
I just started learning python and have some hands on experience in C language. Just starting learning developments.
Is there anyone who can help me on this? I need to have a recursive inotify watch on 5 folders mentioned in a file named /tmp/folderlist.txt
IS there any similar code available anywhere I can refer?
install inotify-tools:
sudo apt-get install inotify-tools
and try something like:
inotifywait = ['inotifywait',
'--recursive',
'--quiet',
'--monitor', ## '--timeout', '1',
'--event',
'CREATE',
'--format', '%f']
from subprocess import PIPE, Popen
p = Popen(inotifywait + paths, stdout=PIPE)
for line in iter(p.stdout.readline, ''):
print(line)
For macosx you can get similar results using fswatch:
if sys.platform == 'darwin':
inotifywait = ['fswatch', '--event', 'Created']
For windows see : Is there anything like inotify on Windows?
So you want to create a list of the folders then do a for a statement to check if they exist.
So do
import os
list = ['folder1', 'folder2', 'folder3', 'folder4', 'folder5']
for i in list:
print("\nDoes", list[i], "exsist?", os.path.exists("/home/serveradmin/" + list[i]))
Replace the items in list with the correct folder names!
Good luck!
This is the sample code I have started working. I need to replace "DIRECTORY_TO_WATCH" to a list of folders which I will add in a file. So basically it will be a list of folders specified in a txt file so that I can just keep adding future folders in that file and no need to add the core code every time.
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class Watcher:
DIRECTORY_TO_WATCH = "/path/to/my/directory"
def __init__(self):
self.observer = Observer()
def run(self):
event_handler = Handler()
self.observer.schedule(event_handler, self.DIRECTORY_TO_WATCH, recursive=True)
self.observer.start()
try:
while True:
time.sleep(5)
except:
self.observer.stop()
print "Error"

Python - How to find UUID of computer and set as variable

I have been looking all over the internet for a way to find a way to get UUID for a computer and set it as a variable in python.
Some of the ways I tried doing didn't work.
Original idea:
import os
x = os.system("wmic diskdrive get serialnumber")
print(x)
However this does not work, and only returns 0.
I am wondering if their is a way i can find a unique Harddrive ID or any other type of identifier in python.
The os.system function returns the exit code of the executed command, not the standard output of it.
According to Python official documentation:
On Unix, the return value is the exit status of the process encoded in the format specified for wait().
On Windows, the return value is that returned by the system shell
after running command.
To get the output as you want, the recommended approach is to use some of the functions defined in the subprocess module. Your scenario is pretty simple, so subprocess.check_output works just fine for it.
You just need to replace the code you posted code with this instead:
import subprocess
x = subprocess.check_output('wmic csproduct get UUID')
print(x)
If the aim is to get the serial number of the HDD, then one can do:
In Linux (replace /dev/sda with the block disk identifier for which you want the info):
>>> import os
>>> os.popen("hdparm -I /dev/sda | grep 'Serial Number'").read().split()[-1]
In Windows:
>>> import os
>>> os.popen("wmic diskdrive get serialnumber").read().split()[-1]
for windows i do work with this one and it works perfectly:
import subprocess
UUID = str(subprocess.check_output('wmic csproduct get UUID'),'utf-8').split('\n')[1].strip()
print(UUID)
For windows:
import wmi
import os
def get_serial_number_of_system_physical_disk():
c = wmi.WMI()
logical_disk = c.Win32_LogicalDisk(Caption=os.getenv("SystemDrive"))[0]
partition = logical_disk.associators()[1]
physical_disc = partition.associators()[0]
return physical_disc.SerialNumber
For Linux try this:
def get_uuid():
dmidecode = subprocess.Popen(['dmidecode'],
stdout=subprocess.PIPE,
bufsize=1,
universal_newlines=True
)
while True:
line = dmidecode.stdout.readline()
if "UUID:" in str(line):
uuid = str(line).split("UUID:", 1)[1].split()[0]
return uuid
if not line:
break
my_uuid = get_uuid()
print("My ID:", my_uuid)

Determining Whether a Directory is Writeable

What would be the best way in Python to determine whether a directory is writeable for the user executing the script? Since this will likely involve using the os module I should mention I'm running it under a *nix environment.
Although what Christophe suggested is a more Pythonic solution, the os module does have the os.access function to check access:
os.access('/path/to/folder', os.W_OK) # W_OK is for writing, R_OK for reading, etc.
It may seem strange to suggest this, but a common Python idiom is
It's easier to ask for forgiveness
than for permission
Following that idiom, one might say:
Try writing to the directory in question, and catch the error if you don't have the permission to do so.
My solution using the tempfile module:
import tempfile
import errno
def isWritable(path):
try:
testfile = tempfile.TemporaryFile(dir = path)
testfile.close()
except OSError as e:
if e.errno == errno.EACCES: # 13
return False
e.filename = path
raise
return True
Update:
After testing the code again on Windows I see that there is indeed an issue when using tempfile there, see issue22107: tempfile module misinterprets access denied error on Windows.
In the case of a non-writable directory, the code hangs for several seconds and finally throws an IOError: [Errno 17] No usable temporary file name found. Maybe this is what user2171842 was observing?
Unfortunately the issue is not resolved for now so to handle this, the error needs to be caught as well:
except (OSError, IOError) as e:
if e.errno == errno.EACCES or e.errno == errno.EEXIST: # 13, 17
The delay is of course still present in these cases then.
Stumbled across this thread searching for examples for someone. First result on Google, congrats!
People talk about the Pythonic way of doing it in this thread, but no simple code examples? Here you go, for anyone else who stumbles in:
import sys
filepath = 'C:\\path\\to\\your\\file.txt'
try:
filehandle = open( filepath, 'w' )
except IOError:
sys.exit( 'Unable to write to file ' + filepath )
filehandle.write("I am writing this text to the file\n")
This attempts to open a filehandle for writing, and exits with an error if the file specified cannot be written to: This is far easier to read, and is a much better way of doing it rather than doing prechecks on the file path or the directory, as it avoids race conditions; cases where the file becomes unwriteable between the time you run the precheck, and when you actually attempt to write to the file.
If you only care about the file perms, os.access(path, os.W_OK) should do what you ask for. If you instead want to know whether you can write to the directory, open() a test file for writing (it shouldn't exist beforehand), catch and examine any IOError, and clean up the test file afterwards.
More generally, to avoid TOCTOU attacks (only a problem if your script runs with elevated privileges -- suid or cgi or so), you shouldn't really trust these ahead-of-time tests, but drop privs, do the open(), and expect the IOError.
Check the mode bits:
import os, stat
def isWritable(dirname):
uid = os.geteuid()
gid = os.getegid()
s = os.stat(dirname)
mode = s[stat.ST_MODE]
return (
((s[stat.ST_UID] == uid) and (mode & stat.S_IWUSR)) or
((s[stat.ST_GID] == gid) and (mode & stat.S_IWGRP)) or
(mode & stat.S_IWOTH)
)
Here is something I created based on ChristopheD's answer:
import os
def isWritable(directory):
try:
tmp_prefix = "write_tester";
count = 0
filename = os.path.join(directory, tmp_prefix)
while(os.path.exists(filename)):
filename = "{}.{}".format(os.path.join(directory, tmp_prefix),count)
count = count + 1
f = open(filename,"w")
f.close()
os.remove(filename)
return True
except Exception as e:
#print "{}".format(e)
return False
directory = "c:\\"
if (isWritable(directory)):
print "directory is writable"
else:
print "directory is not writable"
if os.access(path_to_folder, os.W_OK) is not True:
print("Folder not writable")
else :
print("Folder writable")
more info about access can be find it here
I ran into this same need while adding an argument via argparse. The built in type=FileType('w') wouldn't work for me as I was looking for a directory. I ended up writing my own method to solve my problem. Here is the result with argparse snippet.
#! /usr/bin/env python
import os
import argparse
def writable_dir(dir):
if os.access(dir, os.W_OK) and os.path.isdir(dir):
return os.path.abspath(dir)
else:
raise argparse.ArgumentTypeError(dir + " is not writable or does not exist.")
parser = argparse.ArgumentParser()
parser.add_argument("-d","--dir", type=writable_dir, default='/tmp/',
help="Directory to use. Default: /tmp")
opts = parser.parse_args()
That results in the following:
$ python dir-test.py -h
usage: dir-test.py [-h] [-d DIR]
optional arguments:
-h, --help show this help message and exit
-d DIR, --dir DIR Directory to use. Default: /tmp
$ python dir-test.py -d /not/real
usage: dir-test.py [-h] [-d DIR]
dir-test.py: error: argument -d/--dir: /not/real is not writable or does not exist.
$ python dir-test.py -d ~
I went back and added print opts.dir to the end, and everything appears to be functioning as desired.
If you need to check the permission of another user (yes, I realize this contradicts the question, but may come in handy for someone), you can do it through the pwd module, and the directory's mode bits.
Disclaimer - does not work on Windows, as it doesn't use the POSIX permissions model (and the pwd module is not available there), e.g. - solution only for *nix systems.
Note that a directory has to have all the 3 bits set - Read, Write and eXecute.
Ok, R is not an absolute must, but w/o it you cannot list the entries in the directory (so you have to know their names). Execute on the other hand is absolutely needed - w/o it the user cannot read the file's inodes; so even having W, without X files cannot be created or modified. More detailed explanation at this link.
Finally, the modes are available in the stat module, their descriptions are in inode(7) man.
Sample code how to check:
import pwd
import stat
import os
def check_user_dir(user, directory):
dir_stat = os.stat(directory)
user_id, group_id = pwd.getpwnam(user).pw_uid, pwd.getpwnam(user).pw_gid
directory_mode = dir_stat[stat.ST_MODE]
# use directory_mode as mask
if user_id == dir_stat[stat.ST_UID] and stat.S_IRWXU & directory_mode == stat.S_IRWXU: # owner and has RWX
return True
elif group_id == dir_stat[stat.ST_GID] and stat.S_IRWXG & directory_mode == stat.S_IRWXG: # in group & it has RWX
return True
elif stat.S_IRWXO & directory_mode == stat.S_IRWXO: # everyone has RWX
return True
# no permissions
return False
check if a stat object is readable or writable
useful to check if a pipe is readable or writable
import os, stat
def check_access(s, check="r"):
"check if s=os.stat(path) is readable or writable"
u = os.geteuid(); g = os.getegid(); m = s.st_mode
if check == "r":
return (
((s[stat.ST_UID] == u) and (m & stat.S_IRUSR)) or
((s[stat.ST_GID] == g) and (m & stat.S_IRGRP)) or
(m & stat.S_IROTH)
) != 0
if check == "w":
return (
((s[stat.ST_UID] == u) and (m & stat.S_IWUSR)) or
((s[stat.ST_GID] == g) and (m & stat.S_IWGRP)) or
(m & stat.S_IWOTH)
) != 0
s = os.stat(0) # fd 0 == stdin
print(f"fd 0 is readable?", check_access(s, "r"))
s = os.stat(1) # fd 1 == stdout
print(f"fd 1 is writable?", check_access(s, "w"))
os.write(1, b"hello\n")
based on the answer by Joe Koberg
see also: same thing in javascript
probably breaks on windows

Categories

Resources