Repository: ipython/ipython

Issue:
check_security_dir() and check_pid_dir() fail on network filesystem
For testing purposes and quick setup, I use SSHFS as a simple network filesystem. On Linux and Mac OSX there is FUSE SSHFS available. On Windows there's Dokan SSHFS. 
I use SSHFS because Samba and NFS share were leading to incorrect uid/gid mapping. At least for Mac OSX and Ubuntu is was possble to avoid these problems by using SSHFS.

When starting ipengine on Windows, IPython executes check_security_dir() and check_pid_dir() which both try to set the permissions using chmod to 0700. This fails in my setup. After commenting out the offending lines in core/profiledir.py ipengine works.

It would be nice to be able to configure the permission setup.



Discussion:

Resolving patch:
diff --git a/IPython/core/magic.py b/IPython/core/magic.py
--- a/IPython/core/magic.py
+++ b/IPython/core/magic.py
@@ -53,7 +53,7 @@
 from IPython.lib.pylabtools import mpl_runner
 from IPython.testing.skipdoctest import skip_doctest
 from IPython.utils.io import file_read, nlprint
-from IPython.utils.path import get_py_filename
+from IPython.utils.path import get_py_filename, unquote_filename
 from IPython.utils.process import arg_split, abbrev_cwd
 from IPython.utils.terminal import set_term_title
 from IPython.utils.text import LSString, SList, format_screen
@@ -1426,10 +1426,12 @@ def magic_prun(self, parameter_s ='',user_mode=1,
         dump_file = opts.D[0]
         text_file = opts.T[0]
         if dump_file:
+            dump_file = unquote_filename(dump_file)
             prof.dump_stats(dump_file)
             print '\n*** Profile stats marshalled to file',\
                   `dump_file`+'.',sys_exit
         if text_file:
+            text_file = unquote_filename(text_file)
             pfile = file(text_file,'w')
             pfile.write(output)
             pfile.close()
@@ -2071,7 +2073,7 @@ def magic_save(self,parameter_s = ''):
         it asks for confirmation before overwriting existing files."""
 
         opts,args = self.parse_options(parameter_s,'r',mode='list')
-        fname, codefrom = args[0], " ".join(args[1:])
+        fname, codefrom = unquote_filename(args[0]), " ".join(args[1:])
         if not fname.endswith('.py'):
             fname += '.py'
         if os.path.isfile(fname):
@@ -2111,6 +2113,7 @@ def magic_loadpy(self, arg_s):
         %loadpy myscript.py
         %loadpy http://www.example.com/myscript.py
         """
+        arg_s = unquote_filename(arg_s)
         if not arg_s.endswith('.py'):
             raise ValueError('%%load only works with .py files: %s' % arg_s)
         if arg_s.startswith('http'):
@@ -2127,12 +2130,13 @@ def _find_edit_target(self, args, opts, last_call):
         
         def make_filename(arg):
             "Make a filename from the given args"
+            arg = unquote_filename(arg)
             try:
                 filename = get_py_filename(arg)
             except IOError:
                 # If it ends with .py but doesn't already exist, assume we want
                 # a new file. 
-                if args.endswith('.py'):
+                if arg.endswith('.py'):
                     filename = arg
                 else:
                     filename = None
@@ -2826,8 +2830,7 @@ def magic_cd(self, parameter_s=''):
                               "Use '%%bookmark -l' to see your bookmarks." % ps)
 
         # strip extra quotes on Windows, because os.chdir doesn't like them
-        if sys.platform == 'win32':
-            ps = ps.strip('\'"')
+        ps = unquote_filename(ps)
         # at this point ps should point to the target dir
         if ps:
             try:
@@ -2870,7 +2873,7 @@ def magic_pushd(self, parameter_s=''):
         """
         
         dir_s = self.shell.dir_stack
-        tgt = os.path.expanduser(parameter_s)
+        tgt = os.path.expanduser(unquote_filename(parameter_s))
         cwd = os.getcwdu().replace(self.home_dir,'~')
         if tgt:
             self.magic_cd(parameter_s)
@@ -3531,6 +3534,7 @@ def magic_notebook(self, s):
         args = magic_arguments.parse_argstring(self.magic_notebook, s)
 
         from IPython.nbformat import current
+        args.filename = unquote_filename(args.filename)
         if args.export:
             fname, name, format = current.parse_filename(args.filename)
             cells = []
diff --git a/IPython/utils/path.py b/IPython/utils/path.py
--- a/IPython/utils/path.py
+++ b/IPython/utils/path.py
@@ -82,13 +82,32 @@ def get_long_path_name(path):
     return _get_long_path_name(path)
 
 
-def get_py_filename(name):
+def unquote_filename(name, win32=(sys.platform=='win32')):
+    """ On Windows, remove leading and trailing quotes from filenames.
+    """
+    if win32:
+        if name.startswith(("'", '"')) and name.endswith(("'", '"')):
+            name = name[1:-1]
+    return name
+
+
+def get_py_filename(name, force_win32=None):
     """Return a valid python filename in the current directory.
 
     If the given name is not a file, it adds '.py' and searches again.
