One can't simply change the owner of a process back and forth at will (ok, one can with the proper call: os.seteuid
, I learned after starting this answer. Either way, there are more things required for this particular question than just setting the UID - the bottom part of the answer has a subprocess approach): just the root user can do that, and once its done, it is no longer root to switch back. So the trick is to call it in a process that is exclusive to run the limited tasks.
You can fork the process and call os.setuid
on the child, and then terminate it:
import alsaaudio
import os
import sys
...
if not os.fork():
# this block just runs on the child process: fork returns a non-0 PID on the parent
os.setuid(<usernumber>)
m = alsaaudio.Mixer('Capture')
m.setvolume(10) # set volume
vol = m.getvolume() # get volume float value
sys.exit(0) # terminates the child process.
# normal code that will just run on the parent process (as root)
# continues:
...
Of course, this won't make the vol
variable avaliable on the parent process - you have to setup a way to pass the value along.
In that case, one can use Python multiprocessing, instead of fork, with multiprocessing.Queue to send vol
value, and add the needed pauses to destroy the other user process: that is good if you are writing "production quality" code that will need to handle a lot of corner cases in third party computers. If your object is a simple script for setting up stuff in your box, writting the value to a file, and reading it on parent will be easier:
import alsaaudio
import os
import sys
import tempfile
import time
import pickle
...
_, file_ = tempfile.mkstemp()
if not os.fork():
# this block just runs on the child process: fork returns a non-0 PID on the parent
os.setuid(<usernumber>)
m = alsaaudio.Mixer('Capture')
m.setvolume(10) # set volume
vol = m.getvolume() # get volume float value
with open(file_, "wb") as file:
pickle.dump(vol, file)
sys.exit(0) # terminates the child process.
# normal code that will just run on the parent process (as root)
# continues:
time.sleep(0.1) # may need calibration
vol = pickle.load(open(file_, "rb"))
os.unlink(file_)
...
Given the OP comments, this is not just a matter of changing the effective UID, but also environment variables, and reimporting the alsaaudio
module - a "fork" won't cut it (as env vars are not changed, and changing os.environment
entries on the Pythonside will probably not be reflected on the native-code side of alsalib as it is initialized.
In this case, running the process with subprocess.Popen
and ensuring the correct enviroment prior to it can attain the desired effects. Interprocess comunication can be resolved by capturing the subprocess stdout - so we can refrain from both the pickle.Also, the default subprocess call is synchronous, and no need for an arbitrary pause for the target child to initialize alsa and pick its value: the main process will continue just after the child is done, in the default call.
I am not trying alsaaudio here, it may be that more environment variables than the 2 I set are needed on the subprocess. If that is the case, just go on adding then to the env={ ...}
part of the code as needed.
This sample just do what you asked, and pass the value for "vol" back - if you ever need more data, drop the "encoding" argument to the subprocess, and pickle arbitrary data to sys.stdout
on the child process - you can then retriev it with pickle.loads on proc.stdout
import alsaaudio
import os
import sys
import tempfile
import time
import pickle
vol = None
def child_code():
import alsaaudio
# no need to reload: when running as a subprocess, the top-level import (if any)
# will also run as the new user
m = alsaaudio.Mixer('Capture')
m.setvolume(10) # set volume
vol = m.getvolume() # get volume float value
print(vol)
...
def parent_code():
import subprocess
import pwd
target_user = 1000
user_data = os.getpwuid(target_user).pw_dir()
# if alsalib fails, you may need to setup other variables
env = {"HOME": user_data, "PWD": user_data, }
# __file__ bellow may have to be changed if this code
# is in a submodule in a larger project.
proc = subprocess.run(
["/usr/bin/env", "python3", __file__ ],
env=env,
user=target_user,
capture_output=True,
encoding="utf-8"
)
vol = literal_eval(proc.stdout)
return vol
...
if os.getuid() == 0:
# runs on parent, UID==1, process
vol = parent_code()
else:
# runs on subprocess.
child_code()
sys.exit(0)
...