decommission gateways.php
[racktables] / wwwroot / inc / remote.php
CommitLineData
7cb57a03
AA
1<?php
2
cddbb9fd
DO
3# This file is a part of RackTables, a datacenter and server room management
4# framework. See accompanying file "COPYING" for the full copyright and
5# licensing information.
6
9d3ef879
DO
7// translating functions maps
8$breedfunc = array
9(
10 'ios12-getcdpstatus-main' => 'ios12ReadCDPStatus',
11 'ios12-getlldpstatus-main' => 'ios12ReadLLDPStatus',
12 'ios12-get8021q-main' => 'ios12ReadVLANConfig',
13 'ios12-get8021q-top' => 'ios12ScanTopLevel',
14 'ios12-get8021q-readport' => 'ios12PickSwitchportCommand',
15 'ios12-get8021q-readvlan' => 'ios12PickVLANCommand',
16 'ios12-getportstatus-main' => 'ciscoReadInterfaceStatus',
17 'ios12-getmaclist-main' => 'ios12ReadMacList',
18 'ios12-xlatepushq-main' => 'ios12TranslatePushQueue',
19 'ios12-getallconf-main' => 'ios12SpotConfigText',
20 'fdry5-get8021q-main' => 'fdry5ReadVLANConfig',
21 'fdry5-get8021q-top' => 'fdry5ScanTopLevel',
22 'fdry5-get8021q-readvlan' => 'fdry5PickVLANSubcommand',
23 'fdry5-get8021q-readport' => 'fdry5PickInterfaceSubcommand',
24 'fdry5-xlatepushq-main' => 'fdry5TranslatePushQueue',
25 'fdry5-getallconf-main' => 'fdry5SpotConfigText',
26 'vrp53-getlldpstatus-main' => 'vrp5xReadLLDPStatus',
27 'vrp53-get8021q-main' => 'vrp53ReadVLANConfig',
28 'vrp53-get8021q-top' => 'vrp53ScanTopLevel',
29 'vrp53-get8021q-readport' => 'vrp53PickInterfaceSubcommand',
30 'vrp53-getportstatus-main' => 'vrpReadInterfaceStatus',
31 'vrp53-getmaclist-main' => 'vrp53ReadMacList',
32 'vrp53-xlatepushq-main' => 'vrp53TranslatePushQueue',
33 'vrp53-getallconf-main' => 'vrp5xSpotConfigText',
34 'vrp55-getlldpstatus-main' => 'vrp5xReadLLDPStatus',
35 'vrp55-get8021q-main' => 'vrp55Read8021QConfig',
36 'vrp55-getportstatus-main' => 'vrpReadInterfaceStatus',
37 'vrp55-getmaclist-main' => 'vrp55ReadMacList',
38 'vrp55-xlatepushq-main' => 'vrp55TranslatePushQueue',
39 'vrp55-getallconf-main' => 'vrp5xSpotConfigText',
40 'nxos4-getcdpstatus-main' => 'ios12ReadCDPStatus',
41 'nxos4-getlldpstatus-main' => 'nxos4ReadLLDPStatus',
42 'nxos4-get8021q-main' => 'nxos4Read8021QConfig',
43 'nxos4-get8021q-top' => 'nxos4ScanTopLevel',
44 'nxos4-get8021q-readport' => 'nxos4PickSwitchportCommand',
45 'nxos4-get8021q-readvlan' => 'nxos4PickVLANCommand',
46 'nxos4-getportstatus-main' => 'ciscoReadInterfaceStatus',
47 'nxos4-getmaclist-main' => 'nxos4ReadMacList',
48 'nxos4-xlatepushq-main' => 'nxos4TranslatePushQueue',
49 'nxos4-getallconf-main' => 'nxos4SpotConfigText',
50 'dlink-get8021q-main' => 'dlinkReadVLANConfig',
51 'dlink-get8021q-top' => 'dlinkScanTopLevel',
52 'dlink-get8021q-pickvlan' => 'dlinkPickVLANCommand',
53 'dlink-getportstatus-main' => 'dlinkReadInterfaceStatus',
54 'dlink-getmaclist-main' => 'dlinkReadMacList',
55 'dlink-xlatepushq-main' => 'dlinkTranslatePushQueue',
56 'linux-get8021q-main' => 'linuxReadVLANConfig',
57 'linux-getportstatus-main' => 'linuxReadInterfaceStatus',
58 'linux-getmaclist-main' => 'linuxReadMacList',
59 'linux-xlatepushq-main' => 'linuxTranslatePushQueue',
60 'xos12-getlldpstatus-main' => 'xos12ReadLLDPStatus',
61 'xos12-get8021q-main' => 'xos12Read8021QConfig',
62 'xos12-xlatepushq-main' => 'xos12TranslatePushQueue',
63 'xos12-getallconf-main' => 'xos12SpotConfigText',
64 'jun10-get8021q-main' => 'jun10Read8021QConfig',
65 'jun10-xlatepushq-main' => 'jun10TranslatePushQueue',
66 'jun10-getallconf-main' => 'jun10SpotConfigText',
67 'ftos8-xlatepushq-main' => 'ftos8TranslatePushQueue',
68 'ftos8-getlldpstatus-main' => 'ftos8ReadLLDPStatus',
69 'ftos8-getmaclist-main' => 'ftos8ReadMacList',
70 'ftos8-getportstatus-main' => 'ftos8ReadInterfaceStatus',
71 'ftos8-get8021q-main' => 'ftos8Read8021QConfig',
72 'ftos8-getallconf-main' => 'ftos8SpotConfigText',
73 'air12-xlatepushq-main' => 'air12TranslatePushQueue',
74 'air12-getallconf-main' => 'ios12SpotConfigText',
75 'eos4-getallconf-main' => 'eos4SpotConfigText',
76 'eos4-getmaclist-main' => 'eos4ReadMacList',
77 'eos4-getportstatus-main' => 'eos4ReadInterfaceStatus',
78 'eos4-getlldpstatus-main' => 'eos4ReadLLDPStatus',
79 'eos4-get8021q-main' => 'eos4Read8021QConfig',
80 'eos4-xlatepushq-main' => 'eos4TranslatePushQueue',
81 'ros11-getallconf-main' => 'ros11SpotConfigText',
82 'ros11-xlatepushq-main' => 'ros11TranslatePushQueue',
83 'ros11-getlldpstatus-main' => 'ros11ReadLLDPStatus',
84 'ros11-getportstatus-main' => 'ros11ReadInterfaceStatus',
85 'ros11-getmaclist-main' => 'ros11ReadMacList',
86 'ros11-get8021q-main' => 'ros11Read8021QConfig',
87 'ros11-get8021q-scantop' => 'ros11Read8021QScanTop',
88 'ros11-get8021q-vlandb' => 'ros11Read8021QVLANDatabase',
89 'ros11-get8021q-readports' => 'ros11Read8021QPorts',
90 'iosxr4-xlatepushq-main' => 'iosxr4TranslatePushQueue',
91 'iosxr4-getallconf-main' => 'iosxr4SpotConfigText',
92 'ucs-xlatepushq-main' => 'ucsTranslatePushQueue',
93 'ucs-getinventory-main' => 'ucsReadInventory',
94);
95
96function detectDeviceBreed ($object_id)
97{
98 $breed_by_swcode = array
99 (
100 251 => 'ios12',
101 252 => 'ios12',
102 254 => 'ios12',
103 963 => 'nxos4', // NX-OS 4.0
104 964 => 'nxos4', // NX-OS 4.1
105 1365 => 'nxos4', // NX-OS 4.2
106 1410 => 'nxos4', // NX-OS 5.0, seems compatible
107 1411 => 'nxos4', // NX-OS 5.1
108 1643 => 'nxos4', // NX-OS 6.0
109 1352 => 'xos12',
110 1360 => 'vrp53',
111 1361 => 'vrp55',
112 1369 => 'vrp55', // VRP versions 5.5 and 5.7 seem to be compatible
113 1363 => 'fdry5',
114 1367 => 'jun10', # 10S
115 1597 => 'jun10', # 10R
116 1598 => 'jun10', # 11R
117 1599 => 'jun10', # 12R
118 1594 => 'ftos8',
119 1673 => 'air12', # AIR IOS 12.3
120 1674 => 'air12', # AIR IOS 12.4
121 1675 => 'eos4',
122 1759 => 'iosxr4', # Cisco IOS XR 4.2
123 1786 => 'ros11', # Marvell ROS 1.1
124 242 => 'linux',
125 243 => 'linux',
126 1331 => 'linux',
127 1332 => 'linux',
128 1333 => 'linux',
129 1334 => 'linux',
130 1395 => 'linux',
131 1396 => 'linux',
132 );
133 for ($i = 225; $i <= 235; $i++)
134 $breed_by_swcode[$i] = 'linux';
135 for ($i = 418; $i <= 436; $i++)
136 $breed_by_swcode[$i] = 'linux';
137 for ($i = 1417; $i <= 1422; $i++)
138 $breed_by_swcode[$i] = 'linux';
139 $breed_by_hwcode = array();
140 for ($i = 589; $i <= 637; $i++)
141 $breed_by_hwcode[$i] = 'dlink';
142 $breed_by_mgmtcode = array (1788 => 'ucs');
143 foreach (getAttrValues ($object_id) as $record)
144 if ($record['id'] == 4 and array_key_exists ($record['key'], $breed_by_swcode))
145 return $breed_by_swcode[$record['key']];
146 elseif ($record['id'] == 2 and array_key_exists ($record['key'], $breed_by_hwcode))
147 return $breed_by_hwcode[$record['key']];
148 elseif ($record['id'] == 30 and array_key_exists ($record['key'], $breed_by_mgmtcode))
149 return $breed_by_mgmtcode[$record['key']];
150 return '';
151}
152
153function validBreedFunction ($breed, $command)
154{
155 global $breedfunc;
156 return array_key_exists ("${breed}-${command}-main", $breedfunc);
157}
158
159function assertBreedFunction ($breed, $command)
160{
161 global $breedfunc;
162 if (! validBreedFunction ($breed, $command))
163 throw new RTGatewayError ('unsupported command for this breed');
164}
165
7cb57a03
AA
166function queryDevice ($object_id, $command)
167{
168 $breed = detectDeviceBreed ($object_id);
169 if (empty ($breed))
170 throw new RTGatewayError ("Can not determine device breed");
171
172 if (! validBreedFunction ($breed, $command))
173 throw new RTGatewayError ("unsupported command '$command' for the breed '$breed'");
174
175 require_once 'deviceconfig.php';
176 global $breedfunc;
177 if (! is_callable ($breedfunc["$breed-$command-main"]))
178 throw new RTGatewayError ("undeclared function '" . $breedfunc["$breed-$command-main"] . "'");
179 $query = translateDeviceCommands ($object_id, array (array ('opcode' => $command)));
180 if ($command == 'xlatepushq')
181 return $query;
182 else
183 {
184 $answer = queryTerminal ($object_id, $query, FALSE);
185 return $breedfunc["$breed-$command-main"] ($answer);
186 }
187}
188
189function translateDeviceCommands ($object_id, $crq, $vlan_names = NULL)
190{
191 require_once 'deviceconfig.php';
192 $breed = detectDeviceBreed ($object_id);
193 if (empty ($breed))
194 throw new RTGatewayError ("Can not determine device breed");
195
196 if (! validBreedFunction ($breed, 'xlatepushq'))
197 throw new RTGatewayError ("unsupported command 'xlatepushq' for the breed '$breed'");
198
199 global $breedfunc;
200 return $breedfunc["$breed-xlatepushq-main"] ($object_id, $crq, $vlan_names);
201}
202
203// This function returns a text output received from the device
204// You can override connection settings by implement a callback named 'terminal_settings'.
205// Errors are thrown as exceptions if not $tolerate_remote_errors, and shown as warnings otherwise.
206function queryTerminal ($object_id, $commands, $tolerate_remote_errors = TRUE)
207{
208 $objectInfo = spotEntity ('object', $object_id);
209 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
210 if (count ($endpoints) == 0)
211 throw new RTGatewayError ('no management address set');
212 if (count ($endpoints) > 1)
213 throw new RTGatewayError ('cannot pick management address');
214
5d2988fb 215 // telnet prompt and mode specification
7cb57a03
AA
216 switch ($breed = detectDeviceBreed ($object_id))
217 {
77b7e03c
DO
218 case 'ios12':
219 case 'fdry5':
7a0e9964 220 case 'ftos8':
a3447df9 221 $protocol = 'netcat'; // default is netcat mode
349394c1 222 $prompt = '^(Login|Username|Password): $|^\S+[>#]$'; // set the prompt in case user would like to specify telnet protocol
7cb57a03 223 break;
d4a957e0
DO
224 case 'air12':
225 $protocol = 'telnet'; # Aironet IOS is broken
226 $prompt = '^(Username|Password): $|^\S+[>#]$';
227 break;
7cb57a03
AA
228 case 'vrp53':
229 case 'vrp55':
5d2988fb 230 $protocol = 'telnet';
338465a8 231 $prompt = '^\[[^[\]]+\]$|^<[^<>]+>$|^(Username|Password):$|(?:\[Y\/N\]|\(Y\/N\)\[[YN]\]):?$';
7cb57a03
AA
232 break;
233 case 'nxos4':
5d2988fb 234 $protocol = 'telnet';
7cb57a03
AA
235 $prompt = '[>:#] $';
236 break;
237 case 'xos12':
5d2988fb 238 $protocol = 'telnet';
18997a72 239 $prompt = ': $|\.\d+ # $|\?\s*\([Yy]\/[Nn]\)\s*$';
7cb57a03
AA
240 break;
241 case 'jun10':
5d2988fb 242 $protocol = 'telnet';
7cb57a03
AA
243 $prompt = '^login: $|^Password:$|^\S+@\S+[>#] $';
244 break;
85703049
DO
245 case 'eos4':
246 $protocol = 'telnet'; # strict RFC854 implementation, netcat won't work
247 $prompt = '^(\xf2?login|Username|Password): $|^\S+[>#]$';
248 break;
919bb8b6
DO
249 case 'ros11':
250 $protocol = 'netcat'; # see ftos8 case
251 $prompt = '^(User Name|\rPassword):$|^\r?\S+# $';
252 break;
7a0e9964
AA
253 case 'iosxr4':
254 $protocol = 'telnet';
255 $prompt = '^\r?(Login|Username|Password): $|^\r?\S+[>#]$';
256 break;
be91a564
JS
257 case 'ucs':
258 $protocol = 'ucssdk';
259 break;
5d2988fb
AA
260 default:
261 $protocol = 'netcat';
262 $prompt = NULL;
7cb57a03
AA
263 }
264
265 // set the default settings before calling user-defined callback
266 $settings = array
267 (
268 'hostname' => $endpoints[0],
a3447df9 269 'protocol' => $protocol,
7cb57a03
AA
270 'port' => NULL,
271 'prompt' => $prompt,
272 'username' => NULL,
273 'password' => NULL,
ada8a703 274 'timeout' => 15,
7cb57a03 275 'connect_timeout' => 2,
a9edde6c 276 'prompt_delay' => 0.001, # 1ms
7cb57a03
AA
277 'sudo_user' => NULL,
278 'identity_file' => NULL,
279 );
280 if (is_callable ('terminal_settings'))
281 call_user_func ('terminal_settings', $objectInfo, array (&$settings)); // override settings
282
01b0c68f
AA
283 if (! isset ($settings['port']) and $settings['protocol'] == 'netcat')
284 $settings['port'] = 23;
285
7cb57a03
AA
286 $params = array ( $settings['hostname'] );
287 $params_from_settings = array();
288 switch ($settings['protocol'])
289 {
290 case 'telnet':
01b0c68f
AA
291 case 'netcat':
292 // prepend command list with vendor-specific disabling pager command
7cb57a03
AA
293 switch ($breed)
294 {
295 case 'ios12':
296 $commands = "terminal length 0\n" . $commands;
297 break;
298 case 'nxos4':
52bf6129 299 case 'air12':
0a2d7ff1 300 case 'ftos8':
7cb57a03
AA
301 $commands = "terminal length 0\nterminal no monitor\n" . $commands;
302 break;
303 case 'xos12':
304 $commands = "disable clipaging\n" . $commands;
305 break;
306 case 'vrp55':
307 $commands = "screen-length 0 temporary\n" . $commands;
308 break;
309 case 'fdry5':
310 $commands = "skip-page-display\n" . $commands;
311 break;
312 case 'jun10':
184ec25d 313 $commands = "set cli screen-length 0\n" . $commands;
7cb57a03 314 break;
85703049
DO
315 case 'eos4':
316 $commands = "enable\nno terminal monitor\nterminal length 0\n" . $commands;
317 break;
919bb8b6
DO
318 case 'ros11':
319 $commands = "terminal datadump\n" . $commands;
320 $commands .= "\n\n"; # temporary workaround for telnet server
321 break;
7a0e9964
AA
322 case 'iosxr4':
323 $commands = "terminal length 0\nterminal monitor disable\n" . $commands;
324 break;
fc3a6bd0
IE
325 case 'dlink':
326 $commands = "disable clipaging\n" . $commands;
327 break;
7cb57a03
AA
328 }
329 // prepend telnet commands by credentials
330 if (isset ($settings['password']))
331 $commands = $settings['password'] . "\n" . $commands;
332 if (isset ($settings['username']))
333 $commands = $settings['username'] . "\n" . $commands;
01b0c68f
AA
334 // command-line options are specific to client: telnet or netcat
335 switch ($settings['protocol'])
336 {
337 case 'telnet':
338 $params_from_settings['port'] = 'port';
339 $params_from_settings['prompt'] = 'prompt';
340 $params_from_settings['connect-timeout'] = 'connect_timeout';
341 $params_from_settings['timeout'] = 'timeout';
342 $params_from_settings['prompt-delay'] = 'prompt_delay';
343 break;
344 case 'netcat':
56930ac4 345 $params_from_settings['p'] = 'port';
01b0c68f 346 $params_from_settings['w'] = 'timeout';
56930ac4 347 $params_from_settings['b'] = 'ncbin';
01b0c68f
AA
348 break;
349 }
7cb57a03
AA
350 break;
351 case 'ssh':
352 $params_from_settings['port'] = 'port';
424604b4 353 $params_from_settings['proto'] = 'proto';
7cb57a03
AA
354 $params_from_settings['username'] = 'username';
355 $params_from_settings['i'] = 'identity_file';
356 $params_from_settings['sudo-user'] = 'sudo_user';
357 $params_from_settings['connect-timeout'] = 'connect_timeout';
358 break;
be91a564
JS
359 case 'ucssdk': # remote XML through a Python backend
360 $params = array(); # reset
361 # UCS in its current implementation besides the terminal_settings() provides
362 # an additional username/password feed through the HTML from. Whenever the
363 # user provides the credentials through the form, use these instead of the
364 # credentials [supposedly] set by terminal_settings().
365 if ($script_mode != TRUE && ! isCheckSet ('use_terminal_settings'))
366 {
367 genericAssertion ('ucs_login', 'string');
368 genericAssertion ('ucs_password', 'string');
369 $settings['username'] = $_REQUEST['ucs_login'];
370 $settings['password'] = $_REQUEST['ucs_password'];
371 }
372 foreach (array ('hostname', 'username', 'password') as $item)
373 if (empty ($settings[$item]))
374 throw new RTGatewayError ("${item} not available, check terminal_settings()");
375 $commands = "login ${settings['hostname']} ${settings['username']} ${settings['password']}\n" . $commands;
376 break;
7cb57a03 377 default:
c0b84dfb 378 throw RTGatewayError ("Invalid terminal protocol '${settings['protocol']}' specified");
7cb57a03
AA
379 }
380 foreach ($params_from_settings as $param_name => $setting_name)
381 if (isset ($settings[$setting_name]))
01b0c68f
AA
382 if (is_int ($param_name))
383 $params[] = $settings[$setting_name];
384 else
385 $params[$param_name] = $settings[$setting_name];
7cb57a03
AA
386
387 $ret_code = callScript ($settings['protocol'], $params, $commands, $out, $errors);
01b0c68f 388 if ($settings['protocol'] != 'ssh' || ! $tolerate_remote_errors)
7cb57a03
AA
389 {
390 if (! empty ($errors))
e75026f8 391 throw new RTGatewayError ("${settings['protocol']} error: " . rtrim ($errors));
7cb57a03
AA
392 elseif ($ret_code !== 0)
393 throw new RTGatewayError ("${settings['protocol']} error: result code $ret_code");
394 }
395 elseif (! empty ($errors)) // ssh and not tolerate and non-empty $errors
396 foreach (explode ("\n", $errors) as $line)
397 if (strlen ($line))
398 showWarning ("${settings['protocol']} ${settings['hostname']}: $line");
399 return strtr($out, array("\r" => "")); // cut ^M symbols
400}
401
402function callScript ($gwname, $params, $in, &$out, &$errors)
403{
404 global $racktables_gwdir, $local_gwdir;
405 if (isset ($local_gwdir) && file_exists ("$local_gwdir/$gwname"))
406 $dir = $local_gwdir;
407 elseif (isset ($racktables_gwdir) && file_exists ("$racktables_gwdir/$gwname"))
408 $dir = $racktables_gwdir;
409 if (! isset ($dir))
c0b84dfb 410 throw new RTGatewayError ("Could not find the gateway file called '$gwname'");
7cb57a03
AA
411
412 $cmd_line = "./$gwname";
413 foreach ($params as $key => $value)
414 {
415 if (! isset ($value))
416 continue;
417 if (preg_match ('/^\d+$/', $key))
418 $cmd_line .= " " . escapeshellarg ($value);
419 else
420 {
421 if (strlen ($key) == 1)
422 $cmd_line .= " " . escapeshellarg ("-$key") . " " . escapeshellarg ($value);
423 else
424 $cmd_line .= " " . escapeshellarg("--$key=$value");
425 }
426 }
424604b4 427
7cb57a03
AA
428 $pipes = array();
429 $child = proc_open
430 (
431 $cmd_line,
432 array (
433 0 => array ('pipe', 'r'),
434 1 => array ('pipe', 'w'),
435 2 => array ('pipe', 'w'),
436 ),
437 $pipes,
438 $dir
439 );
440 if (! is_resource ($child))
c0b84dfb 441 throw new RTGatewayError ("cant execute $dir/$gwname");
15da1b37
AA
442
443 $buff_size = 4096;
444 $write_left = array ($pipes[0]);
445 $read_left = array ($pipes[1], $pipes[2]);
446 $write_fd = $write_left;
447 $read_fd = $read_left;
448 $except_fd = array();
e9222a8a
AA
449 $out = '';
450 $errors = '';
15da1b37
AA
451 while ((! empty ($read_fd) || ! empty ($write_fd)) && stream_select ($read_fd, $write_fd, $except_fd, NULL))
452 {
453 foreach ($write_fd as $fd)
454 {
455 $written = fwrite ($fd, $in, $buff_size);
456 $in = substr ($in, $written);
457 if ($written == 0 || empty ($in))
458 {
459 // close input fd
460 $write_left = array_diff ($write_left, array ($fd));
461 fclose ($fd);
462 }
463 }
464 foreach ($read_fd as $fd)
465 {
466 $str = fread ($fd, $buff_size);
467 if (strlen ($str) == 0)
468 {
469 // close output fd
470 $read_left = array_diff ($read_left, array ($fd));
471 fclose ($fd);
472 }
473 else
474 {
475 if ($fd == $pipes[1])
476 $out .= $str;
477 elseif ($fd == $pipes[2])
478 $errors .= $str;
479 }
480 }
481
482 $write_fd = $write_left;
483 $read_fd = $read_left;
484 }
485
7da04382 486 return proc_close ($child);
7cb57a03
AA
487}
488
bc93236a
AA
489function getRunning8021QConfig ($object_id)
490{
491 $ret = queryDevice ($object_id, 'get8021q');
492 // Once there is no default VLAN in the parsed data, it means
493 // something else was parsed instead of config text.
1c0a61e2 494 if (!in_array (VLAN_DFL_ID, $ret['vlanlist']) || empty ($ret['portdata']))
bc93236a
AA
495 throw new RTGatewayError ('communication with device failed');
496 return $ret;
497}
498
499function setDevice8021QConfig ($object_id, $pseudocode, $vlan_names)
500{
501 // FIXME: this is a perfect place to log intended changes
502 // $object_id argument isn't used by default translating functions, but
503 // may come in handy for overloaded versions of these.
9f1da7de 504 $commands = translateDeviceCommands ($object_id, $pseudocode, $vlan_names);
dc68fd36
AA
505 $breed = detectDeviceBreed ($object_id);
506 $output = queryTerminal ($object_id, $commands, FALSE);
507
508 // throw an exception if Juniper did not allow to enter config mode or to commit changes
509 if ($breed == 'jun10')
510 {
511 if (preg_match ('/>\s*configure exclusive\s*$[^#>]*?^error:/sm', $output))
512 throw new RTGatewayError ("Configuration is locked by other user");
513 elseif (preg_match ('/#\s*commit\s*$([^#]*?^error: .*?)$/sm', $output, $m))
514 throw new RTGatewayError ("Commit failed: ${m[1]}");
515 }
bc93236a
AA
516}
517
7cb57a03 518?>