Merge lp:~ed.so/duplicity/0.6-readd_sshpexpect into lp:duplicity/0.6

Proposed by edso
Status: Merged
Merged at revision: 851
Proposed branch: lp:~ed.so/duplicity/0.6-readd_sshpexpect
Merge into: lp:duplicity/0.6
Diff against target: 3287 lines (+2709/-411)
8 files modified
Changelog.GNU (+0/-21)
bin/duplicity.1 (+158/-33)
duplicity/backends/ssh_paramiko.py (+361/-0)
duplicity/backends/ssh_pexpect.py (+317/-0)
duplicity/backends/sshbackend.py (+15/-342)
duplicity/commandline.py (+7/-12)
duplicity/globals.py (+6/-3)
duplicity/pexpect.py (+1845/-0)
To merge this branch: bzr merge lp:~ed.so/duplicity/0.6-readd_sshpexpect
Reviewer Review Type Date Requested Status
duplicity-team Pending
Review via email: mp+97297@code.launchpad.net

Description of the change

- readd ssh pexpect backend as alternative
- added --ssh-backend parameter to switch between paramiko,pexpect
- manpage
-- update to reflect above changes
-- added more backend requirements
- Changelog.GNU removed double entries

To post a comment you must log in.
Revision history for this message
Kenneth Loafman (kenneth-loafman) wrote :

Thanks for the help! All tests passed for both.

