source: branches/2.2/security/distribuicao/ger2.py @ 3481

Revision 3481, 74.8 KB checked in by rafaelraymundo, 14 years ago (diff)

Ticket #1344 - Adicionados scripts python em security/distribuicao

  • Property svn:executable set to *
Line 
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3# dialog.py --- A python interface to the Linux "dialog" utility
4# Copyright (C) 2000  Robb Shecter, Sultanbek Tezadov
5# Copyright (C) 2002, 2003, 2004  Florent Rougon
6#
7# This library is free software; you can redistribute it and/or
8# modify it under the terms of the GNU Lesser General Public
9# License as published by the Free Software Foundation; either
10# version 2.1 of the License, or (at your option) any later version.
11#
12# This library is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15# Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public
18# License along with this library; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21"""Python interface to dialog-like programs.
22
23This module provides a Python interface to dialog-like programs such
24as `dialog', `Xdialog' and `whiptail'.
25
26It provides a Dialog class that retains some parameters such as the
27program name and path as well as the values to pass as DIALOG*
28environment variables to the chosen program.
29
30For a quick start, you should look at the demo.py file that comes
31with pythondialog. It demonstrates a simple use of each widget
32offered by the Dialog class.
33
34See the Dialog class documentation for general usage information,
35list of available widgets and ways to pass options to dialog.
36
37
38Notable exceptions
39------------------
40
41Here is the hierarchy of notable exceptions raised by this module:
42
43  error
44     ExecutableNotFound
45     BadPythonDialogUsage
46     PythonDialogSystemError
47        PythonDialogIOError
48        PythonDialogOSError
49        PythonDialogErrorBeforeExecInChildProcess
50        PythonDialogReModuleError
51     UnexpectedDialogOutput
52     DialogTerminatedBySignal
53     DialogError
54     UnableToCreateTemporaryDirectory
55     PythonDialogBug
56     ProbablyPythonBug
57
58As you can see, every exception `exc' among them verifies:
59
60  issubclass(exc, error)
61
62so if you don't need fine-grained error handling, simply catch
63`error' (which will probably be accessible as dialog.error from your
64program) and you should be safe.
65
66"""
67
68from __future__ import nested_scopes
69import sys, os, tempfile, random, string, re, types
70
71
72# Python < 2.3 compatibility
73if sys.hexversion < 0x02030000:
74    # The assignments would work with Python >= 2.3 but then, pydoc
75    # shows them in the DATA section of the module...
76    True = 0 == 0
77    False = 0 == 1
78
79
80# Exceptions raised by this module
81#
82# When adding, suppressing, renaming exceptions or changing their
83# hierarchy, don't forget to update the module's docstring.
84class error(Exception):
85    """Base class for exceptions in pythondialog."""
86    def __init__(self, message=None):
87        self.message = message
88    def __str__(self):
89        return "<%s: %s>" % (self.__class__.__name__, self.message)
90    def complete_message(self):
91        if self.message:
92            return "%s: %s" % (self.ExceptionShortDescription, self.message)
93        else:
94            return "%s" % self.ExceptionShortDescription
95    ExceptionShortDescription = "pythondialog generic exception"
96
97# For backward-compatibility
98#
99# Note: this exception was not documented (only the specific ones were), so
100#       the backward-compatibility binding could be removed relatively easily.
101PythonDialogException = error
102
103class ExecutableNotFound(error):
104    """Exception raised when the dialog executable can't be found."""
105    ExceptionShortDescription = "Executable not found"
106
107class PythonDialogBug(error):
108    """Exception raised when pythondialog finds a bug in his own code."""
109    ExceptionShortDescription = "Bug in pythondialog"
110
111# Yeah, the "Probably" makes it look a bit ugly, but:
112#   - this is more accurate
113#   - this avoids a potential clash with an eventual PythonBug built-in
114#     exception in the Python interpreter...
115class ProbablyPythonBug(error):
116    """Exception raised when pythondialog behaves in a way that seems to \
117indicate a Python bug."""
118    ExceptionShortDescription = "Bug in python, probably"
119
120class BadPythonDialogUsage(error):
121    """Exception raised when pythondialog is used in an incorrect way."""
122    ExceptionShortDescription = "Invalid use of pythondialog"
123
124class PythonDialogSystemError(error):
125    """Exception raised when pythondialog cannot perform a "system \
126operation" (e.g., a system call) that should work in "normal" situations.
127
128    This is a convenience exception: PythonDialogIOError, PythonDialogOSError
129    and PythonDialogErrorBeforeExecInChildProcess all derive from this
130    exception. As a consequence, watching for PythonDialogSystemError instead
131    of the aformentioned exceptions is enough if you don't need precise
132    details about these kinds of errors.
133
134    Don't confuse this exception with Python's builtin SystemError
135    exception.
136
137    """
138    ExceptionShortDescription = "System error"
139   
140class PythonDialogIOError(PythonDialogSystemError):
141    """Exception raised when pythondialog catches an IOError exception that \
142should be passed to the calling program."""
143    ExceptionShortDescription = "IO error"
144
145class PythonDialogOSError(PythonDialogSystemError):
146    """Exception raised when pythondialog catches an OSError exception that \
147should be passed to the calling program."""
148    ExceptionShortDescription = "OS error"
149
150class PythonDialogErrorBeforeExecInChildProcess(PythonDialogSystemError):
151    """Exception raised when an exception is caught in a child process \
152before the exec sytem call (included).
153
154    This can happen in uncomfortable situations like when the system is out
155    of memory or when the maximum number of open file descriptors has been
156    reached. This can also happen if the dialog-like program was removed
157    (or if it is has been made non-executable) between the time we found it
158    with _find_in_path and the time the exec system call attempted to
159    execute it...
160
161    """
162    ExceptionShortDescription = "Error in a child process before the exec " \
163                                "system call"
164
165class PythonDialogReModuleError(PythonDialogSystemError):
166    """Exception raised when pythondialog catches a re.error exception."""
167    ExceptionShortDescription = "'re' module error"
168
169class UnexpectedDialogOutput(error):
170    """Exception raised when the dialog-like program returns something not \
171expected by pythondialog."""
172    ExceptionShortDescription = "Unexpected dialog output"
173
174class DialogTerminatedBySignal(error):
175    """Exception raised when the dialog-like program is terminated by a \
176signal."""
177    ExceptionShortDescription = "dialog-like terminated by a signal"
178
179class DialogError(error):
180    """Exception raised when the dialog-like program exits with the \
181code indicating an error."""
182    ExceptionShortDescription = "dialog-like terminated due to an error"
183
184class UnableToCreateTemporaryDirectory(error):
185    """Exception raised when we cannot create a temporary directory."""
186    ExceptionShortDescription = "unable to create a temporary directory"
187
188# Values accepted for checklists
189try:
190    _on_rec = re.compile(r"on", re.IGNORECASE)
191    _off_rec = re.compile(r"off", re.IGNORECASE)
192
193    _calendar_date_rec = re.compile(
194        r"(?P<day>\d\d)/(?P<month>\d\d)/(?P<year>\d\d\d\d)$")
195    _timebox_time_rec = re.compile(
196        r"(?P<hour>\d\d):(?P<minute>\d\d):(?P<second>\d\d)$")
197except re.error, v:
198    raise PythonDialogReModuleError(v)
199
200
201# This dictionary allows us to write the dialog common options in a Pythonic
202# way (e.g. dialog_instance.checklist(args, ..., title="Foo", no_shadow=1)).
203#
204# Options such as --separate-output should obviously not be set by the user
205# since they affect the parsing of dialog's output:
206_common_args_syntax = {
207    "aspect": lambda ratio: ("--aspect", str(ratio)),
208    "backtitle": lambda backtitle: ("--backtitle", backtitle),
209    "beep": lambda enable: _simple_option("--beep", enable),
210    "beep_after": lambda enable: _simple_option("--beep-after", enable),
211    # Warning: order = y, x!
212    "begin": lambda coords: ("--begin", str(coords[0]), str(coords[1])),
213    "cancel": lambda string: ("--cancel-label", string),
214    "clear": lambda enable: _simple_option("--clear", enable),
215    "cr_wrap": lambda enable: _simple_option("--cr-wrap", enable),
216    "create_rc": lambda file: ("--create-rc", file),
217    "defaultno": lambda enable: _simple_option("--defaultno", enable),
218    "default_item": lambda string: ("--default-item", string),
219    "help": lambda enable: _simple_option("--help", enable),
220    "help_button": lambda enable: _simple_option("--help-button", enable),
221    "help_label": lambda string: ("--help-label", string),
222    "ignore": lambda enable: _simple_option("--ignore", enable),
223    "item_help": lambda enable: _simple_option("--item-help", enable),
224    "max_input": lambda size: ("--max-input", str(size)),
225    "no_kill": lambda enable: _simple_option("--no-kill", enable),
226    "no_cancel": lambda enable: _simple_option("--no-cancel", enable),
227    "nocancel": lambda enable: _simple_option("--nocancel", enable),
228    "no_shadow": lambda enable: _simple_option("--no-shadow", enable),
229    "ok_label": lambda string: ("--ok-label", string),
230    "print_maxsize": lambda enable: _simple_option("--print-maxsize",
231                                                   enable),
232    "print_size": lambda enable: _simple_option("--print-size", enable),
233    "print_version": lambda enable: _simple_option("--print-version",
234                                                   enable),
235    "separate_output": lambda enable: _simple_option("--separate-output",
236                                                     enable),
237    "separate_widget": lambda string: ("--separate-widget", string),
238    "shadow": lambda enable: _simple_option("--shadow", enable),
239    "size_err": lambda enable: _simple_option("--size-err", enable),
240    "sleep": lambda secs: ("--sleep", str(secs)),
241    "stderr": lambda enable: _simple_option("--stderr", enable),
242    "stdout": lambda enable: _simple_option("--stdout", enable),
243    "tab_correct": lambda enable: _simple_option("--tab-correct", enable),
244    "tab_len": lambda n: ("--tab-len", str(n)),
245    "timeout": lambda secs: ("--timeout", str(secs)),
246    "title": lambda title: ("--title", title),
247    "trim": lambda enable: _simple_option("--trim", enable),
248    "version": lambda enable: _simple_option("--version", enable)}
249   
250
251def _simple_option(option, enable):
252    """Turn on or off the simplest dialog Common Options."""
253    if enable:
254        return (option,)
255    else:
256        # This will not add any argument to the command line
257        return ()
258
259
260def _find_in_path(prog_name):
261    """Search an executable in the PATH.
262
263    If PATH is not defined, the default path ":/bin:/usr/bin" is
264    used.
265
266    Return a path to the file or None if no readable and executable
267    file is found.
268
269    Notable exception: PythonDialogOSError
270
271    """
272    try:
273        # Note that the leading empty component in the default value for PATH
274        # could lead to the returned path not being absolute.
275        PATH = os.getenv("PATH", ":/bin:/usr/bin") # see the execvp(3) man page
276        for dir in string.split(PATH, ":"):
277            file_path = os.path.join(dir, prog_name)
278            if os.path.isfile(file_path) \
279               and os.access(file_path, os.R_OK | os.X_OK):
280                return file_path
281        return None
282    except os.error, v:
283        raise PythonDialogOSError(v.strerror)
284
285
286def _path_to_executable(f):
287    """Find a path to an executable.
288
289    Find a path to an executable, using the same rules as the POSIX
290    exec*p functions (see execvp(3) for instance).
291
292    If `f' contains a '/', it is assumed to be a path and is simply
293    checked for read and write permissions; otherwise, it is looked
294    for according to the contents of the PATH environment variable,
295    which defaults to ":/bin:/usr/bin" if unset.
296
297    The returned path is not necessarily absolute.
298
299    Notable exceptions:
300
301        ExecutableNotFound
302        PythonDialogOSError
303       
304    """
305    try:
306        if '/' in f:
307            if os.path.isfile(f) and \
308                   os.access(f, os.R_OK | os.X_OK):
309                res = f
310            else:
311                raise ExecutableNotFound("%s cannot be read and executed" % f)
312        else:
313            res = _find_in_path(f)
314            if res is None:
315                raise ExecutableNotFound(
316                    "can't find the executable for the dialog-like "
317                    "program")
318    except os.error, v:
319        raise PythonDialogOSError(v.strerror)
320
321    return res
322
323
324def _to_onoff(val):
325    """Convert boolean expressions to "on" or "off"
326
327    This function converts every non-zero integer as well as "on",
328    "ON", "On" and "oN" to "on" and converts 0, "off", "OFF", etc. to
329    "off".
330
331    Notable exceptions:
332
333        PythonDialogReModuleError
334        BadPythonDialogUsage
335
336    """
337    if type(val) == types.IntType:
338        if val:
339            return "on"
340        else:
341            return "off"
342    elif type(val) == types.StringType:
343        try:
344            if _on_rec.match(val):
345                return "on"
346            elif _off_rec.match(val):
347                return "off"
348        except re.error, v:
349            raise PythonDialogReModuleError(v)
350    else:
351        raise BadPythonDialogUsage("invalid boolean value: %s" % val)
352
353
354def _compute_common_args(dict):
355    """Compute the list of arguments for dialog common options.
356
357    Compute a list of the command-line arguments to pass to dialog
358    from a keyword arguments dictionary for options listed as "common
359    options" in the manual page for dialog. These are the options
360    that are not tied to a particular widget.
361
362    This allows to specify these options in a pythonic way, such as:
363
364       d.checklist(<usual arguments for a checklist>,
365                   title="...",
366                   backtitle="...")
367
368    instead of having to pass them with strings like "--title foo" or
369    "--backtitle bar".
370
371    Notable exceptions: None
372
373    """
374    args = []
375    for key in dict.keys():
376        args.extend(_common_args_syntax[key](dict[key]))
377    return args
378
379
380def _create_temporary_directory():
381    """Create a temporary directory (securely).
382
383    Return the directory path.
384
385    Notable exceptions:
386        - UnableToCreateTemporaryDirectory
387        - PythonDialogOSError
388        - exceptions raised by the tempfile module (which are
389          unfortunately not mentioned in its documentation, at
390          least in Python 2.3.3...)
391
392    """
393    find_temporary_nb_attempts = 5
394    for i in range(find_temporary_nb_attempts):
395        try:
396            # Using something >= 2**31 causes an error in Python 2.2...
397            tmp_dir = os.path.join(tempfile.gettempdir(),
398                                   "%s-%u" \
399                                   % ("pythondialog",
400                                      random.randint(0, 2**30-1)))
401        except os.error, v:
402            raise PythonDialogOSError(v.strerror)
403
404        try:
405            os.mkdir(tmp_dir, 0700)
406        except os.error:
407            continue
408        else:
409            break
410    else:
411        raise UnableToCreateTemporaryDirectory(
412            "somebody may be trying to attack us")
413
414    return tmp_dir
415
416
417# DIALOG_OK, DIALOG_CANCEL, etc. are environment variables controlling
418# dialog's exit status in the corresponding situation.
419#
420# Note:
421#    - 127 must not be used for any of the DIALOG_* values. It is used
422#      when a failure occurs in the child process before it exec()s
423#      dialog (where "before" includes a potential exec() failure).
424#    - 126 is also used (although in presumably rare situations).
425_dialog_exit_status_vars = { "OK": 0,
426                             "CANCEL": 1,
427                             "ESC": 2,
428                             "ERROR": 3,
429                             "EXTRA": 4,
430                             "HELP": 5 }
431
432
433# Main class of the module
434class Dialog:
435
436    """Class providing bindings for dialog-compatible programs.
437
438    This class allows you to invoke dialog or a compatible program in
439    a pythonic way to build quicky and easily simple but nice text
440    interfaces.
441
442    An application typically creates one instance of the Dialog class
443    and uses it for all its widgets, but it is possible to use
444    concurrently several instances of this class with different
445    parameters (such as the background title) if you have the need
446    for this.
447
448    The exit code (exit status) returned by dialog is to be
449    compared with the DIALOG_OK, DIALOG_CANCEL, DIALOG_ESC,
450    DIALOG_ERROR, DIALOG_EXTRA and DIALOG_HELP attributes of the
451    Dialog instance (they are integers).
452
453    Note: although this class does all it can to allow the caller to
454          differentiate between the various reasons that caused a
455          dialog box to be closed, its backend, dialog 0.9a-20020309a
456          for my tests, doesn't always return DIALOG_ESC when the
457          user presses the ESC key, but often returns DIALOG_ERROR
458          instead. The exit codes returned by the corresponding
459          Dialog methods are of course just as wrong in these cases.
460          You've been warned.
461
462
463    Public methods of the Dialog class (mainly widgets)
464    ---------------------------------------------------
465
466    The Dialog class has the following methods:
467
468    add_persistent_args
469    calendar
470    checklist
471    fselect
472
473    gauge_start
474    gauge_update
475    gauge_stop
476
477    infobox
478    inputbox
479    menu
480    msgbox
481    passwordbox
482    radiolist
483    scrollbox
484    tailbox
485    textbox
486    timebox
487    yesno
488
489    clear                 (obsolete)
490    setBackgroundTitle    (obsolete)
491
492
493    Passing dialog "Common Options"
494    -------------------------------
495
496    Every widget method has a **kwargs argument allowing you to pass
497    dialog so-called Common Options (see the dialog(1) manual page)
498    to dialog for this widget call. For instance, if `d' is a Dialog
499    instance, you can write:
500
501      d.checklist(args, ..., title="A Great Title", no_shadow=1)
502
503    The no_shadow option is worth looking at:
504
505      1. It is an option that takes no argument as far as dialog is
506         concerned (unlike the "--title" option, for instance). When
507         you list it as a keyword argument, the option is really
508         passed to dialog only if the value you gave it evaluates to
509         true, e.g. "no_shadow=1" will cause "--no-shadow" to be
510         passed to dialog whereas "no_shadow=0" will cause this
511         option not to be passed to dialog at all.
512
513      2. It is an option that has a hyphen (-) in its name, which you
514         must change into an underscore (_) to pass it as a Python
515         keyword argument. Therefore, "--no-shadow" is passed by
516         giving a "no_shadow=1" keyword argument to a Dialog method
517         (the leading two dashes are also consistently removed).
518
519
520    Exceptions
521    ----------
522
523    Please refer to the specific methods' docstrings or simply to the
524    module's docstring for a list of all exceptions that might be
525    raised by this class' methods.
526
527    """
528
529    def __init__(self, dialog="dialog", DIALOGRC=None, compat="dialog",
530                 use_stdout=None):
531        """Constructor for Dialog instances.
532
533        dialog   -- name of (or path to) the dialog-like program to
534                    use; if it contains a '/', it is assumed to be a
535                    path and is used as is; otherwise, it is looked
536                    for according to the contents of the PATH
537                    environment variable, which defaults to
538                    ":/bin:/usr/bin" if unset.
539        DIALOGRC -- string to pass to the dialog-like program as the
540                    DIALOGRC environment variable, or None if no
541                    modification to the environment regarding this
542                    variable should be done in the call to the
543                    dialog-like program
544        compat   -- compatibility mode (see below)
545
546        The officially supported dialog-like program in pythondialog
547        is the well-known dialog program written in C, based on the
548        ncurses library. It is also known as cdialog and its home
549        page is currently (2004-03-15) located at:
550
551            http://dickey.his.com/dialog/dialog.html
552
553        If you want to use a different program such as Xdialog, you
554        should indicate the executable file name with the `dialog'
555        argument *and* the compatibility type that you think it
556        conforms to with the `compat' argument. Currently, `compat'
557        can be either "dialog" (for dialog; this is the default) or
558        "Xdialog" (for, well, Xdialog).
559
560        The `compat' argument allows me to cope with minor
561        differences in behaviour between the various programs
562        implementing the dialog interface (not the text or graphical
563        interface, I mean the "API"). However, having to support
564        various APIs simultaneously is a bit ugly and I would really
565        prefer you to report bugs to the relevant maintainers when
566        you find incompatibilities with dialog. This is for the
567        benefit of pretty much everyone that relies on the dialog
568        interface.
569
570        Notable exceptions:
571
572            ExecutableNotFound
573            PythonDialogOSError
574
575        """       
576        # DIALOGRC differs from the other DIALOG* variables in that:
577        #   1. It should be a string if not None
578        #   2. We may very well want it to be unset
579        if DIALOGRC is not None:
580            self.DIALOGRC = DIALOGRC
581
582        # After reflexion, I think DIALOG_OK, DIALOG_CANCEL, etc.
583        # should never have been instance attributes (I cannot see a
584        # reason why the user would want to change their values or
585        # even read them), but it is a bit late, now. So, we set them
586        # based on the (global) _dialog_exit_status_vars.keys.
587        for var in _dialog_exit_status_vars.keys():
588            varname = "DIALOG_" + var
589            setattr(self, varname, _dialog_exit_status_vars[var])
590
591        self._dialog_prg = _path_to_executable(dialog)
592        self.compat = compat
593        self.dialog_persistent_arglist = []
594
595        # Use stderr or stdout?
596        if self.compat == "Xdialog":
597            # Default to stdout if Xdialog
598            self.use_stdout = True
599        else:
600            self.use_stdout = False
601        if use_stdout != None:
602            # Allow explicit setting
603            self.use_stdout = use_stdout
604        if self.use_stdout:
605            self.add_persistent_args(["--stdout"])
606
607    def add_persistent_args(self, arglist):
608        self.dialog_persistent_arglist.extend(arglist)
609
610    # For compatibility with the old dialog...
611    def setBackgroundTitle(self, text):
612        """Set the background title for dialog.
613
614        This method is obsolete. Please remove calls to it from your
615        programs.
616
617        """
618        self.add_persistent_args(("--backtitle", text))
619
620    def _call_program(self, redirect_child_stdin, cmdargs, **kwargs):
621        """Do the actual work of invoking the dialog-like program.
622
623        Communication with the dialog-like program is performed
624        through one or two pipes, depending on
625        `redirect_child_stdin'. There is always one pipe that is
626        created to allow the parent process to read what dialog
627        writes on its standard error stream.
628       
629        If `redirect_child_stdin' is True, an additional pipe is
630        created whose reading end is connected to dialog's standard
631        input. This is used by the gauge widget to feed data to
632        dialog.
633
634        Beware when interpreting the return value: the length of the
635        returned tuple depends on `redirect_child_stdin'.
636
637        Notable exception: PythonDialogOSError (if pipe() or close()
638                           system calls fail...)
639
640        """
641        # We want to define DIALOG_OK, DIALOG_CANCEL, etc. in the
642        # environment of the child process so that we know (and
643        # even control) the possible dialog exit statuses.
644        new_environ = {}
645        new_environ.update(os.environ)
646        for var in _dialog_exit_status_vars:
647            varname = "DIALOG_" + var
648            new_environ[varname] = str(getattr(self, varname))
649        if hasattr(self, "DIALOGRC"):
650            new_environ["DIALOGRC"] = self.DIALOGRC
651
652        # Create:
653        #   - a pipe so that the parent process can read dialog's output on
654        #     stdout/stderr
655        #   - a pipe so that the parent process can feed data to dialog's
656        #     stdin (this is needed for the gauge widget) if
657        #     redirect_child_stdin is True
658        try:
659            # rfd = File Descriptor for Reading
660            # wfd = File Descriptor for Writing
661            (child_rfd, child_wfd) = os.pipe()
662            if redirect_child_stdin:
663                (child_stdin_rfd,  child_stdin_wfd)  = os.pipe()
664        except os.error, v:
665            raise PythonDialogOSError(v.strerror)
666
667        child_pid = os.fork()
668        if child_pid == 0:
669            # We are in the child process. We MUST NOT raise any exception.
670            try:
671                # The child process doesn't need these file descriptors
672                os.close(child_rfd)
673                if redirect_child_stdin:
674                    os.close(child_stdin_wfd)
675                # We want:
676                #   - dialog's output on stderr/stdout to go to child_wfd
677                #   - data written to child_stdin_wfd to go to dialog's stdin
678                #     if redirect_child_stdin is True
679                if self.use_stdout:
680                    os.dup2(child_wfd, 1)
681                else:
682                    os.dup2(child_wfd, 2)
683                if redirect_child_stdin:
684                    os.dup2(child_stdin_rfd, 0)
685
686                arglist = [self._dialog_prg] + \
687                          self.dialog_persistent_arglist + \
688                          _compute_common_args(kwargs) + \
689                          cmdargs
690                # Insert here the contents of the DEBUGGING file if you want
691                # to obtain a handy string of the complete command line with
692                # arguments quoted for the shell and environment variables
693                # set.
694                os.execve(self._dialog_prg, arglist, new_environ)
695            except:
696                os._exit(127)
697
698            # Should not happen unless there is a bug in Python
699            os._exit(126)
700
701        # We are in the father process.
702        #
703        # It is essential to close child_wfd, otherwise we will never
704        # see EOF while reading on child_rfd and the parent process
705        # will block forever on the read() call.
706        # [ after the fork(), the "reference count" of child_wfd from
707        #   the operating system's point of view is 2; after the child exits,
708        #   it is 1 until the father closes it itself; then it is 0 and a read
709        #   on child_rfd encounters EOF once all the remaining data in
710        #   the pipe has been read. ]
711        try:
712            os.close(child_wfd)
713            if redirect_child_stdin:
714                os.close(child_stdin_rfd)
715                return (child_pid, child_rfd, child_stdin_wfd)
716            else:
717                return (child_pid, child_rfd)
718        except os.error, v:
719            raise PythonDialogOSError(v.strerror)
720
721    def _wait_for_program_termination(self, child_pid, child_rfd):
722        """Wait for a dialog-like process to terminate.
723
724        This function waits for the specified process to terminate,
725        raises the appropriate exceptions in case of abnormal
726        termination and returns the exit status and standard error
727        output of the process as a tuple: (exit_code, stderr_string).
728
729        `child_rfd' must be the file descriptor for the
730        reading end of the pipe created by self._call_program()
731        whose writing end was connected by self._call_program() to
732        the child process's standard error.
733
734        This function reads the process's output on standard error
735        from `child_rfd' and closes this file descriptor once
736        this is done.
737
738        Notable exceptions:
739
740            DialogTerminatedBySignal
741            DialogError
742            PythonDialogErrorBeforeExecInChildProcess
743            PythonDialogIOError
744            PythonDialogBug
745            ProbablyPythonBug
746
747        """
748        exit_info = os.waitpid(child_pid, 0)[1]
749        if os.WIFEXITED(exit_info):
750            exit_code = os.WEXITSTATUS(exit_info)
751        # As we wait()ed for the child process to terminate, there is no
752        # need to call os.WIFSTOPPED()
753        elif os.WIFSIGNALED(exit_info):
754            raise DialogTerminatedBySignal("the dialog-like program was "
755                                           "terminated by signal %u" %
756                                           os.WTERMSIG(exit_info))
757        else:
758            raise PythonDialogBug("please report this bug to the "
759                                  "pythondialog maintainers")
760
761        if exit_code == self.DIALOG_ERROR:
762            raise DialogError("the dialog-like program exited with "
763                              "code %d (was passed to it as the DIALOG_ERROR "
764                              "environment variable)" % exit_code)
765        elif exit_code == 127:
766            raise PythonDialogErrorBeforeExecInChildProcess(
767                "perhaps the dialog-like program could not be executed; "
768                "perhaps the system is out of memory; perhaps the maximum "
769                "number of open file descriptors has been reached")
770        elif exit_code == 126:
771            raise ProbablyPythonBug(
772                "a child process returned with exit status 126; this might "
773                "be the exit status of the dialog-like program, for some "
774                "unknown reason (-> probably a bug in the dialog-like "
775                "program); otherwise, we have probably found a python bug")
776       
777        # We might want to check here whether exit_code is really one of
778        # DIALOG_OK, DIALOG_CANCEL, etc. However, I prefer not doing it
779        # because it would break pythondialog for no strong reason when new
780        # exit codes are added to the dialog-like program.
781        #
782        # As it is now, if such a thing happens, the program using
783        # pythondialog may receive an exit_code it doesn't know about. OK, the
784        # programmer just has to tell the pythondialog maintainer about it and
785        # can temporarily set the appropriate DIALOG_* environment variable if
786        # he wants and assign the corresponding value to the Dialog instance's
787        # DIALOG_FOO attribute from his program. He doesn't even need to use a
788        # patched pythondialog before he upgrades to a version that knows
789        # about the new exit codes.
790        #
791        # The bad thing that might happen is a new DIALOG_FOO exit code being
792        # the same by default as one of those we chose for the other exit
793        # codes already known by pythondialog. But in this situation, the
794        # check that is being discussed wouldn't help at all.
795
796        # Read dialog's output on its stderr
797        try:
798            child_output = os.fdopen(child_rfd, "rb").read()
799            # Now, since the file object has no reference anymore, the
800            # standard IO stream behind it will be closed, causing the
801            # end of the the pipe we used to read dialog's output on its
802            # stderr to be closed (this is important, otherwise invoking
803            # dialog enough times will eventually exhaust the maximum number
804            # of open file descriptors).
805        except IOError, v:
806            raise PythonDialogIOError(v)
807
808        return (exit_code, child_output)
809
810    def _perform(self, cmdargs, **kwargs):
811        """Perform a complete dialog-like program invocation.
812
813        This function invokes the dialog-like program, waits for its
814        termination and returns its exit status and whatever it wrote
815        on its standard error stream.
816
817        Notable exceptions:
818
819            any exception raised by self._call_program() or
820            self._wait_for_program_termination()
821
822        """
823        (child_pid, child_rfd) = \
824                    self._call_program(False, *(cmdargs,), **kwargs)
825        (exit_code, output) = \
826                    self._wait_for_program_termination(child_pid,
827                                                        child_rfd)
828        return (exit_code, output)
829
830    def _strip_xdialog_newline(self, output):
831        """Remove trailing newline (if any), if using Xdialog"""
832        if self.compat == "Xdialog" and output.endswith("\n"):
833            output = output[:-1]
834        return output
835
836    # This is for compatibility with the old dialog.py
837    def _perform_no_options(self, cmd):
838        """Call dialog without passing any more options."""
839        return os.system(self._dialog_prg + ' ' + cmd)
840
841    # For compatibility with the old dialog.py
842    def clear(self):
843        """Clear the screen. Equivalent to the dialog --clear option.
844
845        This method is obsolete. Please remove calls to it from your
846        programs.
847
848        """
849        self._perform_no_options('--clear')
850
851    def calendar(self, text, height=6, width=0, day=0, month=0, year=0,
852                 **kwargs):
853        """Display a calendar dialog box.
854
855        text   -- text to display in the box
856        height -- height of the box (minus the calendar height)
857        width  -- width of the box
858        day    -- inititial day highlighted
859        month  -- inititial month displayed
860        year   -- inititial year selected (0 causes the current date
861                  to be used as the initial date)
862       
863        A calendar box displays month, day and year in separately
864        adjustable windows. If the values for day, month or year are
865        missing or negative, the current date's corresponding values
866        are used. You can increment or decrement any of those using
867        the left, up, right and down arrows. Use tab or backtab to
868        move between windows. If the year is given as zero, the
869        current date is used as an initial value.
870
871        Return a tuple of the form (code, date) where `code' is the
872        exit status (an integer) of the dialog-like program and
873        `date' is a list of the form [day, month, year] (where `day',
874        `month' and `year' are integers corresponding to the date
875        chosen by the user) if the box was closed with OK, or None if
876        it was closed with the Cancel button.
877
878        Notable exceptions:
879            - any exception raised by self._perform()
880            - UnexpectedDialogOutput
881            - PythonDialogReModuleError
882
883        """
884        (code, output) = self._perform(
885            *(["--calendar", text, str(height), str(width), str(day),
886               str(month), str(year)],),
887            **kwargs)
888        if code == self.DIALOG_OK:
889            try:
890                mo = _calendar_date_rec.match(output)
891            except re.error, v:
892                raise PythonDialogReModuleError(v)
893           
894            if mo is None:
895                raise UnexpectedDialogOutput(
896                    "the dialog-like program returned the following "
897                    "unexpected date with the calendar box: %s" % output)
898            date = map(int, mo.group("day", "month", "year"))
899        else:
900            date = None
901        return (code, date)
902
903    def checklist(self, text, height=15, width=54, list_height=7,
904                  choices=[], **kwargs):
905        """Display a checklist box.
906
907        text        -- text to display in the box
908        height      -- height of the box
909        width       -- width of the box
910        list_height -- number of entries displayed in the box (which
911                       can be scrolled) at a given time
912        choices     -- a list of tuples (tag, item, status) where
913                       `status' specifies the initial on/off state of
914                       each entry; can be 0 or 1 (integers, 1 meaning
915                       checked, i.e. "on"), or "on", "off" or any
916                       uppercase variant of these two strings.
917
918        Return a tuple of the form (code, [tag, ...]) with the tags
919        for the entries that were selected by the user. `code' is the
920        exit status of the dialog-like program.
921
922        If the user exits with ESC or CANCEL, the returned tag list
923        is empty.
924
925        Notable exceptions:
926
927            any exception raised by self._perform() or _to_onoff()
928
929        """
930        cmd = ["--checklist", text, str(height), str(width), str(list_height)]
931        for t in choices:
932            cmd.extend(((t[0], t[1], _to_onoff(t[2]))))
933
934        # The dialog output cannot be parsed reliably (at least in dialog
935        # 0.9b-20040301) without --separate-output (because double quotes in
936        # tags are escaped with backslashes, but backslashes are not
937        # themselves escaped and you have a problem when a tag ends with a
938        # backslash--the output makes you think you've encountered an embedded
939        # double-quote).
940        kwargs["separate_output"] = True
941
942        (code, output) = self._perform(*(cmd,), **kwargs)
943
944        # Since we used --separate-output, the tags are separated by a newline
945        # in the output. There is also a final newline after the last tag.
946        if output:
947            return (code, string.split(output, '\n')[:-1])
948        else:                           # empty selection
949            return (code, [])
950
951    def fselect(self, filepath, height, width, **kwargs):
952        """Display a file selection dialog box.
953
954        filepath -- initial file path
955        height   -- height of the box
956        width    -- width of the box
957       
958        The file-selection dialog displays a text-entry window in
959        which you can type a filename (or directory), and above that
960        two windows with directory names and filenames.
961
962        Here, filepath can be a file path in which case the file and
963        directory windows will display the contents of the path and
964        the text-entry window will contain the preselected filename.
965
966        Use tab or arrow keys to move between the windows. Within the
967        directory or filename windows, use the up/down arrow keys to
968        scroll the current selection. Use the space-bar to copy the
969        current selection into the text-entry window.
970
971        Typing any printable character switches focus to the
972        text-entry window, entering that character as well as
973        scrolling the directory and filename windows to the closest
974        match.
975
976        Use a carriage return or the "OK" button to accept the
977        current value in the text-entry window, or the "Cancel"
978        button to cancel.
979
980        Return a tuple of the form (code, path) where `code' is the
981        exit status (an integer) of the dialog-like program and
982        `path' is the path chosen by the user (whose last element may
983        be a directory or a file).
984             
985        Notable exceptions:
986
987            any exception raised by self._perform()
988
989        """
990        (code, output) = self._perform(
991            *(["--fselect", filepath, str(height), str(width)],),
992            **kwargs)
993
994        output = self._strip_xdialog_newline(output)
995       
996        return (code, output)
997   
998    def gauge_start(self, text="", height=8, width=54, percent=0, **kwargs):
999        """Display gauge box.
1000
1001        text    -- text to display in the box
1002        height  -- height of the box
1003        width   -- width of the box
1004        percent -- initial percentage shown in the meter
1005
1006        A gauge box displays a meter along the bottom of the box. The
1007        meter indicates a percentage.
1008
1009        This function starts the dialog-like program telling it to
1010        display a gauge box with a text in it and an initial
1011        percentage in the meter.
1012
1013        Return value: undefined.
1014
1015
1016        Gauge typical usage
1017        -------------------
1018
1019        Gauge typical usage (assuming that `d' is an instance of the
1020        Dialog class) looks like this:
1021            d.gauge_start()
1022            # do something
1023            d.gauge_update(10)       # 10% of the whole task is done
1024            # ...
1025            d.gauge_update(100, "any text here") # work is done
1026            exit_code = d.gauge_stop()           # cleanup actions
1027
1028
1029        Notable exceptions:
1030            - any exception raised by self._call_program()
1031            - PythonDialogOSError
1032
1033        """
1034        (child_pid, child_rfd, child_stdin_wfd) = self._call_program(
1035            True,
1036            *(["--gauge", text, str(height), str(width), str(percent)],),
1037            **kwargs)
1038        try:
1039            self._gauge_process = {
1040                "pid": child_pid,
1041                "stdin": os.fdopen(child_stdin_wfd, "wb"),
1042                "child_rfd": child_rfd
1043                }
1044        except os.error, v:
1045            raise PythonDialogOSError(v.strerror)
1046           
1047    def gauge_update(self, percent, text="", update_text=0):
1048        """Update a running gauge box.
1049       
1050        percent     -- new percentage to show in the gauge meter
1051        text        -- new text to optionally display in the box
1052        update-text -- boolean indicating whether to update the
1053                       text in the box
1054
1055        This function updates the percentage shown by the meter of a
1056        running gauge box (meaning `gauge_start' must have been
1057        called previously). If update_text is true (for instance, 1),
1058        the text displayed in the box is also updated.
1059
1060        See the `gauge_start' function's documentation for
1061        information about how to use a gauge.
1062
1063        Return value: undefined.
1064
1065        Notable exception: PythonDialogIOError can be raised if there
1066                           is an I/O error while writing to the pipe
1067                           used to talk to the dialog-like program.
1068
1069        """
1070        if update_text:
1071            gauge_data = "%d\nXXX\n%s\nXXX\n" % (percent, text)
1072        else:
1073            gauge_data = "%d\n" % percent
1074        try:
1075            self._gauge_process["stdin"].write(gauge_data)
1076            self._gauge_process["stdin"].flush()
1077        except IOError, v:
1078            raise PythonDialogIOError(v)
1079   
1080    # For "compatibility" with the old dialog.py...
1081    gauge_iterate = gauge_update
1082
1083    def gauge_stop(self):
1084        """Terminate a running gauge.
1085
1086        This function performs the appropriate cleanup actions to
1087        terminate a running gauge (started with `gauge_start').
1088       
1089        See the `gauge_start' function's documentation for
1090        information about how to use a gauge.
1091
1092        Return value: undefined.
1093
1094        Notable exceptions:
1095            - any exception raised by
1096              self._wait_for_program_termination()
1097            - PythonDialogIOError can be raised if closing the pipe
1098              used to talk to the dialog-like program fails.
1099
1100        """
1101        p = self._gauge_process
1102        # Close the pipe that we are using to feed dialog's stdin
1103        try:
1104            p["stdin"].close()
1105        except IOError, v:
1106            raise PythonDialogIOError(v)
1107        exit_code = \
1108                  self._wait_for_program_termination(p["pid"],
1109                                                      p["child_rfd"])[0]
1110        return exit_code
1111
1112    def infobox(self, text, height=10, width=30, **kwargs):
1113        """Display an information dialog box.
1114
1115        text   -- text to display in the box
1116        height -- height of the box
1117        width  -- width of the box
1118
1119        An info box is basically a message box. However, in this
1120        case, dialog will exit immediately after displaying the
1121        message to the user. The screen is not cleared when dialog
1122        exits, so that the message will remain on the screen until
1123        the calling shell script clears it later. This is useful
1124        when you want to inform the user that some operations are
1125        carrying on that may require some time to finish.
1126
1127        Return the exit status (an integer) of the dialog-like
1128        program.
1129
1130        Notable exceptions:
1131
1132            any exception raised by self._perform()
1133
1134        """
1135        return self._perform(
1136            *(["--infobox", text, str(height), str(width)],),
1137            **kwargs)[0]
1138
1139    def inputbox(self, text, height=10, width=30, init='', **kwargs):
1140        """Display an input dialog box.
1141
1142        text   -- text to display in the box
1143        height -- height of the box
1144        width  -- width of the box
1145        init   -- default input string
1146
1147        An input box is useful when you want to ask questions that
1148        require the user to input a string as the answer. If init is
1149        supplied it is used to initialize the input string. When
1150        entering the string, the BACKSPACE key can be used to
1151        correct typing errors. If the input string is longer than
1152        can fit in the dialog box, the input field will be scrolled.
1153
1154        Return a tuple of the form (code, string) where `code' is the
1155        exit status of the dialog-like program and `string' is the
1156        string entered by the user.
1157
1158        Notable exceptions:
1159
1160            any exception raised by self._perform()
1161
1162        """
1163        (code, tag) = self._perform(
1164            *(["--inputbox", text, str(height), str(width), init],),
1165            **kwargs)
1166
1167        tag = self._strip_xdialog_newline(tag)
1168       
1169        return (code, tag)
1170
1171    def menu(self, text, height=15, width=54, menu_height=7, choices=[],
1172             **kwargs):
1173        """Display a menu dialog box.
1174
1175        text        -- text to display in the box
1176        height      -- height of the box
1177        width       -- width of the box
1178        menu_height -- number of entries displayed in the box (which
1179                       can be scrolled) at a given time
1180        choices     -- a sequence of (tag, item) or (tag, item, help)
1181                       tuples (the meaning of each `tag', `item' and
1182                       `help' is explained below)
1183
1184
1185        Overview
1186        --------
1187
1188        As its name suggests, a menu box is a dialog box that can be
1189        used to present a list of choices in the form of a menu for
1190        the user to choose. Choices are displayed in the order given.
1191
1192        Each menu entry consists of a `tag' string and an `item'
1193        string. The tag gives the entry a name to distinguish it from
1194        the other entries in the menu. The item is a short
1195        description of the option that the entry represents.
1196
1197        The user can move between the menu entries by pressing the
1198        UP/DOWN keys, the first letter of the tag as a hot-key, or
1199        the number keys 1-9. There are menu-height entries displayed
1200        in the menu at one time, but the menu will be scrolled if
1201        there are more entries than that.
1202
1203
1204        Providing on-line help facilities
1205        ---------------------------------
1206
1207        If this function is called with item_help=1 (keyword
1208        argument), the option --item-help is passed to dialog and the
1209        tuples contained in `choices' must contain 3 elements each :
1210        (tag, item, help). The help string for the highlighted item
1211        is displayed in the bottom line of the screen and updated as
1212        the user highlights other items.
1213
1214        If item_help=0 or if this keyword argument is not passed to
1215        this function, the tuples contained in `choices' must contain
1216        2 elements each : (tag, item).
1217
1218        If this function is called with help_button=1, it must also
1219        be called with item_help=1 (this is a limitation of dialog),
1220        therefore the tuples contained in `choices' must contain 3
1221        elements each as explained in the previous paragraphs. This
1222        will cause a Help button to be added to the right of the
1223        Cancel button (by passing --help-button to dialog).
1224
1225
1226        Return value
1227        ------------
1228
1229        Return a tuple of the form (exit_info, string).
1230
1231        `exit_info' is either:
1232          - an integer, being the the exit status of the dialog-like
1233            program
1234          - or the string "help", meaning that help_button=1 was
1235            passed and that the user chose the Help button instead of
1236            OK or Cancel.
1237
1238        The meaning of `string' depends on the value of exit_info:
1239          - if `exit_info' is 0, `string' is the tag chosen by the
1240            user
1241          - if `exit_info' is "help", `string' is the `help' string
1242            from the `choices' argument corresponding to the item
1243            that was highlighted when the user chose the Help button
1244          - otherwise (the user chose Cancel or pressed Esc, or there
1245            was a dialog error), the value of `string' is undefined.
1246
1247        Notable exceptions:
1248
1249            any exception raised by self._perform()
1250
1251        """
1252        cmd = ["--menu", text, str(height), str(width), str(menu_height)]
1253        for t in choices:
1254            cmd.extend(t)
1255        (code, output) = self._perform(*(cmd,), **kwargs)
1256
1257        output = self._strip_xdialog_newline(output)
1258       
1259        if "help_button" in kwargs.keys() and output.startswith("HELP "):
1260            return ("help", output[5:])
1261        else:
1262            return (code, output)
1263
1264    def msgbox(self, text, height=10, width=30, **kwargs):
1265        """Display a message dialog box.
1266
1267        text   -- text to display in the box
1268        height -- height of the box
1269        width  -- width of the box
1270
1271        A message box is very similar to a yes/no box. The only
1272        difference between a message box and a yes/no box is that a
1273        message box has only a single OK button. You can use this
1274        dialog box to display any message you like. After reading
1275        the message, the user can press the ENTER key so that dialog
1276        will exit and the calling program can continue its
1277        operation.
1278
1279        Return the exit status (an integer) of the dialog-like
1280        program.
1281
1282        Notable exceptions:
1283
1284            any exception raised by self._perform()
1285
1286        """
1287        return self._perform(
1288            *(["--msgbox", text, str(height), str(width)],),
1289            **kwargs)[0]
1290
1291    def passwordbox(self, text, height=10, width=60, init='', **kwargs):
1292        """Display an password input dialog box.
1293
1294        text   -- text to display in the box
1295        height -- height of the box
1296        width  -- width of the box
1297        init   -- default input password
1298
1299        A password box is similar to an input box, except that the
1300        text the user enters is not displayed. This is useful when
1301        prompting for passwords or other sensitive information. Be
1302        aware that if anything is passed in "init", it will be
1303        visible in the system's process table to casual snoopers.
1304        Also, it is very confusing to the user to provide them with a
1305        default password they cannot see. For these reasons, using
1306        "init" is highly discouraged.
1307
1308        Return a tuple of the form (code, password) where `code' is
1309        the exit status of the dialog-like program and `password' is
1310        the password entered by the user.
1311
1312        Notable exceptions:
1313
1314            any exception raised by self._perform()
1315
1316        """
1317        (code, password) = self._perform(
1318            *(["--passwordbox", text, str(height), str(width), init],),
1319            **kwargs)
1320
1321        password = self._strip_xdialog_newline(password)
1322
1323        return (code, password)
1324
1325    def radiolist(self, text, height=15, width=54, list_height=7,
1326                  choices=[], **kwargs):
1327        """Display a radiolist box.
1328
1329        text        -- text to display in the box
1330        height      -- height of the box
1331        width       -- width of the box
1332        list_height -- number of entries displayed in the box (which
1333                       can be scrolled) at a given time
1334        choices     -- a list of tuples (tag, item, status) where
1335                       `status' specifies the initial on/off state
1336                       each entry; can be 0 or 1 (integers, 1 meaning
1337                       checked, i.e. "on"), or "on", "off" or any
1338                       uppercase variant of these two strings.
1339                       No more than one entry should  be set to on.
1340
1341        A radiolist box is similar to a menu box. The main difference
1342        is that you can indicate which entry is initially selected,
1343        by setting its status to on.
1344
1345        Return a tuple of the form (code, tag) with the tag for the
1346        entry that was chosen by the user. `code' is the exit status
1347        of the dialog-like program.
1348
1349        If the user exits with ESC or CANCEL, or if all entries were
1350        initially set to off and not altered before the user chose
1351        OK, the returned tag is the empty string.
1352
1353        Notable exceptions:
1354
1355            any exception raised by self._perform() or _to_onoff()
1356
1357        """
1358        cmd = ["--radiolist", text, str(height), str(width), str(list_height)]
1359        for t in choices:
1360            cmd.extend(((t[0], t[1], _to_onoff(t[2]))))
1361
1362        (code, tag) = self._perform(*(cmd,), **kwargs)
1363
1364        tag = self._strip_xdialog_newline(tag)
1365           
1366        return (code, tag)
1367
1368    def scrollbox(self, text, height=20, width=78, **kwargs):
1369        """Display a string in a scrollable box.
1370
1371        text   -- text to display in the box
1372        height -- height of the box
1373        width  -- width of the box
1374
1375        This method is a layer on top of textbox. The textbox option
1376        in dialog allows to display file contents only. This method
1377        allows you to display any text in a scrollable box. This is
1378        simply done by creating a temporary file, calling textbox and
1379        deleting the temporary file afterwards.
1380
1381        Return the dialog-like program's exit status.
1382
1383        Notable exceptions:
1384            - UnableToCreateTemporaryDirectory
1385            - PythonDialogIOError
1386            - PythonDialogOSError
1387            - exceptions raised by the tempfile module (which are
1388              unfortunately not mentioned in its documentation, at
1389              least in Python 2.3.3...)
1390
1391        """
1392        # In Python < 2.3, the standard library does not have
1393        # tempfile.mkstemp(), and unfortunately, tempfile.mktemp() is
1394        # insecure. So, I create a non-world-writable temporary directory and
1395        # store the temporary file in this directory.
1396        try:
1397            # We want to ensure that f is already bound in the local
1398            # scope when the finally clause (see below) is executed
1399            f = 0
1400            tmp_dir = _create_temporary_directory()
1401            # If we are here, tmp_dir *is* created (no exception was raised),
1402            # so chances are great that os.rmdir(tmp_dir) will succeed (as
1403            # long as tmp_dir is empty).
1404            #
1405            # Don't move the _create_temporary_directory() call inside the
1406            # following try statement, otherwise the user will always see a
1407            # PythonDialogOSError instead of an
1408            # UnableToCreateTemporaryDirectory because whenever
1409            # UnableToCreateTemporaryDirectory is raised, the subsequent
1410            # os.rmdir(tmp_dir) is bound to fail.
1411            try:
1412                fName = os.path.join(tmp_dir, "text")
1413                # No race condition as with the deprecated tempfile.mktemp()
1414                # since tmp_dir is not world-writable.
1415                f = open(fName, "wb")
1416                f.write(text)
1417                f.close()
1418
1419                # Ask for an empty title unless otherwise specified
1420                if not "title" in kwargs.keys():
1421                    kwargs["title"] = ""
1422
1423                return self._perform(
1424                    *(["--textbox", fName, str(height), str(width)],),
1425                    **kwargs)[0]
1426            finally:
1427                if type(f) == types.FileType:
1428                    f.close()           # Safe, even several times
1429                    os.unlink(fName)
1430                os.rmdir(tmp_dir)
1431        except os.error, v:
1432            raise PythonDialogOSError(v.strerror)
1433        except IOError, v:
1434            raise PythonDialogIOError(v)
1435
1436    def tailbox(self, filename, height=20, width=60, **kwargs):
1437        """Display the contents of a file in a dialog box, as in "tail -f".
1438
1439        filename -- name of the file whose contents is to be
1440                    displayed in the box
1441        height   -- height of the box
1442        width    -- width of the box
1443
1444        Display the contents of the specified file, updating the
1445        dialog box whenever the file grows, as with the "tail -f"
1446        command.
1447
1448        Return the exit status (an integer) of the dialog-like
1449        program.
1450
1451        Notable exceptions:
1452
1453            any exception raised by self._perform()
1454
1455        """
1456        return self._perform(
1457            *(["--tailbox", filename, str(height), str(width)],),
1458            **kwargs)[0]
1459    # No tailboxbg widget, at least for now.
1460
1461    def textbox(self, filename, height=20, width=60, **kwargs):
1462        """Display the contents of a file in a dialog box.
1463
1464        filename -- name of the file whose contents is to be
1465                    displayed in the box
1466        height   -- height of the box
1467        width    -- width of the box
1468
1469        A text box lets you display the contents of a text file in a
1470        dialog box. It is like a simple text file viewer. The user
1471        can move through the file by using the UP/DOWN, PGUP/PGDN
1472        and HOME/END keys available on most keyboards. If the lines
1473        are too long to be displayed in the box, the LEFT/RIGHT keys
1474        can be used to scroll the text region horizontally. For more
1475        convenience, forward and backward searching functions are
1476        also provided.
1477
1478        Return the exit status (an integer) of the dialog-like
1479        program.
1480
1481        Notable exceptions:
1482
1483            any exception raised by self._perform()
1484
1485        """
1486        # This is for backward compatibility... not that it is
1487        # stupid, but I prefer explicit programming.
1488        if not "title" in kwargs.keys():
1489            kwargs["title"] = filename
1490        return self._perform(
1491            *(["--textbox", filename, str(height), str(width)],),
1492            **kwargs)[0]
1493
1494    def timebox(self, text, height=3, width=30, hour=-1, minute=-1,
1495                second=-1, **kwargs):
1496        """Display a time dialog box.
1497
1498        text   -- text to display in the box
1499        height -- height of the box
1500        width  -- width of the box
1501        hour   -- inititial hour selected
1502        minute -- inititial minute selected
1503        second -- inititial second selected
1504       
1505        A dialog is displayed which allows you to select hour, minute
1506        and second. If the values for hour, minute or second are
1507        negative (or not explicitely provided, as they default to
1508        -1), the current time's corresponding values are used. You
1509        can increment or decrement any of those using the left-, up-,
1510        right- and down-arrows. Use tab or backtab to move between
1511        windows.
1512
1513        Return a tuple of the form (code, time) where `code' is the
1514        exit status (an integer) of the dialog-like program and
1515        `time' is a list of the form [hour, minute, second] (where
1516        `hour', `minute' and `second' are integers corresponding to
1517        the time chosen by the user) if the box was closed with OK,
1518        or None if it was closed with the Cancel button.
1519
1520        Notable exceptions:
1521            - any exception raised by self._perform()
1522            - PythonDialogReModuleError
1523            - UnexpectedDialogOutput
1524
1525        """
1526        (code, output) = self._perform(
1527            *(["--timebox", text, str(height), str(width),
1528               str(hour), str(minute), str(second)],),
1529            **kwargs)
1530        if code == self.DIALOG_OK:
1531            try:
1532                mo = _timebox_time_rec.match(output)
1533                if mo is None:
1534                    raise UnexpectedDialogOutput(
1535                        "the dialog-like program returned the following "
1536                        "unexpected time with the --timebox option: %s" % output)
1537                time = map(int, mo.group("hour", "minute", "second"))
1538            except re.error, v:
1539                raise PythonDialogReModuleError(v)
1540        else:
1541            time = None
1542        return (code, time)
1543
1544    def yesno(self, text, height=10, width=30, **kwargs):
1545        """Display a yes/no dialog box.
1546
1547        text   -- text to display in the box
1548        height -- height of the box
1549        width  -- width of the box
1550
1551        A yes/no dialog box of size `height' rows by `width' columns
1552        will be displayed. The string specified by `text' is
1553        displayed inside the dialog box. If this string is too long
1554        to fit in one line, it will be automatically divided into
1555        multiple lines at appropriate places. The text string can
1556        also contain the sub-string "\\n" or newline characters to
1557        control line breaking explicitly. This dialog box is useful
1558        for asking questions that require the user to answer either
1559        yes or no. The dialog box has a Yes button and a No button,
1560        in which the user can switch between by pressing the TAB
1561        key.
1562
1563        Return the exit status (an integer) of the dialog-like
1564        program.
1565
1566        Notable exceptions:
1567
1568            any exception raised by self._perform()
1569
1570        """
1571        return self._perform(
1572            *(["--yesno", text, str(height), str(width)],),
1573            **kwargs)[0]
1574
1575#
1576# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1577# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1578#
1579import os, popen2, fcntl, select, sys, os, time
1580from string import *
1581# Prerequisitos: cliente do subversion instalado, e pysvn mais o python-dialog instalado no python.
1582try:
1583                import pysvn
1584except:
1585                print 'Nao foi localizado o produto python-svn.\nInstalar com apt-get install python-svn'
1586                sys.exit(99)
1587               
1588
1589# We should handle the new DIALOG_HELP and DIALOG_EXTRA return codes here.
1590def handle_exit_code(d, code):
1591    # d is supposed to be a Dialog instance
1592    if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
1593        if code == d.DIALOG_CANCEL:
1594            msg = "Voce escolheu Cancelar. Voce quer encerrar " \
1595                  "este programa?"
1596        else:
1597            msg = "Voce pressionou ESC. Voce quer encerrar " \
1598                  "este programa?"
1599        # "No" or "ESC" will bring the user back to the demo.
1600        # DIALOG_ERROR is propagated as an exception and caught in main().
1601        # So we only need to handle OK here.
1602        if d.yesno(msg) == d.DIALOG_OK:
1603            sys.exit(0)
1604        return 0
1605    else:
1606        return 1                        # code is d.DIALOG_OK
1607
1608
1609# Funcao para exibir uma lista para selecionar so um item...
1610def msg_box_radiolist(d,tit,msg):   
1611    while 1:
1612        (code, tag) = d.radiolist(tit, width=65, choices=msg)
1613       
1614        if handle_exit_code(d, code):
1615                if not tag:
1616                        continue
1617                break
1618    return tag
1619
1620
1621def msg_box_inputbox(d,tit):
1622    while 1:
1623        (code, answer) = d.inputbox(tit, init="",width=76,height=20)
1624        if handle_exit_code(d, code):
1625                if not answer:
1626                        continue
1627                break
1628    return answer
1629   
1630#Função para solicitar ao usuario o tipo de distribuicao
1631def mostra(d,escolhas):
1632   return d.menu("Selecione o tipo de distrbuicao desejada:",height=0,choices=escolhas)
1633
1634
1635def msg_box_info(d,msg):
1636    d.infobox(msg,height=19,width=60)
1637    time.sleep(2)
1638    return
1639
1640
1641def msg_box_sim_nao(d,msg):
1642    return d.yesno(msg,height=18,width=76)
1643
1644
1645#As duas funcoes, a seguir, executam comandos e retornam resultado.
1646def makeNonBlocking(fd):
1647    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
1648    try:
1649        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY)
1650    except AttributeError:
1651        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.FNDELAY)
1652
1653
1654def getCommandOutput(command):
1655    f=sys.stdout
1656    child = popen2.Popen3(command, 1) # Captura stdout e stderr do comando
1657    child.tochild.close( )             # nao necessitamos escrever para a  stdin do processo iniciado pelo comando
1658    outfile = child.fromchild
1659    outfd = outfile.fileno( )
1660    errfile = child.childerr
1661    errfd = errfile.fileno( )
1662    makeNonBlocking(outfd)            # Evitamos deadlocks tornando fd's nonblock.
1663    makeNonBlocking(errfd)
1664    outdata, errdata = [  ], [  ]
1665    outeof = erreof = False
1666    while True:
1667        f.write('.')
1668        f.flush()
1669        to_check = [outfd]*(not outeof) + [errfd]*(not erreof)
1670        ready = select.select(to_check, [  ], [  ]) # Espera por dados...
1671        if outfd in ready[0]:
1672            outchunk = outfile.read( )
1673            if outchunk == '':
1674                outeof = True
1675            else:
1676                outdata.append(outchunk)
1677        if errfd in ready[0]:
1678            errchunk = errfile.read( )
1679            if errchunk == '':
1680                erreof = True
1681            else:
1682                errdata.append(errchunk)
1683        if outeof and erreof:
1684            break
1685        select.select([  ],[  ],[  ],.1) # Seta um intervalo de tempo bem curto para os buffers serem preenchidos.
1686    err = child.wait( )
1687    if err != 0:
1688        raise RuntimeError, '%r falhou com codigo %d\n%s' % (
1689            command, err, ''.join(errdata))
1690    return ''.join(outdata)
1691
1692
1693
1694#Callback para login no svn
1695def Login_no_svn( realm, username, may_save ):
1696        from getpass import getpass
1697        global flag
1698        print 'Por favor, identifique-se'
1699        usuario = raw_input('Usuario:').strip()
1700        senha = getpass('Senha:').strip()
1701        if not flag:
1702                flag = True
1703        else:
1704                flag = False
1705                print 'Falhou acesso ao Subversion...\n'
1706                sys.exit(92)
1707        return True, usuario, senha, True
1708
1709
1710
1711def notify_svn( event_dict ):
1712        global chk_rev
1713        chk_rev = 0
1714        if event_dict['action'] == pysvn.wc_notify_action.update_completed:
1715                chk_rev = event_dict['revision'].number
1716        return
1717
1718
1719def get_log_message_svn():
1720        global log_msg
1721        return True, log_msg
1722
1723
1724#Rotina para recuperar dados do svn (log) e gerar arquivo html
1725def  r_svn_to_html(caminho,branch_origem,revisao_branch_origem,comentario_vrs,trac):
1726        arq_sai = caminho + os.sep + 'infodist' + os.sep + 'revisoes-svn.php'
1727        arq_sai_ultima = caminho + os.sep +  'infodist' + os.sep + 'ultima-revisao-svn.php'
1728        saida=''
1729        cor =''
1730        linha = 0       
1731       
1732        try:
1733                cliente = pysvn.Client()
1734                cliente.callback_get_login = Login_no_svn
1735                logs = cliente.log( branch_origem, revision_start=pysvn.Revision( pysvn.opt_revision_kind.number,revisao_branch_origem),
1736                        revision_end=pysvn.Revision( pysvn.opt_revision_kind.number, 0 ),
1737                        discover_changed_paths=True,strict_node_history=True,limit=0 )
1738        except:
1739                print 'Erro Obtendo dados do svn para gerar revisoes-svn.php'
1740                sys.exit(93)     
1741
1742        fd = open(arq_sai, 'w')
1743        saida += '<?php ?>\n'
1744        saida += '<div align=CENTER>\n'
1745        saida += '<h3 align="center" style="color: #0000FF;margin-bottom: 0 ">Expresso - Subversion - R' + str(revisao_branch_origem) +  '</h3>\n'
1746        saida += '<table border="1" witdh="100%">\n'
1747        saida += '<tr bgcolor="#D3DCE3">\n'
1748        saida += '<td>Revis&atilde;o</td><td><font color="#000066">Data</font></td><td><font color="#000066">Implementa&ccedil;&atilde;o - branch ' +  os.path.split(branch_origem)[-1]  + ' R' + revisao_branch_origem +   '</font></td></tr>\n'
1749        for revisao in logs:
1750                linha += 1
1751                if linha%2:
1752                        cor = '#DDDDDD'
1753                else:
1754                        cor = '#EEEEEE'
1755                saida += '<tr bgcolor="'+cor+'">\n'     
1756                saida += '<td valign="top">'+str(revisao['revision'].number)+'</td>\n'
1757                saida += '<td valign="top">'+time.strftime('%Y-%m-%d  %H:%M:%S',time.localtime(revisao['date']))+'</td>\n'
1758                try:
1759                        aux = revisao['message']
1760                        try:
1761                                v = '#' + aux.split('#')[1].split(' ')[0]
1762                                p =  aux.find(v)
1763                                if p > -1:
1764                                     aux1 = aux[:p]
1765                                     aux2 = aux[p+len(v):]
1766                                     saida += '<td>' + aux1 + '<a href="' + trac + aux.split('#')[1].split(' ')[0] + '" TARGET="_blank" >' + v + '</a>' + aux2 + '</td>'
1767                                else:
1768                                     saida += '<td valign="top"><pre>'+aux+'</pre></td>\n'
1769                        except:
1770                                saida += '<td valign="top"><pre>'+aux+'</pre></td>\n'
1771                except:
1772                        saida += '<td valign="top">Nao foi possivel obter a msg no subversion. Falha no encode...</td>\n'
1773                saida += '</tr>\n'
1774        saida += '</table>\n'
1775        saida += '</div>\n'     
1776        fd.write(saida.encode('iso8859','replace'))
1777        fd.close()
1778
1779        fd = open(arq_sai_ultima,'w')
1780        saida = ''
1781        saida += '<?php $ultima_revisao ="' + comentario_vrs + ' , R' + str(revisao_branch_origem) + '" ?>\n'
1782        fd.write(saida.encode('iso8859','replace'))
1783        fd.close()
1784
1785# ##########################################################################################
1786#                                     INICIO
1787# ##########################################################################################
1788
1789#Carrega arquivo com dados que configuram as distribuiçoes possíveis...
1790from ger2_conf import *
1791
1792# Pega identificador de cada distribuicao configurada...
1793aux_tipo_distribuicao = parametros.keys()
1794
1795# Ordena
1796aux_tipo_distribuicao.sort()
1797
1798tipo_distribuicao = {}
1799
1800
1801# Tipo de distribuicao: uma entrada para distribuicao configurada...
1802for i in range(0,len(aux_tipo_distribuicao)):
1803        tipo_distribuicao[str(i+1)] = aux_tipo_distribuicao[i]
1804
1805# ordena pelo enumerador.....
1806Ks = tipo_distribuicao.keys()
1807Ks.sort()
1808escolhas = []
1809
1810# Construindo itens do menu, uma entrada para cada distribuicao configurada...
1811for t in Ks:
1812     escolhas.append([t,tipo_distribuicao[t]])
1813
1814flag = False
1815rev=''
1816params={}
1817msg = ''
1818
1819
1820# Construindo objeto para o dialog...
1821d=Dialog()
1822# Seta titulo geral....
1823d.add_persistent_args(["--backtitle", "Gera Distribuição"])
1824"""
1825# Usuario seleciona a distribuição desejada.
1826# cod = 0 , usuario selecionou um item, ver valor em opc...
1827# cod = 1 , usuario selecionou cancelar, valor em opc é vazio
1828# cod = 2 , usurio usou a tecla esc, valor em opc é vazio
1829
1830#simula retorno do dialog
1831cod = 0
1832opc = '1'   # soh tem um item de parametro ....
1833#[cod,opc] = mostra(d,escolhas)
1834
1835
1836# Se opc for vazio o usuario pediu para cancelar, ou usou a tecla esc...
1837if opc == '':
1838  sys.exit(98)
1839"""
1840opc = '1'   # soh tem um item de parametro ....
1841
1842# Com base na selecao do usuario, carrega variaveis com valÃŽres configurados....         
1843projeto = parametros[tipo_distribuicao[opc]]['projeto']
1844caminho = parametros[tipo_distribuicao[opc]]['caminho']
1845caminho1 = trabalho + os.sep + caminho
1846trac= parametros[tipo_distribuicao[opc]]['trac']
1847s_ftp = parametros[tipo_distribuicao[opc]]['s_ftp']
1848s_path = parametros[tipo_distribuicao[opc]]['s_path']
1849prefixo = parametros[tipo_distribuicao[opc]]['prefixo']
1850script = parametros[tipo_distribuicao[opc]]['script']
1851
1852# Seta titulo da distribuicao que sera efetuada...
1853d.add_persistent_args(["--title", 'Distribuição ExpressoLivre' ])
1854
1855#   1 -  Prepara para buscar os branchs existentes....
1856msg = "\n\n\nBuscando os branchs em: \n\n" + projeto + "\n\n\nAguarde."
1857msg_box_info(d,msg)
1858time.sleep(1)
1859
1860try:
1861        cliente = pysvn.Client()
1862        cliente.callback_get_login = Login_no_svn
1863except:
1864        msg_box_info(d,'\nErro Logando cliente do subversion.')
1865        sys.exit(97)
1866               
1867try:
1868        # Busca branchs no diretorio apontado por projeto.
1869        branchs = cliente.ls(projeto)
1870        msg = []
1871        tab_branchs = {}
1872        # Cria lista para exibir ao usuario ....
1873        for branch in branchs:
1874                msg.append((str(branch['created_rev'].number),os.path.split(branch['name'])[-1],0))
1875                tab_branchs[str(branch['created_rev'].number)] = os.path.split(branch['name'])[-1]
1876except:
1877        msg_box_info(d,'\nErro buscando branchs.')
1878        sys.exit(95)   
1879       
1880tit = "Branchs existentes em " + projeto + '\nSelecione um:'   
1881
1882#   2 -  Mostra branchs  para o usuario selecionar um ...
1883revisao_branch_origem = msg_box_radiolist(d,tit,msg)
1884
1885branch_origem = projeto + '/' + tab_branchs[revisao_branch_origem]
1886
1887#  4A -  Solicita versão para adicionar no arquivo revisoes-svn.php.
1888tit = 'Entre com uma identificao(max 12 caracteres) para a versao sendo distribuida.Este valor vai ser exibido na tela de login.'
1889invalido = 1
1890while invalido:
1891        comentario_vrs = msg_box_inputbox(d,tit)
1892        invalido = 0
1893        if not strip(comentario_vrs) or len(strip(comentario_vrs)) > 12:
1894                invalido = 1
1895                continue
1896
1897#msg_box_info(d,'\n\n\nProcessando o branch como solicitado.\n\n\nAguarde.')
1898msg_box_info(d,'\n\n\nProcessando o branch ' + tab_branchs[revisao_branch_origem] +  '(Rev. ' + revisao_branch_origem + '), como solicitado.\n\n\nAguarde.')
1899
1900# Dados para executar o export:
1901#
1902# revisao_branch_origem
1903# branch_origem
1904#
1905
1906
1907# Verifica a existencia de uma pasta de trabalho.
1908# Nesta pasta serao gerados subdiretorios que vao conter as distribuiçoes.
1909if not os.path.isdir(trabalho):
1910        os.makedirs(trabalho)
1911
1912if os.path.isdir(caminho1):
1913        retorno  = getCommandOutput('rm -fR ' + caminho1)
1914
1915revisao = cliente.export(branch_origem,
1916                caminho1,
1917                force=False,
1918                revision=pysvn.Revision(pysvn.opt_revision_kind.number,revisao_branch_origem),
1919                native_eol=None,
1920                ignore_externals=False,
1921                recurse=True,
1922                peg_revision=pysvn.Revision(pysvn.opt_revision_kind.number, revisao_branch_origem))
1923
1924retorno  = getCommandOutput('mkdir ' + caminho1 + os.sep + 'infodist')
1925
1926#  6 - Gera arqivo com informacoes do Subversion
1927r_svn_to_html(caminho1,branch_origem,revisao_branch_origem,comentario_vrs,trac)
1928
1929# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1930
1931# Se existir um nome de script no arquivo de configuração, vai tentar executar...
1932if script != '':
1933   msg = msg + "\nExecutando o script " + os.sep + script + " \n"
1934   msg_box_info(d,msg)
1935   retorno  = getCommandOutput(script)
1936
1937#Solicita execucao do comando de compactaçao
1938path_corrente = os.getcwd()
1939os.chdir(trabalho)
1940retorno  = getCommandOutput("tar -zcf " + prefixo + str(revisao_branch_origem) + ".tgz ./expresso")
1941os.chdir(path_corrente)
1942msg = ''
1943msg = msg + "\nFoi gerado o arquivo " + prefixo + str(revisao_branch_origem) + ".tgz \n"
1944msg_box_info(d,msg)
1945
1946if s_ftp != '':
1947    from ftplib import FTP
1948    servidor_ftp = s_ftp
1949    #Faz ftp para a maquina de distribuicao...
1950    msg = msg +  'Iniciando copia do arquivo gerado para ambiente de distribuicao(FTP)\n'
1951    msg_box_info(d,msg)
1952    try:
1953            ftp = FTP(servidor_ftp,'anonymous','')
1954            resp = ftp.cwd(s_path)
1955            msg = msg + resp + '\n'
1956            msg_box_info(d,msg)
1957            lista = ftp.nlst()
1958            if 'R'+str(revisao_branch_origem) in lista:
1959                    ftp.cwd('R'+str(revisao_branch_origem))
1960                    # pode existir a pasta mas nao o arquivo....
1961                    try:
1962                       resp = ftp.delete(prefixo+str(revisao_branch_origem)+'.tgz')
1963                    except:
1964                       pass
1965                    msg = msg + resp + '\n'
1966                    msg_box_info(d,msg)
1967                    resp = ftp.cwd('../')
1968                    msg = msg + resp + '\n'
1969                    msg_box_info(d,msg)
1970            else:
1971                    resp = ftp.mkd('R'+str(revisao_branch_origem))
1972                    msg = msg + resp + '\n'
1973                    msg_box_info(d,msg)
1974            resp = ftp.cwd('R'+str(revisao_branch_origem))
1975            msg = msg + resp + '\n'
1976            msg_box_info(d,msg)
1977            print trabalho + os.sep + prefixo+str(revisao_branch_origem)+'.tgz'
1978            f = open(trabalho + os.sep + prefixo+str(revisao_branch_origem)+'.tgz','rb')
1979            resp = ftp.storbinary('STOR ' + prefixo + str(revisao_branch_origem) + '.tgz',f)
1980            msg = msg + resp + '\n'
1981            msg_box_info(d,msg)
1982            msg = msg + 'Verifique os dados abaixo:\n'
1983            msg_box_info(d,msg)
1984            resp = ftp.pwd()
1985            msg = msg + 'Diretorio de distribuicao R'+str(revisao_branch_origem)+' gerado em '+servidor_ftp+' : '+resp
1986            msg_box_info(d,msg)
1987            resp = ftp.retrlines('LIST')
1988            msg = msg + '\nConteudo gerado no diretorio acima : \n'+resp+'\n'
1989            msg_box_info(d,msg)
1990            ftp.close()
1991
1992    except:
1993            msg = msg + '\nErro ao conectar no servidor FTP.\n\n\n'
1994
1995msg = msg + '\nProcessamento concluido.\n'
1996msg_box_info(d,msg)
1997# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1998
1999
2000       
2001       
Note: See TracBrowser for help on using the repository browser.