##// END OF EJS Templates
sshpeer: more thorough shell quoting...
Matt Mackall -
r23671:e3f30068 stable
parent child Browse files
Show More
@@ -1,250 +1,255
1 # sshpeer.py - ssh repository proxy class for mercurial
1 # sshpeer.py - ssh repository proxy class for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 from i18n import _
9 from i18n import _
10 import util, error, wireproto
10 import util, error, wireproto
11
11
12 class remotelock(object):
12 class remotelock(object):
13 def __init__(self, repo):
13 def __init__(self, repo):
14 self.repo = repo
14 self.repo = repo
15 def release(self):
15 def release(self):
16 self.repo.unlock()
16 self.repo.unlock()
17 self.repo = None
17 self.repo = None
18 def __del__(self):
18 def __del__(self):
19 if self.repo:
19 if self.repo:
20 self.release()
20 self.release()
21
21
22 def _serverquote(s):
22 def _serverquote(s):
23 if not s:
24 return s
23 '''quote a string for the remote shell ... which we assume is sh'''
25 '''quote a string for the remote shell ... which we assume is sh'''
24 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
26 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
25 return s
27 return s
26 return "'%s'" % s.replace("'", "'\\''")
28 return "'%s'" % s.replace("'", "'\\''")
27
29
28 class sshpeer(wireproto.wirepeer):
30 class sshpeer(wireproto.wirepeer):
29 def __init__(self, ui, path, create=False):
31 def __init__(self, ui, path, create=False):
30 self._url = path
32 self._url = path
31 self.ui = ui
33 self.ui = ui
32 self.pipeo = self.pipei = self.pipee = None
34 self.pipeo = self.pipei = self.pipee = None
33
35
34 u = util.url(path, parsequery=False, parsefragment=False)
36 u = util.url(path, parsequery=False, parsefragment=False)
35 if u.scheme != 'ssh' or not u.host or u.path is None:
37 if u.scheme != 'ssh' or not u.host or u.path is None:
36 self._abort(error.RepoError(_("couldn't parse location %s") % path))
38 self._abort(error.RepoError(_("couldn't parse location %s") % path))
37
39
38 self.user = u.user
40 self.user = u.user
39 if u.passwd is not None:
41 if u.passwd is not None:
40 self._abort(error.RepoError(_("password in URL not supported")))
42 self._abort(error.RepoError(_("password in URL not supported")))
41 self.host = u.host
43 self.host = u.host
42 self.port = u.port
44 self.port = u.port
43 self.path = u.path or "."
45 self.path = u.path or "."
44
46
45 sshcmd = self.ui.config("ui", "ssh", "ssh")
47 sshcmd = self.ui.config("ui", "ssh", "ssh")
46 remotecmd = self.ui.config("ui", "remotecmd", "hg")
48 remotecmd = self.ui.config("ui", "remotecmd", "hg")
47
49
48 args = util.sshargs(sshcmd, self.host, self.user, self.port)
50 args = util.sshargs(sshcmd,
51 _serverquote(self.host),
52 _serverquote(self.user),
53 _serverquote(self.port))
49
54
50 if create:
55 if create:
51 cmd = '%s %s %s' % (sshcmd, args,
56 cmd = '%s %s %s' % (sshcmd, args,
52 util.shellquote("%s init %s" %
57 util.shellquote("%s init %s" %
53 (_serverquote(remotecmd), _serverquote(self.path))))
58 (_serverquote(remotecmd), _serverquote(self.path))))
54 ui.debug('running %s\n' % cmd)
59 ui.debug('running %s\n' % cmd)
55 res = util.system(cmd, out=ui.fout)
60 res = util.system(cmd, out=ui.fout)
56 if res != 0:
61 if res != 0:
57 self._abort(error.RepoError(_("could not create remote repo")))
62 self._abort(error.RepoError(_("could not create remote repo")))
58
63
59 self._validaterepo(sshcmd, args, remotecmd)
64 self._validaterepo(sshcmd, args, remotecmd)
60
65
61 def url(self):
66 def url(self):
62 return self._url
67 return self._url
63
68
64 def _validaterepo(self, sshcmd, args, remotecmd):
69 def _validaterepo(self, sshcmd, args, remotecmd):
65 # cleanup up previous run
70 # cleanup up previous run
66 self.cleanup()
71 self.cleanup()
67
72
68 cmd = '%s %s %s' % (sshcmd, args,
73 cmd = '%s %s %s' % (sshcmd, args,
69 util.shellquote("%s -R %s serve --stdio" %
74 util.shellquote("%s -R %s serve --stdio" %
70 (_serverquote(remotecmd), _serverquote(self.path))))
75 (_serverquote(remotecmd), _serverquote(self.path))))
71 self.ui.debug('running %s\n' % cmd)
76 self.ui.debug('running %s\n' % cmd)
72 cmd = util.quotecommand(cmd)
77 cmd = util.quotecommand(cmd)
73
78
74 # while self.subprocess isn't used, having it allows the subprocess to
79 # while self.subprocess isn't used, having it allows the subprocess to
75 # to clean up correctly later
80 # to clean up correctly later
76 self.pipeo, self.pipei, self.pipee, self.subprocess = util.popen4(cmd)
81 self.pipeo, self.pipei, self.pipee, self.subprocess = util.popen4(cmd)
77
82
78 # skip any noise generated by remote shell
83 # skip any noise generated by remote shell
79 self._callstream("hello")
84 self._callstream("hello")
80 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
85 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
81 lines = ["", "dummy"]
86 lines = ["", "dummy"]
82 max_noise = 500
87 max_noise = 500
83 while lines[-1] and max_noise:
88 while lines[-1] and max_noise:
84 l = r.readline()
89 l = r.readline()
85 self.readerr()
90 self.readerr()
86 if lines[-1] == "1\n" and l == "\n":
91 if lines[-1] == "1\n" and l == "\n":
87 break
92 break
88 if l:
93 if l:
89 self.ui.debug("remote: ", l)
94 self.ui.debug("remote: ", l)
90 lines.append(l)
95 lines.append(l)
91 max_noise -= 1
96 max_noise -= 1
92 else:
97 else:
93 self._abort(error.RepoError(_('no suitable response from '
98 self._abort(error.RepoError(_('no suitable response from '
94 'remote hg')))
99 'remote hg')))
95
100
96 self._caps = set()
101 self._caps = set()
97 for l in reversed(lines):
102 for l in reversed(lines):
98 if l.startswith("capabilities:"):
103 if l.startswith("capabilities:"):
99 self._caps.update(l[:-1].split(":")[1].split())
104 self._caps.update(l[:-1].split(":")[1].split())
100 break
105 break
101
106
102 def _capabilities(self):
107 def _capabilities(self):
103 return self._caps
108 return self._caps
104
109
105 def readerr(self):
110 def readerr(self):
106 s = util.readpipe(self.pipee)
111 s = util.readpipe(self.pipee)
107 if s:
112 if s:
108 for l in s.splitlines():
113 for l in s.splitlines():
109 self.ui.status(_("remote: "), l, '\n')
114 self.ui.status(_("remote: "), l, '\n')
110
115
111 def _abort(self, exception):
116 def _abort(self, exception):
112 self.cleanup()
117 self.cleanup()
113 raise exception
118 raise exception
114
119
115 def cleanup(self):
120 def cleanup(self):
116 if self.pipeo is None:
121 if self.pipeo is None:
117 return
122 return
118 self.pipeo.close()
123 self.pipeo.close()
119 self.pipei.close()
124 self.pipei.close()
120 try:
125 try:
121 # read the error descriptor until EOF
126 # read the error descriptor until EOF
122 for l in self.pipee:
127 for l in self.pipee:
123 self.ui.status(_("remote: "), l)
128 self.ui.status(_("remote: "), l)
124 except (IOError, ValueError):
129 except (IOError, ValueError):
125 pass
130 pass
126 self.pipee.close()
131 self.pipee.close()
127
132
128 __del__ = cleanup
133 __del__ = cleanup
129
134
130 def _callstream(self, cmd, **args):
135 def _callstream(self, cmd, **args):
131 self.ui.debug("sending %s command\n" % cmd)
136 self.ui.debug("sending %s command\n" % cmd)
132 self.pipeo.write("%s\n" % cmd)
137 self.pipeo.write("%s\n" % cmd)
133 _func, names = wireproto.commands[cmd]
138 _func, names = wireproto.commands[cmd]
134 keys = names.split()
139 keys = names.split()
135 wireargs = {}
140 wireargs = {}
136 for k in keys:
141 for k in keys:
137 if k == '*':
142 if k == '*':
138 wireargs['*'] = args
143 wireargs['*'] = args
139 break
144 break
140 else:
145 else:
141 wireargs[k] = args[k]
146 wireargs[k] = args[k]
142 del args[k]
147 del args[k]
143 for k, v in sorted(wireargs.iteritems()):
148 for k, v in sorted(wireargs.iteritems()):
144 self.pipeo.write("%s %d\n" % (k, len(v)))
149 self.pipeo.write("%s %d\n" % (k, len(v)))
145 if isinstance(v, dict):
150 if isinstance(v, dict):
146 for dk, dv in v.iteritems():
151 for dk, dv in v.iteritems():
147 self.pipeo.write("%s %d\n" % (dk, len(dv)))
152 self.pipeo.write("%s %d\n" % (dk, len(dv)))
148 self.pipeo.write(dv)
153 self.pipeo.write(dv)
149 else:
154 else:
150 self.pipeo.write(v)
155 self.pipeo.write(v)
151 self.pipeo.flush()
156 self.pipeo.flush()
152
157
153 return self.pipei
158 return self.pipei
154
159
155 def _callcompressable(self, cmd, **args):
160 def _callcompressable(self, cmd, **args):
156 return self._callstream(cmd, **args)
161 return self._callstream(cmd, **args)
157
162
158 def _call(self, cmd, **args):
163 def _call(self, cmd, **args):
159 self._callstream(cmd, **args)
164 self._callstream(cmd, **args)
160 return self._recv()
165 return self._recv()
161
166
162 def _callpush(self, cmd, fp, **args):
167 def _callpush(self, cmd, fp, **args):
163 r = self._call(cmd, **args)
168 r = self._call(cmd, **args)
164 if r:
169 if r:
165 return '', r
170 return '', r
166 while True:
171 while True:
167 d = fp.read(4096)
172 d = fp.read(4096)
168 if not d:
173 if not d:
169 break
174 break
170 self._send(d)
175 self._send(d)
171 self._send("", flush=True)
176 self._send("", flush=True)
172 r = self._recv()
177 r = self._recv()
173 if r:
178 if r:
174 return '', r
179 return '', r
175 return self._recv(), ''
180 return self._recv(), ''
176
181
177 def _calltwowaystream(self, cmd, fp, **args):
182 def _calltwowaystream(self, cmd, fp, **args):
178 r = self._call(cmd, **args)
183 r = self._call(cmd, **args)
179 if r:
184 if r:
180 # XXX needs to be made better
185 # XXX needs to be made better
181 raise util.Abort('unexpected remote reply: %s' % r)
186 raise util.Abort('unexpected remote reply: %s' % r)
182 while True:
187 while True:
183 d = fp.read(4096)
188 d = fp.read(4096)
184 if not d:
189 if not d:
185 break
190 break
186 self._send(d)
191 self._send(d)
187 self._send("", flush=True)
192 self._send("", flush=True)
188 return self.pipei
193 return self.pipei
189
194
190 def _recv(self):
195 def _recv(self):
191 l = self.pipei.readline()
196 l = self.pipei.readline()
192 if l == '\n':
197 if l == '\n':
193 err = []
198 err = []
194 while True:
199 while True:
195 line = self.pipee.readline()
200 line = self.pipee.readline()
196 if line == '-\n':
201 if line == '-\n':
197 break
202 break
198 err.extend([line])
203 err.extend([line])
199 if len(err) > 0:
204 if len(err) > 0:
200 # strip the trailing newline added to the last line server-side
205 # strip the trailing newline added to the last line server-side
201 err[-1] = err[-1][:-1]
206 err[-1] = err[-1][:-1]
202 self._abort(error.OutOfBandError(*err))
207 self._abort(error.OutOfBandError(*err))
203 self.readerr()
208 self.readerr()
204 try:
209 try:
205 l = int(l)
210 l = int(l)
206 except ValueError:
211 except ValueError:
207 self._abort(error.ResponseError(_("unexpected response:"), l))
212 self._abort(error.ResponseError(_("unexpected response:"), l))
208 return self.pipei.read(l)
213 return self.pipei.read(l)
209
214
210 def _send(self, data, flush=False):
215 def _send(self, data, flush=False):
211 self.pipeo.write("%d\n" % len(data))
216 self.pipeo.write("%d\n" % len(data))
212 if data:
217 if data:
213 self.pipeo.write(data)
218 self.pipeo.write(data)
214 if flush:
219 if flush:
215 self.pipeo.flush()
220 self.pipeo.flush()
216 self.readerr()
221 self.readerr()
217
222
218 def lock(self):
223 def lock(self):
219 self._call("lock")
224 self._call("lock")
220 return remotelock(self)
225 return remotelock(self)
221
226
222 def unlock(self):
227 def unlock(self):
223 self._call("unlock")
228 self._call("unlock")
224
229
225 def addchangegroup(self, cg, source, url, lock=None):
230 def addchangegroup(self, cg, source, url, lock=None):
226 '''Send a changegroup to the remote server. Return an integer
231 '''Send a changegroup to the remote server. Return an integer
227 similar to unbundle(). DEPRECATED, since it requires locking the
232 similar to unbundle(). DEPRECATED, since it requires locking the
228 remote.'''
233 remote.'''
229 d = self._call("addchangegroup")
234 d = self._call("addchangegroup")
230 if d:
235 if d:
231 self._abort(error.RepoError(_("push refused: %s") % d))
236 self._abort(error.RepoError(_("push refused: %s") % d))
232 while True:
237 while True:
233 d = cg.read(4096)
238 d = cg.read(4096)
234 if not d:
239 if not d:
235 break
240 break
236 self.pipeo.write(d)
241 self.pipeo.write(d)
237 self.readerr()
242 self.readerr()
238
243
239 self.pipeo.flush()
244 self.pipeo.flush()
240
245
241 self.readerr()
246 self.readerr()
242 r = self._recv()
247 r = self._recv()
243 if not r:
248 if not r:
244 return 1
249 return 1
245 try:
250 try:
246 return int(r)
251 return int(r)
247 except ValueError:
252 except ValueError:
248 self._abort(error.ResponseError(_("unexpected response:"), r))
253 self._abort(error.ResponseError(_("unexpected response:"), r))
249
254
250 instance = sshpeer
255 instance = sshpeer
General Comments 0
You need to be logged in to leave comments. Login now