Ticket #1074: unicode-args-and-rm-tahoe-exe.3.dpatch

File unicode-args-and-rm-tahoe-exe.3.dpatch, 21.1 KB (added by davidsarah, at 2010-07-14T02:39:49Z)

Changes to Tahoe needed to work with new zetuptoolz (that does not use .exe wrappers on Windows), and to support Uni code arguments and stdout/stderr.

Line 
1Wed Jul 14 03:20:59 GMT Daylight Time 2010  david-sarah@jacaranda.org
2  * Changes to Tahoe needed to work with new zetuptoolz (that does not use .exe wrappers on Windows), and to support Unicode arguments and stdout/stderr.
3
4New patches:
5
6[Changes to Tahoe needed to work with new zetuptoolz (that does not use .exe wrappers on Windows), and to support Unicode arguments and stdout/stderr.
7david-sarah@jacaranda.org**20100714022059
8 Ignore-this: bae64a497f95e834852de7466ca04066
9] {
10hunk ./bin/tahoe-script.template 8
11 where = os.path.realpath(sys.argv[0])
12 base = os.path.dirname(os.path.dirname(where))
13 
14+if sys.platform == "win32":
15+    installed_tahoe = os.path.join(os.path.dirname(sys.executable), 'Scripts', 'tahoe.pyscript')
16+else:
17+    installed_tahoe = "/usr/bin/tahoe"
18+
19 whoami = '''\
20 I am a "bin/tahoe" executable who is only for the convenience of running
21 Tahoe from its source distribution -- I work only when invoked as the "tahoe"
22hunk ./bin/tahoe-script.template 27
23     print '''\
24 I just tried to run and found that I am not living in such a directory, so I
25 am stopping now. To run Tahoe after it has been is installed, please execute
26-my brother, also named "tahoe", who gets installed into the appropriate place
27-for executables when you run "make install" (perhaps as /usr/bin/tahoe).
28-'''
29+my brother, who gets installed into the appropriate place for executables
30+when you run "make install" (perhaps as %s).
31+''' % (installed_tahoe,)
32     sys.exit(1)
33 
34 # we've found our home. Put the tahoe support/lib etc. in our PYTHONPATH.
35hunk ./bin/tahoe-script.template 49
36     pp = supportdir
37 os.environ["PYTHONPATH"] = pp
38 
39-# find the location of the tahoe executable.
40-bin_dir = "bin"
41+# find commandline args and the location of the tahoe executable.
42 if sys.platform == "win32":
43hunk ./bin/tahoe-script.template 51
44-    bin_dir = "Scripts"
45-executable = os.path.join(base, "support", bin_dir, "tahoe")
46+    import re
47+    from ctypes import WINFUNCTYPE, POINTER, byref, c_wchar_p, c_int, windll
48+
49+    GetCommandLineW = WINFUNCTYPE(c_wchar_p)(("GetCommandLineW", windll.kernel32))
50+    CommandLineToArgvW = WINFUNCTYPE(POINTER(c_wchar_p), c_wchar_p, POINTER(c_int)) \
51+                            (("CommandLineToArgvW", windll.shell32))
52+
53+    argc = c_int(0)
54+    argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
55+
56+    # See src/allmydata/scripts/runner.py for the corresponding unmangler.
57+    def mangle(s):
58+        return str(re.sub(r'[^\x20-\x7E]', lambda m: '\x7F%x;' % (ord(m.group(0)),), s))
59+
60+    argv = [mangle(argv_unicode[i]) for i in xrange(1, argc.value)]
61+    local_tahoe = "Scripts\\tahoe.pyscript"
62+else:
63+    argv = sys.argv
64+    local_tahoe = "bin/tahoe"
65+
66+script = os.path.join(base, "support", local_tahoe)
67 
68 try:
69hunk ./bin/tahoe-script.template 74
70-    res = subprocess.call([executable] + sys.argv[1:], env=os.environ)
71+    res = subprocess.call([sys.executable, script] + argv[1:], env=os.environ)
72 except (OSError, IOError), le:
73     if le.args[0] == errno.ENOENT:
74         print whoami
75hunk ./bin/tahoe-script.template 78
76+
77         print '''\
78 I just tried to run and could not find my brother, named
79hunk ./bin/tahoe-script.template 81
80-"../support/bin/tahoe". To run Tahoe when it is installed, please execute my
81-brother, also named "tahoe", who gets installed into the appropriate place
82-for executables when you run "make install" (perhaps as /usr/bin/tahoe).
83-'''
84+"../support/%s". To run Tahoe when it is installed, please execute my
85+brother, who gets installed into the appropriate place for executables
86+when you run "make install" (perhaps as %s).
87+''' % (local_tahoe, installed_tahoe)
88         raise
89 except Exception, le:
90     print whoami
91hunk ./bin/tahoe-script.template 89
92     print '''\
93-I just tried to invoke my brother, named "../support/bin/tahoe" and got an
94+I just tried to invoke my brother, named "../support/%s" and got an
95 exception.
96hunk ./bin/tahoe-script.template 91
97-'''
98+''' % (local_tahoe,)
99     raise
100 else:
101     sys.exit(res)
102hunk ./setup.py 238
103     def run(self):
104         bin_tahoe_template = os.path.join("bin", "tahoe-script.template")
105 
106-        # Create the 'tahoe-script.py' file under the 'bin' directory. The
107-        # 'tahoe-script.py' file is exactly the same as the
108-        # 'tahoe-script.template' script except that the shebang line is
109-        # rewritten to use our sys.executable for the interpreter. On
110-        # Windows, create a tahoe.exe will execute it. On non-Windows, make a
111-        # symlink to it from 'tahoe'. The tahoe.exe will be copied from the
112-        # setuptools egg's cli.exe and this will work from a zip-safe and
113-        # non-zip-safe setuptools egg.
114+        if sys.platform == 'win32':
115+            # 'tahoe' script is needed for cygwin
116+            script_names = ["tahoe.pyscript", "tahoe"]
117+        else:
118+            script_names = ["tahoe"]
119+
120+        # Create the tahoe script file under the 'bin' directory. This
121+        # file is exactly the same as the 'tahoe-script.template' script
122+        # except that the shebang line is rewritten to use our sys.executable
123+        # for the interpreter.
124         f = open(bin_tahoe_template, "rU")
125         script_lines = f.readlines()
126         f.close()
127hunk ./setup.py 251
128-        script_lines[0] = "#!%s\n" % sys.executable
129-        tahoe_script = os.path.join("bin", "tahoe-script.py")
130-        f = open(tahoe_script, "w")
131-        for line in script_lines:
132-            f.write(line)
133-        f.close()
134-        if sys.platform == "win32":
135-            from pkg_resources import require
136-            setuptools_egg = require("setuptools")[0].location
137-            if os.path.isfile(setuptools_egg):
138-                z = zipfile.ZipFile(setuptools_egg, 'r')
139-                for filename in z.namelist():
140-                    if 'cli.exe' in filename:
141-                        cli_exe = z.read(filename)
142-            else:
143-                cli_exe = os.path.join(setuptools_egg, 'setuptools', 'cli.exe')
144-            tahoe_exe = os.path.join("bin", "tahoe.exe")
145-            if os.path.isfile(setuptools_egg):
146-                f = open(tahoe_exe, 'wb')
147-                f.write(cli_exe)
148-                f.close()
149-            else:
150-                shutil.copy(cli_exe, tahoe_exe)
151-        else:
152+        script_lines[0] = '#!%s\n' % sys.executable
153+        for script_name in script_names:
154+            tahoe_script = os.path.join("bin", script_name)
155             try:
156hunk ./setup.py 255
157-                os.remove(os.path.join('bin', 'tahoe'))
158-            except:
159+                os.remove(tahoe_script)
160+            except Exception:
161                 # okay, probably it was already gone
162                 pass
163hunk ./setup.py 259
164-            os.symlink('tahoe-script.py', os.path.join('bin', 'tahoe'))
165+            f = open(tahoe_script, "wb")
166+            for line in script_lines:
167+                f.write(line)
168+            f.close()
169+
170+            # chmod +x
171+            old_mode = stat.S_IMODE(os.stat(tahoe_script)[stat.ST_MODE])
172+            new_mode = old_mode | (stat.S_IXUSR | stat.S_IRUSR |
173+                                   stat.S_IXGRP | stat.S_IRGRP |
174+                                   stat.S_IXOTH | stat.S_IROTH )
175+            os.chmod(tahoe_script, new_mode)
176 
177hunk ./setup.py 271
178-        # chmod +x bin/tahoe-script.py
179-        old_mode = stat.S_IMODE(os.stat(tahoe_script)[stat.ST_MODE])
180-        new_mode = old_mode | (stat.S_IXUSR | stat.S_IRUSR |
181-                               stat.S_IXGRP | stat.S_IRGRP |
182-                               stat.S_IXOTH | stat.S_IROTH )
183-        os.chmod(tahoe_script, new_mode)
184+        old_tahoe_exe = os.path.join("bin", "tahoe.exe")
185+        try:
186+            os.remove(old_tahoe_exe)
187+        except Exception:
188+            pass
189+        if os.path.exists(old_tahoe_exe):
190+            print >>sys.stderr, "%s could not be deleted. Please delete this file manually." % (old_tahoe_exe,)
191 
192 class MySdist(sdist.sdist):
193     """ A hook in the sdist command so that we can determine whether this the
194hunk ./src/allmydata/scripts/runner.py 12
195 pkg_resources.require('allmydata-tahoe')
196 from allmydata.scripts.common import BaseOptions
197 from allmydata.scripts import debug, create_node, startstop_node, cli, keygen, stats_gatherer
198+from allmydata.util.encodingutil import quote_output, get_argv_encoding, _reload
199 
200 def GROUP(s):
201     # Usage.parseOptions compares argv[1] against command[0], so it will
202hunk ./src/allmydata/scripts/runner.py 22
203 
204 
205 class Options(BaseOptions, usage.Options):
206-    synopsis = "Usage:  tahoe <command> [command options]"
207+    synopsis = "\nUsage:  tahoe <command> [command options]"
208     subCommands = ( GROUP("Administration")
209                     +   create_node.subCommands
210                     +   keygen.subCommands
211hunk ./src/allmydata/scripts/runner.py 45
212 
213 def runner(argv,
214            run_by_human=True,
215-           stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr,
216+           stdin=None, stdout=None, stderr=None,
217            install_node_control=True, additional_commands=None):
218 
219hunk ./src/allmydata/scripts/runner.py 48
220+    stdin  = stdin  or sys.stdin
221+    stdout = stdout or sys.stdout
222+    stderr = stderr or sys.stderr
223+
224     config = Options()
225     if install_node_control:
226         config.subCommands.extend(startstop_node.subCommands)
227hunk ./src/allmydata/scripts/runner.py 70
228         c = config
229         while hasattr(c, 'subOptions'):
230             c = c.subOptions
231-        print str(c)
232-        print "%s:  %s" % (sys.argv[0], e)
233+        print >>stdout, str(c)
234+        try:
235+            msg = e.args[0].decode(get_argv_encoding())
236+        except Exception:
237+            msg = repr(e)
238+        print >>stdout, "%s:  %s\n" % (sys.argv[0], quote_output(msg, quotemarks=False))
239         return 1
240 
241     command = config.subCommand
242hunk ./src/allmydata/scripts/runner.py 110
243 
244     return rc
245 
246+
247 def run(install_node_control=True):
248hunk ./src/allmydata/scripts/runner.py 112
249-    rc = runner(sys.argv[1:])
250+    if sys.platform == "win32":
251+        windows_fixups()
252+
253+    rc = runner(sys.argv[1:], install_node_control=install_node_control)
254     sys.exit(rc)
255hunk ./src/allmydata/scripts/runner.py 117
256+
257+
258+def windows_fixups():
259+    import codecs, re
260+    from ctypes import WINFUNCTYPE, windll, CFUNCTYPE, cdll, POINTER, byref, \
261+        c_wchar_p, c_char_p, c_void_p, c_int, c_size_t
262+
263+    # Work around <http://bugs.python.org/issue6058>.
264+    codecs.register(lambda name: name == 'cp65001' and codecs.lookup('utf-8') or None)
265+
266+    # Make Unicode console output work independently of the current code page.
267+    # This also fixes <http://bugs.python.org/issue1602>.
268+    try:
269+        STDOUT_FILENO = 1
270+        STDERR_FILENO = 2
271+        fix_stdout = hasattr(sys.stdout, 'fileno') and sys.stdout.fileno() == STDOUT_FILENO
272+        fix_stderr = hasattr(sys.stderr, 'fileno') and sys.stderr.fileno() == STDERR_FILENO
273+
274+        if fix_stdout or fix_stderr:
275+            # FILE * _fdopen(int fd, const char *mode);
276+            # #define _IOLBF 0x0040
277+            # int setvbuf(FILE *stream, char *buffer, int mode, size_t size);
278+            # #define _O_U8TEXT 0x40000
279+            # int _setmode(int fd, int mode);
280+            # int fputws(const wchar_t *ws, FILE *stream);
281+            # int fflush(FILE *stream);
282+
283+            c_runtime = cdll.msvcrt
284+            NULL = None
285+            _fdopen = CFUNCTYPE(c_void_p, c_int, c_char_p)(("_fdopen", c_runtime))
286+            _IOLBF = 0x0040
287+            setvbuf = CFUNCTYPE(c_int, c_void_p, c_char_p, c_int, c_size_t)(("setvbuf", c_runtime))
288+            _O_U8TEXT = 0x40000
289+            _setmode = CFUNCTYPE(c_int, c_int, c_int)(("_setmode", c_runtime))
290+            fputws = CFUNCTYPE(c_int, c_wchar_p, c_void_p)(("fputws", c_runtime));
291+            fflush = CFUNCTYPE(c_int, c_void_p)(("fflush", c_runtime));
292+
293+            buffer_chars = 1024
294+
295+            class UnicodeOutput:
296+                def __init__(self, fileno, name):
297+                    self._stream = _fdopen(fileno, "w")
298+                    assert self._stream is not NULL
299+
300+                    # Deep magic. MSVCRT supports writing wide-oriented output to stdout/stderr
301+                    # to the console using the Unicode APIs, but it does the conversion in the
302+                    # stdio buffer, so you need that buffer to be as large as the maximum amount
303+                    # you're going to write in a single call (in bytes, not characters).
304+                    setvbuf(self._stream, NULL, _IOLBF, buffer_chars*4 + 100)
305+                    _setmode(fileno, _O_U8TEXT)
306+
307+                    self._fileno = fileno
308+                    self.closed = False
309+                    self.softspace = False
310+                    self.mode = 'w'
311+                    self.encoding = 'utf-8'
312+                    self.name = name
313+
314+                def isatty(self):
315+                    return False
316+                def close(self):
317+                    self.closed = True
318+                    self.flush()
319+                def fileno(self):
320+                    return self._fileno
321+                def flush(self):
322+                    fflush(self._stream)
323+
324+                def write(self, text):
325+                    if not isinstance(text, unicode):
326+                        text = str(text).decode('utf-8')
327+                    for i in xrange(0, len(text), buffer_chars):
328+                        fputws(text[i:(i+buffer_chars)], self._stream)
329+                        fflush(self._stream)
330+
331+                def writelines(self, lines):
332+                    for line in lines:
333+                        self.write(line)
334+
335+            if fix_stdout:
336+                sys.stdout = UnicodeOutput(STDOUT_FILENO, '<Unicode stdout>')
337+
338+            if fix_stderr:
339+                sys.stderr = UnicodeOutput(STDERR_FILENO, '<Unicode stderr>')
340+
341+            _reload()  # take account of new output encoding
342+    except Exception, e:
343+        print >>sys.stderr, "Warning: %r" % (e,)
344+
345+    # Unmangle command-line arguments.
346+    GetCommandLineW = WINFUNCTYPE(c_wchar_p)(("GetCommandLineW", windll.kernel32))
347+    CommandLineToArgvW = WINFUNCTYPE(POINTER(c_wchar_p), c_wchar_p, POINTER(c_int)) \
348+                            (("CommandLineToArgvW", windll.shell32))
349+
350+    argc = c_int(0)
351+    argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
352+
353+    # The cygwin wrapper script may append '\r' to each argument (bug in cygwin bash).
354+    def unmangle(s):
355+        return re.sub(ur'\x7f[0-9a-fA-F]*\;', lambda m: unichr(int(m.group(0)[1:-1], 16)),
356+                      s.rstrip(u'\r'))
357+
358+    try:
359+        sys.argv = [unmangle(argv_unicode[i]).encode('utf-8') for i in xrange(1, argc.value)]
360+    except Exception, e:
361+        print >>sys.stderr, "%s:  could not unmangle Unicode arguments" % (sys.argv[0],)
362+        print >>sys.stderr, repr(e)
363+        print >>sys.stderr, [argv_unicode[i] for i in xrange(1, argc.value)]
364+        return 1
365hunk ./src/allmydata/test/test_runner.py 16
366 import allmydata
367 
368 bintahoe = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(allmydata.__file__))), 'bin', 'tahoe')
369-if sys.platform == "win32":  # TODO: should this include cygwin?
370-    bintahoe += ".exe"
371+if sys.platform == "win32":
372+    bintahoe += ".pyscript"
373 
374 
375 class SkipMixin:
376hunk ./src/allmydata/util/encodingutil.py 55
377     output_encoding = _canonical_encoding(outenc)
378 
379     if sys.platform == 'win32':
380-        # Unicode arguments are not supported on Windows yet; see #565 and #1074.
381-        argv_encoding = 'ascii'
382+        # On Windows we reencode the arguments as UTF-8; see scripts/runner.py.
383+        argv_encoding = 'utf-8'
384     else:
385         argv_encoding = output_encoding
386     is_unicode_platform = sys.platform in ["win32", "darwin"]
387}
388
389Context:
390
391[Rename stringutils to encodingutil, and drop open_unicode (since the Python 'open' function works fine with Unicode paths).
392david-sarah@jacaranda.org**20100713042715
393 Ignore-this: fa2bb6b5d48ce9ba7ea2b1afc9f3b7b4
394] 
395[Resolve conflicts between NFC normalization changes, and post-1.7 branch.
396david-sarah@jacaranda.org**20100618021642
397 Ignore-this: ed3d0d71b761b1317cff9a4c92c2f5cb
398] 
399[trivial: a copy of tiny code-cleanups suggested by Kevan in reviewing #967
400zooko@zooko.com**20100617045339
401 Ignore-this: 274b3fdbf1531aa053f484a5d47d7447
402] 
403[minor code clean-up in dirnode.py
404zooko@zooko.com**20100221052527
405 Ignore-this: b01bfb47638f5a64256bb306e0022066
406 Impose micro-POLA by passing only the writekey instead of the whole node object to {{{_encrypt_rw_uri()}}}. Remove DummyImmutableFileNode in nodemaker.py, which is obviated by this. Add micro-optimization by precomputing the netstring of the empty string and branching on whether the writekey is present or not outside of {{{_encrypt_rw_uri()}}}. Add doc about writekey to docstring.
407] 
408[Move EncryptedTemporaryFile from SFTP frontend to allmydata.util.fileutil, and make the FTP frontend also use it (fixing #1083).
409david-sarah@jacaranda.org**20100711213721
410 Ignore-this: e452e8ca66391aa2a1a49afe0114f317
411] 
412[Add tests of caps from the future that have non-ASCII characters in them (encoded as UTF-8). The changes to test_uri.py, test_client.py, and test_dirnode.py add tests of non-ASCII future caps in addition to the current tests. The changes to test_web.py just replace the tests of all-ASCII future caps with tests of non-ASCII future caps. We also change uses of failUnlessEqual to failUnlessReallyEqual, in order to catch cases where the type of a string is not as expected.
413david-sarah@jacaranda.org**20100711200252
414 Ignore-this: c2f193352369d32e06865f8f3e951894
415] 
416[Allow URIs passed in the initial JSON for t=mkdir-with-children, t=mkdir-immutable to be Unicode. Also pass the name of each child into nodemaker.create_from_cap for error reporting.
417david-sarah@jacaranda.org**20100711195525
418 Ignore-this: deac32d8b91ba26ede18905d3f7d2b93
419] 
420[docs/logging.txt: note that setting flogging vars might affect tests with race conditions.
421david-sarah@jacaranda.org**20100712050721
422 Ignore-this: fc1609d215fcd5561a57fd1226206f27
423] 
424[test_storage.py: potential fix for failures when logging is enabled.
425david-sarah@jacaranda.org**19700713040546
426 Ignore-this: 5815693a0df3e64c52c3c6b7be2846c7
427] 
428[SFTP: address some of the comments in zooko's review (#1106).
429david-sarah@jacaranda.org**20100712025537
430 Ignore-this: c3921638a2d4f1de2a776ae78e4dc37e
431] 
432[upcase_since_on_welcome
433terrellrussell@gmail.com**20100708193903] 
434[server_version_on_welcome_page.dpatch.txt
435freestorm77@gmail.com**20100605191721
436 Ignore-this: b450c76dc875f5ac8cca229a666cbd0a
437 
438 
439 - The storage server version is 0 for all storage nodes in the Welcome Page
440 
441 
442] 
443[NEWS: add NEWS snippets about two recent patches
444zooko@zooko.com**20100708162058
445 Ignore-this: 6c9da6a0ad7351a960bdd60f81532899
446] 
447[directory_html_top_banner.dpatch
448freestorm77@gmail.com**20100622205301
449 Ignore-this: 1d770d975e0c414c996564774f049bca
450 
451 The div tag with the link "Return to Welcome page" on the directory.xhtml page is not correct
452 
453] 
454[tahoe_css_toolbar.dpatch
455freestorm77@gmail.com**20100622210046
456 Ignore-this: 5b3ebb2e0f52bbba718a932f80c246c0
457 
458 CSS modification to be correctly diplayed with Internet Explorer 8
459 
460 The links on the top of page directory.xhtml are not diplayed in the same line as display with Firefox.
461 
462] 
463[runnin_test_tahoe_css.dpatch
464freestorm77@gmail.com**20100622214714
465 Ignore-this: e0db73d68740aad09a7b9ae60a08c05c
466 
467 Runnin test for changes in tahoe.css file
468 
469] 
470[runnin_test_directory_xhtml.dpatch
471freestorm77@gmail.com**20100622201403
472 Ignore-this: f8962463fce50b9466405cb59fe11d43
473 
474 Runnin test for diretory.xhtml top banner
475 
476] 
477[stringutils.py: tolerate sys.stdout having no 'encoding' attribute.
478david-sarah@jacaranda.org**20100626040817
479 Ignore-this: f42cad81cef645ee38ac1df4660cc850
480] 
481[quickstart.html: python 2.5 -> 2.6 as recommended version
482david-sarah@jacaranda.org**20100705175858
483 Ignore-this: bc3a14645ea1d5435002966ae903199f
484] 
485[SFTP: don't call .stopProducing on the producer registered with OverwriteableFileConsumer (which breaks with warner's new downloader).
486david-sarah@jacaranda.org**20100628231926
487 Ignore-this: 131b7a5787bc85a9a356b5740d9d996f
488] 
489[docs/how_to_make_a_tahoe-lafs_release.txt: trivial correction, install.html should now be quickstart.html.
490david-sarah@jacaranda.org**20100625223929
491 Ignore-this: 99a5459cac51bd867cc11ad06927ff30
492] 
493[setup: in the Makefile, refuse to upload tarballs unless someone has passed the environment variable "BB_BRANCH" with value "trunk"
494zooko@zooko.com**20100619034928
495 Ignore-this: 276ddf9b6ad7ec79e27474862e0f7d6
496] 
497[trivial: tiny update to in-line comment
498zooko@zooko.com**20100614045715
499 Ignore-this: 10851b0ed2abfed542c97749e5d280bc
500 (I'm actually committing this patch as a test of the new eager-annotation-computation of trac-darcs.)
501] 
502[docs: about.html link to home page early on, and be decentralized storage instead of cloud storage this time around
503zooko@zooko.com**20100619065318
504 Ignore-this: dc6db03f696e5b6d2848699e754d8053
505] 
506[docs: update about.html, especially to have a non-broken link to quickstart.html, and also to comment out the broken links to "for Paranoids" and "for Corporates"
507zooko@zooko.com**20100619065124
508 Ignore-this: e292c7f51c337a84ebfeb366fbd24d6c
509] 
510[TAG allmydata-tahoe-1.7.0
511zooko@zooko.com**20100619052631
512 Ignore-this: d21e27afe6d85e2e3ba6a3292ba2be1
513] 
514Patch bundle hash:
51545b4a4597e7297ca2f8868ebd4d1a4d2f9dd9406