I made a couple of cosmetic changes to continue the file naming convention, otherwise, just left it alone. Good job.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Changelog.GNU'
2--- Changelog.GNU 2012-03-13 11:39:53 +0000
3+++ Changelog.GNU 2012-03-13 20:58:22 +0000
4@@ -4,14 +4,6 @@
5
6 add missing_host_key prompt to new sshbackend similar to ssh procedure
7
8-2012-03-12 edso
9-
10- changelog entry
11-
12-2012-03-12 edso
13-
14- add missing_host_key prompt similar to ssh procedure
15-
16 2012-03-13 Kenneth Loafman <kenneth@loafman.com>
17
18 Merged in lp:~carlos-abalde/duplicity/gdocs-backend-gdata-2.0.16.-upgrade.
19@@ -28,14 +20,6 @@
20 @Ken: would you please announce that sshbackend is paramiko based native python now in the Changelog for the next release?
21 this was missing in 0.6.18's Changelog
22
23-2012-03-08 edso
24-
25- changelog entry
26-
27-2012-03-08 edso
28-
29- add ssh_config support (/etc/ssh/ssh_config + ~/.ssh/config) to paramiko sshbackend
30-
31 2012-03-08 edso <edgar.soldin@web.de>
32
33 Merged in lp:~ed.so/duplicity/0.6-webdav_fixes.
34@@ -48,10 +32,6 @@
35 2012-03-08 Kenneth Loafman <kenneth@loafman.com>
36
37 Merged in lp:~ed.so/duplicity/0.6-manpage
38-
39- - added REQUIREMENTS section
40-
41-2012-03-08 edso
42
43 - added REQUIREMENTS section
44 - restructure SYNOPSIS/ACTIONS to have commands sorted by backup lifecycle
45@@ -82,7 +62,6 @@
46
47 2012-02-29 kenneth@loafman.com
48
49- Changes for 0.6.18.
50
51 2012-02-29 kenneth@loafman.com
52
53
54=== modified file 'bin/duplicity.1'
55--- bin/duplicity.1 2012-03-08 12:59:08 +0000
56+++ bin/duplicity.1 2012-03-13 20:58:22 +0000
57@@ -4,13 +4,54 @@
58 duplicity \- Encrypted incremental backup to local or remote storage.
59
60 .SH REQUIREMENTS
61-Duplicity requires a POSIX-like operating system.
62+Duplicity requires a POSIX-like operating system with a
63+.B python
64+interpreter version 2.4+ installed.
65 It is best used under GNU/Linux.
66
67-Some backends also require additional components:
68-.IP * 2
69-.B "ssh backend"
70-(scp/sftp/ssh)
71+Some backends also require additional components (probably available as packages for your specific platform):
72+.IP * 2
73+.B "boto backend"
74+(S3 Amazon Web Services)
75+.RS
76+.IP - 2
77+.B boto
78+- http://github.com/boto/boto
79+.RE
80+.IP * 2
81+.B "ftp backend"
82+.RS
83+.IP - 2
84+.B NcFTP Client
85+- http://www.ncftp.com/
86+.RE
87+.IP * 2
88+.B "ftps backend"
89+.RS
90+.IP - 2
91+.B LFTP Client
92+- http://lftp.yar.ru/
93+.RE
94+.IP * 2
95+.B "gio backend"
96+(Gnome VFS API)
97+.RS
98+.IP - 2
99+.B PyGObject
100+- http://live.gnome.org/PyGObject
101+.IP - 2
102+.B D-Bus
103+(dbus)- http://www.freedesktop.org/wiki/Software/dbus
104+.RE
105+.IP * 2
106+.B "ssh backends"
107+(scp/sftp/ssh see
108+.B --ssh-backend
109+)
110+.RS
111+.IP * 2
112+.B ssh paramiko backend
113+(default)
114 .RS
115 .IP - 2
116 .B paramiko
117@@ -20,12 +61,12 @@
118 Python Cryptography Toolkit - http://www.dlitz.net/software/pycrypto/
119 .RE
120 .IP * 2
121-.B "boto backend"
122-(S3 Amazon Web Services)
123+.B ssh pexpect backend
124 .RS
125 .IP - 2
126-.B boto
127-- http://github.com/boto/boto
128+.B sftp/scp client binaries
129+OpenSSH - http://www.openssh.com/
130+.RE
131 .RE
132
133 .SH SYNOPSIS
134@@ -644,13 +685,33 @@
135
136 .TP
137 .BI "--scp-command " command
138-Deprecated and ignored. The ssh backend does no longer use an external
139-scp client program.
140+.B (only ssh pexpect backend)
141+The
142+.I command
143+will be used instead of scp to send or receive files. The default command
144+is "scp". To list and delete existing files, the sftp command is used. See
145+.BR --ssh-options
146+and
147+.BR --sftp-command .
148
149 .TP
150 .BI "--sftp-command " command
151-Deprecated and ignored. The ssh backend does no longer use an external
152-sftp client program.
153+.B (only ssh pexpect backend)
154+The
155+.I command
156+will be used instead of sftp for listing and deleting files. The
157+default is "sftp". File transfers are done using the sftp command. See
158+.BR --ssh-options ,
159+.BR --use-scp ,
160+and
161+.BR --scp-command .
162+
163+.TP
164+.BI --short-filenames
165+If this option is specified, the names of the files duplicity writes
166+will be shorter (about 30 chars) but less understandable. This may be
167+useful when backing up to MacOS or another OS or FS that doesn't
168+support long filenames.
169
170 .TP
171 .BI "--sign-key " key
172@@ -675,28 +736,38 @@
173 This password is also used for passphrase-protected ssh keys.
174
175 .TP
176+.BI "--ssh-backend " backend
177+Allows the explicit selection of a ssh backend. Defaults to
178+.BR paramiko .
179+Alternatively you might choose
180+.BR pexpect .
181+see also
182+.B "A NOTE ON SSH BACKENDS"
183+
184+.TP
185 .BI "--ssh-options " options
186-Allows you to pass options to the ssh/scp/sftp backend. The
187+Allows you to pass options to the ssh backend. The
188 .I options
189-list should be of the form "-oopt1=parm1 -oopt2=parm2" where the option string is
190-quoted and the only spaces allowed are between options. Options must
191-be given in the long option format described in
192-.BR ssh_config(5) .
193-The sftp/scp backend currently supports only one ssh option, IdentityFile
194+list should be of the form "-oOpt1=parm1 -oOpt2=parm2" where the option string is
195+quoted and the only spaces allowed are between options. The option string
196+will be passed verbatim to both scp and sftp, whose command line syntax
197+differs slightly hence the options should therefore be given in the long option format described in
198+.BR ssh_config(5) ,
199 like in this example:
200-.PP
201 .RS
202-duplicity --ssh-options="-oIdentityFile=/my/backup/id" /home/me sftp://uid@other.host/some_dir
203 .PP
204+.ad l
205+duplicity --ssh-options="-oProtocol=2 -oIdentityFile=/my/backup/id" /home/me scp://uid@other.host/some_dir
206+.TP
207+.B NOTE:
208+.I ssh paramiko backend
209+currently supports only the
210+.nh
211+.B -oIdentityFile
212+.hy
213+setting.
214 .RE
215-
216-
217-.TP
218-.BI --short-filenames
219-If this option is specified, the names of the files duplicity writes
220-will be shorter (about 30 chars) but less understandable. This may be
221-useful when backing up to MacOS or another OS or FS that doesn't
222-support long filenames.
223+.ad n
224
225 .TP
226 .BI "--tempdir " directory
227@@ -740,9 +811,10 @@
228
229 .TP
230 .BI --use-scp
231-If this option is specified, then the sftp/scp backend will use the
232-scp protocol rather than sftp for backend operations. The default is to use
233-sftp, because it does not suffer from shell quoting issues like scp.
234+If this option is specified, then the ssh backend will use the
235+scp protocol rather than sftp for backend operations.
236+see also
237+.B "A NOTE ON SSH BACKENDS"
238
239 .TP
240 .BI "--verbosity " level ", -v" level
241@@ -862,7 +934,10 @@
242 sftp://user[:password]@other.host[:port]/[/]some_dir
243 .br
244 see also
245-.BI "--use-scp"
246+.BR --ssh-backend ,
247+.B --use-scp
248+and
249+.B "A NOTE ON SSH BACKENDS"
250 .PP
251 tahoe://alias/directory
252 .PP
253@@ -1216,6 +1291,56 @@
254 .B from_address_prefix
255 will distinguish between different backups.
256
257+.SH A NOTE ON SSH BACKENDS
258+The
259+.I ssh backends
260+support sftp and scp transports. This is a known user-confusing issue.
261+These are fundamentally different. If you plan to access your backend via one
262+of those please inform yourself about the requirements for a server to support
263+scp or sftp.
264+To make it even more confusing the user can choose between two ssh backends
265+see
266+.nh
267+.B --ssh\-backend
268+.hy
269+for details.
270+.PP
271+.BR "SSH paramiko backend " "(selected by default)"
272+is complete reimplementation of ssh protocols natively in python. Advantages
273+are speed and maintainability. Minor disadvantage is that extra packages are
274+needed see
275+.nh
276+.B REQUIREMENTS
277+.hy
278+above. In
279+.I sftp
280+(default) mode all operations are done via the according sftp commands. In
281+.I scp
282+mode (
283+.I --use-scp
284+) though scp access is used for put/get operations but listing is done via ssh remote shell.
285+.PP
286+.B SSH pexpect backend
287+is the legacy ssh backend using the command line ssh binaries via pexpect.
288+Older versions used
289+.I scp
290+for get and put operations and
291+.I sftp
292+for list and
293+delete operations. The current version uses
294+.I sftp
295+for all four supported
296+operations, unless the
297+.I --use-scp
298+option is used to revert to old behavior.
299+.PP
300+.B Why use sftp instead of scp?
301+The change to sftp was made in order to allow the remote system to chroot the backup,
302+thus providing better security and because it does not suffer from shell quoting issues like scp.
303+Scp also does not support any kind of file listing, so sftp or ssh access will always be needed
304+in addition for this backend mode to work properly. Sftp does not have these limitations but needs
305+an sftp service running on the backend server, which is sometimes not an option.
306+
307 .SH A NOTE ON UBUNTU ONE
308 Connecting to Ubuntu One requires that you be running duplicity inside of an X
309 session so that you can be prompted for your credentials if necessary by the
310
311=== added file 'duplicity/backends/ssh_paramiko.py'
312--- duplicity/backends/ssh_paramiko.py 1970-01-01 00:00:00 +0000
313+++ duplicity/backends/ssh_paramiko.py 2012-03-13 20:58:22 +0000
314@@ -0,0 +1,361 @@
315+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
316+#
317+# Copyright 2002 Ben Escoto <ben@emerose.org>
318+# Copyright 2007 Kenneth Loafman <kenneth@loafman.com>
319+# Copyright 2011 Alexander Zangerl <az@snafu.priv.at>
320+# Copyright 2012 edso (ssh_config added)
321+#
322+# $Id: sshbackend.py,v 1.2 2011/12/31 04:44:12 az Exp $
323+#
324+# This file is part of duplicity.
325+#
326+# Duplicity is free software; you can redistribute it and/or modify it
327+# under the terms of the GNU General Public License as published by the
328+# Free Software Foundation; either version 2 of the License, or (at your
329+# option) any later version.
330+#
331+# Duplicity is distributed in the hope that it will be useful, but
332+# WITHOUT ANY WARRANTY; without even the implied warranty of
333+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
334+# General Public License for more details.
335+#
336+# You should have received a copy of the GNU General Public License
337+# along with duplicity; if not, write to the Free Software Foundation,
338+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
339+
340+import re
341+import string
342+import os
343+import errno
344+import sys
345+import getpass
346+from binascii import hexlify
347+
348+# debian squeeze's paramiko is a bit old, so we silence randompool depreciation warning
349+# note also: passphrased private keys work with squeeze's paramiko only if done with DES, not AES
350+import warnings
351+warnings.simplefilter("ignore")
352+import paramiko
353+warnings.resetwarnings()
354+
355+import duplicity.backend
356+from duplicity import globals
357+from duplicity import log
358+from duplicity.errors import *
359+
360+read_blocksize=65635 # for doing scp retrievals, where we need to read ourselves
361+
362+class SSHParamikoBackend(duplicity.backend.Backend):
363+ """This backend accesses files using the sftp protocol, or scp when the --use-scp option is given.
364+ It does not need any local client programs, but an ssh server and the sftp program must be installed on the remote
365+ side (or with --use-scp, the programs scp, ls, mkdir, rm and a POSIX-compliant shell).
366+
367+ Authentication keys are requested from an ssh agent if present, then ~/.ssh/id_rsa/dsa are tried.
368+ If -oIdentityFile=path is present in --ssh-options, then that file is also tried.
369+ The passphrase for any of these keys is taken from the URI or FTP_PASSWORD.
370+ If none of the above are available, password authentication is attempted (using the URI or FTP_PASSWORD).
371+
372+ Missing directories on the remote side will be created.
373+
374+ If --use-scp is active then all operations on the remote side require passing arguments through a shell,
375+ which introduces unavoidable quoting issues: directory and file names that contain single quotes will not work.
376+ This problem does not exist with sftp.
377+ """
378+ def __init__(self, parsed_url):
379+ duplicity.backend.Backend.__init__(self, parsed_url)
380+
381+ if parsed_url.path:
382+ # remove first leading '/'
383+ self.remote_dir = re.sub(r'^/', r'', parsed_url.path, 1)
384+ else:
385+ self.remote_dir = '.'
386+
387+ self.client = paramiko.SSHClient()
388+ self.client.set_missing_host_key_policy(AgreedAddPolicy())
389+ # load known_hosts files
390+ # paramiko is very picky wrt format and bails out on any problem...
391+ try:
392+ if os.path.isfile("/etc/ssh/ssh_known_hosts"):
393+ self.client.load_system_host_keys("/etc/ssh/ssh_known_hosts")
394+ except Exception, e:
395+ raise BackendException("could not load /etc/ssh/ssh_known_hosts, maybe corrupt?")
396+ try:
397+ # use load_host_keys() to signal it's writable to paramiko
398+ # load if file exists or add filename to create it if needed
399+ file = os.path.expanduser('~/.ssh/known_hosts')
400+ if os.path.isfile(file):
401+ self.client.load_host_keys(file)
402+ else:
403+ self.client._host_keys_filename = file
404+ except Exception, e:
405+ raise BackendException("could not load ~/.ssh/known_hosts, maybe corrupt?")
406+
407+ """ the next block reorganizes all host parameters into a
408+ dictionary like SSHConfig does. this dictionary 'self.config'
409+ becomes the authorative source for these values from here on.
410+ rationale is that it is easiest to deal wrt overwriting multiple
411+ values from ssh_config file. (ede 03/2012)
412+ """
413+ self.config={'hostname':parsed_url.hostname}
414+ # get system host config entries
415+ self.config.update(self.gethostconfig('/etc/ssh/ssh_config',parsed_url.hostname))
416+ # update with user's config file
417+ self.config.update(self.gethostconfig('~/.ssh/config',parsed_url.hostname))
418+ # update with url values
419+ ## username from url
420+ if parsed_url.username:
421+ self.config.update({'user':parsed_url.username})
422+ ## username from input
423+ if not 'user' in self.config:
424+ self.config.update({'user':getpass.getuser()})
425+ ## port from url
426+ if parsed_url.port:
427+ self.config.update({'port':parsed_url.port})
428+ ## ensure there is deafult 22 or an int value
429+ if 'port' in self.config:
430+ self.config.update({'port':int(self.config['port'])})
431+ else:
432+ self.config.update({'port':22})
433+ ## alternative ssh private key, identity file
434+ m=re.search("-oidentityfile=(\S+)",globals.ssh_options,re.I)
435+ if (m!=None):
436+ keyfilename=m.group(1)
437+ self.config['identityfile'] = keyfilename
438+ ## ensure ~ is expanded and identity exists in dictionary
439+ if 'identityfile' in self.config:
440+ self.config['identityfile'] = os.path.expanduser(
441+ self.config['identityfile'])
442+ else:
443+ self.config['identityfile'] = None
444+
445+ # get password, enable prompt if askpass is set
446+ self.use_getpass = globals.ssh_askpass
447+ ## set url values for beautiful login prompt
448+ parsed_url.username = self.config['user']
449+ parsed_url.hostname = self.config['hostname']
450+ password = self.get_password()
451+
452+ try:
453+ self.client.connect(hostname=self.config['hostname'],
454+ port=self.config['port'],
455+ username=self.config['user'],
456+ password=password,
457+ allow_agent=True,
458+ look_for_keys=True,
459+ key_filename=self.config['identityfile'])
460+ except Exception, e:
461+ raise BackendException("ssh connection to %s@%s:%d failed: %s" % (
462+ self.config['user'],
463+ self.config['hostname'],
464+ self.config['port'],e))
465+ self.client.get_transport().set_keepalive((int)(globals.timeout / 2))
466+
467+ # scp or sftp?
468+ if (globals.use_scp):
469+ # sanity-check the directory name
470+ if (re.search("'",self.remote_dir)):
471+ raise BackendException("cannot handle directory names with single quotes with --use-scp!")
472+
473+ # make directory if needed
474+ self.runremote("test -d '%s' || mkdir -p '%s'" % (self.remote_dir,self.remote_dir),False,"scp mkdir ")
475+ else:
476+ try:
477+ self.sftp=self.client.open_sftp()
478+ except Exception, e:
479+ raise BackendException("sftp negotiation failed: %s" % e)
480+
481+
482+ # move to the appropriate directory, possibly after creating it and its parents
483+ dirs = self.remote_dir.split(os.sep)
484+ if len(dirs) > 0:
485+ if not dirs[0]:
486+ dirs = dirs[1:]
487+ dirs[0]= '/' + dirs[0]
488+ for d in dirs:
489+ if (d == ''):
490+ continue
491+ try:
492+ attrs=self.sftp.stat(d)
493+ except IOError, e:
494+ if e.errno == errno.ENOENT:
495+ try:
496+ self.sftp.mkdir(d)
497+ except Exception, e:
498+ raise BackendException("sftp mkdir %s failed: %s" % (self.sftp.normalize(".")+"/"+d,e))
499+ else:
500+ raise BackendException("sftp stat %s failed: %s" % (self.sftp.normalize(".")+"/"+d,e))
501+ try:
502+ self.sftp.chdir(d)
503+ except Exception, e:
504+ raise BackendException("sftp chdir to %s failed: %s" % (self.sftp.normalize(".")+"/"+d,e))
505+
506+ def put(self, source_path, remote_filename = None):
507+ """transfers a single file to the remote side.
508+ In scp mode unavoidable quoting issues will make this fail if the remote directory or file name
509+ contain single quotes."""
510+ if not remote_filename:
511+ remote_filename = source_path.get_filename()
512+ if (globals.use_scp):
513+ f=file(source_path.name,'rb')
514+ try:
515+ chan=self.client.get_transport().open_session()
516+ chan.settimeout(globals.timeout)
517+ chan.exec_command("scp -t '%s'" % self.remote_dir) # scp in sink mode uses the arg as base directory
518+ except Exception, e:
519+ raise BackendException("scp execution failed: %s" % e)
520+ # scp protocol: one 0x0 after startup, one after the Create meta, one after saving
521+ # if there's a problem: 0x1 or 0x02 and some error text
522+ response=chan.recv(1)
523+ if (response!="\0"):
524+ raise BackendException("scp remote error: %s" % chan.recv(-1))
525+ fstat=os.stat(source_path.name)
526+ chan.send('C%s %d %s\n' %(oct(fstat.st_mode)[-4:], fstat.st_size, remote_filename))
527+ response=chan.recv(1)
528+ if (response!="\0"):
529+ raise BackendException("scp remote error: %s" % chan.recv(-1))
530+ chan.sendall(f.read()+'\0')
531+ f.close()
532+ response=chan.recv(1)
533+ if (response!="\0"):
534+ raise BackendException("scp remote error: %s" % chan.recv(-1))
535+ chan.close()
536+ else:
537+ try:
538+ self.sftp.put(source_path.name,remote_filename)
539+ except Exception, e:
540+ raise BackendException("sftp put of %s (as %s) failed: %s" % (source_path.name,remote_filename,e))
541+
542+
543+ def get(self, remote_filename, local_path):
544+ """retrieves a single file from the remote side.
545+ In scp mode unavoidable quoting issues will make this fail if the remote directory or file names
546+ contain single quotes."""
547+ if (globals.use_scp):
548+ try:
549+ chan=self.client.get_transport().open_session()
550+ chan.settimeout(globals.timeout)
551+ chan.exec_command("scp -f '%s/%s'" % (self.remote_dir,remote_filename))
552+ except Exception, e:
553+ raise BackendException("scp execution failed: %s" % e)
554+
555+ chan.send('\0') # overall ready indicator
556+ msg=chan.recv(-1)
557+ m=re.match(r"C([0-7]{4})\s+(\d+)\s+(\S.*)$",msg)
558+ if (m==None or m.group(3)!=remote_filename):
559+ raise BackendException("scp get %s failed: incorrect response '%s'" % (remote_filename,msg))
560+ chan.recv(1) # dispose of the newline trailing the C message
561+
562+ size=int(m.group(2))
563+ togo=size
564+ f=file(local_path.name,'wb')
565+ chan.send('\0') # ready for data
566+ try:
567+ while togo>0:
568+ if togo>read_blocksize:
569+ blocksize = read_blocksize
570+ else:
571+ blocksize = togo
572+ buff=chan.recv(blocksize)
573+ f.write(buff)
574+ togo-=len(buff)
575+ except Exception, e:
576+ raise BackendException("scp get %s failed: %s" % (remote_filename,e))
577+
578+ msg=chan.recv(1) # check the final status
579+ if msg!='\0':
580+ raise BackendException("scp get %s failed: %s" % (remote_filename,chan.recv(-1)))
581+ f.close()
582+ chan.send('\0') # send final done indicator
583+ chan.close()
584+ else:
585+ try:
586+ self.sftp.get(remote_filename,local_path.name)
587+ except Exception, e:
588+ raise BackendException("sftp get of %s (to %s) failed: %s" % (remote_filename,local_path.name,e))
589+ local_path.setdata()
590+
591+ def list(self):
592+ """lists the contents of the one-and-only duplicity dir on the remote side.
593+ In scp mode unavoidable quoting issues will make this fail if the directory name
594+ contains single quotes."""
595+ if (globals.use_scp):
596+ output=self.runremote("ls -1 '%s'" % self.remote_dir,False,"scp dir listing ")
597+ return output.splitlines()
598+ else:
599+ try:
600+ return self.sftp.listdir()
601+ except Exception, e:
602+ raise BackendException("sftp listing of %s failed: %s" % (self.sftp.getcwd(),e))
603+
604+ def delete(self, filename_list):
605+ """deletes all files in the list on the remote side. In scp mode unavoidable quoting issues
606+ will cause failures if filenames containing single quotes are encountered."""
607+ for fn in filename_list:
608+ if (globals.use_scp):
609+ self.runremote("rm '%s/%s'" % (self.remote_dir,fn),False,"scp rm ")
610+ else:
611+ try:
612+ self.sftp.remove(fn)
613+ except Exception, e:
614+ raise BackendException("sftp rm %s failed: %s" % (fn,e))
615+
616+ def runremote(self,cmd,ignoreexitcode=False,errorprefix=""):
617+ """small convenience function that opens a shell channel, runs remote command and returns
618+ stdout of command. throws an exception if exit code!=0 and not ignored"""
619+ try:
620+ chan=self.client.get_transport().open_session()
621+ chan.settimeout(globals.timeout)
622+ chan.exec_command(cmd)
623+ except Exception, e:
624+ raise BackendException("%sexecution failed: %s" % (errorprefix,e))
625+ output=chan.recv(-1)
626+ res=chan.recv_exit_status()
627+ if (res!=0 and not ignoreexitcode):
628+ raise BackendException("%sfailed(%d): %s" % (errorprefix,res,chan.recv_stderr(4096)))
629+ return output
630+
631+ def gethostconfig(self, file, host):
632+ file = os.path.expanduser(file)
633+ if not os.path.isfile(file):
634+ return {}
635+
636+ sshconfig = paramiko.SSHConfig()
637+ try:
638+ sshconfig.parse(open(file))
639+ except Exception, e:
640+ raise BackendException("could not load '%s', maybe corrupt?" % (file))
641+
642+ return sshconfig.lookup(host)
643+
644+class AgreedAddPolicy (paramiko.AutoAddPolicy):
645+ """
646+ Policy for showing a yes/no prompt and adding the hostname and new
647+ host key to the known host file accordingly.
648+
649+ This class simply extends the AutoAddPolicy class with a yes/no prompt.
650+ """
651+ def missing_host_key(self, client, hostname, key):
652+ fp = hexlify(key.get_fingerprint())
653+ fingerprint = ':'.join(a+b for a,b in zip(fp[::2], fp[1::2]))
654+ question = """The authenticity of host '%s' can't be established.
655+%s key fingerprint is %s.
656+Are you sure you want to continue connecting (yes/no)? """ % (hostname, key.get_name().upper(), fingerprint)
657+ while True:
658+ sys.stdout.write(question)
659+ choice = raw_input().lower()
660+ if choice in ['yes','y']:
661+ super(AgreedAddPolicy, self).missing_host_key(client, hostname, key)
662+ return
663+ elif choice in ['no','n']:
664+ raise AuthenticityException( hostname )
665+ else:
666+ question = "Please type 'yes' or 'no': "
667+
668+class AuthenticityException (paramiko.SSHException):
669+ def __init__(self, hostname):
670+ paramiko.SSHException.__init__(self, 'Host key verification for server %s failed.' % hostname)
671+
672+
673+duplicity.backend.register_backend("sftp", SSHParamikoBackend)
674+duplicity.backend.register_backend("scp", SSHParamikoBackend)
675+duplicity.backend.register_backend("ssh", SSHParamikoBackend)
676
677=== added file 'duplicity/backends/ssh_pexpect.py'
678--- duplicity/backends/ssh_pexpect.py 1970-01-01 00:00:00 +0000
679+++ duplicity/backends/ssh_pexpect.py 2012-03-13 20:58:22 +0000
680@@ -0,0 +1,317 @@
681+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
682+#
683+# Copyright 2002 Ben Escoto <ben@emerose.org>
684+# Copyright 2007 Kenneth Loafman <kenneth@loafman.com>
685+#
686+# This file is part of duplicity.
687+#
688+# Duplicity is free software; you can redistribute it and/or modify it
689+# under the terms of the GNU General Public License as published by the
690+# Free Software Foundation; either version 2 of the License, or (at your
691+# option) any later version.
692+#
693+# Duplicity is distributed in the hope that it will be useful, but
694+# WITHOUT ANY WARRANTY; without even the implied warranty of
695+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
696+# General Public License for more details.
697+#
698+# You should have received a copy of the GNU General Public License
699+# along with duplicity; if not, write to the Free Software Foundation,
700+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
701+
702+# The following can be redefined to use different shell commands from
703+# ssh or scp or to add more arguments. However, the replacements must
704+# have the same syntax. Also these strings will be executed by the
705+# shell, so shouldn't have strange characters in them.
706+
707+import re
708+import string
709+import time
710+import os
711+
712+import duplicity.backend
713+from duplicity import globals
714+from duplicity import log
715+from duplicity import pexpect
716+from duplicity.errors import * #@UnusedWildImport
717+
718+class SSHPExpectBackend(duplicity.backend.Backend):
719+ """This backend copies files using scp. List not supported"""
720+ def __init__(self, parsed_url):
721+ """scpBackend initializer"""
722+ duplicity.backend.Backend.__init__(self, parsed_url)
723+
724+ self.scp_command = "scp"
725+ if globals.scp_command: self.scp_command = globals.scp_command
726+
727+ self.sftp_command = "sftp"
728+ if globals.sftp_command: self.sftp_command = globals.sftp_command
729+
730+ # host string of form [user@]hostname
731+ if parsed_url.username:
732+ self.host_string = parsed_url.username + "@" + parsed_url.hostname
733+ else:
734+ self.host_string = parsed_url.hostname
735+ # make sure remote_dir is always valid
736+ if parsed_url.path:
737+ # remove leading '/'
738+ self.remote_dir = re.sub(r'^/', r'', parsed_url.path, 1)
739+ else:
740+ self.remote_dir = '.'
741+ self.remote_prefix = self.remote_dir + '/'
742+ # maybe use different ssh port
743+ if parsed_url.port:
744+ globals.ssh_options = globals.ssh_options + " -oPort=%s" % parsed_url.port
745+ # set some defaults if user has not specified already.
746+ if "ServerAliveInterval" not in globals.ssh_options:
747+ globals.ssh_options += " -oServerAliveInterval=%d" % ((int)(globals.timeout / 2))
748+ if "ServerAliveCountMax" not in globals.ssh_options:
749+ globals.ssh_options += " -oServerAliveCountMax=2"
750+ # set up password
751+ if globals.ssh_askpass:
752+ self.password = self.get_password()
753+ else:
754+ if parsed_url.password:
755+ self.password = parsed_url.password
756+ globals.ssh_askpass = True
757+ else:
758+ self.password = ''
759+
760+ def run_scp_command(self, commandline):
761+ """ Run an scp command, responding to password prompts """
762+ for n in range(1, globals.num_retries+1):
763+ if n > 1:
764+ # sleep before retry
765+ time.sleep(30)
766+ log.Info("Running '%s' (attempt #%d)" % (commandline, n))
767+ child = pexpect.spawn(commandline, timeout = None)
768+ if globals.ssh_askpass:
769+ state = "authorizing"
770+ else:
771+ state = "copying"
772+ while 1:
773+ if state == "authorizing":
774+ match = child.expect([pexpect.EOF,
775+ "(?i)timeout, server not responding",
776+ "(?i)pass(word|phrase .*):",
777+ "(?i)permission denied",
778+ "authenticity"])
779+ log.Debug("State = %s, Before = '%s'" % (state, child.before.strip()))
780+ if match == 0:
781+ log.Warn("Failed to authenticate")
782+ break
783+ elif match == 1:
784+ log.Warn("Timeout waiting to authenticate")
785+ break
786+ elif match == 2:
787+ child.sendline(self.password)
788+ state = "copying"
789+ elif match == 3:
790+ log.Warn("Invalid SSH password")
791+ break
792+ elif match == 4:
793+ log.Warn("Remote host authentication failed (missing known_hosts entry?)")
794+ break
795+ elif state == "copying":
796+ match = child.expect([pexpect.EOF,
797+ "(?i)timeout, server not responding",
798+ "stalled",
799+ "authenticity",
800+ "ETA"])
801+ log.Debug("State = %s, Before = '%s'" % (state, child.before.strip()))
802+ if match == 0:
803+ break
804+ elif match == 1:
805+ log.Warn("Timeout waiting for response")
806+ break
807+ elif match == 2:
808+ state = "stalled"
809+ elif match == 3:
810+ log.Warn("Remote host authentication failed (missing known_hosts entry?)")
811+ break
812+ elif state == "stalled":
813+ match = child.expect([pexpect.EOF,
814+ "(?i)timeout, server not responding",
815+ "ETA"])
816+ log.Debug("State = %s, Before = '%s'" % (state, child.before.strip()))
817+ if match == 0:
818+ break
819+ elif match == 1:
820+ log.Warn("Stalled for too long, aborted copy")
821+ break
822+ elif match == 2:
823+ state = "copying"
824+ child.close(force = True)
825+ if child.exitstatus == 0:
826+ return
827+ log.Warn("Running '%s' failed (attempt #%d)" % (commandline, n))
828+ log.Warn("Giving up trying to execute '%s' after %d attempts" % (commandline, globals.num_retries))
829+ raise BackendException("Error running '%s'" % commandline)
830+
831+ def run_sftp_command(self, commandline, commands):
832+ """ Run an sftp command, responding to password prompts, passing commands from list """
833+ maxread = 2000 # expected read buffer size
834+ responses = [pexpect.EOF,
835+ "(?i)timeout, server not responding",
836+ "sftp>",
837+ "(?i)pass(word|phrase .*):",
838+ "(?i)permission denied",
839+ "authenticity",
840+ "(?i)no such file or directory",
841+ "Couldn't delete file: No such file or directory",
842+ "Couldn't delete file",
843+ "open(.*): Failure"]
844+ max_response_len = max([len(p) for p in responses[1:]])
845+ for n in range(1, globals.num_retries+1):
846+ if n > 1:
847+ # sleep before retry
848+ time.sleep(30)
849+ log.Info("Running '%s' (attempt #%d)" % (commandline, n))
850+ child = pexpect.spawn(commandline, timeout = None, maxread=maxread)
851+ cmdloc = 0
852+ while 1:
853+ match = child.expect(responses,
854+ searchwindowsize=maxread+max_response_len)
855+ log.Debug("State = sftp, Before = '%s'" % (child.before.strip()))
856+ if match == 0:
857+ break
858+ elif match == 1:
859+ log.Info("Timeout waiting for response")
860+ break
861+ if match == 2:
862+ if cmdloc < len(commands):
863+ command = commands[cmdloc]
864+ log.Info("sftp command: '%s'" % (command,))
865+ child.sendline(command)
866+ cmdloc += 1
867+ else:
868+ command = 'quit'
869+ child.sendline(command)
870+ res = child.before
871+ elif match == 3:
872+ child.sendline(self.password)
873+ elif match == 4:
874+ if not child.before.strip().startswith("mkdir"):
875+ log.Warn("Invalid SSH password")
876+ break
877+ elif match == 5:
878+ log.Warn("Host key authenticity could not be verified (missing known_hosts entry?)")
879+ break
880+ elif match == 6:
881+ if not child.before.strip().startswith("rm"):
882+ log.Warn("Remote file or directory does not exist in command='%s'" % (commandline,))
883+ break
884+ elif match == 7:
885+ if not child.before.strip().startswith("Removing"):
886+ log.Warn("Could not delete file in command='%s'" % (commandline,))
887+ break;
888+ elif match == 8:
889+ log.Warn("Could not delete file in command='%s'" % (commandline,))
890+ break
891+ elif match == 9:
892+ log.Warn("Could not open file in command='%s'" % (commandline,))
893+ break
894+ child.close(force = True)
895+ if child.exitstatus == 0:
896+ return res
897+ log.Warn("Running '%s' failed (attempt #%d)" % (commandline, n))
898+ log.Warn("Giving up trying to execute '%s' after %d attempts" % (commandline, globals.num_retries))
899+ raise BackendException("Error running '%s'" % commandline)
900+
901+ def put(self, source_path, remote_filename = None):
902+ if globals.use_scp:
903+ self.put_scp(source_path, remote_filename = remote_filename)
904+ else:
905+ self.put_sftp(source_path, remote_filename = remote_filename)
906+
907+ def put_sftp(self, source_path, remote_filename = None):
908+ """Use sftp to copy source_dir/filename to remote computer"""
909+ if not remote_filename:
910+ remote_filename = source_path.get_filename()
911+ commands = ["put \"%s\" \"%s.%s.part\"" %
912+ (source_path.name, self.remote_prefix, remote_filename),
913+ "rename \"%s.%s.part\" \"%s%s\"" %
914+ (self.remote_prefix, remote_filename,self.remote_prefix, remote_filename)]
915+ commandline = ("%s %s %s" % (self.sftp_command,
916+ globals.ssh_options,
917+ self.host_string))
918+ self.run_sftp_command(commandline, commands)
919+
920+ def put_scp(self, source_path, remote_filename = None):
921+ """Use scp to copy source_dir/filename to remote computer"""
922+ if not remote_filename:
923+ remote_filename = source_path.get_filename()
924+ commandline = "%s %s %s %s:%s%s" % \
925+ (self.scp_command, globals.ssh_options, source_path.name, self.host_string,
926+ self.remote_prefix, remote_filename)
927+ self.run_scp_command(commandline)
928+
929+ def get(self, remote_filename, local_path):
930+ if globals.use_scp:
931+ self.get_scp(remote_filename, local_path)
932+ else:
933+ self.get_sftp(remote_filename, local_path)
934+
935+ def get_sftp(self, remote_filename, local_path):
936+ """Use sftp to get a remote file"""
937+ commands = ["get \"%s%s\" \"%s\"" %
938+ (self.remote_prefix, remote_filename, local_path.name)]
939+ commandline = ("%s %s %s" % (self.sftp_command,
940+ globals.ssh_options,
941+ self.host_string))
942+ self.run_sftp_command(commandline, commands)
943+ local_path.setdata()
944+ if not local_path.exists():
945+ raise BackendException("File %s not found locally after get "
946+ "from backend" % local_path.name)
947+
948+ def get_scp(self, remote_filename, local_path):
949+ """Use scp to get a remote file"""
950+ commandline = "%s %s %s:%s%s %s" % \
951+ (self.scp_command, globals.ssh_options, self.host_string, self.remote_prefix,
952+ remote_filename, local_path.name)
953+ self.run_scp_command(commandline)
954+ local_path.setdata()
955+ if not local_path.exists():
956+ raise BackendException("File %s not found locally after get "
957+ "from backend" % local_path.name)
958+
959+ def list(self):
960+ """
961+ List files available for scp
962+
963+ Note that this command can get confused when dealing with
964+ files with newlines in them, as the embedded newlines cannot
965+ be distinguished from the file boundaries.
966+ """
967+ dirs = self.remote_dir.split(os.sep)
968+ if len(dirs) > 0:
969+ if not dirs[0] :
970+ dirs = dirs[1:]
971+ dirs[0]= '/' + dirs[0]
972+ mkdir_commands = [];
973+ for d in dirs:
974+ mkdir_commands += ["mkdir \"%s\"" % (d)] + ["cd \"%s\"" % (d)]
975+
976+ commands = mkdir_commands + ["ls -1"]
977+ commandline = ("%s %s %s" % (self.sftp_command,
978+ globals.ssh_options,
979+ self.host_string))
980+
981+ l = self.run_sftp_command(commandline, commands).split('\n')[1:]
982+
983+ return filter(lambda x: x, map(string.strip, l))
984+
985+ def delete(self, filename_list):
986+ """
987+ Runs sftp rm to delete files. Files must not require quoting.
988+ """
989+ commands = ["cd \"%s\"" % (self.remote_dir,)]
990+ for fn in filename_list:
991+ commands.append("rm \"%s\"" % fn)
992+ commandline = ("%s %s %s" % (self.sftp_command, globals.ssh_options, self.host_string))
993+ self.run_sftp_command(commandline, commands)
994+
995+duplicity.backend.register_backend("ssh", SSHPExpectBackend)
996+duplicity.backend.register_backend("scp", SSHPExpectBackend)
997+duplicity.backend.register_backend("sftp", SSHPExpectBackend)
998
999=== modified file 'duplicity/backends/sshbackend.py'
1000--- duplicity/backends/sshbackend.py 2012-03-12 18:29:42 +0000
1001+++ duplicity/backends/sshbackend.py 2012-03-13 20:58:22 +0000
1002@@ -1,11 +1,6 @@
1003 # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
1004 #
1005-# Copyright 2002 Ben Escoto <ben@emerose.org>
1006-# Copyright 2007 Kenneth Loafman <kenneth@loafman.com>
1007-# Copyright 2011 Alexander Zangerl <az@snafu.priv.at>
1008-# Copyright 2012 edso (ssh_config added)
1009-#
1010-# $Id: sshbackend.py,v 1.2 2011/12/31 04:44:12 az Exp $
1011+# Copyright 2012 edso
1012 #
1013 # This file is part of duplicity.
1014 #
1015@@ -23,339 +18,17 @@
1016 # along with duplicity; if not, write to the Free Software Foundation,
1017 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1018
1019-import re
1020-import string
1021-import os
1022-import errno
1023-import sys
1024-import getpass
1025-from binascii import hexlify
1026-
1027-# debian squeeze's paramiko is a bit old, so we silence randompool depreciation warning
1028-# note also: passphrased private keys work with squeeze's paramiko only if done with DES, not AES
1029-import warnings
1030-warnings.simplefilter("ignore")
1031-import paramiko
1032-warnings.resetwarnings()
1033-
1034-import duplicity.backend
1035-from duplicity import globals
1036-from duplicity import log
1037-from duplicity.errors import *
1038-
1039-read_blocksize=65635 # for doing scp retrievals, where we need to read ourselves
1040-
1041-class SftpBackend(duplicity.backend.Backend):
1042- """This backend accesses files using the sftp protocol, or scp when the --use-scp option is given.
1043- It does not need any local client programs, but an ssh server and the sftp program must be installed on the remote
1044- side (or with --use-scp, the programs scp, ls, mkdir, rm and a POSIX-compliant shell).
1045-
1046- Authentication keys are requested from an ssh agent if present, then ~/.ssh/id_rsa/dsa are tried.
1047- If -oIdentityFile=path is present in --ssh-options, then that file is also tried.
1048- The passphrase for any of these keys is taken from the URI or FTP_PASSWORD.
1049- If none of the above are available, password authentication is attempted (using the URI or FTP_PASSWORD).
1050-
1051- Missing directories on the remote side will be created.
1052-
1053- If --use-scp is active then all operations on the remote side require passing arguments through a shell,
1054- which introduces unavoidable quoting issues: directory and file names that contain single quotes will not work.
1055- This problem does not exist with sftp.
1056- """
1057- def __init__(self, parsed_url):
1058- duplicity.backend.Backend.__init__(self, parsed_url)
1059-
1060- if parsed_url.path:
1061- # remove first leading '/'
1062- self.remote_dir = re.sub(r'^/', r'', parsed_url.path, 1)
1063- else:
1064- self.remote_dir = '.'
1065-
1066- self.client = paramiko.SSHClient()
1067- self.client.set_missing_host_key_policy(AgreedAddPolicy())
1068- # load known_hosts files
1069- # paramiko is very picky wrt format and bails out on any problem...
1070- try:
1071- if os.path.isfile("/etc/ssh/ssh_known_hosts"):
1072- self.client.load_system_host_keys("/etc/ssh/ssh_known_hosts")
1073- except Exception, e:
1074- raise BackendException("could not load /etc/ssh/ssh_known_hosts, maybe corrupt?")
1075- try:
1076- # use load_host_keys() to signal it's writable to paramiko
1077- # load if file exists or add filename to create it if needed
1078- file = os.path.expanduser('~/.ssh/known_hosts')
1079- if os.path.isfile(file):
1080- self.client.load_host_keys(file)
1081- else:
1082- self.client._host_keys_filename = file
1083- except Exception, e:
1084- raise BackendException("could not load ~/.ssh/known_hosts, maybe corrupt?")
1085-
1086- """ the next block reorganizes all host parameters into a
1087- dictionary like SSHConfig does. this dictionary 'self.config'
1088- becomes the authorative source for these values from here on.
1089- rationale is that it is easiest to deal wrt overwriting multiple
1090- values from ssh_config file. (ede 03/2012)
1091- """
1092- self.config={'hostname':parsed_url.hostname}
1093- # get system host config entries
1094- self.config.update(self.gethostconfig('/etc/ssh/ssh_config',parsed_url.hostname))
1095- # update with user's config file
1096- self.config.update(self.gethostconfig('~/.ssh/config',parsed_url.hostname))
1097- # update with url values
1098- ## username from url
1099- if parsed_url.username:
1100- self.config.update({'user':parsed_url.username})
1101- ## username from input
1102- if not 'user' in self.config:
1103- self.config.update({'user':getpass.getuser()})
1104- ## port from url
1105- if parsed_url.port:
1106- self.config.update({'port':parsed_url.port})
1107- ## ensure there is deafult 22 or an int value
1108- if 'port' in self.config:
1109- self.config.update({'port':int(self.config['port'])})
1110- else:
1111- self.config.update({'port':22})
1112- ## alternative ssh private key, identity file
1113- m=re.search("-oidentityfile=(\S+)",globals.ssh_options,re.I)
1114- if (m!=None):
1115- keyfilename=m.group(1)
1116- self.config['identityfile'] = keyfilename
1117- ## ensure ~ is expanded and identity exists in dictionary
1118- if 'identityfile' in self.config:
1119- self.config['identityfile'] = os.path.expanduser(
1120- self.config['identityfile'])
1121- else:
1122- self.config['identityfile'] = None
1123-
1124- # get password, enable prompt if askpass is set
1125- self.use_getpass = globals.ssh_askpass
1126- ## set url values for beautiful login prompt
1127- parsed_url.username = self.config['user']
1128- parsed_url.hostname = self.config['hostname']
1129- password = self.get_password()
1130-
1131- try:
1132- self.client.connect(hostname=self.config['hostname'],
1133- port=self.config['port'],
1134- username=self.config['user'],
1135- password=password,
1136- allow_agent=True,
1137- look_for_keys=True,
1138- key_filename=self.config['identityfile'])
1139- except Exception, e:
1140- raise BackendException("ssh connection to %s@%s:%d failed: %s" % (
1141- self.config['user'],
1142- self.config['hostname'],
1143- self.config['port'],e))
1144- self.client.get_transport().set_keepalive((int)(globals.timeout / 2))
1145-
1146- # scp or sftp?
1147- if (globals.use_scp):
1148- # sanity-check the directory name
1149- if (re.search("'",self.remote_dir)):
1150- raise BackendException("cannot handle directory names with single quotes with --use-scp!")
1151-
1152- # make directory if needed
1153- self.runremote("test -d '%s' || mkdir -p '%s'" % (self.remote_dir,self.remote_dir),False,"scp mkdir ")
1154- else:
1155- try:
1156- self.sftp=self.client.open_sftp()
1157- except Exception, e:
1158- raise BackendException("sftp negotiation failed: %s" % e)
1159-
1160-
1161- # move to the appropriate directory, possibly after creating it and its parents
1162- dirs = self.remote_dir.split(os.sep)
1163- if len(dirs) > 0:
1164- if not dirs[0]:
1165- dirs = dirs[1:]
1166- dirs[0]= '/' + dirs[0]
1167- for d in dirs:
1168- if (d == ''):
1169- continue
1170- try:
1171- attrs=self.sftp.stat(d)
1172- except IOError, e:
1173- if e.errno == errno.ENOENT:
1174- try:
1175- self.sftp.mkdir(d)
1176- except Exception, e:
1177- raise BackendException("sftp mkdir %s failed: %s" % (self.sftp.normalize(".")+"/"+d,e))
1178- else:
1179- raise BackendException("sftp stat %s failed: %s" % (self.sftp.normalize(".")+"/"+d,e))
1180- try:
1181- self.sftp.chdir(d)
1182- except Exception, e:
1183- raise BackendException("sftp chdir to %s failed: %s" % (self.sftp.normalize(".")+"/"+d,e))
1184-
1185- def put(self, source_path, remote_filename = None):
1186- """transfers a single file to the remote side.
1187- In scp mode unavoidable quoting issues will make this fail if the remote directory or file name
1188- contain single quotes."""
1189- if not remote_filename:
1190- remote_filename = source_path.get_filename()
1191- if (globals.use_scp):
1192- f=file(source_path.name,'rb')
1193- try:
1194- chan=self.client.get_transport().open_session()
1195- chan.settimeout(globals.timeout)
1196- chan.exec_command("scp -t '%s'" % self.remote_dir) # scp in sink mode uses the arg as base directory
1197- except Exception, e:
1198- raise BackendException("scp execution failed: %s" % e)
1199- # scp protocol: one 0x0 after startup, one after the Create meta, one after saving
1200- # if there's a problem: 0x1 or 0x02 and some error text
1201- response=chan.recv(1)
1202- if (response!="\0"):
1203- raise BackendException("scp remote error: %s" % chan.recv(-1))
1204- fstat=os.stat(source_path.name)
1205- chan.send('C%s %d %s\n' %(oct(fstat.st_mode)[-4:], fstat.st_size, remote_filename))
1206- response=chan.recv(1)
1207- if (response!="\0"):
1208- raise BackendException("scp remote error: %s" % chan.recv(-1))
1209- chan.sendall(f.read()+'\0')
1210- f.close()
1211- response=chan.recv(1)
1212- if (response!="\0"):
1213- raise BackendException("scp remote error: %s" % chan.recv(-1))
1214- chan.close()
1215- else:
1216- try:
1217- self.sftp.put(source_path.name,remote_filename)
1218- except Exception, e:
1219- raise BackendException("sftp put of %s (as %s) failed: %s" % (source_path.name,remote_filename,e))
1220-
1221-
1222- def get(self, remote_filename, local_path):
1223- """retrieves a single file from the remote side.
1224- In scp mode unavoidable quoting issues will make this fail if the remote directory or file names
1225- contain single quotes."""
1226- if (globals.use_scp):
1227- try:
1228- chan=self.client.get_transport().open_session()
1229- chan.settimeout(globals.timeout)
1230- chan.exec_command("scp -f '%s/%s'" % (self.remote_dir,remote_filename))
1231- except Exception, e:
1232- raise BackendException("scp execution failed: %s" % e)
1233-
1234- chan.send('\0') # overall ready indicator
1235- msg=chan.recv(-1)
1236- m=re.match(r"C([0-7]{4})\s+(\d+)\s+(\S.*)$",msg)
1237- if (m==None or m.group(3)!=remote_filename):
1238- raise BackendException("scp get %s failed: incorrect response '%s'" % (remote_filename,msg))
1239- chan.recv(1) # dispose of the newline trailing the C message
1240-
1241- size=int(m.group(2))
1242- togo=size
1243- f=file(local_path.name,'wb')
1244- chan.send('\0') # ready for data
1245- try:
1246- while togo>0:
1247- if togo>read_blocksize:
1248- blocksize = read_blocksize
1249- else:
1250- blocksize = togo
1251- buff=chan.recv(blocksize)
1252- f.write(buff)
1253- togo-=len(buff)
1254- except Exception, e:
1255- raise BackendException("scp get %s failed: %s" % (remote_filename,e))
1256-
1257- msg=chan.recv(1) # check the final status
1258- if msg!='\0':
1259- raise BackendException("scp get %s failed: %s" % (remote_filename,chan.recv(-1)))
1260- f.close()
1261- chan.send('\0') # send final done indicator
1262- chan.close()
1263- else:
1264- try:
1265- self.sftp.get(remote_filename,local_path.name)
1266- except Exception, e:
1267- raise BackendException("sftp get of %s (to %s) failed: %s" % (remote_filename,local_path.name,e))
1268- local_path.setdata()
1269-
1270- def list(self):
1271- """lists the contents of the one-and-only duplicity dir on the remote side.
1272- In scp mode unavoidable quoting issues will make this fail if the directory name
1273- contains single quotes."""
1274- if (globals.use_scp):
1275- output=self.runremote("ls -1 '%s'" % self.remote_dir,False,"scp dir listing ")
1276- return output.splitlines()
1277- else:
1278- try:
1279- return self.sftp.listdir()
1280- except Exception, e:
1281- raise BackendException("sftp listing of %s failed: %s" % (self.sftp.getcwd(),e))
1282-
1283- def delete(self, filename_list):
1284- """deletes all files in the list on the remote side. In scp mode unavoidable quoting issues
1285- will cause failures if filenames containing single quotes are encountered."""
1286- for fn in filename_list:
1287- if (globals.use_scp):
1288- self.runremote("rm '%s/%s'" % (self.remote_dir,fn),False,"scp rm ")
1289- else:
1290- try:
1291- self.sftp.remove(fn)
1292- except Exception, e:
1293- raise BackendException("sftp rm %s failed: %s" % (fn,e))
1294-
1295- def runremote(self,cmd,ignoreexitcode=False,errorprefix=""):
1296- """small convenience function that opens a shell channel, runs remote command and returns
1297- stdout of command. throws an exception if exit code!=0 and not ignored"""
1298- try:
1299- chan=self.client.get_transport().open_session()
1300- chan.settimeout(globals.timeout)
1301- chan.exec_command(cmd)
1302- except Exception, e:
1303- raise BackendException("%sexecution failed: %s" % (errorprefix,e))
1304- output=chan.recv(-1)
1305- res=chan.recv_exit_status()
1306- if (res!=0 and not ignoreexitcode):
1307- raise BackendException("%sfailed(%d): %s" % (errorprefix,res,chan.recv_stderr(4096)))
1308- return output
1309-
1310- def gethostconfig(self, file, host):
1311- file = os.path.expanduser(file)
1312- if not os.path.isfile(file):
1313- return {}
1314-
1315- sshconfig = paramiko.SSHConfig()
1316- try:
1317- sshconfig.parse(open(file))
1318- except Exception, e:
1319- raise BackendException("could not load '%s', maybe corrupt?" % (file))
1320-
1321- return sshconfig.lookup(host)
1322-
1323-class AgreedAddPolicy (paramiko.AutoAddPolicy):
1324- """
1325- Policy for showing a yes/no prompt and adding the hostname and new
1326- host key to the known host file accordingly.
1327-
1328- This class simply extends the AutoAddPolicy class with a yes/no prompt.
1329- """
1330- def missing_host_key(self, client, hostname, key):
1331- fp = hexlify(key.get_fingerprint())
1332- fingerprint = ':'.join(a+b for a,b in zip(fp[::2], fp[1::2]))
1333- question = """The authenticity of host '%s' can't be established.
1334-%s key fingerprint is %s.
1335-Are you sure you want to continue connecting (yes/no)? """ % (hostname, key.get_name().upper(), fingerprint)
1336- while True:
1337- sys.stdout.write(question)
1338- choice = raw_input().lower()
1339- if choice in ['yes','y']:
1340- super(AgreedAddPolicy, self).missing_host_key(client, hostname, key)
1341- return
1342- elif choice in ['no','n']:
1343- raise AuthenticityException( hostname )
1344- else:
1345- question = "Please type 'yes' or 'no': "
1346-
1347-class AuthenticityException (paramiko.SSHException):
1348- def __init__(self, hostname):
1349- paramiko.SSHException.__init__(self, 'Host key verification for server %s failed.' % hostname)
1350-
1351-
1352-duplicity.backend.register_backend("sftp", SftpBackend)
1353-duplicity.backend.register_backend("scp", SftpBackend)
1354-duplicity.backend.register_backend("ssh", SftpBackend)
1355+from duplicity import globals, log
1356+
1357+def warn_option(option,optionvar):
1358+ if optionvar:
1359+ log.Warn("Warning: Option %s is supported by ssh pexpect backend only and will be ignored. " % option )
1360+
1361+if globals.ssh_backend and \
1362+ globals.ssh_backend.lower().strip() == 'pexpect':
1363+ import ssh_pexpect
1364+else:
1365+ warn_option("--scp-command",globals.scp_command)
1366+ warn_option("--sftp-command",globals.sftp_command)
1367+ import ssh_paramiko
1368+
1369
1370=== modified file 'duplicity/commandline.py'
1371--- duplicity/commandline.py 2012-02-05 19:07:35 +0000
1372+++ duplicity/commandline.py 2012-03-13 20:58:22 +0000
1373@@ -68,10 +68,6 @@
1374 "and will be removed in a future release.\n"
1375 "Use of default filenames is strongly suggested.") % opt
1376
1377-def scp_deprecation(o,s,v,p):
1378- print >>sys.stderr, "Warning: Option %s is deprecated and ignored. Use --ssh-options instead." % o
1379-
1380-
1381 def expand_fn(filename):
1382 return os.path.expanduser(os.path.expandvars(filename))
1383
1384@@ -473,15 +469,11 @@
1385 if sys.version_info[:2] >= (2,6):
1386 parser.add_option("--s3-use-multiprocessing", action="store_true")
1387
1388- # scp command to use
1389- # TRANSL: noun
1390- parser.add_option("--scp-command", nargs=1, type="string",
1391- action="callback", callback=scp_deprecation)
1392+ # scp command to use (ssh pexpect backend)
1393+ parser.add_option("--scp-command", metavar=_("command"))
1394
1395- # sftp command to use
1396- # TRANSL: noun
1397- parser.add_option("--sftp-command", nargs=1, type="string",
1398- action="callback", callback=scp_deprecation)
1399+ # sftp command to use (ssh pexpect backend)
1400+ parser.add_option("--sftp-command", metavar=_("command"))
1401
1402 # If set, use short (< 30 char) filenames for all the remote files.
1403 parser.add_option("--short-filenames", action="callback",
1404@@ -498,6 +490,9 @@
1405 # default to batch mode using public-key encryption
1406 parser.add_option("--ssh-askpass", action="store_true")
1407
1408+ # allow the user to switch ssh backend
1409+ parser.add_option("--ssh-backend", metavar=_("paramiko|pexpect"))
1410+
1411 # user added ssh options
1412 parser.add_option("--ssh-options", action="extend", metavar=_("options"))
1413
1414
1415=== modified file 'duplicity/globals.py'
1416--- duplicity/globals.py 2012-02-05 18:13:40 +0000
1417+++ duplicity/globals.py 2012-03-13 20:58:22 +0000
1418@@ -198,13 +198,16 @@
1419 # Wheter to specify --use-agent in GnuPG options
1420 use_agent = False
1421
1422-# ssh commands to use
1423-scp_command = "scp"
1424-sftp_command = "sftp"
1425+# ssh commands to use, used by ssh_pexpect (defaults to sftp, scp)
1426+scp_command = None
1427+sftp_command = None
1428
1429 # default to batch mode using public-key encryption
1430 ssh_askpass = False
1431
1432+# default ssh backend is paramiko
1433+ssh_backend = "paramiko"
1434+
1435 # user added ssh options
1436 ssh_options = ""
1437
1438
1439=== added file 'duplicity/pexpect.py'
1440--- duplicity/pexpect.py 1970-01-01 00:00:00 +0000
1441+++ duplicity/pexpect.py 2012-03-13 20:58:22 +0000
1442@@ -0,0 +1,1845 @@
1443+"""Pexpect is a Python module for spawning child applications and controlling
1444+them automatically. Pexpect can be used for automating interactive applications
1445+such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup
1446+scripts for duplicating software package installations on different servers. It
1447+can be used for automated software testing. Pexpect is in the spirit of Don
1448+Libes' Expect, but Pexpect is pure Python. Other Expect-like modules for Python
1449+require TCL and Expect or require C extensions to be compiled. Pexpect does not
1450+use C, Expect, or TCL extensions. It should work on any platform that supports
1451+the standard Python pty module. The Pexpect interface focuses on ease of use so
1452+that simple tasks are easy.
1453+
1454+There are two main interfaces to Pexpect -- the function, run() and the class,
1455+spawn. You can call the run() function to execute a command and return the
1456+output. This is a handy replacement for os.system().
1457+
1458+For example::
1459+
1460+ pexpect.run('ls -la')
1461+
1462+The more powerful interface is the spawn class. You can use this to spawn an
1463+external child command and then interact with the child by sending lines and
1464+expecting responses.
1465+
1466+For example::
1467+
1468+ child = pexpect.spawn('scp foo myname@host.example.com:.')
1469+ child.expect ('Password:')
1470+ child.sendline (mypassword)
1471+
1472+This works even for commands that ask for passwords or other input outside of
1473+the normal stdio streams.
1474+
1475+Credits: Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett,
1476+Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids
1477+vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin,
1478+Geoffrey Marshall, Francisco Lourenco, Glen Mabey, Karthik Gurusamy, Fernando
1479+Perez, Corey Minyard, Jon Cohen, Guillaume Chazarain, Andrew Ryan, Nick
1480+Craig-Wood, Andrew Stone, Jorgen Grahn (Let me know if I forgot anyone.)
1481+
1482+Free, open source, and all that good stuff.
1483+
1484+Permission is hereby granted, free of charge, to any person obtaining a copy of
1485+this software and associated documentation files (the "Software"), to deal in
1486+the Software without restriction, including without limitation the rights to
1487+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
1488+of the Software, and to permit persons to whom the Software is furnished to do
1489+so, subject to the following conditions:
1490+
1491+The above copyright notice and this permission notice shall be included in all
1492+copies or substantial portions of the Software.
1493+
1494+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1495+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1496+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1497+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1498+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1499+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1500+SOFTWARE.
1501+
1502+Pexpect Copyright (c) 2008 Noah Spurrier
1503+http://pexpect.sourceforge.net/
1504+
1505+$Id: pexpect.py,v 1.1 2009/01/06 22:11:37 loafman Exp $
1506+"""
1507+
1508+try:
1509+ import os, sys, time
1510+ import select
1511+ import string
1512+ import re
1513+ import struct
1514+ import resource
1515+ import types
1516+ import pty
1517+ import tty
1518+ import termios
1519+ import fcntl
1520+ import errno
1521+ import traceback
1522+ import signal
1523+except ImportError, e:
1524+ raise ImportError (str(e) + """
1525+
1526+A critical module was not found. Probably this operating system does not
1527+support it. Pexpect is intended for UNIX-like operating systems.""")
1528+
1529+__version__ = '2.3'
1530+__revision__ = '$Revision: 1.1 $'
1531+__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'run', 'which',
1532+ 'split_command_line', '__version__', '__revision__']
1533+
1534+# Exception classes used by this module.
1535+class ExceptionPexpect(Exception):
1536+
1537+ """Base class for all exceptions raised by this module.
1538+ """
1539+
1540+ def __init__(self, value):
1541+
1542+ self.value = value
1543+
1544+ def __str__(self):
1545+
1546+ return str(self.value)
1547+
1548+ def get_trace(self):
1549+
1550+ """This returns an abbreviated stack trace with lines that only concern
1551+ the caller. In other words, the stack trace inside the Pexpect module
1552+ is not included. """
1553+
1554+ tblist = traceback.extract_tb(sys.exc_info()[2])
1555+ #tblist = filter(self.__filter_not_pexpect, tblist)
1556+ tblist = [item for item in tblist if self.__filter_not_pexpect(item)]
1557+ tblist = traceback.format_list(tblist)
1558+ return ''.join(tblist)
1559+
1560+ def __filter_not_pexpect(self, trace_list_item):
1561+
1562+ """This returns True if list item 0 the string 'pexpect.py' in it. """
1563+
1564+ if trace_list_item[0].find('pexpect.py') == -1:
1565+ return True
1566+ else:
1567+ return False
1568+
1569+class EOF(ExceptionPexpect):
1570+
1571+ """Raised when EOF is read from a child. This usually means the child has exited."""
1572+
1573+class TIMEOUT(ExceptionPexpect):
1574+
1575+ """Raised when a read time exceeds the timeout. """
1576+
1577+##class TIMEOUT_PATTERN(TIMEOUT):
1578+## """Raised when the pattern match time exceeds the timeout.
1579+## This is different than a read TIMEOUT because the child process may
1580+## give output, thus never give a TIMEOUT, but the output
1581+## may never match a pattern.
1582+## """
1583+##class MAXBUFFER(ExceptionPexpect):
1584+## """Raised when a scan buffer fills before matching an expected pattern."""
1585+
1586+def run (command, timeout=-1, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None):
1587+
1588+ """
1589+ This function runs the given command; waits for it to finish; then
1590+ returns all output as a string. STDERR is included in output. If the full
1591+ path to the command is not given then the path is searched.
1592+
1593+ Note that lines are terminated by CR/LF (\\r\\n) combination even on
1594+ UNIX-like systems because this is the standard for pseudo ttys. If you set
1595+ 'withexitstatus' to true, then run will return a tuple of (command_output,
1596+ exitstatus). If 'withexitstatus' is false then this returns just
1597+ command_output.
1598+
1599+ The run() function can often be used instead of creating a spawn instance.
1600+ For example, the following code uses spawn::
1601+
1602+ from pexpect import * #@UnusedWildImport
1603+ child = spawn('scp foo myname@host.example.com:.')
1604+ child.expect ('(?i)password')
1605+ child.sendline (mypassword)
1606+
1607+ The previous code can be replace with the following::
1608+
1609+ from pexpect import * #@UnusedWildImport
1610+ run ('scp foo myname@host.example.com:.', events={'(?i)password': mypassword})
1611+
1612+ Examples
1613+ ========
1614+
1615+ Start the apache daemon on the local machine::
1616+
1617+ from pexpect import * #@UnusedWildImport
1618+ run ("/usr/local/apache/bin/apachectl start")
1619+
1620+ Check in a file using SVN::
1621+
1622+ from pexpect import * #@UnusedWildImport
1623+ run ("svn ci -m 'automatic commit' my_file.py")
1624+
1625+ Run a command and capture exit status::
1626+
1627+ from pexpect import * #@UnusedWildImport
1628+ (command_output, exitstatus) = run ('ls -l /bin', withexitstatus=1)
1629+
1630+ Tricky Examples
1631+ ===============
1632+
1633+ The following will run SSH and execute 'ls -l' on the remote machine. The
1634+ password 'secret' will be sent if the '(?i)password' pattern is ever seen::
1635+
1636+ run ("ssh username@machine.example.com 'ls -l'", events={'(?i)password':'secret\\n'})
1637+
1638+ This will start mencoder to rip a video from DVD. This will also display
1639+ progress ticks every 5 seconds as it runs. For example::
1640+
1641+ from pexpect import * #@UnusedWildImport
1642+ def print_ticks(d):
1643+ print d['event_count'],
1644+ run ("mencoder dvd://1 -o video.avi -oac copy -ovc copy", events={TIMEOUT:print_ticks}, timeout=5)
1645+
1646+ The 'events' argument should be a dictionary of patterns and responses.
1647+ Whenever one of the patterns is seen in the command out run() will send the
1648+ associated response string. Note that you should put newlines in your
1649+ string if Enter is necessary. The responses may also contain callback
1650+ functions. Any callback is function that takes a dictionary as an argument.
1651+ The dictionary contains all the locals from the run() function, so you can
1652+ access the child spawn object or any other variable defined in run()
1653+ (event_count, child, and extra_args are the most useful). A callback may
1654+ return True to stop the current run process otherwise run() continues until
1655+ the next event. A callback may also return a string which will be sent to
1656+ the child. 'extra_args' is not used by directly run(). It provides a way to
1657+ pass data to a callback function through run() through the locals
1658+ dictionary passed to a callback. """
1659+
1660+ if timeout == -1:
1661+ child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env)
1662+ else:
1663+ child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile, cwd=cwd, env=env)
1664+ if events is not None:
1665+ patterns = events.keys()
1666+ responses = events.values()
1667+ else:
1668+ patterns=None # We assume that EOF or TIMEOUT will save us.
1669+ responses=None
1670+ child_result_list = []
1671+ event_count = 0
1672+ while 1:
1673+ try:
1674+ index = child.expect (patterns)
1675+ if type(child.after) in types.StringTypes:
1676+ child_result_list.append(child.before + child.after)
1677+ else: # child.after may have been a TIMEOUT or EOF, so don't cat those.
1678+ child_result_list.append(child.before)
1679+ if type(responses[index]) in types.StringTypes:
1680+ child.send(responses[index])
1681+ elif type(responses[index]) is types.FunctionType:
1682+ callback_result = responses[index](locals())
1683+ sys.stdout.flush()
1684+ if type(callback_result) in types.StringTypes:
1685+ child.send(callback_result)
1686+ elif callback_result:
1687+ break
1688+ else:
1689+ raise TypeError ('The callback must be a string or function type.')
1690+ event_count = event_count + 1
1691+ except TIMEOUT, e:
1692+ child_result_list.append(child.before)
1693+ break
1694+ except EOF, e:
1695+ child_result_list.append(child.before)
1696+ break
1697+ child_result = ''.join(child_result_list)
1698+ if withexitstatus:
1699+ child.close()
1700+ return (child_result, child.exitstatus)
1701+ else:
1702+ return child_result
1703+
1704+class spawn (object):
1705+
1706+ """This is the main class interface for Pexpect. Use this class to start
1707+ and control child applications. """
1708+
1709+ def __init__(self, command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None):
1710+
1711+ """This is the constructor. The command parameter may be a string that
1712+ includes a command and any arguments to the command. For example::
1713+
1714+ child = pexpect.spawn ('/usr/bin/ftp')
1715+ child = pexpect.spawn ('/usr/bin/ssh user@example.com')
1716+ child = pexpect.spawn ('ls -latr /tmp')
1717+
1718+ You may also construct it with a list of arguments like so::
1719+
1720+ child = pexpect.spawn ('/usr/bin/ftp', [])
1721+ child = pexpect.spawn ('/usr/bin/ssh', ['user@example.com'])
1722+ child = pexpect.spawn ('ls', ['-latr', '/tmp'])
1723+
1724+ After this the child application will be created and will be ready to
1725+ talk to. For normal use, see expect() and send() and sendline().
1726+
1727+ Remember that Pexpect does NOT interpret shell meta characters such as
1728+ redirect, pipe, or wild cards (>, |, or *). This is a common mistake.
1729+ If you want to run a command and pipe it through another command then
1730+ you must also start a shell. For example::
1731+
1732+ child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > log_list.txt"')
1733+ child.expect(pexpect.EOF)
1734+
1735+ The second form of spawn (where you pass a list of arguments) is useful
1736+ in situations where you wish to spawn a command and pass it its own
1737+ argument list. This can make syntax more clear. For example, the
1738+ following is equivalent to the previous example::
1739+
1740+ shell_cmd = 'ls -l | grep LOG > log_list.txt'
1741+ child = pexpect.spawn('/bin/bash', ['-c', shell_cmd])
1742+ child.expect(pexpect.EOF)
1743+
1744+ The maxread attribute sets the read buffer size. This is maximum number
1745+ of bytes that Pexpect will try to read from a TTY at one time. Setting
1746+ the maxread size to 1 will turn off buffering. Setting the maxread
1747+ value higher may help performance in cases where large amounts of
1748+ output are read back from the child. This feature is useful in
1749+ conjunction with searchwindowsize.
1750+
1751+ The searchwindowsize attribute sets the how far back in the incomming
1752+ seach buffer Pexpect will search for pattern matches. Every time
1753+ Pexpect reads some data from the child it will append the data to the
1754+ incomming buffer. The default is to search from the beginning of the
1755+ imcomming buffer each time new data is read from the child. But this is
1756+ very inefficient if you are running a command that generates a large
1757+ amount of data where you want to match The searchwindowsize does not
1758+ effect the size of the incomming data buffer. You will still have
1759+ access to the full buffer after expect() returns.
1760+
1761+ The logfile member turns on or off logging. All input and output will
1762+ be copied to the given file object. Set logfile to None to stop
1763+ logging. This is the default. Set logfile to sys.stdout to echo
1764+ everything to standard output. The logfile is flushed after each write.
1765+
1766+ Example log input and output to a file::
1767+
1768+ child = pexpect.spawn('some_command')
1769+ fout = file('mylog.txt','w')
1770+ child.logfile = fout
1771+
1772+ Example log to stdout::
1773+
1774+ child = pexpect.spawn('some_command')
1775+ child.logfile = sys.stdout
1776+
1777+ The logfile_read and logfile_send members can be used to separately log
1778+ the input from the child and output sent to the child. Sometimes you
1779+ don't want to see everything you write to the child. You only want to
1780+ log what the child sends back. For example::
1781+
1782+ child = pexpect.spawn('some_command')
1783+ child.logfile_read = sys.stdout
1784+
1785+ To separately log output sent to the child use logfile_send::
1786+
1787+ self.logfile_send = fout
1788+
1789+ The delaybeforesend helps overcome a weird behavior that many users
1790+ were experiencing. The typical problem was that a user would expect() a
1791+ "Password:" prompt and then immediately call sendline() to send the
1792+ password. The user would then see that their password was echoed back
1793+ to them. Passwords don't normally echo. The problem is caused by the
1794+ fact that most applications print out the "Password" prompt and then
1795+ turn off stdin echo, but if you send your password before the
1796+ application turned off echo, then you get your password echoed.
1797+ Normally this wouldn't be a problem when interacting with a human at a
1798+ real keyboard. If you introduce a slight delay just before writing then
1799+ this seems to clear up the problem. This was such a common problem for
1800+ many users that I decided that the default pexpect behavior should be
1801+ to sleep just before writing to the child application. 1/20th of a
1802+ second (50 ms) seems to be enough to clear up the problem. You can set
1803+ delaybeforesend to 0 to return to the old behavior. Most Linux machines
1804+ don't like this to be below 0.03. I don't know why.
1805+
1806+ Note that spawn is clever about finding commands on your path.
1807+ It uses the same logic that "which" uses to find executables.
1808+
1809+ If you wish to get the exit status of the child you must call the
1810+ close() method. The exit or signal status of the child will be stored
1811+ in self.exitstatus or self.signalstatus. If the child exited normally
1812+ then exitstatus will store the exit return code and signalstatus will
1813+ be None. If the child was terminated abnormally with a signal then
1814+ signalstatus will store the signal value and exitstatus will be None.
1815+ If you need more detail you can also read the self.status member which
1816+ stores the status returned by os.waitpid. You can interpret this using
1817+ os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. """
1818+
1819+ self.STDIN_FILENO = pty.STDIN_FILENO
1820+ self.STDOUT_FILENO = pty.STDOUT_FILENO
1821+ self.STDERR_FILENO = pty.STDERR_FILENO
1822+ self.stdin = sys.stdin
1823+ self.stdout = sys.stdout
1824+ self.stderr = sys.stderr
1825+
1826+ self.searcher = None
1827+ self.ignorecase = False
1828+ self.before = None
1829+ self.after = None
1830+ self.match = None
1831+ self.match_index = None
1832+ self.terminated = True
1833+ self.exitstatus = None
1834+ self.signalstatus = None
1835+ self.status = None # status returned by os.waitpid
1836+ self.flag_eof = False
1837+ self.pid = None
1838+ self.child_fd = -1 # initially closed
1839+ self.timeout = timeout
1840+ self.delimiter = EOF
1841+ self.logfile = logfile
1842+ self.logfile_read = None # input from child (read_nonblocking)
1843+ self.logfile_send = None # output to send (send, sendline)
1844+ self.maxread = maxread # max bytes to read at one time into buffer
1845+ self.buffer = '' # This is the read buffer. See maxread.
1846+ self.searchwindowsize = searchwindowsize # Anything before searchwindowsize point is preserved, but not searched.
1847+ # Most Linux machines don't like delaybeforesend to be below 0.03 (30 ms).
1848+ self.delaybeforesend = 0.05 # Sets sleep time used just before sending data to child. Time in seconds.
1849+ self.delayafterclose = 0.1 # Sets delay in close() method to allow kernel time to update process status. Time in seconds.
1850+ self.delayafterterminate = 0.1 # Sets delay in terminate() method to allow kernel time to update process status. Time in seconds.
1851+ self.softspace = False # File-like object.
1852+ self.name = '<' + repr(self) + '>' # File-like object.
1853+ self.encoding = None # File-like object.
1854+ self.closed = True # File-like object.
1855+ self.cwd = cwd
1856+ self.env = env
1857+ self.__irix_hack = (sys.platform.lower().find('irix')>=0) # This flags if we are running on irix
1858+ # Solaris uses internal __fork_pty(). All others use pty.fork().
1859+ if (sys.platform.lower().find('solaris')>=0) or (sys.platform.lower().find('sunos5')>=0):
1860+ self.use_native_pty_fork = False
1861+ else:
1862+ self.use_native_pty_fork = True
1863+
1864+
1865+ # allow dummy instances for subclasses that may not use command or args.
1866+ if command is None:
1867+ self.command = None
1868+ self.args = None
1869+ self.name = '<pexpect factory incomplete>'
1870+ else:
1871+ self._spawn (command, args)
1872+
1873+ def __del__(self):
1874+
1875+ """This makes sure that no system resources are left open. Python only
1876+ garbage collects Python objects. OS file descriptors are not Python
1877+ objects, so they must be handled explicitly. If the child file
1878+ descriptor was opened outside of this class (passed to the constructor)
1879+ then this does not close it. """
1880+
1881+ if not self.closed:
1882+ # It is possible for __del__ methods to execute during the
1883+ # teardown of the Python VM itself. Thus self.close() may
1884+ # trigger an exception because os.close may be None.
1885+ # -- Fernando Perez
1886+ try:
1887+ self.close()
1888+ except AttributeError:
1889+ pass
1890+
1891+ def __str__(self):
1892+
1893+ """This returns a human-readable string that represents the state of
1894+ the object. """
1895+
1896+ s = []
1897+ s.append(repr(self))
1898+ s.append('version: ' + __version__ + ' (' + __revision__ + ')')
1899+ s.append('command: ' + str(self.command))
1900+ s.append('args: ' + str(self.args))
1901+ s.append('searcher: ' + str(self.searcher))
1902+ s.append('buffer (last 100 chars): ' + str(self.buffer)[-100:])
1903+ s.append('before (last 100 chars): ' + str(self.before)[-100:])
1904+ s.append('after: ' + str(self.after))
1905+ s.append('match: ' + str(self.match))
1906+ s.append('match_index: ' + str(self.match_index))
1907+ s.append('exitstatus: ' + str(self.exitstatus))
1908+ s.append('flag_eof: ' + str(self.flag_eof))
1909+ s.append('pid: ' + str(self.pid))
1910+ s.append('child_fd: ' + str(self.child_fd))
1911+ s.append('closed: ' + str(self.closed))
1912+ s.append('timeout: ' + str(self.timeout))
1913+ s.append('delimiter: ' + str(self.delimiter))
1914+ s.append('logfile: ' + str(self.logfile))
1915+ s.append('logfile_read: ' + str(self.logfile_read))
1916+ s.append('logfile_send: ' + str(self.logfile_send))
1917+ s.append('maxread: ' + str(self.maxread))
1918+ s.append('ignorecase: ' + str(self.ignorecase))
1919+ s.append('searchwindowsize: ' + str(self.searchwindowsize))
1920+ s.append('delaybeforesend: ' + str(self.delaybeforesend))
1921+ s.append('delayafterclose: ' + str(self.delayafterclose))
1922+ s.append('delayafterterminate: ' + str(self.delayafterterminate))
1923+ return '\n'.join(s)
1924+
1925+ def _spawn(self,command,args=[]):
1926+
1927+ """This starts the given command in a child process. This does all the
1928+ fork/exec type of stuff for a pty. This is called by __init__. If args
1929+ is empty then command will be parsed (split on spaces) and args will be
1930+ set to parsed arguments. """
1931+
1932+ # The pid and child_fd of this object get set by this method.
1933+ # Note that it is difficult for this method to fail.
1934+ # You cannot detect if the child process cannot start.
1935+ # So the only way you can tell if the child process started
1936+ # or not is to try to read from the file descriptor. If you get
1937+ # EOF immediately then it means that the child is already dead.
1938+ # That may not necessarily be bad because you may haved spawned a child
1939+ # that performs some task; creates no stdout output; and then dies.
1940+
1941+ # If command is an int type then it may represent a file descriptor.
1942+ if type(command) == type(0):
1943+ raise ExceptionPexpect ('Command is an int type. If this is a file descriptor then maybe you want to use fdpexpect.fdspawn which takes an existing file descriptor instead of a command string.')
1944+
1945+ if type (args) != type([]):
1946+ raise TypeError ('The argument, args, must be a list.')
1947+
1948+ if args == []:
1949+ self.args = split_command_line(command)
1950+ self.command = self.args[0]
1951+ else:
1952+ self.args = args[:] # work with a copy
1953+ self.args.insert (0, command)
1954+ self.command = command
1955+
1956+ command_with_path = which(self.command)
1957+ if command_with_path is None:
1958+ raise ExceptionPexpect ('The command was not found or was not executable: %s.' % self.command)
1959+ self.command = command_with_path
1960+ self.args[0] = self.command
1961+
1962+ self.name = '<' + ' '.join (self.args) + '>'
1963+
1964+ assert self.pid is None, 'The pid member should be None.'
1965+ assert self.command is not None, 'The command member should not be None.'
1966+
1967+ if self.use_native_pty_fork:
1968+ try:
1969+ self.pid, self.child_fd = pty.fork()
1970+ except OSError, e:
1971+ raise ExceptionPexpect('Error! pty.fork() failed: ' + str(e))
1972+ else: # Use internal __fork_pty
1973+ self.pid, self.child_fd = self.__fork_pty()
1974+
1975+ if self.pid == 0: # Child
1976+ try:
1977+ self.child_fd = sys.stdout.fileno() # used by setwinsize()
1978+ self.setwinsize(24, 80)
1979+ except Exception:
1980+ # Some platforms do not like setwinsize (Cygwin).
1981+ # This will cause problem when running applications that
1982+ # are very picky about window size.
1983+ # This is a serious limitation, but not a show stopper.
1984+ pass
1985+ # Do not allow child to inherit open file descriptors from parent.
1986+ max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
1987+ for i in range (3, max_fd):
1988+ try:
1989+ os.close (i)
1990+ except OSError:
1991+ pass
1992+
1993+ # I don't know why this works, but ignoring SIGHUP fixes a
1994+ # problem when trying to start a Java daemon with sudo
1995+ # (specifically, Tomcat).
1996+ signal.signal(signal.SIGHUP, signal.SIG_IGN)
1997+
1998+ if self.cwd is not None:
1999+ os.chdir(self.cwd)
2000+ if self.env is None:
2001+ os.execv(self.command, self.args)
2002+ else:
2003+ os.execvpe(self.command, self.args, self.env)
2004+
2005+ # Parent
2006+ self.terminated = False
2007+ self.closed = False
2008+
2009+ def __fork_pty(self):
2010+
2011+ """This implements a substitute for the forkpty system call. This
2012+ should be more portable than the pty.fork() function. Specifically,
2013+ this should work on Solaris.
2014+
2015+ Modified 10.06.05 by Geoff Marshall: Implemented __fork_pty() method to
2016+ resolve the issue with Python's pty.fork() not supporting Solaris,
2017+ particularly ssh. Based on patch to posixmodule.c authored by Noah
2018+ Spurrier::
2019+
2020+ http://mail.python.org/pipermail/python-dev/2003-May/035281.html
2021+
2022+ """
2023+
2024+ parent_fd, child_fd = os.openpty()
2025+ if parent_fd < 0 or child_fd < 0:
2026+ raise ExceptionPexpect, "Error! Could not open pty with os.openpty()."
2027+
2028+ pid = os.fork()
2029+ if pid < 0:
2030+ raise ExceptionPexpect, "Error! Failed os.fork()."
2031+ elif pid == 0:
2032+ # Child.
2033+ os.close(parent_fd)
2034+ self.__pty_make_controlling_tty(child_fd)
2035+
2036+ os.dup2(child_fd, 0)
2037+ os.dup2(child_fd, 1)
2038+ os.dup2(child_fd, 2)
2039+
2040+ if child_fd > 2:
2041+ os.close(child_fd)
2042+ else:
2043+ # Parent.
2044+ os.close(child_fd)
2045+
2046+ return pid, parent_fd
2047+
2048+ def __pty_make_controlling_tty(self, tty_fd):
2049+
2050+ """This makes the pseudo-terminal the controlling tty. This should be
2051+ more portable than the pty.fork() function. Specifically, this should
2052+ work on Solaris. """
2053+
2054+ child_name = os.ttyname(tty_fd)
2055+
2056+ # Disconnect from controlling tty if still connected.
2057+ fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY);
2058+ if fd >= 0:
2059+ os.close(fd)
2060+
2061+ os.setsid()
2062+
2063+ # Verify we are disconnected from controlling tty
2064+ try:
2065+ fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY);
2066+ if fd >= 0:
2067+ os.close(fd)
2068+ raise ExceptionPexpect, "Error! We are not disconnected from a controlling tty."
2069+ except Exception:
2070+ # Good! We are disconnected from a controlling tty.
2071+ pass
2072+
2073+ # Verify we can open child pty.
2074+ fd = os.open(child_name, os.O_RDWR);
2075+ if fd < 0:
2076+ raise ExceptionPexpect, "Error! Could not open child pty, " + child_name
2077+ else:
2078+ os.close(fd)
2079+
2080+ # Verify we now have a controlling tty.
2081+ fd = os.open("/dev/tty", os.O_WRONLY)
2082+ if fd < 0:
2083+ raise ExceptionPexpect, "Error! Could not open controlling tty, /dev/tty"
2084+ else:
2085+ os.close(fd)
2086+
2087+ def fileno (self): # File-like object.
2088+
2089+ """This returns the file descriptor of the pty for the child.
2090+ """
2091+
2092+ return self.child_fd
2093+
2094+ def close (self, force=True): # File-like object.
2095+
2096+ """This closes the connection with the child application. Note that
2097+ calling close() more than once is valid. This emulates standard Python
2098+ behavior with files. Set force to True if you want to make sure that
2099+ the child is terminated (SIGKILL is sent if the child ignores SIGHUP
2100+ and SIGINT). """
2101+
2102+ if not self.closed:
2103+ self.flush()
2104+ os.close (self.child_fd)
2105+ time.sleep(self.delayafterclose) # Give kernel time to update process status.
2106+ if self.isalive():
2107+ if not self.terminate(force):
2108+ raise ExceptionPexpect ('close() could not terminate the child using terminate()')
2109+ self.child_fd = -1
2110+ self.closed = True
2111+ #self.pid = None
2112+
2113+ def flush (self): # File-like object.
2114+
2115+ """This does nothing. It is here to support the interface for a
2116+ File-like object. """
2117+
2118+ pass
2119+
2120+ def isatty (self): # File-like object.
2121+
2122+ """This returns True if the file descriptor is open and connected to a
2123+ tty(-like) device, else False. """
2124+
2125+ return os.isatty(self.child_fd)
2126+
2127+ def waitnoecho (self, timeout=-1):
2128+
2129+ """This waits until the terminal ECHO flag is set False. This returns
2130+ True if the echo mode is off. This returns False if the ECHO flag was
2131+ not set False before the timeout. This can be used to detect when the
2132+ child is waiting for a password. Usually a child application will turn
2133+ off echo mode when it is waiting for the user to enter a password. For
2134+ example, instead of expecting the "password:" prompt you can wait for
2135+ the child to set ECHO off::
2136+
2137+ p = pexpect.spawn ('ssh user@example.com')
2138+ p.waitnoecho()
2139+ p.sendline(mypassword)
2140+
2141+ If timeout is None then this method to block forever until ECHO flag is
2142+ False.
2143+
2144+ """
2145+
2146+ if timeout == -1:
2147+ timeout = self.timeout
2148+ if timeout is not None:
2149+ end_time = time.time() + timeout
2150+ while True:
2151+ if not self.getecho():
2152+ return True
2153+ if timeout < 0 and timeout is not None:
2154+ return False
2155+ if timeout is not None:
2156+ timeout = end_time - time.time()
2157+ time.sleep(0.1)
2158+
2159+ def getecho (self):
2160+
2161+ """This returns the terminal echo mode. This returns True if echo is
2162+ on or False if echo is off. Child applications that are expecting you
2163+ to enter a password often set ECHO False. See waitnoecho(). """
2164+
2165+ attr = termios.tcgetattr(self.child_fd)
2166+ if attr[3] & termios.ECHO:
2167+ return True
2168+ return False
2169+
2170+ def setecho (self, state):
2171+
2172+ """This sets the terminal echo mode on or off. Note that anything the
2173+ child sent before the echo will be lost, so you should be sure that
2174+ your input buffer is empty before you call setecho(). For example, the
2175+ following will work as expected::
2176+
2177+ p = pexpect.spawn('cat')
2178+ p.sendline ('1234') # We will see this twice (once from tty echo and again from cat).
2179+ p.expect (['1234'])
2180+ p.expect (['1234'])
2181+ p.setecho(False) # Turn off tty echo
2182+ p.sendline ('abcd') # We will set this only once (echoed by cat).
2183+ p.sendline ('wxyz') # We will set this only once (echoed by cat)
2184+ p.expect (['abcd'])
2185+ p.expect (['wxyz'])
2186+
2187+ The following WILL NOT WORK because the lines sent before the setecho
2188+ will be lost::
2189+
2190+ p = pexpect.spawn('cat')
2191+ p.sendline ('1234') # We will see this twice (once from tty echo and again from cat).
2192+ p.setecho(False) # Turn off tty echo
2193+ p.sendline ('abcd') # We will set this only once (echoed by cat).
2194+ p.sendline ('wxyz') # We will set this only once (echoed by cat)
2195+ p.expect (['1234'])
2196+ p.expect (['1234'])
2197+ p.expect (['abcd'])
2198+ p.expect (['wxyz'])
2199+ """
2200+
2201+ self.child_fd
2202+ attr = termios.tcgetattr(self.child_fd)
2203+ if state:
2204+ attr[3] = attr[3] | termios.ECHO
2205+ else:
2206+ attr[3] = attr[3] & ~termios.ECHO
2207+ # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent
2208+ # and blocked on some platforms. TCSADRAIN is probably ideal if it worked.
2209+ termios.tcsetattr(self.child_fd, termios.TCSANOW, attr)
2210+
2211+ def read_nonblocking (self, size = 1, timeout = -1):
2212+
2213+ """This reads at most size characters from the child application. It
2214+ includes a timeout. If the read does not complete within the timeout
2215+ period then a TIMEOUT exception is raised. If the end of file is read
2216+ then an EOF exception will be raised. If a log file was set using
2217+ setlog() then all data will also be written to the log file.
2218+
2219+ If timeout is None then the read may block indefinitely. If timeout is -1
2220+ then the self.timeout value is used. If timeout is 0 then the child is
2221+ polled and if there was no data immediately ready then this will raise
2222+ a TIMEOUT exception.
2223+
2224+ The timeout refers only to the amount of time to read at least one
2225+ character. This is not effected by the 'size' parameter, so if you call
2226+ read_nonblocking(size=100, timeout=30) and only one character is
2227+ available right away then one character will be returned immediately.
2228+ It will not wait for 30 seconds for another 99 characters to come in.
2229+
2230+ This is a wrapper around os.read(). It uses select.select() to
2231+ implement the timeout. """
2232+
2233+ if self.closed:
2234+ raise ValueError ('I/O operation on closed file in read_nonblocking().')
2235+
2236+ if timeout == -1:
2237+ timeout = self.timeout
2238+
2239+ # Note that some systems such as Solaris do not give an EOF when
2240+ # the child dies. In fact, you can still try to read
2241+ # from the child_fd -- it will block forever or until TIMEOUT.
2242+ # For this case, I test isalive() before doing any reading.
2243+ # If isalive() is false, then I pretend that this is the same as EOF.
2244+ if not self.isalive():
2245+ r,w,e = self.__select([self.child_fd], [], [], 0) # timeout of 0 means "poll" @UnusedVariable
2246+ if not r:
2247+ self.flag_eof = True
2248+ raise EOF ('End Of File (EOF) in read_nonblocking(). Braindead platform.')
2249+ elif self.__irix_hack:
2250+ # This is a hack for Irix. It seems that Irix requires a long delay before checking isalive.
2251+ # This adds a 2 second delay, but only when the child is terminated.
2252+ r, w, e = self.__select([self.child_fd], [], [], 2) #@UnusedVariable
2253+ if not r and not self.isalive():
2254+ self.flag_eof = True
2255+ raise EOF ('End Of File (EOF) in read_nonblocking(). Pokey platform.')
2256+
2257+ r,w,e = self.__select([self.child_fd], [], [], timeout) #@UnusedVariable
2258+
2259+ if not r:
2260+ if not self.isalive():
2261+ # Some platforms, such as Irix, will claim that their processes are alive;
2262+ # then timeout on the select; and then finally admit that they are not alive.
2263+ self.flag_eof = True
2264+ raise EOF ('End of File (EOF) in read_nonblocking(). Very pokey platform.')
2265+ else:
2266+ raise TIMEOUT ('Timeout exceeded in read_nonblocking().')
2267+
2268+ if self.child_fd in r:
2269+ try:
2270+ s = os.read(self.child_fd, size)
2271+ except OSError, e: # Linux does this
2272+ self.flag_eof = True
2273+ raise EOF ('End Of File (EOF) in read_nonblocking(). Exception style platform.')
2274+ if s == '': # BSD style
2275+ self.flag_eof = True
2276+ raise EOF ('End Of File (EOF) in read_nonblocking(). Empty string style platform.')
2277+
2278+ if self.logfile is not None:
2279+ self.logfile.write (s)
2280+ self.logfile.flush()
2281+ if self.logfile_read is not None:
2282+ self.logfile_read.write (s)
2283+ self.logfile_read.flush()
2284+
2285+ return s
2286+
2287+ raise ExceptionPexpect ('Reached an unexpected state in read_nonblocking().')
2288+
2289+ def read (self, size = -1): # File-like object.
2290+
2291+ """This reads at most "size" bytes from the file (less if the read hits
2292+ EOF before obtaining size bytes). If the size argument is negative or
2293+ omitted, read all data until EOF is reached. The bytes are returned as
2294+ a string object. An empty string is returned when EOF is encountered
2295+ immediately. """
2296+
2297+ if size == 0:
2298+ return ''
2299+ if size < 0:
2300+ self.expect (self.delimiter) # delimiter default is EOF
2301+ return self.before
2302+
2303+ # I could have done this more directly by not using expect(), but
2304+ # I deliberately decided to couple read() to expect() so that
2305+ # I would catch any bugs early and ensure consistant behavior.
2306+ # It's a little less efficient, but there is less for me to
2307+ # worry about if I have to later modify read() or expect().
2308+ # Note, it's OK if size==-1 in the regex. That just means it
2309+ # will never match anything in which case we stop only on EOF.
2310+ cre = re.compile('.{%d}' % size, re.DOTALL)
2311+ index = self.expect ([cre, self.delimiter]) # delimiter default is EOF
2312+ if index == 0:
2313+ return self.after ### self.before should be ''. Should I assert this?
2314+ return self.before
2315+
2316+ def readline (self, size = -1): # File-like object.
2317+
2318+ """This reads and returns one entire line. A trailing newline is kept
2319+ in the string, but may be absent when a file ends with an incomplete
2320+ line. Note: This readline() looks for a \\r\\n pair even on UNIX
2321+ because this is what the pseudo tty device returns. So contrary to what
2322+ you may expect you will receive the newline as \\r\\n. An empty string
2323+ is returned when EOF is hit immediately. Currently, the size argument is
2324+ mostly ignored, so this behavior is not standard for a file-like
2325+ object. If size is 0 then an empty string is returned. """
2326+
2327+ if size == 0:
2328+ return ''
2329+ index = self.expect (['\r\n', self.delimiter]) # delimiter default is EOF
2330+ if index == 0:
2331+ return self.before + '\r\n'
2332+ else:
2333+ return self.before
2334+
2335+ def __iter__ (self): # File-like object.
2336+
2337+ """This is to support iterators over a file-like object.
2338+ """
2339+
2340+ return self
2341+
2342+ def next (self): # File-like object.
2343+
2344+ """This is to support iterators over a file-like object.
2345+ """
2346+
2347+ result = self.readline()
2348+ if result == "":
2349+ raise StopIteration
2350+ return result
2351+
2352+ def readlines (self, sizehint = -1): # File-like object.
2353+
2354+ """This reads until EOF using readline() and returns a list containing
2355+ the lines thus read. The optional "sizehint" argument is ignored. """
2356+
2357+ lines = []
2358+ while True:
2359+ line = self.readline()
2360+ if not line:
2361+ break
2362+ lines.append(line)
2363+ return lines
2364+
2365+ def write(self, s): # File-like object.
2366+
2367+ """This is similar to send() except that there is no return value.
2368+ """
2369+
2370+ self.send (s)
2371+
2372+ def writelines (self, sequence): # File-like object.
2373+
2374+ """This calls write() for each element in the sequence. The sequence
2375+ can be any iterable object producing strings, typically a list of
2376+ strings. This does not add line separators There is no return value.
2377+ """
2378+
2379+ for s in sequence:
2380+ self.write (s)
2381+
2382+ def send(self, s):
2383+
2384+ """This sends a string to the child process. This returns the number of
2385+ bytes written. If a log file was set then the data is also written to
2386+ the log. """
2387+
2388+ time.sleep(self.delaybeforesend)
2389+ if self.logfile is not None:
2390+ self.logfile.write (s)
2391+ self.logfile.flush()
2392+ if self.logfile_send is not None:
2393+ self.logfile_send.write (s)
2394+ self.logfile_send.flush()
2395+ c = os.write(self.child_fd, s)
2396+ return c
2397+
2398+ def sendline(self, s=''):
2399+
2400+ """This is like send(), but it adds a line feed (os.linesep). This
2401+ returns the number of bytes written. """
2402+
2403+ n = self.send(s)
2404+ n = n + self.send (os.linesep)
2405+ return n
2406+
2407+ def sendcontrol(self, char):
2408+
2409+ """This sends a control character to the child such as Ctrl-C or
2410+ Ctrl-D. For example, to send a Ctrl-G (ASCII 7)::
2411+
2412+ child.sendcontrol('g')
2413+
2414+ See also, sendintr() and sendeof().
2415+ """
2416+
2417+ char = char.lower()
2418+ a = ord(char)
2419+ if a>=97 and a<=122:
2420+ a = a - ord('a') + 1
2421+ return self.send (chr(a))
2422+ d = {'@':0, '`':0,
2423+ '[':27, '{':27,
2424+ '\\':28, '|':28,
2425+ ']':29, '}': 29,
2426+ '^':30, '~':30,
2427+ '_':31,
2428+ '?':127}
2429+ if char not in d:
2430+ return 0
2431+ return self.send (chr(d[char]))
2432+
2433+ def sendeof(self):
2434+
2435+ """This sends an EOF to the child. This sends a character which causes
2436+ the pending parent output buffer to be sent to the waiting child
2437+ program without waiting for end-of-line. If it is the first character
2438+ of the line, the read() in the user program returns 0, which signifies
2439+ end-of-file. This means to work as expected a sendeof() has to be
2440+ called at the beginning of a line. This method does not send a newline.
2441+ It is the responsibility of the caller to ensure the eof is sent at the
2442+ beginning of a line. """
2443+
2444+ ### Hmmm... how do I send an EOF?
2445+ ###C if ((m = write(pty, *buf, p - *buf)) < 0)
2446+ ###C return (errno == EWOULDBLOCK) ? n : -1;
2447+ #fd = sys.stdin.fileno()
2448+ #old = termios.tcgetattr(fd) # remember current state
2449+ #attr = termios.tcgetattr(fd)
2450+ #attr[3] = attr[3] | termios.ICANON # ICANON must be set to recognize EOF
2451+ #try: # use try/finally to ensure state gets restored
2452+ # termios.tcsetattr(fd, termios.TCSADRAIN, attr)
2453+ # if hasattr(termios, 'CEOF'):
2454+ # os.write (self.child_fd, '%c' % termios.CEOF)
2455+ # else:
2456+ # # Silly platform does not define CEOF so assume CTRL-D
2457+ # os.write (self.child_fd, '%c' % 4)
2458+ #finally: # restore state
2459+ # termios.tcsetattr(fd, termios.TCSADRAIN, old)
2460+ if hasattr(termios, 'VEOF'):
2461+ char = termios.tcgetattr(self.child_fd)[6][termios.VEOF]
2462+ else:
2463+ # platform does not define VEOF so assume CTRL-D
2464+ char = chr(4)
2465+ self.send(char)
2466+
2467+ def sendintr(self):
2468+
2469+ """This sends a SIGINT to the child. It does not require
2470+ the SIGINT to be the first character on a line. """
2471+
2472+ if hasattr(termios, 'VINTR'):
2473+ char = termios.tcgetattr(self.child_fd)[6][termios.VINTR]
2474+ else:
2475+ # platform does not define VINTR so assume CTRL-C
2476+ char = chr(3)
2477+ self.send (char)
2478+
2479+ def eof (self):
2480+
2481+ """This returns True if the EOF exception was ever raised.
2482+ """
2483+
2484+ return self.flag_eof
2485+
2486+ def terminate(self, force=False):
2487+
2488+ """This forces a child process to terminate. It starts nicely with
2489+ SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This
2490+ returns True if the child was terminated. This returns False if the
2491+ child could not be terminated. """
2492+
2493+ if not self.isalive():
2494+ return True
2495+ try:
2496+ self.kill(signal.SIGHUP)
2497+ time.sleep(self.delayafterterminate)
2498+ if not self.isalive():
2499+ return True
2500+ self.kill(signal.SIGCONT)
2501+ time.sleep(self.delayafterterminate)
2502+ if not self.isalive():
2503+ return True
2504+ self.kill(signal.SIGINT)
2505+ time.sleep(self.delayafterterminate)
2506+ if not self.isalive():
2507+ return True
2508+ if force:
2509+ self.kill(signal.SIGKILL)
2510+ time.sleep(self.delayafterterminate)
2511+ if not self.isalive():
2512+ return True
2513+ else:
2514+ return False
2515+ return False
2516+ except OSError, e:
2517+ # I think there are kernel timing issues that sometimes cause
2518+ # this to happen. I think isalive() reports True, but the
2519+ # process is dead to the kernel.
2520+ # Make one last attempt to see if the kernel is up to date.
2521+ time.sleep(self.delayafterterminate)
2522+ if not self.isalive():
2523+ return True
2524+ else:
2525+ return False
2526+
2527+ def wait(self):
2528+
2529+ """This waits until the child exits. This is a blocking call. This will
2530+ not read any data from the child, so this will block forever if the
2531+ child has unread output and has terminated. In other words, the child
2532+ may have printed output then called exit(); but, technically, the child
2533+ is still alive until its output is read. """
2534+
2535+ if self.isalive():
2536+ pid, status = os.waitpid(self.pid, 0) #@UnusedVariable
2537+ else:
2538+ raise ExceptionPexpect ('Cannot wait for dead child process.')
2539+ self.exitstatus = os.WEXITSTATUS(status)
2540+ if os.WIFEXITED (status):
2541+ self.status = status
2542+ self.exitstatus = os.WEXITSTATUS(status)
2543+ self.signalstatus = None
2544+ self.terminated = True
2545+ elif os.WIFSIGNALED (status):
2546+ self.status = status
2547+ self.exitstatus = None
2548+ self.signalstatus = os.WTERMSIG(status)
2549+ self.terminated = True
2550+ elif os.WIFSTOPPED (status):
2551+ raise ExceptionPexpect ('Wait was called for a child process that is stopped. This is not supported. Is some other process attempting job control with our child pid?')
2552+ return self.exitstatus
2553+
2554+ def isalive(self):
2555+
2556+ """This tests if the child process is running or not. This is
2557+ non-blocking. If the child was terminated then this will read the
2558+ exitstatus or signalstatus of the child. This returns True if the child
2559+ process appears to be running or False if not. It can take literally
2560+ SECONDS for Solaris to return the right status. """
2561+
2562+ if self.terminated:
2563+ return False
2564+
2565+ if self.flag_eof:
2566+ # This is for Linux, which requires the blocking form of waitpid to get
2567+ # status of a defunct process. This is super-lame. The flag_eof would have
2568+ # been set in read_nonblocking(), so this should be safe.
2569+ waitpid_options = 0
2570+ else:
2571+ waitpid_options = os.WNOHANG
2572+
2573+ try:
2574+ pid, status = os.waitpid(self.pid, waitpid_options)
2575+ except OSError, e: # No child processes
2576+ if e[0] == errno.ECHILD:
2577+ raise ExceptionPexpect ('isalive() encountered condition where "terminated" is 0, but there was no child process. Did someone else call waitpid() on our process?')
2578+ else:
2579+ raise e
2580+
2581+ # I have to do this twice for Solaris. I can't even believe that I figured this out...
2582+ # If waitpid() returns 0 it means that no child process wishes to
2583+ # report, and the value of status is undefined.
2584+ if pid == 0:
2585+ try:
2586+ pid, status = os.waitpid(self.pid, waitpid_options) ### os.WNOHANG) # Solaris!
2587+ except OSError, e: # This should never happen...
2588+ if e[0] == errno.ECHILD:
2589+ raise ExceptionPexpect ('isalive() encountered condition that should never happen. There was no child process. Did someone else call waitpid() on our process?')
2590+ else:
2591+ raise e
2592+
2593+ # If pid is still 0 after two calls to waitpid() then
2594+ # the process really is alive. This seems to work on all platforms, except
2595+ # for Irix which seems to require a blocking call on waitpid or select, so I let read_nonblocking
2596+ # take care of this situation (unfortunately, this requires waiting through the timeout).
2597+ if pid == 0:
2598+ return True
2599+
2600+ if pid == 0:
2601+ return True
2602+
2603+ if os.WIFEXITED (status):
2604+ self.status = status
2605+ self.exitstatus = os.WEXITSTATUS(status)
2606+ self.signalstatus = None
2607+ self.terminated = True
2608+ elif os.WIFSIGNALED (status):
2609+ self.status = status
2610+ self.exitstatus = None
2611+ self.signalstatus = os.WTERMSIG(status)
2612+ self.terminated = True
2613+ elif os.WIFSTOPPED (status):
2614+ raise ExceptionPexpect ('isalive() encountered condition where child process is stopped. This is not supported. Is some other process attempting job control with our child pid?')
2615+ return False
2616+
2617+ def kill(self, sig):
2618+
2619+ """This sends the given signal to the child application. In keeping
2620+ with UNIX tradition it has a misleading name. It does not necessarily
2621+ kill the child unless you send the right signal. """
2622+
2623+ # Same as os.kill, but the pid is given for you.
2624+ if self.isalive():
2625+ os.kill(self.pid, sig)
2626+
2627+ def compile_pattern_list(self, patterns):
2628+
2629+ """This compiles a pattern-string or a list of pattern-strings.
2630+ Patterns must be a StringType, EOF, TIMEOUT, SRE_Pattern, or a list of
2631+ those. Patterns may also be None which results in an empty list (you
2632+ might do this if waiting for an EOF or TIMEOUT condition without
2633+ expecting any pattern).
2634+
2635+ This is used by expect() when calling expect_list(). Thus expect() is
2636+ nothing more than::
2637+
2638+ cpl = self.compile_pattern_list(pl)
2639+ return self.expect_list(cpl, timeout)
2640+
2641+ If you are using expect() within a loop it may be more
2642+ efficient to compile the patterns first and then call expect_list().
2643+ This avoid calls in a loop to compile_pattern_list()::
2644+
2645+ cpl = self.compile_pattern_list(my_pattern)
2646+ while some_condition:
2647+ ...
2648+ i = self.expect_list(clp, timeout)
2649+ ...
2650+ """
2651+
2652+ if patterns is None:
2653+ return []
2654+ if type(patterns) is not types.ListType:
2655+ patterns = [patterns]
2656+
2657+ compile_flags = re.DOTALL # Allow dot to match \n
2658+ if self.ignorecase:
2659+ compile_flags = compile_flags | re.IGNORECASE
2660+ compiled_pattern_list = []
2661+ for p in patterns:
2662+ if type(p) in types.StringTypes:
2663+ compiled_pattern_list.append(re.compile(p, compile_flags))
2664+ elif p is EOF:
2665+ compiled_pattern_list.append(EOF)
2666+ elif p is TIMEOUT:
2667+ compiled_pattern_list.append(TIMEOUT)
2668+ elif type(p) is type(re.compile('')):
2669+ compiled_pattern_list.append(p)
2670+ else:
2671+ raise TypeError ('Argument must be one of StringTypes, EOF, TIMEOUT, SRE_Pattern, or a list of those type. %s' % str(type(p)))
2672+
2673+ return compiled_pattern_list
2674+
2675+ def expect(self, pattern, timeout = -1, searchwindowsize=None):
2676+
2677+ """This seeks through the stream until a pattern is matched. The
2678+ pattern is overloaded and may take several types. The pattern can be a
2679+ StringType, EOF, a compiled re, or a list of any of those types.
2680+ Strings will be compiled to re types. This returns the index into the
2681+ pattern list. If the pattern was not a list this returns index 0 on a
2682+ successful match. This may raise exceptions for EOF or TIMEOUT. To
2683+ avoid the EOF or TIMEOUT exceptions add EOF or TIMEOUT to the pattern
2684+ list. That will cause expect to match an EOF or TIMEOUT condition
2685+ instead of raising an exception.
2686+
2687+ If you pass a list of patterns and more than one matches, the first match
2688+ in the stream is chosen. If more than one pattern matches at that point,
2689+ the leftmost in the pattern list is chosen. For example::
2690+
2691+ # the input is 'foobar'
2692+ index = p.expect (['bar', 'foo', 'foobar'])
2693+ # returns 1 ('foo') even though 'foobar' is a "better" match
2694+
2695+ Please note, however, that buffering can affect this behavior, since
2696+ input arrives in unpredictable chunks. For example::
2697+
2698+ # the input is 'foobar'
2699+ index = p.expect (['foobar', 'foo'])
2700+ # returns 0 ('foobar') if all input is available at once,
2701+ # but returs 1 ('foo') if parts of the final 'bar' arrive late
2702+
2703+ After a match is found the instance attributes 'before', 'after' and
2704+ 'match' will be set. You can see all the data read before the match in
2705+ 'before'. You can see the data that was matched in 'after'. The
2706+ re.MatchObject used in the re match will be in 'match'. If an error
2707+ occurred then 'before' will be set to all the data read so far and
2708+ 'after' and 'match' will be None.
2709+
2710+ If timeout is -1 then timeout will be set to the self.timeout value.
2711+
2712+ A list entry may be EOF or TIMEOUT instead of a string. This will
2713+ catch these exceptions and return the index of the list entry instead
2714+ of raising the exception. The attribute 'after' will be set to the
2715+ exception type. The attribute 'match' will be None. This allows you to
2716+ write code like this::
2717+
2718+ index = p.expect (['good', 'bad', pexpect.EOF, pexpect.TIMEOUT])
2719+ if index == 0:
2720+ do_something()
2721+ elif index == 1:
2722+ do_something_else()
2723+ elif index == 2:
2724+ do_some_other_thing()
2725+ elif index == 3:
2726+ do_something_completely_different()
2727+
2728+ instead of code like this::
2729+
2730+ try:
2731+ index = p.expect (['good', 'bad'])
2732+ if index == 0:
2733+ do_something()
2734+ elif index == 1:
2735+ do_something_else()
2736+ except EOF:
2737+ do_some_other_thing()
2738+ except TIMEOUT:
2739+ do_something_completely_different()
2740+
2741+ These two forms are equivalent. It all depends on what you want. You
2742+ can also just expect the EOF if you are waiting for all output of a
2743+ child to finish. For example::
2744+
2745+ p = pexpect.spawn('/bin/ls')
2746+ p.expect (pexpect.EOF)
2747+ print p.before
2748+
2749+ If you are trying to optimize for speed then see expect_list().
2750+ """
2751+
2752+ compiled_pattern_list = self.compile_pattern_list(pattern)
2753+ return self.expect_list(compiled_pattern_list, timeout, searchwindowsize)
2754+
2755+ def expect_list(self, pattern_list, timeout = -1, searchwindowsize = -1):
2756+
2757+ """This takes a list of compiled regular expressions and returns the
2758+ index into the pattern_list that matched the child output. The list may
2759+ also contain EOF or TIMEOUT (which are not compiled regular
2760+ expressions). This method is similar to the expect() method except that
2761+ expect_list() does not recompile the pattern list on every call. This
2762+ may help if you are trying to optimize for speed, otherwise just use
2763+ the expect() method. This is called by expect(). If timeout==-1 then
2764+ the self.timeout value is used. If searchwindowsize==-1 then the
2765+ self.searchwindowsize value is used. """
2766+
2767+ return self.expect_loop(searcher_re(pattern_list), timeout, searchwindowsize)
2768+
2769+ def expect_exact(self, pattern_list, timeout = -1, searchwindowsize = -1):
2770+
2771+ """This is similar to expect(), but uses plain string matching instead
2772+ of compiled regular expressions in 'pattern_list'. The 'pattern_list'
2773+ may be a string; a list or other sequence of strings; or TIMEOUT and
2774+ EOF.
2775+
2776+ This call might be faster than expect() for two reasons: string
2777+ searching is faster than RE matching and it is possible to limit the
2778+ search to just the end of the input buffer.
2779+
2780+ This method is also useful when you don't want to have to worry about
2781+ escaping regular expression characters that you want to match."""
2782+
2783+ if type(pattern_list) in types.StringTypes or pattern_list in (TIMEOUT, EOF):
2784+ pattern_list = [pattern_list]
2785+ return self.expect_loop(searcher_string(pattern_list), timeout, searchwindowsize)
2786+
2787+ def expect_loop(self, searcher, timeout = -1, searchwindowsize = -1):
2788+
2789+ """This is the common loop used inside expect. The 'searcher' should be
2790+ an instance of searcher_re or searcher_string, which describes how and what
2791+ to search for in the input.
2792+
2793+ See expect() for other arguments, return value and exceptions. """
2794+
2795+ self.searcher = searcher
2796+
2797+ if timeout == -1:
2798+ timeout = self.timeout
2799+ if timeout is not None:
2800+ end_time = time.time() + timeout
2801+ if searchwindowsize == -1:
2802+ searchwindowsize = self.searchwindowsize
2803+
2804+ try:
2805+ incoming = self.buffer
2806+ freshlen = len(incoming)
2807+ while True: # Keep reading until exception or return.
2808+ index = searcher.search(incoming, freshlen, searchwindowsize)
2809+ if index >= 0:
2810+ self.buffer = incoming[searcher.end : ]
2811+ self.before = incoming[ : searcher.start]
2812+ self.after = incoming[searcher.start : searcher.end]
2813+ self.match = searcher.match
2814+ self.match_index = index
2815+ return self.match_index
2816+ # No match at this point
2817+ if timeout < 0 and timeout is not None:
2818+ raise TIMEOUT ('Timeout exceeded in expect_any().')
2819+ # Still have time left, so read more data
2820+ c = self.read_nonblocking (self.maxread, timeout)
2821+ freshlen = len(c)
2822+ time.sleep (0.0001)
2823+ incoming = incoming + c
2824+ if timeout is not None:
2825+ timeout = end_time - time.time()
2826+ except EOF, e:
2827+ self.buffer = ''
2828+ self.before = incoming
2829+ self.after = EOF
2830+ index = searcher.eof_index
2831+ if index >= 0:
2832+ self.match = EOF
2833+ self.match_index = index
2834+ return self.match_index
2835+ else:
2836+ self.match = None
2837+ self.match_index = None
2838+ raise EOF (str(e) + '\n' + str(self))
2839+ except TIMEOUT, e:
2840+ self.buffer = incoming
2841+ self.before = incoming
2842+ self.after = TIMEOUT
2843+ index = searcher.timeout_index
2844+ if index >= 0:
2845+ self.match = TIMEOUT
2846+ self.match_index = index
2847+ return self.match_index
2848+ else:
2849+ self.match = None
2850+ self.match_index = None
2851+ raise TIMEOUT (str(e) + '\n' + str(self))
2852+ except Exception:
2853+ self.before = incoming
2854+ self.after = None
2855+ self.match = None
2856+ self.match_index = None
2857+ raise
2858+
2859+ def getwinsize(self):
2860+
2861+ """This returns the terminal window size of the child tty. The return
2862+ value is a tuple of (rows, cols). """
2863+
2864+ TIOCGWINSZ = getattr(termios, 'TIOCGWINSZ', 1074295912L)
2865+ s = struct.pack('HHHH', 0, 0, 0, 0)
2866+ x = fcntl.ioctl(self.fileno(), TIOCGWINSZ, s)
2867+ return struct.unpack('HHHH', x)[0:2]
2868+
2869+ def setwinsize(self, r, c):
2870+
2871+ """This sets the terminal window size of the child tty. This will cause
2872+ a SIGWINCH signal to be sent to the child. This does not change the
2873+ physical window size. It changes the size reported to TTY-aware
2874+ applications like vi or curses -- applications that respond to the
2875+ SIGWINCH signal. """
2876+
2877+ # Check for buggy platforms. Some Python versions on some platforms
2878+ # (notably OSF1 Alpha and RedHat 7.1) truncate the value for
2879+ # termios.TIOCSWINSZ. It is not clear why this happens.
2880+ # These platforms don't seem to handle the signed int very well;
2881+ # yet other platforms like OpenBSD have a large negative value for
2882+ # TIOCSWINSZ and they don't have a truncate problem.
2883+ # Newer versions of Linux have totally different values for TIOCSWINSZ.
2884+ # Note that this fix is a hack.
2885+ TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561)
2886+ if TIOCSWINSZ == 2148037735L: # L is not required in Python >= 2.2.
2887+ TIOCSWINSZ = -2146929561 # Same bits, but with sign.
2888+ # Note, assume ws_xpixel and ws_ypixel are zero.
2889+ s = struct.pack('HHHH', r, c, 0, 0)
2890+ fcntl.ioctl(self.fileno(), TIOCSWINSZ, s)
2891+
2892+ def interact(self, escape_character = chr(29), input_filter = None, output_filter = None):
2893+
2894+ """This gives control of the child process to the interactive user (the
2895+ human at the keyboard). Keystrokes are sent to the child process, and
2896+ the stdout and stderr output of the child process is printed. This
2897+ simply echos the child stdout and child stderr to the real stdout and
2898+ it echos the real stdin to the child stdin. When the user types the
2899+ escape_character this method will stop. The default for
2900+ escape_character is ^]. This should not be confused with ASCII 27 --
2901+ the ESC character. ASCII 29 was chosen for historical merit because
2902+ this is the character used by 'telnet' as the escape character. The
2903+ escape_character will not be sent to the child process.
2904+
2905+ You may pass in optional input and output filter functions. These
2906+ functions should take a string and return a string. The output_filter
2907+ will be passed all the output from the child process. The input_filter
2908+ will be passed all the keyboard input from the user. The input_filter
2909+ is run BEFORE the check for the escape_character.
2910+
2911+ Note that if you change the window size of the parent the SIGWINCH
2912+ signal will not be passed through to the child. If you want the child
2913+ window size to change when the parent's window size changes then do
2914+ something like the following example::
2915+
2916+ import pexpect, struct, fcntl, termios, signal, sys
2917+ def sigwinch_passthrough (sig, data):
2918+ s = struct.pack("HHHH", 0, 0, 0, 0)
2919+ a = struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ , s))
2920+ global p
2921+ p.setwinsize(a[0],a[1])
2922+ p = pexpect.spawn('/bin/bash') # Note this is global and used in sigwinch_passthrough.
2923+ signal.signal(signal.SIGWINCH, sigwinch_passthrough)
2924+ p.interact()
2925+ """
2926+
2927+ # Flush the buffer.
2928+ self.stdout.write (self.buffer)
2929+ self.stdout.flush()
2930+ self.buffer = ''
2931+ mode = tty.tcgetattr(self.STDIN_FILENO)
2932+ tty.setraw(self.STDIN_FILENO)
2933+ try:
2934+ self.__interact_copy(escape_character, input_filter, output_filter)
2935+ finally:
2936+ tty.tcsetattr(self.STDIN_FILENO, tty.TCSAFLUSH, mode)
2937+
2938+ def __interact_writen(self, fd, data):
2939+
2940+ """This is used by the interact() method.
2941+ """
2942+
2943+ while data != '' and self.isalive():
2944+ n = os.write(fd, data)
2945+ data = data[n:]
2946+
2947+ def __interact_read(self, fd):
2948+
2949+ """This is used by the interact() method.
2950+ """
2951+
2952+ return os.read(fd, 1000)
2953+
2954+ def __interact_copy(self, escape_character = None, input_filter = None, output_filter = None):
2955+
2956+ """This is used by the interact() method.
2957+ """
2958+
2959+ while self.isalive():
2960+ r,w,e = self.__select([self.child_fd, self.STDIN_FILENO], [], []) #@UnusedVariable
2961+ if self.child_fd in r:
2962+ data = self.__interact_read(self.child_fd)
2963+ if output_filter: data = output_filter(data)
2964+ if self.logfile is not None:
2965+ self.logfile.write (data)
2966+ self.logfile.flush()
2967+ os.write(self.STDOUT_FILENO, data)
2968+ if self.STDIN_FILENO in r:
2969+ data = self.__interact_read(self.STDIN_FILENO)
2970+ if input_filter: data = input_filter(data)
2971+ i = data.rfind(escape_character)
2972+ if i != -1:
2973+ data = data[:i]
2974+ self.__interact_writen(self.child_fd, data)
2975+ break
2976+ self.__interact_writen(self.child_fd, data)
2977+
2978+ def __select (self, iwtd, owtd, ewtd, timeout=None):
2979+
2980+ """This is a wrapper around select.select() that ignores signals. If
2981+ select.select raises a select.error exception and errno is an EINTR
2982+ error then it is ignored. Mainly this is used to ignore sigwinch
2983+ (terminal resize). """
2984+
2985+ # if select() is interrupted by a signal (errno==EINTR) then
2986+ # we loop back and enter the select() again.
2987+ if timeout is not None:
2988+ end_time = time.time() + timeout
2989+ while True:
2990+ try:
2991+ return select.select (iwtd, owtd, ewtd, timeout)
2992+ except select.error, e:
2993+ if e[0] == errno.EINTR:
2994+ # if we loop back we have to subtract the amount of time we already waited.
2995+ if timeout is not None:
2996+ timeout = end_time - time.time()
2997+ if timeout < 0:
2998+ return ([],[],[])
2999+ else: # something else caused the select.error, so this really is an exception
3000+ raise
3001+
3002+##############################################################################
3003+# The following methods are no longer supported or allowed.
3004+
3005+ def setmaxread (self, maxread):
3006+
3007+ """This method is no longer supported or allowed. I don't like getters
3008+ and setters without a good reason. """
3009+
3010+ raise ExceptionPexpect ('This method is no longer supported or allowed. Just assign a value to the maxread member variable.')
3011+
3012+ def setlog (self, fileobject):
3013+
3014+ """This method is no longer supported or allowed.
3015+ """
3016+
3017+ raise ExceptionPexpect ('This method is no longer supported or allowed. Just assign a value to the logfile member variable.')
3018+
3019+##############################################################################
3020+# End of spawn class
3021+##############################################################################
3022+
3023+class searcher_string (object):
3024+
3025+ """This is a plain string search helper for the spawn.expect_any() method.
3026+
3027+ Attributes:
3028+
3029+ eof_index - index of EOF, or -1
3030+ timeout_index - index of TIMEOUT, or -1
3031+
3032+ After a successful match by the search() method the following attributes
3033+ are available:
3034+
3035+ start - index into the buffer, first byte of match
3036+ end - index into the buffer, first byte after match
3037+ match - the matching string itself
3038+ """
3039+
3040+ def __init__(self, strings):
3041+
3042+ """This creates an instance of searcher_string. This argument 'strings'
3043+ may be a list; a sequence of strings; or the EOF or TIMEOUT types. """
3044+
3045+ self.eof_index = -1
3046+ self.timeout_index = -1
3047+ self._strings = []
3048+ for n, s in zip(range(len(strings)), strings):
3049+ if s is EOF:
3050+ self.eof_index = n
3051+ continue
3052+ if s is TIMEOUT:
3053+ self.timeout_index = n
3054+ continue
3055+ self._strings.append((n, s))
3056+
3057+ def __str__(self):
3058+
3059+ """This returns a human-readable string that represents the state of
3060+ the object."""
3061+
3062+ ss = [ (ns[0],' %d: "%s"' % ns) for ns in self._strings ]
3063+ ss.append((-1,'searcher_string:'))
3064+ if self.eof_index >= 0:
3065+ ss.append ((self.eof_index,' %d: EOF' % self.eof_index))
3066+ if self.timeout_index >= 0:
3067+ ss.append ((self.timeout_index,' %d: TIMEOUT' % self.timeout_index))
3068+ ss.sort()
3069+ ss = zip(*ss)[1]
3070+ return '\n'.join(ss)
3071+
3072+ def search(self, buffer, freshlen, searchwindowsize=None):
3073+
3074+ """This searches 'buffer' for the first occurence of one of the search
3075+ strings. 'freshlen' must indicate the number of bytes at the end of
3076+ 'buffer' which have not been searched before. It helps to avoid
3077+ searching the same, possibly big, buffer over and over again.
3078+
3079+ See class spawn for the 'searchwindowsize' argument.
3080+
3081+ If there is a match this returns the index of that string, and sets
3082+ 'start', 'end' and 'match'. Otherwise, this returns -1. """
3083+
3084+ absurd_match = len(buffer)
3085+ first_match = absurd_match
3086+
3087+ # 'freshlen' helps a lot here. Further optimizations could
3088+ # possibly include:
3089+ #
3090+ # using something like the Boyer-Moore Fast String Searching
3091+ # Algorithm; pre-compiling the search through a list of
3092+ # strings into something that can scan the input once to
3093+ # search for all N strings; realize that if we search for
3094+ # ['bar', 'baz'] and the input is '...foo' we need not bother
3095+ # rescanning until we've read three more bytes.
3096+ #
3097+ # Sadly, I don't know enough about this interesting topic. /grahn
3098+
3099+ for index, s in self._strings:
3100+ if searchwindowsize is None:
3101+ # the match, if any, can only be in the fresh data,
3102+ # or at the very end of the old data
3103+ offset = -(freshlen+len(s))
3104+ else:
3105+ # better obey searchwindowsize
3106+ offset = -searchwindowsize
3107+ n = buffer.find(s, offset)
3108+ if n >= 0 and n < first_match:
3109+ first_match = n
3110+ best_index, best_match = index, s
3111+ if first_match == absurd_match:
3112+ return -1
3113+ self.match = best_match
3114+ self.start = first_match
3115+ self.end = self.start + len(self.match)
3116+ return best_index
3117+
3118+class searcher_re (object):
3119+
3120+ """This is regular expression string search helper for the
3121+ spawn.expect_any() method.
3122+
3123+ Attributes:
3124+
3125+ eof_index - index of EOF, or -1
3126+ timeout_index - index of TIMEOUT, or -1
3127+
3128+ After a successful match by the search() method the following attributes
3129+ are available:
3130+
3131+ start - index into the buffer, first byte of match
3132+ end - index into the buffer, first byte after match
3133+ match - the re.match object returned by a succesful re.search
3134+
3135+ """
3136+
3137+ def __init__(self, patterns):
3138+
3139+ """This creates an instance that searches for 'patterns' Where
3140+ 'patterns' may be a list or other sequence of compiled regular
3141+ expressions, or the EOF or TIMEOUT types."""
3142+
3143+ self.eof_index = -1
3144+ self.timeout_index = -1
3145+ self._searches = []
3146+ for n, s in zip(range(len(patterns)), patterns):
3147+ if s is EOF:
3148+ self.eof_index = n
3149+ continue
3150+ if s is TIMEOUT:
3151+ self.timeout_index = n
3152+ continue
3153+ self._searches.append((n, s))
3154+
3155+ def __str__(self):
3156+
3157+ """This returns a human-readable string that represents the state of
3158+ the object."""
3159+
3160+ ss = [ (n,' %d: re.compile("%s")' % (n,str(s.pattern))) for n,s in self._searches]
3161+ ss.append((-1,'searcher_re:'))
3162+ if self.eof_index >= 0:
3163+ ss.append ((self.eof_index,' %d: EOF' % self.eof_index))
3164+ if self.timeout_index >= 0:
3165+ ss.append ((self.timeout_index,' %d: TIMEOUT' % self.timeout_index))
3166+ ss.sort()
3167+ ss = zip(*ss)[1]
3168+ return '\n'.join(ss)
3169+
3170+ def search(self, buffer, freshlen, searchwindowsize=None):
3171+
3172+ """This searches 'buffer' for the first occurence of one of the regular
3173+ expressions. 'freshlen' must indicate the number of bytes at the end of
3174+ 'buffer' which have not been searched before.
3175+
3176+ See class spawn for the 'searchwindowsize' argument.
3177+
3178+ If there is a match this returns the index of that string, and sets
3179+ 'start', 'end' and 'match'. Otherwise, returns -1."""
3180+
3181+ absurd_match = len(buffer)
3182+ first_match = absurd_match
3183+ # 'freshlen' doesn't help here -- we cannot predict the
3184+ # length of a match, and the re module provides no help.
3185+ if searchwindowsize is None:
3186+ searchstart = 0
3187+ else:
3188+ searchstart = max(0, len(buffer)-searchwindowsize)
3189+ for index, s in self._searches:
3190+ match = s.search(buffer, searchstart)
3191+ if match is None:
3192+ continue
3193+ n = match.start()
3194+ if n < first_match:
3195+ first_match = n
3196+ the_match = match
3197+ best_index = index
3198+ if first_match == absurd_match:
3199+ return -1
3200+ self.start = first_match
3201+ self.match = the_match
3202+ self.end = self.match.end()
3203+ return best_index
3204+
3205+def which (filename):
3206+
3207+ """This takes a given filename; tries to find it in the environment path;
3208+ then checks if it is executable. This returns the full path to the filename
3209+ if found and executable. Otherwise this returns None."""
3210+
3211+ # Special case where filename already contains a path.
3212+ if os.path.dirname(filename) != '':
3213+ if os.access (filename, os.X_OK):
3214+ return filename
3215+
3216+ if not os.environ.has_key('PATH') or os.environ['PATH'] == '':
3217+ p = os.defpath
3218+ else:
3219+ p = os.environ['PATH']
3220+
3221+ # Oddly enough this was the one line that made Pexpect
3222+ # incompatible with Python 1.5.2.
3223+ #pathlist = p.split (os.pathsep)
3224+ pathlist = string.split (p, os.pathsep)
3225+
3226+ for path in pathlist:
3227+ f = os.path.join(path, filename)
3228+ if os.access(f, os.X_OK):
3229+ return f
3230+ return None
3231+
3232+def split_command_line(command_line):
3233+
3234+ """This splits a command line into a list of arguments. It splits arguments
3235+ on spaces, but handles embedded quotes, doublequotes, and escaped
3236+ characters. It's impossible to do this with a regular expression, so I
3237+ wrote a little state machine to parse the command line. """
3238+
3239+ arg_list = []
3240+ arg = ''
3241+
3242+ # Constants to name the states we can be in.
3243+ state_basic = 0
3244+ state_esc = 1
3245+ state_singlequote = 2
3246+ state_doublequote = 3
3247+ state_whitespace = 4 # The state of consuming whitespace between commands.
3248+ state = state_basic
3249+
3250+ for c in command_line:
3251+ if state == state_basic or state == state_whitespace:
3252+ if c == '\\': # Escape the next character
3253+ state = state_esc
3254+ elif c == r"'": # Handle single quote
3255+ state = state_singlequote
3256+ elif c == r'"': # Handle double quote
3257+ state = state_doublequote
3258+ elif c.isspace():
3259+ # Add arg to arg_list if we aren't in the middle of whitespace.
3260+ if state == state_whitespace:
3261+ None # Do nothing.
3262+ else:
3263+ arg_list.append(arg)
3264+ arg = ''
3265+ state = state_whitespace
3266+ else:
3267+ arg = arg + c
3268+ state = state_basic
3269+ elif state == state_esc:
3270+ arg = arg + c
3271+ state = state_basic
3272+ elif state == state_singlequote:
3273+ if c == r"'":
3274+ state = state_basic
3275+ else:
3276+ arg = arg + c
3277+ elif state == state_doublequote:
3278+ if c == r'"':
3279+ state = state_basic
3280+ else:
3281+ arg = arg + c
3282+
3283+ if arg != '':
3284+ arg_list.append(arg)
3285+ return arg_list
3286+
3287+# vi:ts=4:sw=4:expandtab:ft=python:

Subscribers

People subscribed via source and target branches

to all changes: