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