-    Raises IOError with an informative message if the file isn't found."""
+    Raises IOError with an informative message if the file isn't found.
+    
+    On Windows, apply Windows semantics to the filename. In particular, remove
+    any quoting that has been applied to it. This option can be forced for
+    testing purposes.
+    """
 
     name = os.path.expanduser(name)
+    if force_win32 is None:
+        win32 = (sys.platform == 'win32')
+    else:
+        win32 = force_win32
+    name = unquote_filename(name, win32=win32)
     if not os.path.isfile(name) and not name.endswith('.py'):
         name += '.py'
     if os.path.isfile(name):


Test patch:
diff --git a/IPython/testing/globalipapp.py b/IPython/testing/globalipapp.py
--- a/IPython/testing/globalipapp.py
+++ b/IPython/testing/globalipapp.py
@@ -63,14 +63,14 @@ class py_file_finder(object):
     def __init__(self,test_filename):
         self.test_filename = test_filename
         
-    def __call__(self,name):
+    def __call__(self,name,win32=False):
         from IPython.utils.path import get_py_filename
         try:
-            return get_py_filename(name)
+            return get_py_filename(name,win32=win32)
         except IOError:
             test_dir = os.path.dirname(self.test_filename)
             new_path = os.path.join(test_dir,name)
-            return get_py_filename(new_path)
+            return get_py_filename(new_path,win32=win32)
     
 
 def _run_ns_sync(self,arg_s,runner=None):
diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py
--- a/IPython/testing/tools.py
+++ b/IPython/testing/tools.py
@@ -330,4 +330,16 @@ def mute_warn():
     try:
         yield
     finally:
-        warn.warn = save_warn
\ No newline at end of file
+        warn.warn = save_warn
+
+@contextmanager
+def make_tempfile(name):
+    """ Create an empty, named, temporary file for the duration of the context.
+    """
+    f = open(name, 'w')
+    f.close()
+    try:
+        yield
+    finally:
+        os.unlink(name)
+
diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py
--- a/IPython/utils/tests/test_path.py
+++ b/IPython/utils/tests/test_path.py
@@ -12,6 +12,8 @@
 # Imports
 #-----------------------------------------------------------------------------
 
+from __future__ import with_statement
+
 import os
 import shutil
 import sys
@@ -27,6 +29,7 @@
 import IPython
 from IPython.testing import decorators as dec
 from IPython.testing.decorators import skip_if_not_win32, skip_win32
+from IPython.testing.tools import make_tempfile
 from IPython.utils import path, io
 
 # Platform-dependent imports
@@ -83,7 +86,7 @@ def setup_environment():
     each testfunction needs a pristine environment.
     """
     global oldstuff, platformstuff
-    oldstuff = (env.copy(), os.name, path.get_home_dir, IPython.__file__)
+    oldstuff = (env.copy(), os.name, path.get_home_dir, IPython.__file__, os.getcwd())
 
     if os.name == 'nt':
         platformstuff = (wreg.OpenKey, wreg.QueryValueEx,)
@@ -92,7 +95,8 @@ def setup_environment():
 def teardown_environment():
     """Restore things that were remebered by the setup_environment function
     """
-    (oldenv, os.name, path.get_home_dir, IPython.__file__,) = oldstuff
+    (oldenv, os.name, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
+    os.chdir(old_wd)
     reload(path)
     
     for key in env.keys():
@@ -401,4 +405,39 @@ def test_not_writable_ipdir():
     io.stderr = stderr
     nt.assert_true('WARNING' in pipe.getvalue())
     env.pop('IPYTHON_DIR', None)
-    
\ No newline at end of file
+
+def test_unquote_filename():
+    for win32 in (True, False):
+        nt.assert_equals(path.unquote_filename('foo.py', win32=win32), 'foo.py')
+        nt.assert_equals(path.unquote_filename('foo bar.py', win32=win32), 'foo bar.py')
+    nt.assert_equals(path.unquote_filename('"foo.py"', win32=True), 'foo.py')
+    nt.assert_equals(path.unquote_filename('"foo bar.py"', win32=True), 'foo bar.py')
+    nt.assert_equals(path.unquote_filename("'foo.py'", win32=True), 'foo.py')
+    nt.assert_equals(path.unquote_filename("'foo bar.py'", win32=True), 'foo bar.py')
+    nt.assert_equals(path.unquote_filename('"foo.py"', win32=False), '"foo.py"')
+    nt.assert_equals(path.unquote_filename('"foo bar.py"', win32=False), '"foo bar.py"')
+    nt.assert_equals(path.unquote_filename("'foo.py'", win32=False), "'foo.py'")
+    nt.assert_equals(path.unquote_filename("'foo bar.py'", win32=False), "'foo bar.py'")
+
+@with_environment
+def test_get_py_filename():
+    os.chdir(TMP_TEST_DIR)
+    for win32 in (True, False):
+        with make_tempfile('foo.py'):
+            nt.assert_equals(path.get_py_filename('foo.py', force_win32=win32), 'foo.py')
+            nt.assert_equals(path.get_py_filename('foo', force_win32=win32), 'foo.py')
+        with make_tempfile('foo'):
+            nt.assert_equals(path.get_py_filename('foo', force_win32=win32), 'foo')
+            nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
+        nt.assert_raises(IOError, path.get_py_filename, 'foo', force_win32=win32)
+        nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
+        true_fn = 'foo with spaces.py'
+        with make_tempfile(true_fn):
+            nt.assert_equals(path.get_py_filename('foo with spaces', force_win32=win32), true_fn)
+            nt.assert_equals(path.get_py_filename('foo with spaces.py', force_win32=win32), true_fn)
+            if win32:
+                nt.assert_equals(path.get_py_filename('"foo with spaces.py"', force_win32=True), true_fn)
+                nt.assert_equals(path.get_py_filename("'foo with spaces.py'", force_win32=True), true_fn)
+            else:
+                nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"', force_win32=False)
+                nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'", force_win32=False)