Are there any problems with this symlink traversal code for Windows? - python

In my efforts to resolve Python issue 1578269, I've been working on trying to resolve the target of a symlink in a robust way. I started by using GetFinalPathNameByHandle as recommended here on stackoverflow and by Microsoft, but it turns out that technique fails when the target is in use (such as with pagefile.sys).
So, I've written a new routine to accomplish this using CreateFile and DeviceIoControl (as it appears this is what Explorer does). The relevant code from jaraco.windows.filesystem is included below.
The question is, is there a better technique for reliably resolving symlinks in Windows? Can you identify any issues with this implementation?
def relpath(path, start=os.path.curdir):
"""
Like os.path.relpath, but actually honors the start path
if supplied. See http://bugs.python.org/issue7195
"""
return os.path.normpath(os.path.join(start, path))
def trace_symlink_target(link):
"""
Given a file that is known to be a symlink, trace it to its ultimate
target.
Raises TargetNotPresent when the target cannot be determined.
Raises ValueError when the specified link is not a symlink.
"""
if not is_symlink(link):
raise ValueError("link must point to a symlink on the system")
while is_symlink(link):
orig = os.path.dirname(link)
link = _trace_symlink_immediate_target(link)
link = relpath(link, orig)
return link
def _trace_symlink_immediate_target(link):
handle = CreateFile(
link,
0,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
None,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS,
None,
)
res = DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, None, 10240)
bytes = create_string_buffer(res)
p_rdb = cast(bytes, POINTER(REPARSE_DATA_BUFFER))
rdb = p_rdb.contents
if not rdb.tag == IO_REPARSE_TAG_SYMLINK:
raise RuntimeError("Expected IO_REPARSE_TAG_SYMLINK, but got %d" % rdb.tag)
return rdb.get_print_name()

Unfortunately I can't test with Vista until next week, but GetFinalPathNameByHandle should work, even for files in use - what's the problem you noticed?
In your code above, you forget to close the file handle.

Related

How to change username of job in print queue using python & win32print

