1 // vim: set ts=4 sts=4 sw=4 et:
2 var VM = require('/usr/vm/node_modules/VM');
3 var ZWatch = require('./zwatch');
4 var common = require('./common');
5 var crc32 = require('./crc32');
6 var async = require('/usr/node/node_modules/async');
7 var execFile = require('child_process').execFile;
8 var fs = require('fs');
9 var net = require('net');
10 var path = require('path');
11 var util = require('util');
12 var zsock = require('/usr/node/node_modules/zsock');
13 var zutil = require('/usr/node/node_modules/zutil');
14
15 var sdc_fields = [
16 'alias',
17 'billing_id',
18 'brand',
19 'cpu_cap',
20 'cpu_shares',
21 'create_timestamp',
22 'server_uuid',
23 'image_uuid',
24 'datacenter_name',
25 'do_not_inventory',
26 'dns_domain',
27 'force_metadata_socket',
28 'fs_allowed',
29 'hostname',
30 'limit_priv',
31 'last_modified',
32 'max_physical_memory',
33 'max_locked_memory',
34 'max_lwps',
35 'max_swap',
36 'nics',
37 'owner_uuid',
38 'package_name',
39 'package_version',
40 'quota',
41 'ram',
42 'resolvers',
43 'routes',
44 'state',
45 'tmpfs',
46 'uuid',
47 'vcpus',
48 'vnc_port',
49 'zfs_io_priority',
50 'zonepath',
51 'zonename'
52 ];
53
54 var MetadataAgent = module.exports = function (options) {
55 this.log = options.log;
56 this.zlog = {};
57 this.zones = {};
58 this.zoneConnections = {};
59 };
60
61 MetadataAgent.prototype.createZoneLog = function (type, zonename) {
62 var self = this;
63 self.zlog[zonename] = self.log.child({brand: type, 'zonename': zonename});
64 return (self.zlog[zonename]);
65 };
66
67 MetadataAgent.prototype.updateZone = function (zonename, callback) {
68 var self = this;
69 var log = self.log;
70
71 function shouldLoad(cb) {
72 if (!self.zones.hasOwnProperty(zonename)) {
73 // don't have a cache, load this guy
74 log.info('no cache for: ' + zonename + ', loading');
75 cb(true);
76 return;
77 }
78
79 // we do have a cached version, we'll reload only if timestamp changed.
80 fs.stat('/etc/zones/' + zonename + '.xml', function (err, stats) {
81 var old_mtime;
82
83 if (err) {
84 // fail open when we can't stat
85 log.error({err: err}, 'cannot fs.stat() ' + zonename);
86 cb(true);
87 return;
88 }
89
90 old_mtime = (new Date(self.zones[zonename].last_modified));
91 if (stats.mtime.getTime() > old_mtime.getTime()) {
92 log.info('last_modified was updated, reloading: ' + zonename);
93 cb(true);
94 return;
95 }
96
97 log.debug('using cache for: ' + zonename);
98 cb(false);
99 });
100 }
101
102 shouldLoad(function (load) {
103 if (load) {
104 VM.lookup({ zonename: zonename }, { fields: sdc_fields },
105 function (error, machines) {
106 self.zones[zonename] = machines[0];
107 callback();
108 return;
109 }
110 );
111 } else {
112 // no need to reload since there's no change, use existing data
113 callback();
114 return;
115 }
116 });
117 };
118
119 MetadataAgent.prototype.createServersOnExistingZones = function () {
120 var self = this;
121
122 VM.lookup({}, { fields: sdc_fields }, function (error, zones) {
123 async.forEach(zones, function (zone, cb) {
124 if (zone.zonename === 'global') {
125 cb();
126 return;
127 }
128
129 self.zones[zone.zonename] = zone;
130
131 if (zone.state !== 'running') {
132 self.log.debug('skipping zone ' + zone.zonename + ' which has '
133 + 'non-running state: ' + zone.state);
134 cb();
135 return;
136 }
137
138 if (error) {
139 throw error;
140 }
141
142 if (!self.zlog[zone.zonename]) {
143 // create a logger specific to this VM
144 self.createZoneLog(zone.brand, zone.zonename);
145 }
146
147 if (zone.brand === 'kvm') {
148 self.startKVMSocketServer(zone.zonename, function (err) {
149 cb();
150 });
151 } else {
152 self.startZoneSocketServer(zone.zonename, function (err) {
153 cb();
154 });
155 }
156 }, function (err) {
157 self.log.info('Created zone metadata sockets on ' + zones.length
158 + ' zones');
159 });
160 });
161 };
162
163 MetadataAgent.prototype.startDeletedZonesPurger = function () {
164 var cmd = '/usr/sbin/zoneadm';
165 var self = this;
166
167 // Every 5 minutes we check to see whether zones we've got in self.zones
168 // were deleted. If they are, we delete the record from the cache.
169 setInterval(function () {
170 execFile(cmd, ['list', '-c'], function (err, stdout, stderr) {
171 var zones = {};
172 if (err) {
173 self.log.error({err: err}, 'unable to get list of zones');
174 return;
175 }
176
177 // each output line is a zonename, so we turn this into an object
178 // that looks like:
179 //
180 // {
181 // zonename: true,
182 // zonename: true
183 // ...
184 // }
185 //
186 // so we can then loop through all the cached zonenames and remove
187 // those that don't exist on the system any longer.
188 stdout.split(/\n/).forEach(function (z) {
189 zones[z] = true;
190 });
191 Object.keys(self.zones).forEach(function (z) {
192 if (!zones.hasOwnProperty(z)) {
193 self.log.info(z + ' no longer exists, purging from cache');
194 delete self.zones[z];
195 if (self.zlog.hasOwnProperty(z)) {
196 delete self.zlog[z];
197 }
198 }
199 });
200 });
201 }, (5 * 60 * 1000));
202
203 self.log.info('Setup interval to purge deleted zones.');
204 };
205
206 MetadataAgent.prototype.start = function () {
207 var self = this;
208 var zwatch = this.zwatch = new ZWatch();
209 self.createServersOnExistingZones();
210 self.startDeletedZonesPurger();
211
212 zwatch.on('zone_transition', function (msg) {
213 if (msg.cmd === 'start') {
214 self.updateZone(msg.zonename, function (error) {
215 if (error) {
216 self.log.error({err: error}, 'Error updating attributes: '
217 + error.message);
218 return;
219 }
220 if (!self.zlog[msg.zonename]) {
221 // create a logger specific to this VM
222 self.createZoneLog(self.zones[msg.zonename].brand,
223 msg.zonename);
224 }
225 if (self.zones[msg.zonename].brand === 'kvm') {
226 self.startKVMSocketServer(msg.zonename);
227 } else {
228 self.startZoneSocketServer(msg.zonename);
229 }
230 });
231 } else if (msg.cmd === 'stop') {
232 if (self.zoneConnections[msg.zonename]) {
233 self.zoneConnections[msg.zonename].end();
234 }
235 }
236 });
237
238 zwatch.start(self.log);
239 };
240
241 MetadataAgent.prototype.stop = function () {
242 this.zwatch.stop();
243 };
244
245 MetadataAgent.prototype.startKVMSocketServer = function (zonename, callback) {
246 var self = this;
247 var vmobj = self.zones[zonename];
248 var zlog = self.zlog[zonename];
249 var sockpath = path.join(vmobj.zonepath, '/root/tmp/vm.ttyb');
250
251 zlog.info('Starting socket server');
252
253 async.waterfall([
254 function (cb) {
255 common.retryUntil(2000, 120000,
256 function (c) {
257 fs.exists(sockpath, function (exists) {
258 zlog.debug(sockpath + ' exists: ' + exists);
259 setTimeout(function () {
260 c(null, exists);
261 }, 1000);
262 });
263 }, function (error) {
264 if (error) {
265 zlog.error({err: error}, 'Timed out waiting for '
266 + 'metadata socket');
267 } else {
268 zlog.debug('returning from startKVMSocketServer w/o '
269 + 'error');
270 }
271 cb(error);
272 }
273 );
274 }
275 ], function (error) {
276 var zopts = { zone: zonename, sockpath: sockpath };
277 self.createKVMServer(zopts, function () {
278 if (callback) {
279 callback();
280 }
281 });
282 });
283 };
284
285 function rtrim(str, chars) {
286 chars = chars || '\\s';
287 str = str || '';
288 return str.replace(new RegExp('[' + chars + ']+$', 'g'), '');
289 }
290
291 MetadataAgent.prototype.createKVMServer = function (zopts, callback) {
292 var self = this;
293 var zlog = self.zlog[zopts.zone];
294 var kvmstream = new net.Stream();
295
296 self.zoneConnections[zopts.zone] = {
297 conn: new net.Stream(),
298 done: false,
299 end: function () {
300 if (this.done) {
301 return;
302 }
303 this.done = true;
304 zlog.info('Closing kvm stream for ' + zopts.zone);
305 kvmstream.end();
306 }
307 };
308
309 var buffer = '';
310 var handler = self.makeMetadataHandler(zopts.zone, kvmstream);
311
312 kvmstream.on('data', function (data) {
313 var chunk, chunks;
314 buffer += data.toString();
315 chunks = buffer.split('\n');
316 while (chunks.length > 1) {
317 chunk = chunks.shift();
318 handler(chunk);
319 }
320 buffer = chunks.pop();
321 });
322
323 kvmstream.on('error', function (e) {
324 zlog.error({err: e}, 'KVM Socket error: ' + e.message);
325 });
326
327 kvmstream.connect(zopts.sockpath);
328 callback();
329 };
330
331 MetadataAgent.prototype.startZoneSocketServer =
332 function (zonename, callback) {
333 var self = this;
334 var zlog = self.zlog[zonename];
335 var zonePath = self.zones[zonename].zonepath;
336 var localpath = '/.zonecontrol';
337 var zonecontrolpath = path.join(zonePath, 'root', localpath);
338
339 zlog.info('Starting socket server');
340
341 function ensureZonecontrolExists(cb) {
342 fs.exists(zonecontrolpath, function (exists) {
343 if (exists) {
344 cb();
345 return;
346 } else {
347 fs.mkdir(zonecontrolpath, parseInt('700', 8), function (error) {
348 cb(error);
349 });
350 }
351 });
352 }
353
354 ensureZonecontrolExists(function (err) {
355 var sockpath = path.join(localpath, 'metadata.sock');
356 var zopts = {
357 zone: zonename,
358 path: sockpath
359 };
360
361 if (err) {
362 callback({err: err}, 'unable to create ' + zonecontrolpath);
363 return;
364 }
365
366 self.createZoneSocket(zopts, function (createErr) {
367 if (createErr) {
368 // We call callback here, but don't include the error because
369 // this is running in async.forEach and we don't want to fail
370 // the others and there's nothing we can do to recover anyway.
371 if (callback) {
372 callback();
373 }
374 return;
375 }
376
377 zlog.info('Zone socket created.');
378
379 if (callback) {
380 callback();
381 }
382 });
383 });
384 };
385
386 MetadataAgent.prototype.createZoneSocket =
387 function (zopts, callback, waitSecs) {
388 var self = this;
389 var zlog = self.zlog[zopts.zone];
390 waitSecs = waitSecs || 1;
391
392 zsock.createZoneSocket(zopts, function (error, fd) {
393 if (error) {
394 // If we get errors trying to create the zone socket, wait and then
395 // keep retrying.
396 waitSecs = waitSecs * 2;
397 zlog.error(
398 { err: error },
399 'createZoneSocket error, %s seconds before next attempt',
400 waitSecs);
401 setTimeout(function () {
402 self.createZoneSocket(zopts, function () {}, waitSecs);
403 }, waitSecs * 1000);
404 return;
405 }
406
407 var server = net.createServer(function (socket) {
408 var handler = self.makeMetadataHandler(zopts.zone, socket);
409 var buffer = '';
410 socket.on('data', function (data) {
411 var chunk, chunks;
412 buffer += data.toString();
413 chunks = buffer.split('\n');
414 while (chunks.length > 1) {
415 chunk = chunks.shift();
416 handler(chunk);
417 }
418 buffer = chunks.pop();
419 });
420
421 socket.on('error', function (err) {
422 zlog.error({err: err}, 'ZSocket error: ' + err.message);
423 zlog.info('Attempting to recover; closing and recreating zone '
424 + 'socket and server.');
425 try {
426 server.close();
427 } catch (e) {
428 zlog.error({err: e}, 'Caught exception closing server: '
429 + e.message);
430 }
431
432 socket.end();
433 self.createZoneSocket(zopts);
434 });
435 });
436
437 self.zoneConnections[zopts.zone] = {
438 conn: server,
439 done: false,
440 end: function () {
441 if (this.done) {
442 return;
443 }
444 this.done = true;
445 zlog.info('Closing server');
446 server.close();
447 }
448 };
449
450 server.on('error', function (e) {
451 zlog.error({err: e}, 'Zone socket error: ' + e.message);
452 if (e.code !== 'EINTR') {
453 throw e;
454 }
455 });
456 var Pipe = process.binding('pipe_wrap').Pipe;
457 var p = new Pipe(true);
458 p.open(fd);
459 p.readable = p.writable = true;
460 server._handle = p;
461
462 server.listen();
463 });
464
465 if (callback) {
466 callback();
467 }
468 };
469
470 MetadataAgent.prototype.makeMetadataHandler = function (zone, socket) {
471 var self = this;
472 var zlog = self.zlog[zone];
473 var write = function (str) {
474 if (socket.writable) {
475 socket.write(str);
476 } else {
477 zlog.error('Socket for ' + zone + ' closed before we could write '
478 + 'anything.');
479 }
480 };
481
482 return function (data) {
483 var cmd;
484 var parts;
485 var val;
486 var vmobj;
487 var want;
488 var reqid;
489 var req_is_v2 = false;
490
491 parts = rtrim(data.toString()).replace(/\n$/, '')
492 .match(/^([^\s]+)\s?(.*)/);
493
494 if (!parts) {
495 write('invalid command\n');
496 return;
497 }
498
499 cmd = parts[1];
500 want = parts[2];
501
502 if ((cmd === 'NEGOTIATE' || cmd === 'GET') && !want) {
503 write('invalid command\n');
504 return;
505 }
506
507 vmobj = self.zones[zone];
508
509 // Unbox V2 protocol frames:
510 if (cmd === 'V2') {
511 if (!parse_v2_request(want))
512 return;
513 }
514
515 if (cmd === 'GET') {
516 zlog.info('Serving ' + want);
517 if (want.slice(0, 4) === 'sdc:') {
518 want = want.slice(4);
519
520 // NOTE: sdc:nics, sdc:resolvers and sdc:routes are not a
521 // committed interface, do not rely on it. At this point it
522 // should only be used by mdata-fetch, if you add a consumer
523 // that depends on it, please add a note about that here
524 // otherwise expect it will be removed on you sometime.
525 if (want === 'nics' && vmobj.hasOwnProperty('nics')) {
526 val = JSON.stringify(vmobj.nics);
527 returnit(null, val);
528 return;
529 } else if (want === 'resolvers'
530 && vmobj.hasOwnProperty('resolvers')) {
531
532 // resolvers and routes are special because we might reload
533 // metadata trying to get the new ones w/o zone reboot. To
534 // ensure these are fresh we always run updateZone which
535 // reloads the data if stale.
536 self.updateZone(zone, function () {
537 // See NOTE above about nics, same applies to resolvers.
538 // It's here solely for the use of mdata-fetch.
539 val = JSON.stringify(self.zones[zone].resolvers);
540 returnit(null, val);
541 return;
542 });
543 } else if (want === 'routes'
544 && vmobj.hasOwnProperty('routes')) {
545
546 var vmRoutes = [];
547
548 self.updateZone(zone, function () {
549
550 vmobj = self.zones[zone];
551
552 // The notes above about resolvers also to routes. It's
553 // here solely for the use of mdata-fetch, and we need
554 // to do the updateZone here so that we have latest
555 // data.
556 for (var r in vmobj.routes) {
557 var route = { linklocal: false, dst: r };
558 var nicIdx = vmobj.routes[r].match(/nics\[(\d+)\]/);
559 if (!nicIdx) {
560 // Non link-local route: we have all the
561 // information we need already
562 route.gateway = vmobj.routes[r];
563 vmRoutes.push(route);
564 continue;
565 }
566 nicIdx = Number(nicIdx[1]);
567
568 // Link-local route: we need the IP of the local nic
569 if (!vmobj.hasOwnProperty('nics')
570 || !vmobj.nics[nicIdx]
571 || !vmobj.nics[nicIdx].hasOwnProperty('ip')
572 || vmobj.nics[nicIdx].ip === 'dhcp') {
573
574 continue;
575 }
576
577 route.gateway = vmobj.nics[nicIdx].ip;
578 route.linklocal = true;
579 vmRoutes.push(route);
580 }
581
582 returnit(null, JSON.stringify(vmRoutes));
583 return;
584 });
585 } else if (want === 'operator-script') {
586 addMetadata(function (err) {
587 if (err) {
588 returnit(new Error('Unable to load metadata: '
589 + err.message));
590 return;
591 }
592
593 returnit(null,
594 vmobj.internal_metadata['operator-script']);
595 return;
596 });
597 } else {
598 addTags(function (err) {
599 if (!err) {
600 val = VM.flatten(vmobj, want);
601 }
602 returnit(err, val);
603 return;
604 });
605 }
606 } else {
607 // not sdc:, so key will come from *_mdata
608 addMetadata(function (err) {
609 var which_mdata = 'customer_metadata';
610
611 if (err) {
612 returnit(new Error('Unable to load metadata: '
613 + err.message));
614 return;
615 }
616
617 if (want.match(/_pw$/)) {
618 which_mdata = 'internal_metadata';
619 }
620
621 if (vmobj.hasOwnProperty(which_mdata)) {
622 returnit(null, vmobj[which_mdata][want]);
623 return;
624 } else {
625 returnit(new Error('Zone did not contain '
626 + which_mdata));
627 return;
628 }
629 });
630 }
631 } else if (cmd === 'NEGOTIATE') {
632 if (want === 'V2') {
633 write('V2_OK\n');
634 } else {
635 write('FAILURE\n');
636 }
637 return;
638 } else if (req_is_v2 && cmd === 'DELETE') {
639 want = want.trim();
640
641 if (!want) {
642 returnit(new Error('Invalid DELETE Request'));
643 return;
644 }
645
646 setMetadata(want, null, function (err) {
647 if (err) {
648 returnit(err);
649 } else {
650 returnit(null, 'OK');
651 }
652 });
653 } else if (req_is_v2 && cmd === 'PUT') {
654 var key;
655 var value;
656 var terms;
657
658 terms = want.trim().split(' ');
659
660 if (terms.length !== 2) {
661 returnit(new Error('Invalid PUT Request'));
662 return;
663 }
664
665 // PUT requests have two space-separated BASE64-encoded
666 // arguments: the Key and then the Value.
667 key = (new Buffer(terms[0], 'base64')).toString().trim();
668 value = (new Buffer(terms[1], 'base64')).toString();
669
670 if (!key) {
671 returnit(new Error('Invalid PUT Request'));
672 return;
673 }
674
675 if (key.slice(0, 4) === 'sdc:') {
676 returnit(new Error('Cannot update the "sdc" Namespace.'));
677 return;
678 }
679
680 zlog.info('PUT of key "' + key + '"');
681 setMetadata(key, value, function (err) {
682 if (err) {
683 zlog.error(err, 'could not set metadata (key "' + key
684 + '")');
685 returnit(err);
686 } else {
687 returnit(null, 'OK');
688 }
689 });
690
691 return;
692 } else if (cmd === 'KEYS') {
693 addMetadata(function (err) {
694 var ckeys = [];
695 var ikeys = [];
696
697 if (err) {
698 returnit(new Error('Unable to load metadata: '
699 + err.message));
700 return;
701 }
702
703 // *_pw$ keys come from internal_metadata, everything else comes
704 // from customer_metadata
705 ckeys = Object.keys(vmobj.customer_metadata)
706 .filter(function (k) {
707
708 return (!k.match(/_pw$/));
709 });
710 ikeys = Object.keys(vmobj.internal_metadata)
711 .filter(function (k) {
712
713 return (k.match(/_pw$/));
714 });
715
716 returnit(null, ckeys.concat(ikeys).join('\n'));
717 return;
718 });
719 } else {
720 zlog.error('Unknown command ' + cmd);
721 returnit(new Error('Unknown command ' + cmd));
722 return;
723 }
724
725 function addTags(cb) {
726 var filename;
727
728 filename = vmobj.zonepath + '/config/tags.json';
729 fs.readFile(filename, function (err, file_data) {
730
731 if (err && err.code === 'ENOENT') {
732 vmobj.tags = {};
733 cb();
734 return;
735 }
736
737 if (err) {
738 zlog.error({err: err}, 'failed to load tags.json: '
739 + err.message);
740 cb(err);
741 return;
742 }
743
744 try {
745 vmobj.tags = JSON.parse(file_data.toString());
746 cb();
747 } catch (e) {
748 zlog.error({err: e}, 'unable to tags.json for ' + zone
749 + ': ' + e.message);
750 cb(e);
751 }
752
753 return;
754 });
755 }
756
757 function addMetadata(cb) {
758 var filename;
759
760 // If we got here, our answer comes from metadata.
761 // XXX In the future, if the require overhead here ends up being
762 // larger than a stat would be, we might want to cache these and
763 // reload when mtime changes.
764
765 filename = vmobj.zonepath + '/config/metadata.json';
766
767 fs.readFile(filename, function (err, file_data) {
768 var json = {};
769 var mdata_types = [ 'customer_metadata', 'internal_metadata' ];
770
771 // start w/ both empty, if we fail partway through there will
772 // just be no metadata instead of wrong metadata.
773 vmobj.customer_metadata = {};
774 vmobj.internal_metadata = {};
775
776 if (err && err.code === 'ENOENT') {
777 cb();
778 return;
779 }
780
781 if (err) {
782 zlog.error({err: err}, 'failed to load mdata.json: '
783 + err.message);
784 cb(err);
785 return;
786 }
787
788 try {
789 json = JSON.parse(file_data.toString());
790 mdata_types.forEach(function (mdata) {
791 if (json.hasOwnProperty(mdata)) {
792 vmobj[mdata] = json[mdata];
793 }
794 });
795 cb();
796 } catch (e) {
797 zlog.error({err: e}, 'unable to load metadata.json for '
798 + zone + ': ' + e.message);
799 cb(e);
800 }
801
802 return;
803 });
804 }
805
806 function setMetadata(_key, _value, cb) {
807 var payload = {};
808 var which = 'customer_metadata';
809
810 // Some keys come from "internal_metadata":
811 if (_key.match(/_pw$/) || _key === 'operator-script') {
812 which = 'internal_metadata';
813 }
814
815 // Construct payload for VM.update()
816 if (_value) {
817 payload['set_' + which] = {};
818 payload['set_' + which][_key] = _value;
819 } else {
820 payload['remove_' + which] = [ _key ];
821 }
822
823 zlog.trace({ payload: payload }, 'calling VM.update()');
824 VM.update(vmobj.uuid, payload, zlog, cb);
825 }
826
827 function parse_v2_request(inframe) {
828 var m;
829 var m2;
830 var newcrc32;
831 var framecrc32;
832 var clen;
833
834 m = inframe.match(
835 /\s*([0-9]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+(.*)/);
836 if (!m) {
837 zlog.error('V2 frame did not parse: ', inframe);
838 return (false);
839 }
840
841 clen = Number(m[1]);
842 if (!(clen > 0) || clen !== (m[3] + ' ' + m[4]).length) {
843 zlog.error('V2 invalid clen: ' + m[1]);
844 return (false);
845 }
846
847 newcrc32 = crc32.crc32_calc(m[3] + ' ' + m[4]);
848 framecrc32 = m[2];
849 if (framecrc32 !== newcrc32) {
850 zlog.error('V2 crc mismatch (ours ' + newcrc32
851 + ' theirs ' + framecrc32 + '): ' + want);
852 return (false);
853 }
854
855 reqid = m[3];
856
857 m2 = m[4].match(/\s*(\S+)\s*(.*)/);
858 if (!m2) {
859 zlog.error('V2 invalid body: ' + m[4]);
860 return (false);
861 }
862
863 cmd = m2[1];
864 want = (new Buffer(m2[2], 'base64')).toString('utf8');
865
866 req_is_v2 = true;
867
868 return (true);
869 }
870
871
872 function format_v2_response(code, body) {
873 var resp;
874 var fullresp;
875
876 resp = reqid + ' ' + code;
877 if (body)
878 resp += ' ' + (new Buffer(body).toString('base64'));
879
880 fullresp = 'V2 ' + resp.length + ' ' + crc32.crc32_calc(
881 resp) + ' ' + resp + '\n';
882
883 zlog.trace({ response: fullresp }, 'formatted V2 response');
884
885 return (fullresp);
886 }
887
888 function returnit(error, retval) {
889 var towrite;
890
891 if (error) {
892 zlog.error(error.message);
893 if (req_is_v2)
894 write(format_v2_response('FAILURE', error.message));
895 else
896 write('FAILURE\n');
897 return;
898 }
899
900 // String value
901 if (common.isString(retval)) {
902 if (req_is_v2) {
903 write(format_v2_response('SUCCESS', retval));
904 } else {
905 towrite = retval.replace(/^\./mg, '..');
906 write('SUCCESS\n' + towrite + '\n.\n');
907 }
908 return;
909 } else if (!isNaN(retval)) {
910 if (req_is_v2) {
911 write(format_v2_response('SUCCESS', retval.toString()));
912 } else {
913 towrite = retval.toString().replace(/^\./mg, '..');
914 write('SUCCESS\n' + towrite + '\n.\n');
915 }
916 return;
917 } else if (retval) {
918 // Non-string value
919 if (req_is_v2)
920 write(format_v2_response('FAILURE'));
921 else
922 write('FAILURE\n');
923 return;
924 } else {
925 // Nothing to return
926 if (req_is_v2)
927 write(format_v2_response('NOTFOUND'));
928 else
929 write('NOTFOUND\n');
930 return;
931 }
932 }
933 };
934 };