Merge lp:~mandel/ubuntuone-client/illegal_windows_chars into lp:ubuntuone-client
- illegal_windows_chars
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Manuel de la Peña |
Approved revision: | 1040 |
Merged at revision: | 1007 |
Proposed branch: | lp:~mandel/ubuntuone-client/illegal_windows_chars |
Merge into: | lp:ubuntuone-client |
Diff against target: |
763 lines (+377/-150) 3 files modified
tests/platform/windows/test_os_helper.py (+189/-100) ubuntuone/platform/windows/ipc.py (+2/-3) ubuntuone/platform/windows/os_helper.py (+186/-47) |
To merge this branch: | bzr merge lp:~mandel/ubuntuone-client/illegal_windows_chars |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Natalia Bidart (community) | Approve | ||
Alejandro J. Cura (community) | Approve | ||
Review via email: mp+65195@code.launchpad.net |
Description of the change
This branches fixes two different bugs:
1. The correct credentials management tool is used in the windows IPC.
2. Allowed to use illegal chars to be used in the os_helper so that they can be used in the local file system. In order to be able to get this branch working pywin32 has to be patch (https:/
diff -r 7dce71d174a9 win32/src/
--- a/win32/
+++ b/win32/
@@ -2108,7 +2108,7 @@
if (!PyWinObject_
goto done;
- if (GetFileSecurit
+ if (GetFileSecurit
PyErr_
goto done;
}
@@ -2117,7 +2117,7 @@
PyErr_
goto done;
}
- if (!GetFileSecuri
+ if (!GetFileSecuri
PyWin_
goto done;
}
@@ -2153,7 +2153,7 @@
PSECURITY_
if (!PyWinObject_
goto done;
- if (!SetFileSecuri
+ if (!SetFileSecuri
PyWin_
goto done;
}
Once pywin32 has been patched in your system please run the following test on windows:
python C:\Python27\Scripts u1trial tests\platform\
python C:\Python27\Scripts u1trial tests\platform\
python C:\Python27\Scripts u1trial tests\syncdaemo
The changes in the code should have not added too much overhead to the IO operations meaning that the test_vm tests should not timeout.
Alejandro J. Cura (alecu) wrote : | # |
"if e.winerror and e.winerror == 123" should be formulated as """if getattr(e, "winerror", None) == 123""", in case the exception has no winerror attribute. And it should perhaps be moved into a function called let's say "checkWinError1
calling set_no_rights from inside set_no_rights (in the case of errors) risks the possibility of entering an infinite recursion loop; I suggest moving the three lines inside the try to a small new function, and calling it twice, once from the try, and once from the "if e.winerror...".
typos:
* "illechal" -> "illegal"
* "there reason" -> "the reason"
* spurious spaces have appeared at the end of lines 487 and 496 of the diff
* "illegla" -> "illegal"
Manuel de la Peña (mandel) wrote : | # |
> * Can you please replace:
> for char in WINDOWS_
> self.testfile = self.testfile + char
> with:
> self.testfile = ''.join(
>
> * typos: "illechal", "illegla"
>
> * can you please use os.path.
> (http://
> the custom logic in diff line 392 (and everywhere else where you split the
> volume letter)?
>
> * can you replace:
>
> for illegal_char in WINDOWS_
> if illegal_char in path:
> return True
>
> with:
>
> any(c in WINDOWS_
>
> * can you please replace:
>
> for illegal_char in WINDOWS_
> partial = partial.
> WINDOWS_
>
> with:
>
> for illegal_char, replacement in WINDOWS_
> partial = partial.
>
> * can you please add a logger.exception statement inside the except OSError,
> e: within set_no_rights? it will be useful to diagnose potential issues from
> log reports from our users. Same for remove_file, remove_dir, make_dir,
> open_file, rename, stat_path.
>
All the above are fixed.
> * why are you doing this in abspath? path = path.replace('/', '\\') Seems
> unnecessary and may hide bugs in our code regarding multiplatform path
> management.
I have left it because if not there is a vm test that fails... the test uses / for the paths, and while we return the correct path, the assertion fails because on windows we use \\. Since verterok is not here I have left it there with a #TODO asking if it is needed.
Alejandro J. Cura (alecu) wrote : | # |
there are still some instances of the old == 123 check, lines 459 and 638 in the diff
Natalia Bidart (nataliabidart) wrote : | # |
* Unless I'm missing something, seems like the custom logic to separate the drive letter has not been replaced by os.path.splitdrive.
* All the logger.exception calls should not add the exception itself to the message string since is automatically added by the logger. So, a sentence like this:
should be:
* As Alejandro mentioned, there are some new trailing spaces added by this branch, please remove that.
Alejandro J. Cura (alecu) wrote : | # |
Just a few typos:
"Set the rigths" -> "Set the rights"
^
"Get exception when triying" -> "Got exception when trying"
^ ^
'Got exception when trun to remove path %s'
'Got exception when reyting to remove dir %s'
"TODO: o we really" -> "TODO: do we really"
^
Natalia Bidart (nataliabidart) wrote : | # |
Looks good now!
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
The attempt to merge lp:~mandel/ubuntuone-client/illegal_windows_chars into lp:ubuntuone-client failed. Below is the output from the failed tests.
/usr/bin/
checking for autoconf >= 2.53...
testing autoconf2.50... not found.
testing autoconf... found 2.67
checking for automake >= 1.10...
testing automake-1.11... found 1.11.1
checking for libtool >= 1.5...
testing libtoolize... found 2.2.6b
checking for intltool >= 0.30...
testing intltoolize... found 0.41.1
checking for pkg-config >= 0.14.0...
testing pkg-config... found 0.25
checking for gtk-doc >= 1.0...
testing gtkdocize... found 1.17
Checking for required M4 macros...
Checking for forbidden M4 macros...
Processing ./configure.ac
Running libtoolize...
libtoolize: putting auxiliary files in `.'.
libtoolize: copying file `./ltmain.sh'
libtoolize: putting macros in AC_CONFIG_
libtoolize: copying file `m4/libtool.m4'
libtoolize: copying file `m4/ltoptions.m4'
libtoolize: copying file `m4/ltsugar.m4'
libtoolize: copying file `m4/ltversion.m4'
libtoolize: copying file `m4/lt~obsolete.m4'
Running intltoolize...
Running gtkdocize...
Running aclocal-1.11...
Running autoconf...
Running autoheader...
Running automake-1.11...
Running ./configure --enable-gtk-doc --enable-debug ...
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for style of include used by make... GNU
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking dependency style of gcc... gcc3
checking for library containing strerror... none required
checking for gcc... (cached) gcc
checking whether we are using the GNU C compiler... (cached) yes
checking whether gcc accepts -g... (cached) yes
checking for gcc option to accept ISO C89... (cached) none needed
checking dependency style of gcc... (cached) gcc3
checking build system type... i686-pc-linux-gnu
checking host system type... i686-pc-linux-gnu
checking for a sed that does not truncate output... /bin/sed
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /bin/grep -E
checking for fgrep... /bin/grep -F
checking for ld used by gcc... /usr/bin/ld
checking if the linker (/usr/bin/ld) is GNU ld... yes
checking for BSD- or MS-compatible name lister (nm)... /usr/bin/nm -B
checking the name lister (/usr/bin/nm -B) interface... BSD nm
checking whether ln -s works... yes
checking the maximum length of command line arguments... 1572864
checking whether the shell understands some XSI constructs... yes
checking whether the shell understands "+="... yes
checking for /usr/bin/ld option to reload object files... -r
checking for objdump... objdump
checking how to recognize dependent...
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
Preview Diff
1 | === modified file 'tests/platform/windows/test_os_helper.py' |
2 | --- tests/platform/windows/test_os_helper.py 2011-02-07 15:34:55 +0000 |
3 | +++ tests/platform/windows/test_os_helper.py 2011-06-21 00:52:37 +0000 |
4 | @@ -17,115 +17,204 @@ |
5 | """Specific tests for the os_helper on Windows.""" |
6 | |
7 | import os |
8 | - |
9 | -from win32api import GetUserName |
10 | - |
11 | -from ntsecuritycon import ( |
12 | - FILE_ALL_ACCESS, |
13 | - FILE_GENERIC_READ, |
14 | - FILE_GENERIC_WRITE, |
15 | -) |
16 | +import errno |
17 | + |
18 | from ubuntuone.platform.windows.os_helper import ( |
19 | - _set_file_attributes, |
20 | + _remove_windows_illegal_chars, |
21 | access, |
22 | - set_no_rights |
23 | + listdir, |
24 | + make_dir, |
25 | + open_file, |
26 | + path_exists, |
27 | + remove_dir, |
28 | + remove_file, |
29 | + rename, |
30 | + set_no_rights, |
31 | + set_file_readwrite, |
32 | + stat_path, |
33 | + WINDOWS_ILLEGAL_CHARS_MAP |
34 | ) |
35 | from contrib.testing.testcase import BaseTwistedTestCase |
36 | |
37 | -class OSWrapperTests(BaseTwistedTestCase): |
38 | - """Test specific windows implementation details.""" |
39 | + |
40 | +class TestIllegalPaths(BaseTwistedTestCase): |
41 | + """Test all the operations using illegal paths.""" |
42 | |
43 | def setUp(self): |
44 | """Setup for the tests.""" |
45 | - super(OSWrapperTests, self).setUp() |
46 | + super(TestIllegalPaths, self).setUp() |
47 | self.basedir = self.mktemp('test_root') |
48 | self.testfile = os.path.join(self.basedir, "test_file") |
49 | - self.fd = open(self.testfile, 'w') |
50 | - |
51 | - def tearDown(self): |
52 | - """Clean the env for the next test.""" |
53 | - self.fd.close(); |
54 | - self.rmtree(self.basedir) |
55 | - super(OSWrapperTests, self).tearDown() |
56 | - |
57 | - def test_access_no_rights(self): |
58 | - """Test when the sid is not present.""" |
59 | - # remove all the rights from the test file so that |
60 | - # we cannot read or write |
61 | + self.testfile += ''.join(WINDOWS_ILLEGAL_CHARS_MAP) |
62 | + self.fd = open(_remove_windows_illegal_chars(self.testfile), 'w') |
63 | + |
64 | + def test_path_file_exist_yes(self): |
65 | + """Test that the file exists.""" |
66 | + self.assertTrue(path_exists(self.testfile)) |
67 | + |
68 | + def test_path_file_exist_no(self): |
69 | + """Test that the file doesn't exist.""" |
70 | + self.fd.close() |
71 | + os.remove(_remove_windows_illegal_chars(self.testfile)) |
72 | + self.assertFalse(path_exists(self.testfile)) |
73 | + |
74 | + def test_path_dir_exist_yes(self): |
75 | + """Test that the dir exists.""" |
76 | + self.assertTrue(path_exists(self.basedir)) |
77 | + |
78 | + def test_path_dir_exist_no(self): |
79 | + """Test that the dir doesn't exist.""" |
80 | + self.fd.close() |
81 | + os.remove(_remove_windows_illegal_chars(self.testfile)) |
82 | + self.assertFalse(path_exists(os.path.join(self.basedir, 'nodir'))) |
83 | + |
84 | + def test_remove_file(self): |
85 | + """Test the remove file.""" |
86 | + self.fd.close() |
87 | + remove_file(self.testfile) |
88 | + self.assertFalse(os.path.exists( |
89 | + _remove_windows_illegal_chars(self.testfile))) |
90 | + |
91 | + def test_remove_dir(self): |
92 | + """Test the remove dir.""" |
93 | + testdir = os.path.join(self.basedir, 'foodir') |
94 | + os.mkdir(testdir) |
95 | + assert os.path.exists(testdir) |
96 | + remove_dir(testdir) |
97 | + self.assertFalse(path_exists(testdir)) |
98 | + |
99 | + def test_make_dir_one(self): |
100 | + """Test the make dir with one dir.""" |
101 | + testdir = os.path.join(self.basedir, 'foodir') |
102 | + assert not os.path.exists(testdir) |
103 | + make_dir(testdir) |
104 | + self.assertTrue(os.path.exists(testdir)) |
105 | + |
106 | + def test_make_dir_already_there(self): |
107 | + """Test the make dir with one dir that exists.""" |
108 | + self.assertRaises(OSError, make_dir, self.basedir) |
109 | + |
110 | + def test_make_dir_recursive_no(self): |
111 | + """Test the make dir with some dirs, not recursive explicit.""" |
112 | + testdir = os.path.join(self.basedir, 'foo', 'bar') |
113 | + assert not os.path.exists(testdir) |
114 | + self.assertRaises(OSError, make_dir, testdir) |
115 | + |
116 | + def test_make_dir_recursive_yes(self): |
117 | + """Test the make dir with some dirs, recursive.""" |
118 | + testdir = os.path.join(self.basedir, 'foo', 'bar') |
119 | + assert not os.path.exists(testdir) |
120 | + make_dir(testdir, recursive=True) |
121 | + self.assertTrue(os.path.exists(testdir)) |
122 | + |
123 | + def test_open_file_not_there(self): |
124 | + """Open a file that does not exist.""" |
125 | + self.assertRaises(IOError, open_file, os.path.join(self.basedir, 'no')) |
126 | + |
127 | + def test_open_file_gets_a_fileobject(self): |
128 | + """Open a file, and get a file object.""" |
129 | + testfile = os.path.join(self.basedir, 'testfile') |
130 | + open(testfile, 'w').close() |
131 | + f = open_file(testfile) |
132 | + self.assertTrue(isinstance(f, file)) |
133 | + |
134 | + def test_open_file_read(self): |
135 | + """Open a file, and read.""" |
136 | + testfile = os.path.join(self.basedir, 'testfile') |
137 | + with open(testfile, 'w') as fh: |
138 | + fh.write("foo") |
139 | + f = open_file(testfile, 'r') |
140 | + self.assertTrue(f.read(), "foo") |
141 | + |
142 | + def test_open_file_write(self): |
143 | + """Open a file, and write.""" |
144 | + testfile = os.path.join(self.basedir, 'testfile') |
145 | + with open_file(testfile, 'w') as fh: |
146 | + fh.write("foo") |
147 | + f = open(testfile) |
148 | + self.assertTrue(f.read(), "foo") |
149 | + |
150 | + def test_rename_not_there(self): |
151 | + """Rename something that does not exist.""" |
152 | + self.assertRaises(OSError, rename, |
153 | + os.path.join(self.basedir, 'no>'), 'foo') |
154 | + |
155 | + def test_rename_file(self): |
156 | + """Rename a file.""" |
157 | + testfile1 = os.path.join(self.basedir, 'testfile1>') |
158 | + testfile2 = os.path.join(self.basedir, 'testfile2<') |
159 | + open(_remove_windows_illegal_chars(testfile1), 'w').close() |
160 | + rename(testfile1, testfile2) |
161 | + self.assertFalse(os.path.exists( |
162 | + _remove_windows_illegal_chars(testfile1))) |
163 | + self.assertTrue(os.path.exists( |
164 | + _remove_windows_illegal_chars(testfile2))) |
165 | + |
166 | + def test_rename_dir(self): |
167 | + """Rename a dir.""" |
168 | + testdir1 = os.path.join(self.basedir, 'testdir1>') |
169 | + testdir2 = os.path.join(self.basedir, 'testdir2<') |
170 | + os.mkdir(_remove_windows_illegal_chars(testdir1)) |
171 | + rename(testdir1, testdir2) |
172 | + self.assertFalse(os.path.exists( |
173 | + _remove_windows_illegal_chars(testdir1))) |
174 | + self.assertTrue(os.path.exists( |
175 | + _remove_windows_illegal_chars(testdir2))) |
176 | + |
177 | + def test_listdir(self): |
178 | + """Return a list of the files in a dir.""" |
179 | + open(os.path.join(self.basedir, 'foo'), 'w').close() |
180 | + open(os.path.join(self.basedir, 'bar'), 'w').close() |
181 | + l = listdir(self.basedir) |
182 | + test_file = u'test_file\u200b\u2033\u200b\u200b\u203d\u200b\u200b' +\ |
183 | + u'\u26b9\u200b\u200b\u2223\u200b\u200b\u2236\u200b\u200b' +\ |
184 | + u'\u2039\u200b\u200b\u2044\u200b\u200b\u203a\u200b' |
185 | + self.assertEqual(sorted(l), ['bar', 'foo', test_file]) |
186 | + |
187 | + def test_access_rw(self): |
188 | + """Test access on a file with full permission.""" |
189 | + self.assertTrue(access(self.testfile)) |
190 | + |
191 | + def test_access_ro(self): |
192 | + """Test access on a file with read only permission.""" |
193 | + fixed_path = _remove_windows_illegal_chars(self.testfile) |
194 | + os.chmod(fixed_path, 0o444) |
195 | + self.assertTrue(access(self.testfile)) |
196 | + os.chmod(fixed_path, 0o664) |
197 | + |
198 | + def test_access_nothing(self): |
199 | + """Test access on a file with no permission at all.""" |
200 | set_no_rights(self.testfile) |
201 | self.assertFalse(access(self.testfile)) |
202 | - |
203 | - def test_access_read_write_user(self): |
204 | - """Test when the user sid has rw rights.""" |
205 | - # set the file to be read and write just by the user |
206 | - groups = {} |
207 | - groups[GetUserName()] = FILE_GENERIC_READ | FILE_GENERIC_WRITE |
208 | - _set_file_attributes(self.testfile, groups) |
209 | - self.assertTrue(access(self.testfile)) |
210 | - |
211 | - def test_access_read_write_everyone(self): |
212 | - """Test when the everyone sid has rw rights.""" |
213 | - groups = {} |
214 | - groups['Everyone'] = FILE_GENERIC_READ | FILE_GENERIC_WRITE |
215 | - _set_file_attributes(self.testfile, groups) |
216 | - self.assertTrue(access(self.testfile)) |
217 | - |
218 | - def test_access_write_user_everyone_read(self): |
219 | - """Test when the user sid has w rights.""" |
220 | - groups = {} |
221 | - groups[GetUserName()] = FILE_GENERIC_WRITE |
222 | - groups['Everyone'] = FILE_GENERIC_READ |
223 | - _set_file_attributes(self.testfile, groups) |
224 | - self.assertTrue(access(self.testfile)) |
225 | - |
226 | - def test_access_write_everyone_user_read(self): |
227 | - """Test when the everyone sid has w rights""" |
228 | - groups = {} |
229 | - groups[GetUserName()] = FILE_GENERIC_READ |
230 | - groups['Everyone'] = FILE_GENERIC_WRITE |
231 | - _set_file_attributes(self.testfile, groups) |
232 | - self.assertTrue(access(self.testfile)) |
233 | - |
234 | - def test_access_write_user_everyone(self): |
235 | - """Test when everyone and user have w rights.""" |
236 | - groups = {} |
237 | - groups[GetUserName()] = FILE_GENERIC_WRITE |
238 | - groups['Everyone'] = FILE_GENERIC_WRITE |
239 | - _set_file_attributes(self.testfile, groups) |
240 | - self.assertFalse(access(self.testfile)) |
241 | - |
242 | - def test_access_read_user(self): |
243 | - """Test when the sid has r rights.""" |
244 | - groups = {} |
245 | - groups[GetUserName()] = FILE_GENERIC_READ |
246 | - _set_file_attributes(self.testfile, groups) |
247 | - self.assertTrue(access(self.testfile)) |
248 | - |
249 | - def test_access_read_everyone(self): |
250 | - """Test when everyone has r rights.""" |
251 | - groups = {} |
252 | - groups['Everyone'] = FILE_GENERIC_READ |
253 | - _set_file_attributes(self.testfile, groups) |
254 | - self.assertTrue(access(self.testfile)) |
255 | - |
256 | - def test_access_read_user_everyone(self): |
257 | - """Test when user and everyone have r rights.""" |
258 | - groups = {} |
259 | - groups[GetUserName()] = FILE_GENERIC_READ |
260 | - groups['Everyone'] = FILE_GENERIC_READ |
261 | - _set_file_attributes(self.testfile, groups) |
262 | - self.assertTrue(access(self.testfile)) |
263 | - |
264 | - def test_access_full_user(self): |
265 | - """Test when the sid has full control.""" |
266 | - groups = {} |
267 | - groups[GetUserName()] = FILE_ALL_ACCESS |
268 | - _set_file_attributes(self.testfile, groups) |
269 | - self.assertTrue(access(self.testfile)) |
270 | - |
271 | - def test_access_full_everyone(self): |
272 | - """test when everyone has full control.""" |
273 | - groups = {} |
274 | - groups['Everyone'] = FILE_ALL_ACCESS |
275 | - _set_file_attributes(self.testfile, groups) |
276 | - self.assertTrue(access(self.testfile)) |
277 | + set_file_readwrite(self.testfile) |
278 | + |
279 | + def test_stat_normal(self): |
280 | + """Test on a normal file.""" |
281 | + self.assertEqual(os.stat(_remove_windows_illegal_chars(self.testfile)), |
282 | + stat_path(self.testfile)) |
283 | + |
284 | + def test_stat_no_path(self): |
285 | + """Test that it raises proper error when no file is there.""" |
286 | + try: |
287 | + return stat_path(os.path.join(self.basedir, 'nofile')) |
288 | + except OSError, e: |
289 | + self.assertEqual(e.errno, errno.ENOENT) |
290 | + |
291 | + def test_path_exists_file_yes(self): |
292 | + """The file is there.""" |
293 | + self.assertTrue(path_exists(self.testfile)) |
294 | + |
295 | + def test_path_exists_file_no(self): |
296 | + """The file is not there.""" |
297 | + self.fd.close() |
298 | + os.remove(_remove_windows_illegal_chars(self.testfile)) |
299 | + self.assertFalse(path_exists(self.testfile)) |
300 | + |
301 | + def test_path_exists_dir_yes(self): |
302 | + """The dir is there.""" |
303 | + self.assertTrue(path_exists(self.basedir)) |
304 | + |
305 | + def test_path_exists_dir_no(self): |
306 | + """The dir is not there.""" |
307 | + self.fd.close() |
308 | + self.assertFalse(path_exists(os.path.join(self.basedir, 'subdir'))) |
309 | \ No newline at end of file |
310 | |
311 | === modified file 'ubuntuone/platform/windows/ipc.py' |
312 | --- ubuntuone/platform/windows/ipc.py 2011-06-15 14:46:04 +0000 |
313 | +++ ubuntuone/platform/windows/ipc.py 2011-06-21 00:52:37 +0000 |
314 | @@ -58,8 +58,7 @@ |
315 | from twisted.spread.pb import Referenceable, Root, PBServerFactory |
316 | from twisted.python.failure import Failure |
317 | from ubuntuone.syncdaemon.interfaces import IMarker |
318 | -from ubuntuone.credentials import CredentialsManagementTool |
319 | -from ubuntuone.platform import get_creds_proxy |
320 | +from ubuntuone.platform.windows import CredentialsManagementTool |
321 | from ubuntuone.platform.windows.network_manager import NetworkManager |
322 | from ubuntuone.syncdaemon.interaction_interfaces import ( |
323 | bool_str, |
324 | @@ -1243,7 +1242,7 @@ |
325 | |
326 | def _register_to_signals(self): |
327 | """Register to the creds signals.""" |
328 | - self.creds = get_creds_proxy() |
329 | + self.creds = CredentialsManagementTool() |
330 | self.creds.register_to_credentials_found(self.on_credentials_found_cb) |
331 | self.creds.register_to_credentials_not_found( |
332 | self.on_credentials_error_cb) |
333 | |
334 | === modified file 'ubuntuone/platform/windows/os_helper.py' |
335 | --- ubuntuone/platform/windows/os_helper.py 2011-06-17 09:31:15 +0000 |
336 | +++ ubuntuone/platform/windows/os_helper.py 2011-06-21 00:52:37 +0000 |
337 | @@ -26,8 +26,8 @@ |
338 | from functools import wraps |
339 | from contextlib import contextmanager |
340 | from pywintypes import error as PyWinError |
341 | -from win32api import MoveFileEx, GetUserName, GetFileAttributes |
342 | -from win32file import FindFilesW |
343 | +from win32api import GetUserName, GetFileAttributes |
344 | +from win32file import FindFilesW, MoveFileExW |
345 | from win32com.client import Dispatch |
346 | from pywintypes import error |
347 | from win32con import FILE_ATTRIBUTE_READONLY |
348 | @@ -58,11 +58,36 @@ |
349 | if sys.platform != 'win32': |
350 | WindowsError = None |
351 | |
352 | + |
353 | +logger = logging.getLogger('ubuntuone.SyncDaemon.VM') |
354 | platform = 'win32' |
355 | |
356 | LONG_PATH_PREFIX = '\\\\?\\' |
357 | EVERYONE_GROUP = 'Everyone' |
358 | ADMINISTRATORS_GROUP = 'Administrators' |
359 | +# a map that contains the illegal chars and their 'utf8' representation on |
360 | +# windows so that we can show something to the user |
361 | +WINDOWS_ILLEGAL_CHARS_MAP = { |
362 | + '<':u'\N{ZERO WIDTH SPACE}\N{SINGLE LEFT-POINTING ANGLE QUOTATION MARK}\N{ZERO WIDTH SPACE}', |
363 | + '>':u'\N{ZERO WIDTH SPACE}\N{SINGLE RIGHT-POINTING ANGLE QUOTATION MARK}\N{ZERO WIDTH SPACE}', |
364 | + ':':u'\N{ZERO WIDTH SPACE}\N{RATIO}\N{ZERO WIDTH SPACE}', |
365 | + '"':u'\N{ZERO WIDTH SPACE}\N{DOUBLE PRIME}\N{ZERO WIDTH SPACE}', |
366 | + '/':u'\N{ZERO WIDTH SPACE}\N{FRACTION SLASH}\N{ZERO WIDTH SPACE}', |
367 | + '|':u'\N{ZERO WIDTH SPACE}\N{DIVIDES}\N{ZERO WIDTH SPACE}', |
368 | + '?':u'\N{ZERO WIDTH SPACE}\N{INTERROBANG}\N{ZERO WIDTH SPACE}', |
369 | + '*':u'\N{ZERO WIDTH SPACE}\N{SEXTILE}\N{ZERO WIDTH SPACE}' |
370 | +} |
371 | +LINUX_ILLEGAL_CHARS_MAP = { |
372 | + u'\N{ZERO WIDTH SPACE}\N{SINGLE LEFT-POINTING ANGLE QUOTATION MARK}\N{ZERO WIDTH SPACE}':'<', |
373 | + u'\N{ZERO WIDTH SPACE}\N{SINGLE RIGHT-POINTING ANGLE QUOTATION MARK}\N{ZERO WIDTH SPACE}':'>', |
374 | + u'\N{ZERO WIDTH SPACE}\N{RATIO}\N{ZERO WIDTH SPACE}':':', |
375 | + u'\N{ZERO WIDTH SPACE}\N{DOUBLE PRIME}\N{ZERO WIDTH SPACE}':'"', |
376 | + u'\N{ZERO WIDTH SPACE}\N{FRACTION SLASH}\N{ZERO WIDTH SPACE}':'/', |
377 | + u'\N{ZERO WIDTH SPACE}\N{SET MINUS}\N{ZERO WIDTH SPACE}':'\\', |
378 | + u'\N{ZERO WIDTH SPACE}\N{DIVIDES}\N{ZERO WIDTH SPACE}':'|', |
379 | + u'\N{ZERO WIDTH SPACE}\N{INTERROBANG}\N{ZERO WIDTH SPACE}':'?', |
380 | + u'\N{ZERO WIDTH SPACE}\N{SEXTILE}\N{ZERO WIDTH SPACE}':'*' |
381 | +} |
382 | |
383 | |
384 | def _int_to_bin(n): |
385 | @@ -83,9 +108,79 @@ |
386 | return LookupAccountName('', group_name)[0] |
387 | |
388 | |
389 | +def _has_read_mask(number): |
390 | + """Return if the read flag is present.""" |
391 | + # get the bin representation of the mask |
392 | + binary = _int_to_bin(number) |
393 | + # there is actual no documentation of this in MSDN but if bt 28 is set, |
394 | + # the mask has full access, more info can be found here: |
395 | + # http://www.iu.hio.no/cfengine/docs/cfengine-NT/node47.html |
396 | + if binary[28] == '1': |
397 | + return True |
398 | + # there is no documentation in MSDN about this, but if bit 0 and 3 are true |
399 | + # we have the read flag, more info can be found here: |
400 | + # http://www.iu.hio.no/cfengine/docs/cfengine-NT/node47.html |
401 | + return binary[0] == '1' and binary[3] == '1' |
402 | + |
403 | + |
404 | +def _check_winerror_123(exception, cb, path, *args, **kwargs): |
405 | + """Checks if an exception is a 123 error and executes the cb.""" |
406 | + if getattr(exception, "winerror", None) == 123 : |
407 | + path = _remove_windows_illegal_chars(path) |
408 | + return cb(path, *args, **kwargs) |
409 | + else: |
410 | + raise exception |
411 | + |
412 | + |
413 | +def _is_illegal_path(path): |
414 | + """Return if the path is illegal in the system.""" |
415 | + if LONG_PATH_PREFIX in path: |
416 | + path = path.replace(LONG_PATH_PREFIX, '') |
417 | + return any(c in WINDOWS_ILLEGAL_CHARS_MAP |
418 | + for c in os.path.splitdrive(path)[1]) |
419 | + |
420 | + |
421 | +def _remove_windows_illegal_chars(path): |
422 | + """Remove illegal chars and return a valid windows path.""" |
423 | + if not _is_illegal_path(path): |
424 | + return path |
425 | + prefix = False |
426 | + # do not split the path if you have a long path prefix since the |
427 | + # partial paths will be wrong. |
428 | + if LONG_PATH_PREFIX in path: |
429 | + # remove it and remember to add it |
430 | + path = path.replace(LONG_PATH_PREFIX, '') |
431 | + prefix = True |
432 | + volume, path = os.path.splitdrive(path) |
433 | + partial_paths = path.split('\\') |
434 | + result = [] |
435 | + for partial in partial_paths: |
436 | + # ensure that the illegal chars are not there |
437 | + for illegal_char, replacement in WINDOWS_ILLEGAL_CHARS_MAP.iteritems(): |
438 | + partial = partial.replace(illegal_char, replacement) |
439 | + result.append(partial) |
440 | + result = os.path.join(*result) |
441 | + if volume: |
442 | + result = volume + '\\' + result |
443 | + if prefix: |
444 | + result = LONG_PATH_PREFIX + result |
445 | + return unicode(result) |
446 | + |
447 | def _set_file_attributes(path, groups): |
448 | """Set file attributes using the wind32api.""" |
449 | - security_descriptor = GetFileSecurity(path, DACL_SECURITY_INFORMATION) |
450 | + security_descriptor = None |
451 | + try: |
452 | + security_descriptor = GetFileSecurity(path, DACL_SECURITY_INFORMATION) |
453 | + except PyWinError, e: |
454 | + logger.exception('Got exception when setting file attributes %s ' + |
455 | + 'for path %s', path, groups) |
456 | + #check if the issue was due to an illegal path |
457 | + if getattr(e, "winerror", None) == 123: |
458 | + path = _remove_windows_illegal_chars(path) |
459 | + return _set_file_attributes(path, groups) |
460 | + else: |
461 | + raise |
462 | + |
463 | dacl = ACL() |
464 | for group_name in groups: |
465 | # set the attributes of the group only if not null |
466 | @@ -94,30 +189,15 @@ |
467 | dacl.AddAccessAllowedAceEx(ACL_REVISION, |
468 | CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, groups[group_name], |
469 | group_sid) |
470 | - # the dacl has all the info of the dff groups passed in the parameters |
471 | + # the dacl has all the info of the diff groups passed in the parameters |
472 | security_descriptor.SetSecurityDescriptorDacl(1, dacl, 0) |
473 | SetFileSecurity(path, DACL_SECURITY_INFORMATION, security_descriptor) |
474 | |
475 | |
476 | -def _has_read_mask(number): |
477 | - """Return if the read flag is present.""" |
478 | - # get the bin representation of the mask |
479 | - binary = _int_to_bin(number) |
480 | - # there is actual no documentation of this in MSDN but if bt 28 is set, |
481 | - # the mask has full access, more info can be found here: |
482 | - # http://www.iu.hio.no/cfengine/docs/cfengine-NT/node47.html |
483 | - if binary[28] == '1': |
484 | - return True |
485 | - # there is no documentation in MSDN about this, but if bit 0 and 3 are true |
486 | - # we have the read flag, more info can be found here: |
487 | - # http://www.iu.hio.no/cfengine/docs/cfengine-NT/node47.html |
488 | - return binary[0] == '1' and binary[3] == '1' |
489 | - |
490 | - |
491 | def absparam(paths_indexes=[], paths_names=[]): |
492 | """Ensure that the paths are absolute.""" |
493 | def decorator(function): |
494 | - """Decorate the funtion to make sure the paths are absolute.""" |
495 | + """Decorate the function to make sure the paths are absolute.""" |
496 | @wraps(function) |
497 | def abspath_wrapper(*args, **kwargs): |
498 | """Set the paths to be absolute.""" |
499 | @@ -131,7 +211,7 @@ |
500 | kwargs[current_key] = \ |
501 | os.path.abspath(kwargs[current_key]) |
502 | except KeyError: |
503 | - logging.warn('Patameter %s could not be made a long path', |
504 | + logger.warn('Parameter %s could not be made a long path', |
505 | current_key) |
506 | return function(*fixed_args, **kwargs) |
507 | return abspath_wrapper |
508 | @@ -155,26 +235,36 @@ |
509 | kwargs[current_key] = \ |
510 | _set_as_long_path(kwargs[current_key]) |
511 | except KeyError: |
512 | - logging.warn('Patameter %s could not be made a long path', |
513 | + logger.warn('Parameter %s could not be made a long path', |
514 | current_key) |
515 | return function(*fixed_args, **kwargs) |
516 | return long_path_wrapper |
517 | return decorator |
518 | |
519 | |
520 | +def _set_no_rights_helper(path): |
521 | + """Set the rights to be none.""" |
522 | + os.chmod(path, 0o000) |
523 | + groups = {} |
524 | + _set_file_attributes(path, groups) |
525 | + |
526 | + |
527 | @longpath(paths_indexes=[0]) |
528 | def set_no_rights(path): |
529 | # set the groups to be empty which will remove all the rights of the file |
530 | - os.chmod(path, 0o000) |
531 | - groups = {} |
532 | - _set_file_attributes(path, groups) |
533 | + try: |
534 | + _set_no_rights_helper(path) |
535 | + except OSError, e: |
536 | + logger.exception('Got exception when setting path %s to no rights', |
537 | + path) |
538 | + return _check_winerror_123(e, _set_no_rights_helper, path) |
539 | |
540 | |
541 | @longpath(paths_indexes=[0]) |
542 | def set_file_readonly(path): |
543 | """Change path permissions to readonly in a file.""" |
544 | # we use the win32 api because chmod just sets the readonly flag and |
545 | - # we want to have imore control over the permissions |
546 | + # we want to have more control over the permissions |
547 | groups = {} |
548 | groups[ADMINISTRATORS_GROUP] = FILE_GENERIC_READ | FILE_GENERIC_WRITE |
549 | groups[GetUserName()] = FILE_GENERIC_READ |
550 | @@ -191,7 +281,12 @@ |
551 | groups[GetUserName()] = FILE_ALL_ACCESS |
552 | # the above equals more or less to 0774 |
553 | _set_file_attributes(path, groups) |
554 | - os.chmod(path, stat.S_IWRITE) |
555 | + try: |
556 | + os.chmod(path, stat.S_IWRITE) |
557 | + except OSError, e: |
558 | + logger.exception('Get exception when trying to set readwrite ' + |
559 | + 'path %s', path) |
560 | + return _check_winerror_123(e, os.chmod, path, stat.S_IWRITE) |
561 | |
562 | |
563 | @longpath(paths_indexes=[0]) |
564 | @@ -229,7 +324,7 @@ |
565 | @contextmanager |
566 | @longpath(paths_indexes=[0]) |
567 | def allow_writes(path): |
568 | - """A very simple context manager to allow writting in RO dirs.""" |
569 | + """A very simple context manager to allow witting in RO dirs.""" |
570 | # get the old dacl of the file so that we can reset it back when done |
571 | security_descriptor = GetFileSecurity(path, DACL_SECURITY_INFORMATION) |
572 | old_dacl = security_descriptor.GetSecurityDescriptorDacl() |
573 | @@ -243,18 +338,28 @@ |
574 | @longpath(paths_indexes=[0]) |
575 | def remove_file(path): |
576 | """Remove a file.""" |
577 | - os.remove(path) |
578 | + try: |
579 | + os.remove(path) |
580 | + except OSError, e: |
581 | + logger.exception('Got exception when trying to remove path %s', path) |
582 | + return _check_winerror_123(e, os.remove, path) |
583 | |
584 | |
585 | @longpath(paths_indexes=[0]) |
586 | def remove_dir(path): |
587 | """Remove a dir.""" |
588 | - os.rmdir(path) |
589 | + try: |
590 | + os.rmdir(path) |
591 | + except OSError, e: |
592 | + logger.exception('Got exception when trying to remove dir %s', path) |
593 | + return _check_winerror_123(e, os.rmdir, path) |
594 | |
595 | |
596 | @longpath(paths_indexes=[0]) |
597 | def path_exists(path): |
598 | """Return if the path exists.""" |
599 | + if _is_illegal_path(path): |
600 | + path = _remove_windows_illegal_chars(path) |
601 | return os.path.exists(path) |
602 | |
603 | |
604 | @@ -264,37 +369,60 @@ |
605 | if recursive: |
606 | try: |
607 | os.makedirs(path) |
608 | - except WindowsError, e: |
609 | - raise OSError(str(e)) |
610 | + except OSError, e: |
611 | + logger.exception('Got exception when trying to make dirs %s', path) |
612 | + return _check_winerror_123(e, os.makedirs, path) |
613 | else: |
614 | try: |
615 | os.mkdir(path) |
616 | - except WindowsError, e: |
617 | - raise OSError(str(e)) |
618 | + except OSError, e: |
619 | + logger.exception('Got exception when trying to make dirs %s', path) |
620 | + return _check_winerror_123(e, os.mkdir, path) |
621 | |
622 | |
623 | @longpath(paths_indexes=[0]) |
624 | def open_file(path, mode='r'): |
625 | """Open a file.""" |
626 | - return open(path, mode) |
627 | + try: |
628 | + return open(path, mode) |
629 | + except OSError, e: |
630 | + logger.exception('Got exception when trying to open path %s', path) |
631 | + return _check_winerror_123(e, open, path, mode) |
632 | |
633 | |
634 | @absparam(paths_indexes=[0, 1]) |
635 | @longpath(paths_indexes=[0, 1]) |
636 | def rename(path_from, path_to): |
637 | """Rename a file or directory.""" |
638 | - # to ensure the same behavious as on linux, use the MoveEx function from |
639 | - # win32 which will allow to replace the destionation path if exists and |
640 | + # to ensure the same behaviors as on linux, use the MoveEx function from |
641 | + # win32 which will allow to replace the destination path if exists and |
642 | # the user has the rights see: |
643 | # http://msdn.microsoft.com/en-us/library/aa365240(v=vs.85).aspx |
644 | try: |
645 | - MoveFileEx( |
646 | + MoveFileExW( |
647 | path_from, path_to, |
648 | MOVEFILE_COPY_ALLOWED | |
649 | MOVEFILE_REPLACE_EXISTING | |
650 | MOVEFILE_WRITE_THROUGH) |
651 | except PyWinError, e: |
652 | - raise OSError(str(e)) |
653 | + logger.exception('Got exception when trying to rename from ' + |
654 | + '%s to %s', path_from, path_to) |
655 | + #check if the issue was due to an illegal path |
656 | + if getattr(e, "winerror", None) == 123: |
657 | + path_from = _remove_windows_illegal_chars(path_from) |
658 | + path_to = _remove_windows_illegal_chars(path_to) |
659 | + try: |
660 | + MoveFileExW( |
661 | + path_from, path_to, |
662 | + MOVEFILE_COPY_ALLOWED | |
663 | + MOVEFILE_REPLACE_EXISTING | |
664 | + MOVEFILE_WRITE_THROUGH) |
665 | + except PyWinError, e: |
666 | + logger.exception('Got exception when trying to rename' + |
667 | + ' from %s to %s', path_from, path_to) |
668 | + raise OSError(str(e)) |
669 | + else: |
670 | + raise OSError(str(e)) |
671 | |
672 | |
673 | @absparam(paths_indexes=[0, 1]) |
674 | @@ -343,7 +471,7 @@ |
675 | @longpath(paths_indexes=[0]) |
676 | def listdir(directory): |
677 | """List a directory.""" |
678 | - # os.listdir combines / and \ in its search which breacks things on |
679 | + # os.listdir combines / and \ in its search which breaks things on |
680 | # windows, we use the win32 api instead. |
681 | result = [] |
682 | for file_info in FindFilesW(os.path.join(directory, '*.*')): |
683 | @@ -357,8 +485,12 @@ |
684 | @longpath(paths_indexes=[0]) |
685 | def access(path): |
686 | """Return if the path is at least readable.""" |
687 | + # lets consider the access on an illegal path to be a special case |
688 | + # since that will only occur in the case where the user created the path |
689 | # for a file to be readable it has to be readable either by the user or |
690 | # by the everyone group |
691 | + if _is_illegal_path(path): |
692 | + path = _remove_windows_illegal_chars(path) |
693 | if not os.path.exists(path): |
694 | return False |
695 | security_descriptor = GetFileSecurity(path, DACL_SECURITY_INFORMATION) |
696 | @@ -380,13 +512,14 @@ |
697 | # in our case we can ignore this since we are looking for visible |
698 | # users |
699 | continue |
700 | - return (GetUserName() in accounts or EVERYONE_GROUP in accounts) |
701 | + return (GetUserName() in accounts or EVERYONE_GROUP in accounts) and\ |
702 | + os.access(path, os.R_OK) |
703 | |
704 | |
705 | @longpath(paths_indexes=[0]) |
706 | def can_write(path): |
707 | """Return if the path can be written to.""" |
708 | - file_att = GetFileAttributes(path) |
709 | + file_att = GetFileAttributes(path) |
710 | if file_att and FILE_ATTRIBUTE_READONLY: |
711 | return False |
712 | return True |
713 | @@ -395,14 +528,18 @@ |
714 | def stat_path(path, look_for_link=True): |
715 | """Return stat info about a path.""" |
716 | # if the path end with .lnk, that means we are dealing with a link |
717 | - # and we should return the stat of th target path |
718 | + # and we should return the stat of the target path |
719 | if path.endswith('.lnk'): |
720 | shell = Dispatch('WScript.Shell') |
721 | shortcut = shell.CreateShortCut(path) |
722 | path = shortcut.Targetpath |
723 | if look_for_link and os.path.exists(path + '.lnk'): |
724 | return stat_path(path + '.lnk') |
725 | - return os.lstat(path) |
726 | + try: |
727 | + return os.lstat(path) |
728 | + except OSError, e: |
729 | + logger.exception('Got exception when trying to stat path %s', path) |
730 | + return _check_winerror_123(e, stat_path, path, look_for_link) |
731 | |
732 | |
733 | def move_to_trash(path): |
734 | @@ -415,7 +552,7 @@ |
735 | def set_application_name(app_name): |
736 | """Set the app name.""" |
737 | # there is not way to do this on windows. The name will be correct when |
738 | - # executed from a bundle .exe otherwhise it will be python |
739 | + # executed from a bundle .exe otherwise it will be python |
740 | |
741 | |
742 | def is_root(): |
743 | @@ -432,7 +569,7 @@ |
744 | |
745 | |
746 | def validate_path_from_unix(path): |
747 | - """Get a unix path and return it so that it cna be used.""" |
748 | + """Get a unix path and return it so that it can be used.""" |
749 | path = path.replace('/', '\\') |
750 | return path |
751 | |
752 | @@ -449,7 +586,9 @@ |
753 | |
754 | def abspath(path): |
755 | """Return an abspath.""" |
756 | - abs = os.path.abspath(path) |
757 | + # TODO: do we really need to do this, are the tests broken? |
758 | + path = path.replace('/', '\\') |
759 | + abs = os.path.abspath(_remove_windows_illegal_chars(path)) |
760 | if not LONG_PATH_PREFIX in abs: |
761 | abs = LONG_PATH_PREFIX + abs |
762 | return abs |
763 | \ No newline at end of file |
* Can you please replace: ILLEGAL_ CHARS_MAP:
self. testfile = self.testfile + char WINDOWS_ ILLEGAL_ CHARS_MAP)
for char in WINDOWS_
with:
self.testfile = ''.join(
* typos: "illechal", "illegla"
* can you please use os.path. splitdrive( path) (http:// docs.python. org/library/ os.path. html#os. path.splitdrive) instead of the custom logic in diff line 392 (and everywhere else where you split the volume letter)?
* can you replace:
for illegal_char in WINDOWS_ ILLEGAL_ CHARS_MAP:
if illegal_char in path:
return True
with:
any(c in WINDOWS_ ILLEGAL_ CHARS_MAP for c in path)
* can you please replace:
for illegal_char in WINDOWS_ ILLEGAL_ CHARS_MAP: replace( illegal_ char,
WINDOWS_ ILLEGAL_ CHARS_MAP[ illegal_ char])
partial = partial.
with:
for illegal_char, replacement in WINDOWS_ ILLEGAL_ CHARS_MAP. iteritems( ): replace( illegal_ char, replacement)
partial = partial.
* can you please add a logger.exception statement inside the except OSError, e: within set_no_rights? it will be useful to diagnose potential issues from log reports from our users. Same for remove_file, remove_dir, make_dir, open_file, rename, stat_path.
* why are you doing this in abspath? path = path.replace('/', '\\') Seems unnecessary and may hide bugs in our code regarding multiplatform path management.