I am trying to change the user of a print job in the queue, as I want to create it on a service account but send the job to another users follow-me printing queue. I'm using the win32 module in python. Here is an example of my code:
from win32 import win32print
JOB_INFO_LEVEL = 2
pclExample = open("sample.pcl")
printer_name = win32print.GetDefaultPrinter()
hPrinter = win32print.OpenPrinter(printer_name)
try:
jobID = win32print.StartDocPrinter(hPrinter, 1, ("PCL Data test", None, "RAW"))
# Here we try to change the user by extracting the job and then setting it again
jobInfoDict = win32print.GetJob(hPrinter, jobID , JOB_INFO_LEVEL )
jobInfoDict["pUserName"] = "exampleUser"
win32print.SetJob(hPrinter, jobID , JOB_INFO_LEVEL , jobInfoDict , win32print.JOB_CONTROL_RESUME )
try:
win32print.StartPagePrinter(hPrinter)
win32print.WritePrinter(hPrinter, pclExample)
win32print.EndPagePrinter(hPrinter)
finally:
win32print.EndDocPrinter(hPrinter)
finally:
win32print.ClosePrinter(hPrinter)
The problem is I get an error at the win32print.SetJob() line. If JOB_INFO_LEVEL is set to 1, then I get the following error:
(1804, 'SetJob', 'The specified datatype is invalid.')
This is a known bug to do with how the C++ works in the background (Issue here).
If JOB_INFO_LEVEL is set to 2, then I get the following error:
(1798, 'SetJob', 'The print processor is unknown.')
However, this is the processor that came from win32print.GetJob(). Without trying to change the user this prints fine, so I'm not sure what is wrong.
Any help would be hugely appreciated! :)
EDIT:
Using Python 3.8.5 and Pywin32 303
At the beginning I thought it was a misunderstanding (I was also a bit skeptical about the bug report), mainly because of the following paragraph (which apparently seems to be wrong) from [MS.Docs]: SetJob function (emphasis is mine):
The following members of a JOB_INFO_1, JOB_INFO_2, or JOB_INFO_4 structure are ignored on a call to SetJob: JobId, pPrinterName, pMachineName, pUserName, pDrivername, Size, Submitted, Time, and TotalPages.
But I did some tests and ran into the problem. The problem is as described in the bug: filling JOB_INFO_* string members (which are LPTSTRs) with char* data.
Submitted [GitHub]: mhammond/pywin32 - Fix: win32print.SetJob sending ANSI to UNICODE API (and none of the 2 errors pops up). It was merged to main on 220331.
When testing the fix, I was able to change various properties of an existing job, I was amazed that it didn't have to be valid data (like below), I'm a bit curious to see what would happen when the job would be executed (as now I don't have a connection to a printer):
Change pUserName to str(random.randint(0, 10000)) to make sure it changes on each script run (PrintScreens taken separately and assembled in Paint):
Ways to go further:
Wait for a new PyWin32 version (containing this fix) to be released. This is the recommended approach, but it will also take more time (and it's unclear when it will happen)
Get the sources, either:
from main
from b303 (last stable branch), and apply the (above) patch(1)
build the module (.pyd) and copy it in the PyWin32's site-packages directory on your Python installation(s). Faster, but it requires some deeper knowledge, and maintenance might become a nightmare
Footnotes
#1: Check [SO]: Run / Debug a Django application's UnitTests from the mouse right click context menu in PyCharm Community Edition? (#CristiFati's answer) (Patching UTRunner section) for how to apply patches (on Win).

bingads V13 report request fails in python sdk

I try to download a bingads report using python SDK, but I keep getting an error says: "Type not found: 'Aggregation'" after submitting a report request. I've tried all 4 options mentioned in the following link:
https://github.com/BingAds/BingAds-Python-SDK/blob/master/examples/v13/report_requests.py
Authentication process prior to request works just fine.
I execute the following:
report_request = get_report_request(authorization_data.account_id)
reporting_download_parameters = ReportingDownloadParameters(
report_request=report_request,
result_file_directory=FILE_DIRECTORY,
result_file_name=RESULT_FILE_NAME,
overwrite_result_file=True, # Set this value true if you want to overwrite the same file.
timeout_in_milliseconds=TIMEOUT_IN_MILLISECONDS
)
output_status_message("-----\nAwaiting download_report...")
download_report(reporting_download_parameters)
after a careful debugging, it seems that the program fails when trying to execute a command within "reporting_service_manager.py". Here is workflow:
download_report(self, download_parameters):
report_file_path = self.download_file(download_parameters)
then:
download_file(self, download_parameters):
operation = self.submit_download(download_parameters.report_request)
then:
submit_download(self, report_request):
self.normalize_request(report_request)
response = self.service_client.SubmitGenerateReport(report_request)
SubmitGenerateReport starts a sequence of events ending with a call to "_SeviceCall.init" function within "service_client.py", returning an exception "Type not found: 'Aggregation'"
try:
response = self.service_client.soap_client.service.__getattr__(self.name)(*args, **kwargs)
return response
except Exception as ex:
if need_to_refresh_token is False \
and self.service_client.refresh_oauth_tokens_automatically \
and self.service_client._is_expired_token_exception(ex):
need_to_refresh_token = True
else:
raise ex
Can anyone shed some light? .
Thanks
Please be sure to set Aggregation e.g., as shown here.
aggregation = 'Daily'
If the report type does not use aggregation, you can set Aggregation=None.
Does this help?
This may be a bit late 2 months after the fact but maybe this will help someone else. I had the same error (though I suppose it may not be the same issue). It does look like you did what I did (and I'm sure others will as well): copy-paste the Microsoft example code and tried to run it only to find that it didn't work.
I spent quite some time trying to debug the issue and it looked to me like the XML wasn't being searched correctly. I was using suds-py3 for the script at the time so I tried suds-community and everything just worked after that.
I also re-read the Bing Ads API walkthrough for getting started again and found that they recommend suds-jurko instead.
Long story short: If you want to use the bingads API don't use suds-py3, use either suds-community (which I can confirm works for everything I've used the API for) or suds-jurko (which is the one recommended by Microsoft).

How to use TideSDK openFolderChooseDialog

I'm trying to use TideSDK and python tp get the user to select a folder from the hard drive. Everything works, but I have no idea how obtain which folder the user selected.
I can't seem to find documentation on what Ti.UI.UserWindow.openFolderChooseDialog returns and what kind of object the callback function uses. I just get errors that "window" in "onopen" in my code below is a None Type object when I try to print it out.
Is there any documentation on the proper use of the openFolderChooseDialog, what signature the callback needs to be and how to get the Folder/directory from the dialog?
My code:
def onopen(window):
Ti.App.stdout("------------------ Opening Dialog")
Ti.App.stdout(window)
def burndir():
try:
dir = Ti.UI.getCurrentWindow().openFolderChooserDialog(onopen)
Ti.App.stdout(dir)
except:
Ti.App.stderr("------ There was an error: ")
Ti.App.stderr(sys.exc_info()[0])
Ti.App.stderr(sys.exc_info()[1])
Ti.App.stderr(sys.exc_info()[2])
Any help is much appreciated
I found the answer in a Javascript Code example here:
https://github.com/appcelerator/titanium_developer/blob/master/Resources/perspectives/projects/js/projects.js#L1338
It appears that openFolderChooserDialog return nothing (a None object in Python). The callback function passes one argument which is a StaticBoundList (a Tuple object in Python) that contains all of the selected folders (in case of allowing multiple selections)
Here is the updated code:
def onopen(window):
if (len(window) > 0):
Ti.App.stdout("------------------ Opening Dialog")
Ti.App.stdout(window[0])
else:
Ti.App.stdout("------------------ Nothing Selected")
def burndir():
try:
Ti.UI.getCurrentWindow().openFolderChooserDialog(onopen)
except:
Ti.App.stderr("------ There was an error: ")
Ti.App.stderr(sys.exc_info()[0])
Ti.App.stderr(sys.exc_info()[1])
Ti.App.stderr(sys.exc_info()[2])
I hope this helps someone struggling to find the same documentation!

gsm location in python

I have this script file in python running on S60:
import location
def current_location():
gsm_loc = location.gsm_location()
print gsm_loc
current_location()
instead of printing a tuple of mcc, mnc, lac and cellId it prints None.
on top of my python shell I see location between the capabilities included.
what can be the problem?
Development of the situation:
I thought maybe nevertheless, my problem is lack of capabilities. So I went to sign the PythonScriptShell file.
I used OPDA website - I know they sign all the capabilities but three - which I don't use.
I installed the signed PythonScriptShell file on my phone (N95). On the top the list of capabilities didn't change. tried to run the script again:
same result - prints None.
If anyone can help me with this, it's really important.
thank you.
I think I can answer now one part of the problem:
the reason for printing None is because there needed another capability: ReadDeviceData which wasn't included in the capabilities list on top of python shell.
still, remaining the other part of the problem, why this capability wasn't included when I signed PythonScriptShell file? It is not one of the three restricted capabilities.
I have same issue with all necessary scriptshell capabilities: 'PowerMgmnt', 'ReadDeviceData', 'WriteDeviceData', 'TrustedUI', 'ProtServ', 'SwEvent', 'Network services', 'LocalServices', 'ReadUserData', 'WriteUserData', 'Location', 'SurroundingsDD', 'UserEnviroment'.
Let's take a look at source code from PythonForS60/module-repo/dev-modules/location.py:
import e32
import _location
def gsm_location():
if e32.s60_version_info>=(3,0):
ret = _location.gsm_location()
if ret[4]==1: # relevant information ?
return (int(ret[0]),int(ret[1]),ret[2],ret[3])
else:
return None # information returned by _location.gsm_location() not relevant
else:
return _location.gsm_location()
On my Nokia E71 e32.s60_version_info == (3,1) and I get None value too.
I don't really know what means 'not relevant', but direct calling
>>> import _location
>>> _location.gsm_location()
(u'257', u'01', 555, 11, 0)
returns something close to my objective reality.

trac-past-commit-hook on remote repository

Trying to set up the svn commit with trac using this script.
It is being called without issue, but the problem is this line here:
144 repos = self.env.get_repository()
Because I am calling this remotely self.env_get_repository() looks for the repository using the server drive and not the local drive mapping. That is, it is looking for E:/Projects/svn/InfoProj and not Y:/Projects/sv/InfoProj
I noticed a changeset on the trac set for being able to call get_repository() and passing in the path as the variable, but it seems this hasn't made it into the latest stable release yet.
This version of the script (the one submitted by code monkey) appears to do things differently, but is throwing an error that seems related:
154 if url is None:
155 url = self.env.config.get('project', 'url')
156 self.env.href = Href(url)
157 self.env.abs_href = Href(url)
Lines 156 / 157 throw error: Warning: TypeError: 'str' object is not callable
The 10.3 stable version of the script throws a completely different error:
Warning: NameError: global name 'core' is not defined
I'm setting up trac for the first time on a Windows box with a remote repository. I'm using trac 0.11 stable with Python 2.6.
I thought there would have been a lot more people out there trying to commit across servers who had come across this problem. I've looked around and couldn't find a solution. I'm supposing Linux has a more graceful way of handling this.
Thanks in advance.
This is totally do-able and just requires a couple of small hacks... woo hoo!
The problem I was having is that get_repository reads the value of the svn repository from the trac.ini file. This was pointing at E:/ and not at Y:/. The simple fix involves a check to see if the repository is at repository_dir and if not, then check at a new variable remote_repository_dir. The second part of the fix involves removing the error message from cache.py that checks to see if the current repository address matches the one being passed in.
As always, use this at your own risk and back everything up before hand!!!
First open you trac.ini file and add a new variable 'remote_repository_dir' underneath the 'repository_dir' variable. Remote repository dir will point to the mapped drive on your local machine. It should now look something like this:
repository_dir = E:/Projects/svn/InfoProj
remote_repository_dir = Y:/Projects/svn/InfoProj
Next we will modify the api.py file to check for the new variable if it can't find the repository at the repository_dir location. Around :71 you should have something like this:
repository_dir = Option('trac', 'repository_dir', '',
"""Path to local repository. This can also be a relative path
(''since 0.11'').""")
Underneath this line add:
remote_repository_dir = Option('trac', 'remote_repository_dir', '',
"""Path to remote repository.""")
Next near :156 you will have this:
rtype, rdir = self.repository_type, self.repository_dir
if not os.path.isabs(rdir):
rdir = os.path.join(self.env.path, rdir)
Change that to this:
rtype, rdir = self.repository_type, self.repository_dir
if not os.path.isdir(rdir):
rdir = self.remote_repository_dir
if not os.path.isabs(rdir):
rdir = os.path.join(self.env.path, rdir)
Finally you will need to remove the alert in the cache.py file (note this is not the best way to do this, you should be able to include the remote variable as part of the check, but for now it works).
In cache.py near :97 it should look like this:
if repository_dir:
# directory part of the repo name can vary on case insensitive fs
if os.path.normcase(repository_dir) != os.path.normcase(self.name):
self.log.info("'repository_dir' has changed from %r to %r"
% (repository_dir, self.name))
raise TracError(_("The 'repository_dir' has changed, a "
"'trac-admin resync' operation is needed."))
elif repository_dir is None: #
self.log.info('Storing initial "repository_dir": %s' % self.name)
cursor.execute("INSERT INTO system (name,value) VALUES (%s,%s)",
(CACHE_REPOSITORY_DIR, self.name,))
else: # 'repository_dir' cleared by a resync
self.log.info('Resetting "repository_dir": %s' % self.name)
cursor.execute("UPDATE system SET value=%s WHERE name=%s",
(self.name, CACHE_REPOSITORY_DIR))
We are going to remove the first part of the if statement so it now should look like this:
if repository_dir is None: #
self.log.info('Storing initial "repository_dir": %s' % self.name)
cursor.execute("INSERT INTO system (name,value) VALUES (%s,%s)",
(CACHE_REPOSITORY_DIR, self.name,))
else: # 'repository_dir' cleared by a resync
self.log.info('Resetting "repository_dir": %s' % self.name)
cursor.execute("UPDATE system SET value=%s WHERE name=%s",
(self.name, CACHE_REPOSITORY_DIR))
Warning! Doing this will mean that it no longer gives you an error if your directory has changed and you need a resync.
Hope this helps someone.

Categories

Resources