1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at http://smartos.org/CDDL
  10  *
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file.
  16  *
  17  * If applicable, add the following below this CDDL HEADER, with the
  18  * fields enclosed by brackets "[]" replaced with your own identifying
  19  * information: Portions Copyright [yyyy] [name of copyright owner]
  20  *
  21  * CDDL HEADER END
  22  *
  23  * Copyright (c) 2013, Joyent, Inc. All rights reserved.
  24  *
  25  * Experimental functions, expect these interfaces to be unstable and
  26  * potentially go away entirely:
  27  *
  28  * create_snapshot(uuid, snapname, options, callback)
  29  * delete_snapshot(uuid, snapname, options, callback)
  30  * install(uuid, callback)
  31  * receive(target, options, callback)
  32  * reprovision(uuid, payload, options, callback)
  33  * rollback_snapshot(uuid, snapname, options, callback)
  34  * send(uuid, where, options, callback)
  35  * getSysinfo(args, callback)
  36  * validate(brand, action, payload, callback)
  37  * waitForZoneState(payload, state, options, callback)
  38  *
  39  * Exported functions:
  40  *
  41  * console(uuid, callback)
  42  * create(properties, callback)
  43  * delete(uuid, callback)
  44  * flatten(vmobj, key)
  45  * info(uuid, types, callback)
  46  * load([zonename|uuid], callback)
  47  * lookup(match, callback)
  48  * reboot(uuid, options={[force=true]}, callback)
  49  * start(uuid, extra, callback)
  50  * stop(uuid, options={[force=true]}, callback)
  51  * sysrq(uuid, req=[nmi|screenshot], options={}, callback)
  52  * update(uuid, properties, callback)
  53  *
  54  * Exported variables:
  55  *
  56  * logname - you can set this to a string [a-zA-Z_] to use as log name
  57  * logger - you can set this to a node-bunyan log stream to capture the logs
  58  * INFO_TYPES - list of supported types for the info command
  59  * SYSRQ_TYPES - list of supported requests for sysrq
  60  */
  61 
  62 // Ensure we're using the platform's node
  63 require('/usr/node/node_modules/platform_node_version').assert();
  64 
  65 var assert = require('assert');
  66 var async = require('/usr/node/node_modules/async');
  67 var bunyan = require('/usr/node/node_modules/bunyan');
  68 var cp = require('child_process');
  69 var dladm = require('/usr/vm/node_modules/dladm');
  70 var lock = require('/usr/vm/node_modules/locker').lock;
  71 var EventEmitter = require('events').EventEmitter;
  72 var exec = cp.exec;
  73 var execFile = cp.execFile;
  74 var expat = require('/usr/node/node_modules/node-expat');
  75 var fs = require('fs');
  76 var fw = require('/usr/fw/lib/fw');
  77 var http = require('http');
  78 var net = require('net');
  79 var path = require('path');
  80 var Qmp = require('/usr/vm/node_modules/qmp').Qmp;
  81 var spawn = cp.spawn;
  82 var sprintf = require('/usr/node/node_modules/sprintf').sprintf;
  83 var tty = require('tty');
  84 var util = require('util');
  85 
  86 var log_to_file = false;
  87 
  88 // keep the last 512 messages just in case we end up wanting them.
  89 var ringbuffer = new bunyan.RingBuffer({ limit: 512 });
  90 
  91 // zfs_list_queue variables for the serialization of 'zfs list' calls
  92 var zfs_list_in_progress = {};
  93 var zfs_list_queue;
  94 
  95 // global handle for the zoneevent watcher
  96 var zoneevent;
  97 
  98 /*
  99  * zone states from libzonecfg/common/zonecfg_impl.h
 100  *
 101  * #define ZONE_STATE_STR_CONFIGURED       "configured"
 102  * #define ZONE_STATE_STR_INCOMPLETE       "incomplete"
 103  * #define ZONE_STATE_STR_INSTALLED        "installed"
 104  * #define ZONE_STATE_STR_READY            "ready"
 105  * #define ZONE_STATE_STR_MOUNTED          "mounted"
 106  * #define ZONE_STATE_STR_RUNNING          "running"
 107  * #define ZONE_STATE_STR_SHUTTING_DOWN    "shutting_down"
 108  * #define ZONE_STATE_STR_DOWN             "down"
 109  *
 110  */
 111 
 112 exports.FLATTENABLE_ARRAYS = [
 113     'resolvers'
 114 ];
 115 exports.FLATTENABLE_ARRAY_HASH_KEYS = [
 116     'disks',
 117     'nics'
 118 ];
 119 exports.FLATTENABLE_HASH_KEYS = [
 120     'customer_metadata',
 121     'internal_metadata',
 122     'routes',
 123     'tags'
 124 ];
 125 
 126 var DEFAULT_MDATA_TIMEOUT = 300;
 127 var DISABLED = 0;
 128 var MAX_SNAPNAME_LENGTH = 64;
 129 var MINIMUM_MAX_SWAP = 256;
 130 var PROVISION_TIMEOUT = 300;
 131 var STOP_TIMEOUT = 60;
 132 var VM = this;
 133 
 134 VM.log = null;
 135 
 136 // can be (re)set by loader before we start.
 137 exports.logger = null;
 138 exports.loglevel = 'debug';
 139 
 140 // OpenOnErrorFileStream is a bunyan stream that only creates the file when
 141 // there's an error or higher level message or when the global log_to_file
 142 // variable is set. For actions that modify things log_to_file is always set.
 143 // For other actions we shouldn't log in the normal case but where we do want
 144 // logs when something breaks. Thanks to Trent++ for most of this code.
 145 //
 146 // Note: if you want to rotate the logs while this is writing to a file, you
 147 // can first move it. The watcher will notice that the log file was moved and
 148 // reopen a new file with the original name.
 149 
 150 function OpenOnErrorFileStream(filename) {
 151     this.path = filename;
 152     this.write = this.constructor.prototype.write1;
 153     this.end = this.constructor.prototype.end1;
 154     this.emit = this.constructor.prototype.emit1;
 155     this.once = this.constructor.prototype.once1;
 156 
 157     this.newStream = function () {
 158         var self = this;
 159         var watcher;
 160 
 161         self.stream = fs.createWriteStream(self.path,
 162             {flags: 'a', encoding: 'utf8'});
 163 
 164         watcher = fs.watch(self.path, {persistent: false}, function (evt) {
 165             if (evt != 'rename') {
 166                 return;
 167             }
 168             // file was renamed, we want to reopen.
 169             if (self.stream) {
 170                 self.stream.destroySoon();
 171             }
 172             watcher.close();
 173             self.stream = null;
 174         });
 175     };
 176 }
 177 
 178 OpenOnErrorFileStream.prototype.end1 = function () {
 179     // in initial mode we're not writing anything, so nothing to flush
 180     return;
 181 };
 182 
 183 OpenOnErrorFileStream.prototype.emit1 = function () {
 184     return;
 185 };
 186 
 187 // Warning: never emits anything
 188 OpenOnErrorFileStream.prototype.once1 = function () {
 189     return;
 190 };
 191 
 192 // used until first ERROR or higher, then opens file and ensures future writes
 193 // go to .write2()
 194 OpenOnErrorFileStream.prototype.write1 = function (rec) {
 195     var r;
 196     var stream;
 197 
 198     if (rec.level >= bunyan.ERROR || log_to_file) {
 199         if (! this.stream) {
 200             this.newStream();
 201         }
 202 
 203         stream = this.stream;
 204 
 205         this.emit = function () { stream.emit.apply(stream, arguments); };
 206         this.end = function () { stream.end.apply(stream, arguments); };
 207         this.once = function () { stream.once.apply(stream, arguments); };
 208         this.write = this.constructor.prototype.write2;
 209         // dump out logs from ringbuffer too since there was an error so we can
 210         // figure out what's going on.
 211         for (r in ringbuffer.records) {
 212             r = ringbuffer.records[r];
 213             if (r != rec) {
 214                 this.write(r);
 215             }
 216         }
 217 
 218         this.write(rec);
 219     }
 220 
 221     // This write doesn't fail (since it's going to memory or nowhere) so we
 222     // always return true so that callers don't try to wait for 'drain' which
 223     // we'll not emit.
 224     return true;
 225 };
 226 
 227 // used when writing to file
 228 OpenOnErrorFileStream.prototype.write2 = function (rec) {
 229     var str;
 230 
 231     // need to support writing '' so we know when to drain
 232     if (typeof (rec) === 'string' && rec.length < 1) {
 233         str = '';
 234     } else {
 235         str = JSON.stringify(rec, bunyan.safeCycles()) + '\n';
 236     }
 237 
 238     if (! this.stream) {
 239         this.newStream();
 240     }
 241 
 242     return this.stream.write(str);
 243 };
 244 
 245 // This function should be called by any exported function from this module.
 246 // It ensures that a logger is setup. If side_effects is true, we'll start
 247 // writing log messages to the file right away. If not, we'll only start
 248 // logging after we hit a message error or higher. This is intended such that
 249 // things that are expected to change the state or modify VMs on the system:
 250 // eg. create, start, stop, delete should have this set true.  It should be
 251 // set false when the action should not cause changes to the system:
 252 // eg.: load, lookup, info, console, &c.
 253 function ensureLogging(side_effects)
 254 {
 255     side_effects = !!side_effects; // make it boolean (undef === false)
 256 
 257     var filename;
 258     var logname;
 259     var streams = [];
 260 
 261     function start_logging() {
 262         var params = {
 263             name: logname,
 264             streams: streams,
 265             serializers: bunyan.stdSerializers
 266         };
 267 
 268         if (process.env.REQ_ID) {
 269             params.req_id = process.env.REQ_ID;
 270         } else if (process.env.req_id) {
 271             params.req_id = process.env.req_id;
 272         }
 273         VM.log = bunyan.createLogger(params);
 274     }
 275 
 276     // This is here in case an app calls a lookup first and then a create.  The
 277     // logger will get created in no-sideeffects mode for the lookup but when
 278     // the create is called this will force the switch to writing.
 279     if (side_effects) {
 280         log_to_file = true;
 281     }
 282 
 283     if (VM.log) {
 284         // We're already logging, don't break things.
 285         return;
 286     }
 287 
 288     if (VM.hasOwnProperty('logname')) {
 289         logname = VM.logname.replace(/[^a-zA-Z\_]/g, '');
 290     }
 291     if (!logname || logname.length < 1) {
 292         logname = 'VM';
 293     }
 294 
 295     if (VM.hasOwnProperty('logger') && VM.logger) {
 296         // Use concat, in case someone's sneaky and makes more than one logger.
 297         // We don't officially support that yet though.
 298         streams = streams.concat(VM.logger);
 299     }
 300 
 301     // Add the ringbuffer which we'll dump if we switch from not writing to
 302     // writing, and so that they'll show up in dumps.
 303     streams.push({
 304         level: 'trace',
 305         type: 'raw',
 306         stream: ringbuffer
 307     });
 308 
 309     try {
 310         if (!fs.existsSync('/var/log/vm')) {
 311             fs.mkdirSync('/var/log/vm');
 312         }
 313         if (!fs.existsSync('/var/log/vm/logs')) {
 314             fs.mkdirSync('/var/log/vm/logs');
 315         }
 316     } catch (e) {
 317         // We can't ever log to a file in /var/log/vm/logs if we can't create
 318         // it, so we just log to ring buffer (above).
 319         start_logging();
 320         return;
 321     }
 322 
 323     filename = '/var/log/vm/logs/' + Date.now(0) + '-'
 324         + sprintf('%06d', process.pid) + '-' + logname + '.log';
 325 
 326     streams.push({
 327         type: 'raw',
 328         stream: new OpenOnErrorFileStream(filename),
 329         level: VM.loglevel
 330     });
 331 
 332     start_logging();
 333 }
 334 
 335 function ltrim(str, chars)
 336 {
 337     chars = chars || '\\s';
 338     str = str || '';
 339     return str.replace(new RegExp('^[' + chars + ']+', 'g'), '');
 340 }
 341 
 342 function rtrim(str, chars)
 343 {
 344     chars = chars || '\\s';
 345     str = str || '';
 346     return str.replace(new RegExp('[' + chars + ']+$', 'g'), '');
 347 }
 348 
 349 function trim(str, chars)
 350 {
 351     return ltrim(rtrim(str, chars), chars);
 352 }
 353 
 354 function isUUID(str) {
 355     var re = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
 356     if (str && str.length === 36 && str.match(re)) {
 357         return true;
 358     } else {
 359         return false;
 360     }
 361 }
 362 
 363 function fixBoolean(str)
 364 {
 365     if (str === 'true') {
 366         return true;
 367     } else if (str === 'false') {
 368         return false;
 369     } else {
 370         return str;
 371     }
 372 }
 373 
 374 function fixBooleanLoose(str)
 375 {
 376     if (str === 'true' || str === '1' || str === 1) {
 377         return true;
 378     } else if (str === 'false' || str === '0' || str === 0) {
 379         return false;
 380     } else {
 381         return str;
 382     }
 383 }
 384 
 385 function isCIDR(str) {
 386     if (typeof (str) !== 'string') {
 387         return false;
 388     }
 389     var parts = str.split('/');
 390     if (parts.length !== 2 || !net.isIPv4(parts[0])) {
 391         return false;
 392     }
 393 
 394     var size = Number(parts[1]);
 395     if (!size || size < 8 || size > 32) {
 396         return false;
 397     }
 398 
 399     return true;
 400 }
 401 
 402 // IMPORTANT:
 403 //
 404 //  Some of these properties get translated below into backward compatible
 405 //  names.
 406 //
 407 
 408 var UPDATABLE_NIC_PROPS = [
 409     'primary',
 410     'nic_tag',
 411     'vrrp_vrid',
 412     'vrrp_primary_ip',
 413     'blocked_outgoing_ports',
 414     'mac',
 415     'gateway',
 416     'ip',
 417     'model',
 418     'netmask',
 419     'network_uuid',
 420     'dhcp_server',
 421     'allow_dhcp_spoofing',
 422     'allow_ip_spoofing',
 423     'allow_mac_spoofing',
 424     'allow_restricted_traffic',
 425     'allow_unfiltered_promisc',
 426     'vlan_id'
 427 ];
 428 
 429 var UPDATABLE_DISK_PROPS = [
 430     'boot',
 431     'model'
 432 ];
 433 
 434 // Note: this doesn't include 'state' because of 'stopping' which is a virtual
 435 // state and therefore lookups would be wrong (because they'd search on real
 436 // state).
 437 var QUICK_LOOKUP = [
 438     'zoneid',
 439     'zonename',
 440     'zonepath',
 441     'uuid',
 442     'brand',
 443     'ip_type'
 444 ];
 445 
 446 exports.DISK_MODELS = [
 447     'virtio',
 448     'ide',
 449     'scsi'
 450 ];
 451 
 452 exports.VGA_TYPES = [
 453     'cirrus',
 454     'std',
 455     'vmware',
 456     'qxl',
 457     'xenfb'
 458 ];
 459 
 460 exports.INFO_TYPES = [
 461     'all',
 462     'block',
 463     'blockstats',
 464     'chardev',
 465     'cpus',
 466     'kvm',
 467     'pci',
 468     'spice',
 469     'status',
 470     'version',
 471     'vnc'
 472 ];
 473 
 474 exports.SYSRQ_TYPES = [
 475     'nmi',
 476     'screenshot'
 477 ];
 478 
 479 exports.COMPRESSION_TYPES = [
 480     'on',
 481     'off',
 482     'lzjb',
 483     'gzip',
 484     'gzip-1',
 485     'gzip-2',
 486     'gzip-3',
 487     'gzip-4',
 488     'gzip-5',
 489     'gzip-6',
 490     'gzip-7',
 491     'gzip-8',
 492     'gzip-9',
 493     'zle'
 494 ];
 495 
 496 exports.KVM_MEM_OVERHEAD = 1024;
 497 exports.KVM_MIN_MEM_OVERHEAD = 256;
 498 
 499 var XML_PROPERTIES = {
 500     'zone': {
 501         'name': 'zonename',
 502         'zonepath': 'zonepath',
 503         'autoboot': 'autoboot',
 504         'brand': 'brand',
 505         'limitpriv': 'limit_priv',
 506         'fs-allowed': 'fs_allowed'
 507     },
 508     'zone.attr': {
 509         'alias': 'alias',
 510         'archive-on-delete': 'archive_on_delete',
 511         'billing-id': 'billing_id',
 512         'boot': 'boot',
 513         'cpu-type': 'cpu_type',
 514         'create-timestamp': 'create_timestamp',
 515         'dataset-uuid': 'image_uuid',
 516         'default-gateway': 'default_gateway',
 517         'disk-driver': 'disk_driver',
 518         'dns-domain': 'dns_domain',
 519         'do-not-inventory': 'do_not_inventory',
 520         'failed': 'failed',
 521         'firewall-enabled': 'firewall_enabled',
 522         'hostname': 'hostname',
 523         'init-name': 'init_name',
 524         'never-booted': 'never_booted',
 525         'nic-driver': 'nic_driver',
 526         'owner-uuid': 'owner_uuid',
 527         'package-name': 'package_name',
 528         'package-version': 'package_version',
 529         'qemu-extra-opts': 'qemu_extra_opts',
 530         'qemu-opts': 'qemu_opts',
 531         'ram': 'ram',
 532         'restart-init': 'restart_init',
 533         'resolvers': 'resolvers',
 534         'spice-opts': 'spice_opts',
 535         'spice-password': 'spice_password',
 536         'spice-port': 'spice_port',
 537         'tmpfs': 'tmpfs',
 538         'transition': 'transition',
 539         'vcpus': 'vcpus',
 540         'vga': 'vga',
 541         'virtio-txtimer': 'virtio_txtimer',
 542         'virtio-txburst': 'virtio_txburst',
 543         'vm-version': 'v',
 544         'vm-autoboot': 'vm_autoboot',
 545         'vnc-password': 'vnc_password',
 546         'vnc-port': 'vnc_port'
 547     },
 548     'zone.rctl.zone.cpu-shares.rctl-value': {
 549         'limit': 'cpu_shares'
 550     },
 551     'zone.rctl.zone.cpu-cap.rctl-value': {
 552         'limit': 'cpu_cap'
 553     },
 554     'zone.rctl.zone.zfs-io-priority.rctl-value': {
 555         'limit': 'zfs_io_priority'
 556     },
 557     'zone.rctl.zone.max-lwps.rctl-value': {
 558         'limit': 'max_lwps'
 559     },
 560     'zone.rctl.zone.max-physical-memory.rctl-value': {
 561         'limit': 'max_physical_memory'
 562     },
 563     'zone.rctl.zone.max-locked-memory.rctl-value': {
 564         'limit': 'max_locked_memory'
 565     },
 566     'zone.rctl.zone.max-swap.rctl-value': {
 567         'limit': 'max_swap'
 568     },
 569     'nic': {
 570         'ip': 'ip',
 571         'mac-addr': 'mac',
 572         'physical': 'interface',
 573         'vlan-id': 'vlan_id',
 574         'global-nic': 'nic_tag',
 575         'dhcp_server': 'dhcp_server',
 576         'allow_dhcp_spoofing': 'allow_dhcp_spoofing',
 577         'allow_ip_spoofing': 'allow_ip_spoofing',
 578         'allow_mac_spoofing': 'allow_mac_spoofing',
 579         'allow_restricted_traffic': 'allow_restricted_traffic',
 580         'allow_unfiltered_promisc': 'allow_unfiltered_promisc',
 581         'allowed_ips': 'allowed_ips',
 582         'netmask': 'netmask',
 583         'network_uuid': 'network_uuid',
 584         'model': 'model',
 585         'gateway': 'gateway',
 586         'primary': 'primary',
 587         'vrrp_vrid': 'vrrp_vrid',
 588         'vrrp_primary_ip': 'vrrp_primary_ip',
 589         'blocked-outgoing-ports': 'blocked_outgoing_ports'
 590     },
 591     'filesystem': {
 592         'special': 'source',
 593         'directory': 'target',
 594         'type': 'type',
 595         'raw': 'raw'
 596     },
 597     'disk': {
 598         'boot': 'boot',
 599         'image-size': 'image_size',
 600         'image-name': 'image_name',
 601         'image-uuid': 'image_uuid',
 602         'match': 'path',
 603         'media': 'media',
 604         'model': 'model',
 605         'size': 'size'
 606     }
 607 };
 608 
 609 /*
 610  * This allows one to define a function that will be run over the values from
 611  * the zonecfg at the point where we transform that data into a VM object.
 612  *
 613  */
 614 var XML_PROPERTY_TRANSFORMS = {
 615     'alias': unbase64,
 616     'archive_on_delete': fixBoolean,
 617     'autoboot': fixBoolean,
 618     'cpu_cap': numberify,
 619     'cpu_shares': numberify,
 620     'disks': {
 621         'boot': fixBoolean,
 622         'image_size': numberify,
 623         'size': numberify
 624     },
 625     'do_not_inventory': fixBoolean,
 626     'firewall_enabled': fixBoolean,
 627     'max_locked_memory': unmangleMem,
 628     'max_lwps': numberify,
 629     'max_physical_memory': unmangleMem,
 630     'max_swap': unmangleMem,
 631     'never_booted': fixBoolean,
 632     'nics': {
 633         'dhcp_server': fixBoolean,
 634         'allow_dhcp_spoofing': fixBoolean,
 635         'allow_ip_spoofing': fixBoolean,
 636         'allow_mac_spoofing': fixBoolean,
 637         'allow_restricted_traffic': fixBoolean,
 638         'allow_unfiltered_promisc': fixBoolean,
 639         'allowed_ips': separateCommas,
 640         'primary': fixBooleanLoose,
 641         'vrrp_vrid': numberify,
 642         'vlan_id': numberify
 643     },
 644     'qemu_extra_opts': unbase64,
 645     'qemu_opts': unbase64,
 646     'ram': numberify,
 647     'restart_init': fixBoolean,
 648     'resolvers': separateCommas,
 649     'spice_password': unbase64,
 650     'spice_port': numberify,
 651     'spice_opts': unbase64,
 652     'tmpfs': numberify,
 653     'v': numberify,
 654     'vcpus': numberify,
 655     'virtio_txburst': numberify,
 656     'virtio_txtimer': numberify,
 657     'vnc_password': unbase64,
 658     'vnc_port': numberify,
 659     'zfs_io_priority': numberify,
 660     'zoneid': numberify
 661 };
 662 
 663 /*
 664  * This defines all of the possible properties that could be in a create/update
 665  * payload and their types. Each of the entries are required to have at least
 666  * a 'type' property which is one of:
 667  *
 668  *  object-array    -- an array of objects
 669  *  boolean         -- true or false
 670  *  flat-object     -- an object that has only string properties
 671  *  integer         -- integers only
 672  *  list            -- Either comma separated or array list of strings
 673  *  string          -- Simple string
 674  *  uuid            -- A standard 00000000-0000-0000-0000-000000000000 type uuid
 675  *  zpool           -- The name of an existing zpool
 676  *
 677  */
 678 var PAYLOAD_PROPERTIES = {
 679     'add_disks': {'type': 'object-array', 'check_as': 'disks'},
 680     'add_nics': {'type': 'object-array', 'check_as': 'nics'},
 681     'alias': {'type': 'string'},
 682     'archive_on_delete': {'type': 'boolean'},
 683     'autoboot': {'type': 'boolean'},
 684     'billing_id': {'type': 'string'},
 685     'boot': {'type': 'string'},
 686     'brand': {'type': 'string'},
 687     'cpu_cap': {'type': 'integer'},
 688     'cpu_shares': {'type': 'integer'},
 689     'cpu_type': {'type': 'string'},
 690     'create_only': {'type': 'boolean'},
 691     'create_timestamp': {'type': 'string'},
 692     'customer_metadata': {'type': 'flat-object'},
 693     'dataset_uuid': {'type': 'uuid'},
 694     'delegate_dataset': {'type': 'boolean'},
 695     'disks': {'type': 'object-array'},
 696     'disks.*.block_size': {'type': 'integer'},
 697     'disks.*.boot': {'type': 'boolean'},
 698     'disks.*.compression': {'type': 'string'},
 699     'disks.*.image_name': {'type': 'string'},
 700     'disks.*.image_size': {'type': 'integer'},
 701     'disks.*.image_uuid': {'type': 'uuid'},
 702     'disks.*.refreservation': {'type': 'integer'},
 703     'disks.*.size': {'type': 'integer'},
 704     'disks.*.media': {'type': 'string'},
 705     'disks.*.model': {'type': 'string'},
 706     'disks.*.nocreate': {'type': 'boolean'},
 707     'disks.*.path': {'type': 'string'},
 708     'disks.*.zpool': {'type': 'zpool'},
 709     'disk_driver': {'type': 'string'},
 710     'do_not_inventory': {'type': 'boolean'},
 711     'dns_domain': {'type': 'string'},
 712     'filesystems': {'type': 'object-array'},
 713     'filesystems.*.type': {'type': 'string'},
 714     'filesystems.*.source': {'type': 'string'},
 715     'filesystems.*.target': {'type': 'string'},
 716     'filesystems.*.raw': {'type': 'string'},
 717     'filesystems.*.options': {'type': 'list'},
 718     'firewall': {'type': 'object'},
 719     'firewall_enabled': {'type': 'boolean'},
 720     'fs_allowed': {'type': 'list'},
 721     'hostname': {'type': 'string'},
 722     'image_uuid': {'type': 'uuid'},
 723     'init_name': {'type': 'string'},
 724     'internal_metadata': {'type': 'flat-object'},
 725     'limit_priv': {'type': 'list'},
 726     'max_locked_memory': {'type': 'integer'},
 727     'max_lwps': {'type': 'integer'},
 728     'max_physical_memory': {'type': 'integer'},
 729     'max_swap': {'type': 'integer'},
 730     'mdata_exec_timeout': {'type': 'integer'},
 731     'nics': {'type': 'object-array'},
 732     'nics.*.allow_dhcp_spoofing': {'type': 'boolean'},
 733     'nics.*.allow_ip_spoofing': {'type': 'boolean'},
 734     'nics.*.allow_mac_spoofing': {'type': 'boolean'},
 735     'nics.*.allow_restricted_traffic': {'type': 'boolean'},
 736     'nics.*.allow_unfiltered_promisc': {'type': 'boolean'},
 737     'nics.*.allowed_ips': {'type': 'list'},
 738     'nics.*.blocked_outgoing_ports': {'type': 'list'},
 739     'nics.*.dhcp_server': {'type': 'boolean'},
 740     'nics.*.gateway': {'type': 'string'},
 741     'nics.*.interface': {'type': 'string'},
 742     'nics.*.ip': {'type': 'string'},
 743     'nics.*.mac': {'type': 'string'},
 744     'nics.*.model': {'type': 'string'},
 745     'nics.*.netmask': {'type': 'string'},
 746     'nics.*.network_uuid': {'type': 'uuid'},
 747     'nics.*.nic_tag': {'type': 'string'},
 748     'nics.*.primary': {'type': 'boolean'},
 749     'nics.*.vrrp_vrid': {'type': 'integer-8bit'},
 750     'nics.*.vrrp_primary_ip': {'type': 'string'},
 751     'nics.*.vlan_id': {'type': 'integer'},
 752     'nic_driver': {'type': 'string'},
 753     'nowait': {'type': 'boolean'},
 754     'owner_uuid': {'type': 'string'},
 755     'package_name': {'type': 'string'},
 756     'package_version': {'type': 'string'},
 757     'qemu_opts': {'type': 'string'},
 758     'qemu_extra_opts': {'type': 'string'},
 759     'quota': {'type': 'integer'},
 760     'ram': {'type': 'integer'},
 761     'remove_customer_metadata': {'type': 'list'},
 762     'remove_disks': {'type': 'list'},
 763     'remove_internal_metadata': {'type': 'list'},
 764     'remove_nics': {'type': 'list'},
 765     'remove_routes': {'type': 'list'},
 766     'remove_tags': {'type': 'list'},
 767     'restart_init': {'type': 'boolean'},
 768     'resolvers': {'type': 'list'},
 769     'routes': {'type': 'flat-object'},
 770     'set_routes': {'type': 'flat-object'},
 771     'set_tags': {'type': 'flat-object'},
 772     'set_customer_metadata': {'type': 'flat-object'},
 773     'set_internal_metadata': {'type': 'flat-object'},
 774     'spice_opts': {'type': 'string'},
 775     'spice_password': {'type': 'string'},
 776     'spice_port': {'type': 'integer'},
 777     'tags': {'type': 'flat-object'},
 778     'tmpfs': {'type': 'integer'},
 779     'transition': {'type': 'flat-object'},
 780     'update_disks': {'type': 'object-array', 'check_as': 'disks'},
 781     'update_nics': {'type': 'object-array', 'check_as': 'nics'},
 782     'uuid': {'type': 'uuid'},
 783     'v': {'type': 'integer'},
 784     'vcpus': {'type': 'integer'},
 785     'vga': {'type': 'string'},
 786     'virtio_txburst': {'type': 'integer'},
 787     'virtio_txtimer': {'type': 'integer'},
 788     'vnc_password': {'type': 'string'},
 789     'vnc_port': {'type': 'integer'},
 790     'zfs_data_compression': {'type': 'string'},
 791     'zfs_data_recsize': {'type': 'integer'},
 792     'zfs_io_priority': {'type': 'integer'},
 793     'zfs_root_compression': {'type': 'string'},
 794     'zfs_root_recsize': {'type': 'integer'},
 795     'zone_dataset_uuid': {'type': 'uuid'},
 796     'zonename': {'type': 'string'},
 797     'zfs_storage_pool_name': {'type': 'zpool'},
 798     'zpool': {'type': 'zpool'}
 799 };
 800 
 801 // shared between 'joyent' and 'joyent-minimal'
 802 var joyent_allowed = {
 803     'add_nics': ['update'],
 804     'alias': ['create', 'receive', 'update'],
 805     'archive_on_delete': ['create', 'receive', 'update'],
 806     'autoboot': ['create', 'receive', 'update'],
 807     'billing_id': ['create', 'receive', 'update'],
 808     'brand': ['create', 'receive'],
 809     'cpu_cap': ['create', 'receive', 'update'],
 810     'cpu_shares': ['create', 'receive', 'update'],
 811     'create_only': ['receive'],
 812     'create_timestamp': ['receive'],
 813     'customer_metadata': ['create', 'receive'],
 814     'dataset_uuid': ['create', 'receive'],
 815     'delegate_dataset': ['create', 'receive'],
 816     'do_not_inventory': ['create', 'receive', 'update'],
 817     'dns_domain': ['create', 'receive'],
 818     'filesystems': ['create', 'receive'],
 819     'filesystems.*.type': ['add'],
 820     'filesystems.*.source': ['add'],
 821     'filesystems.*.target': ['add'],
 822     'filesystems.*.raw': ['add'],
 823     'filesystems.*.options': ['add'],
 824     'firewall': ['create'],
 825     'firewall_enabled': ['create', 'receive', 'update'],
 826     'fs_allowed': ['create', 'receive', 'update'],
 827     'hostname': ['create', 'receive', 'update'],
 828     'image_uuid': ['create', 'receive'],
 829     'init_name': ['create', 'receive', 'update'],
 830     'internal_metadata': ['create', 'receive'],
 831     'limit_priv': ['create', 'receive', 'update'],
 832     'max_locked_memory': ['create', 'receive', 'update'],
 833     'max_lwps': ['create', 'receive', 'update'],
 834     'max_physical_memory': ['create', 'receive', 'update'],
 835     'max_swap': ['create', 'receive', 'update'],
 836     'mdata_exec_timeout': ['create'],
 837     'nics': ['create', 'receive'],
 838     'nics.*.allow_dhcp_spoofing': ['add', 'update'],
 839     'nics.*.allow_ip_spoofing': ['add', 'update'],
 840     'nics.*.allow_mac_spoofing': ['add', 'update'],
 841     'nics.*.allow_restricted_traffic': ['add', 'update'],
 842     'nics.*.allowed_ips': ['add', 'update'],
 843     'nics.*.blocked_outgoing_ports': ['add', 'update'],
 844     'nics.*.dhcp_server': ['add', 'update'],
 845     'nics.*.gateway': ['add', 'update'],
 846     'nics.*.interface': ['add', 'update'],
 847     'nics.*.ip': ['add', 'update'],
 848     'nics.*.mac': ['add', 'update'],
 849     'nics.*.netmask': ['add', 'update'],
 850     'nics.*.network_uuid': ['add', 'update'],
 851     'nics.*.nic_tag': ['add', 'update'],
 852     'nics.*.vrrp_vrid': ['add', 'update'],
 853     'nics.*.vrrp_primary_ip': ['add', 'update'],
 854     'nics.*.primary': ['add', 'update'],
 855     'nics.*.vlan_id': ['add', 'update'],
 856     'nowait': ['create', 'receive'],
 857     'owner_uuid': ['create', 'receive', 'update'],
 858     'package_name': ['create', 'receive', 'update'],
 859     'package_version': ['create', 'receive', 'update'],
 860     'quota': ['create', 'receive', 'update'],
 861     'ram': ['create', 'receive', 'update'],
 862     'remove_customer_metadata': ['update'],
 863     'remove_internal_metadata': ['update'],
 864     'remove_nics': ['update'],
 865     'remove_routes': ['update'],
 866     'remove_tags': ['update'],
 867     'restart_init': ['create', 'receive', 'update'],
 868     'resolvers': ['create', 'receive', 'update'],
 869     'routes': ['create', 'receive'],
 870     'set_customer_metadata': ['update'],
 871     'set_internal_metadata': ['update'],
 872     'set_routes': ['update'],
 873     'set_tags': ['update'],
 874     'tags': ['create', 'receive'],
 875     'tmpfs': ['create', 'receive', 'update'],
 876     'transition': ['receive'],
 877     'update_nics': ['update'],
 878     'uuid': ['create', 'receive'],
 879     'v': ['receive'],
 880     'zfs_data_compression': ['create', 'receive', 'update'],
 881     'zfs_data_recsize': ['create', 'receive', 'update'],
 882     'zfs_io_priority': ['create', 'receive', 'update'],
 883     'zfs_root_compression': ['create', 'receive', 'update'],
 884     'zfs_root_recsize': ['create', 'receive', 'update'],
 885     'zfs_storage_pool_name': ['create', 'receive'],
 886     'zonename': ['create', 'receive'],
 887     'zpool': ['create', 'receive']
 888 };
 889 
 890 /*
 891  * This defines all of the properties allowed, required and features that a
 892  * brand has. For each of the allowed/required properties you have a list of
 893  * actions for which this is allowed/required. For properties that are lists
 894  * of objects, you can specify the action as 'add' or 'update' for when you're
 895  * adding or updating one of those objects.
 896  *
 897  * Features can currently be one of:
 898  *
 899  * 'cleanup_dataset' -- (boolean) whether to remove trash before booting
 900  * 'default_memory_overhead' -- (integer) memory above 'ram' that's added
 901  * 'limit_priv': (list) list of priviledges for this zone (if not 'default')
 902  * 'mdata_restart' -- (boolean) whether the brand supports restarting its
 903  *                    mdata:fetch service to update properties in the zone
 904  * 'min_memory_overhead' -- (integer) minimum delta between ram + max_physical
 905  * 'model_required' -- (boolean) whether a .model is required on nics and disks
 906  * 'pid_file' -- (pathname) file containing the PID for zones with one process
 907  * 'runtime_info' -- (boolean) whether this zone supports the 'info' command
 908  * 'serial_console' -- (boolean) whether this zone uses serial console
 909  * 'type' -- the type of the VM (OS or KVM), all brands should include this
 910  * 'update_mdata_exec_timeout' (boolean) whether to update mdata:exec timeout
 911  * 'update_rctls' (boolean) whether we can update rctls 'live' for this zone
 912  * 'use_tmpfs' -- (boolean) whether this type of zone uses tmpfs
 913  * 'use_vm_autoboot' -- (boolean) use vm-autoboot instead of autoboot
 914  * 'use_vmadmd' -- (boolean) use vmadmd for some actions instead of direct
 915  * 'var_svc_provisioning' -- (boolean) whether brand uses /var/svc/provisioning
 916  * 'wait_for_hwsetup' -- (boolean) use QMP and provision_success when hwsetup
 917  * 'write_zone_netfiles' -- (boolean) write out files like /etc/hostname.net0
 918  * 'zlogin_console' -- (boolean) use zlogin -C for console (vs. serial_console)
 919  * 'zoneinit' -- (boolean) this brand's setup may be controlled by zoneinit
 920  *
 921  * All of the keys:
 922  *
 923  *  allowed_properties
 924  *  required_properties
 925  *  features
 926  *
 927  * should be defined for each brand. Even if empty.
 928  */
 929 var BRAND_OPTIONS = {
 930     'joyent': {
 931         'allowed_properties': joyent_allowed,
 932         'required_properties': {
 933             'brand': ['create', 'receive'],
 934             'image_uuid': ['create', 'receive']
 935         }, 'features': {
 936             'brand_install_script': '/usr/lib/brand/joyent/jinstall',
 937             'cleanup_dataset': true,
 938             'mdata_restart': true,
 939             'reprovision': true,
 940             'type': 'OS',
 941             'update_mdata_exec_timeout': true,
 942             'update_rctls': true,
 943             'use_tmpfs': true,
 944             'write_zone_netfiles': true,
 945             'zlogin_console': true,
 946             'zoneinit': true
 947         }
 948     }, 'joyent-minimal': {
 949         'allowed_properties': joyent_allowed,
 950         'required_properties': {
 951             'brand': ['create', 'receive'],
 952             'image_uuid': ['create', 'receive']
 953         }, 'features': {
 954             'brand_install_script': '/usr/lib/brand/joyent-minimal/jinstall',
 955             'cleanup_dataset': true,
 956             'mdata_restart': true,
 957             'reprovision': true,
 958             'type': 'OS',
 959             'update_mdata_exec_timeout': true,
 960             'update_rctls': true,
 961             'use_tmpfs': true,
 962             'var_svc_provisioning': true,
 963             'write_zone_netfiles': true,
 964             'zlogin_console': true
 965         }
 966     }, 'sngl': {
 967         'allowed_properties': joyent_allowed,
 968         'required_properties': {
 969             'brand': ['create', 'receive'],
 970             'image_uuid': ['create', 'receive']
 971         }, 'features': {
 972             'cleanup_dataset': true,
 973             'mdata_restart': true,
 974             'type': 'OS',
 975             'update_mdata_exec_timeout': true,
 976             'update_rctls': true,
 977             'use_tmpfs': true,
 978             'write_zone_netfiles': true,
 979             'zlogin_console': true,
 980             'zoneinit': true
 981         }
 982     }, 'kvm': {
 983         'allowed_properties': {
 984             'add_disks': ['update'],
 985             'add_nics': ['update'],
 986             'alias': ['create', 'receive', 'update'],
 987             'archive_on_delete': ['create', 'receive', 'update'],
 988             'autoboot': ['create', 'receive', 'update'],
 989             'billing_id': ['create', 'receive', 'update'],
 990             'boot': ['create', 'receive', 'update'],
 991             'brand': ['create', 'receive'],
 992             'cpu_cap': ['create', 'receive', 'update'],
 993             'cpu_shares': ['create', 'receive', 'update'],
 994             'cpu_type': ['create', 'receive', 'update'],
 995             'create_only': ['receive'],
 996             'create_timestamp': ['receive'],
 997             'customer_metadata': ['create', 'receive'],
 998             'disks': ['create', 'receive'],
 999             'disks.*.block_size': ['add'],
1000             'disks.*.boot': ['add', 'update'],
1001             'disks.*.compression': ['add', 'update'],
1002             'disks.*.image_name': ['add', 'update'],
1003             'disks.*.image_size': ['add'],
1004             'disks.*.image_uuid': ['add'],
1005             'disks.*.refreservation': ['add', 'update'],
1006             'disks.*.size': ['add'],
1007             'disks.*.media': ['add', 'update'],
1008             'disks.*.model': ['add', 'update'],
1009             'disks.*.nocreate': ['add'],
1010             'disks.*.path': ['add', 'update'],
1011             'disks.*.zpool': ['add'],
1012             'disk_driver': ['create', 'receive', 'update'],
1013             'do_not_inventory': ['create', 'receive', 'update'],
1014             'firewall': ['create'],
1015             'firewall_enabled': ['create', 'receive', 'update'],
1016             'hostname': ['create', 'receive', 'update'],
1017             'image_uuid': ['create', 'receive'],
1018             'internal_metadata': ['create', 'receive'],
1019             'limit_priv': ['create', 'receive', 'update'],
1020             'max_locked_memory': ['create', 'receive', 'update'],
1021             'max_lwps': ['create', 'receive', 'update'],
1022             'max_physical_memory': ['create', 'receive', 'update'],
1023             'max_swap': ['create', 'receive', 'update'],
1024             'nics': ['create', 'receive'],
1025             'nics.*.allow_dhcp_spoofing': ['add', 'update'],
1026             'nics.*.allow_ip_spoofing': ['add', 'update'],
1027             'nics.*.allow_mac_spoofing': ['add', 'update'],
1028             'nics.*.allow_restricted_traffic': ['add', 'update'],
1029             'nics.*.allow_unfiltered_promisc': ['add', 'update'],
1030             'nics.*.allowed_ips': ['add', 'update'],
1031             'nics.*.blocked_outgoing_ports': ['add', 'update'],
1032             'nics.*.dhcp_server': ['add', 'update'],
1033             'nics.*.gateway': ['add', 'update'],
1034             'nics.*.interface': ['add', 'update'],
1035             'nics.*.ip': ['add', 'update'],
1036             'nics.*.mac': ['add', 'update'],
1037             'nics.*.model': ['add', 'update'],
1038             'nics.*.netmask': ['add', 'update'],
1039             'nics.*.network_uuid': ['add', 'update'],
1040             'nics.*.nic_tag': ['add', 'update'],
1041             'nics.*.primary': ['add', 'update'],
1042             'nics.*.vlan_id': ['add', 'update'],
1043             'nic_driver': ['create', 'receive', 'update'],
1044             'owner_uuid': ['create', 'receive', 'update'],
1045             'package_name': ['create', 'receive', 'update'],
1046             'package_version': ['create', 'receive', 'update'],
1047             'qemu_opts': ['create', 'receive', 'update'],
1048             'qemu_extra_opts': ['create', 'receive', 'update'],
1049             'quota': ['create', 'receive', 'update'],
1050             'ram': ['create', 'receive', 'update'],
1051             'remove_customer_metadata': ['update'],
1052             'remove_disks': ['update'],
1053             'remove_internal_metadata': ['update'],
1054             'remove_nics': ['update'],
1055             'remove_routes': ['update'],
1056             'remove_tags': ['update'],
1057             'resolvers': ['create', 'receive', 'update'],
1058             'set_customer_metadata': ['update'],
1059             'set_internal_metadata': ['update'],
1060             'set_routes': ['update'],
1061             'set_tags': ['update'],
1062             'spice_opts': ['create', 'receive', 'update'],
1063             'spice_password': ['create', 'receive', 'update'],
1064             'spice_port': ['create', 'receive', 'update'],
1065             'tags': ['create', 'receive'],
1066             'transition': ['receive'],
1067             'update_disks': ['update'],
1068             'update_nics': ['update'],
1069             'uuid': ['create', 'receive'],
1070             'v': ['receive'],
1071             'vcpus': ['create', 'receive', 'update'],
1072             'vga': ['create', 'receive', 'update'],
1073             'virtio_txburst': ['create', 'receive', 'update'],
1074             'virtio_txtimer': ['create', 'receive', 'update'],
1075             'vnc_password': ['create', 'receive', 'update'],
1076             'vnc_port': ['create', 'receive', 'update'],
1077             'zfs_io_priority': ['create', 'receive', 'update'],
1078             'zfs_root_compression': ['create', 'receive', 'update'],
1079             'zfs_root_recsize': ['create', 'receive', 'update'],
1080             'zone_dataset_uuid': ['create', 'receive'],
1081             'zpool': ['create', 'receive']
1082         }, 'required_properties': {
1083             'brand': ['create', 'receive']
1084         }, 'features': {
1085             'default_memory_overhead': VM.KVM_MEM_OVERHEAD,
1086             'limit_priv': ['default', '-file_link_any', '-net_access',
1087                 '-proc_fork', '-proc_info', '-proc_session'],
1088             'min_memory_overhead': VM.KVM_MIN_MEM_OVERHEAD,
1089             'model_required': true,
1090             'pid_file': '/tmp/vm.pid',
1091             'runtime_info': true,
1092             'serial_console': true,
1093             'type': 'KVM',
1094             'use_vm_autoboot': true,
1095             'use_vmadmd': true,
1096             'var_svc_provisioning': true,
1097             'wait_for_hwsetup': true
1098         }
1099     }
1100 };
1101 
1102 var VIRTIO_TXTIMER_DEFAULT = 200000;
1103 var VIRTIO_TXBURST_DEFAULT = 128;
1104 
1105 function getZpools(log, callback)
1106 {
1107     var args = ['list', '-H', '-p', '-o', 'name'];
1108     var cmd = '/usr/sbin/zpool';
1109     var idx;
1110     var raw = [];
1111     var zpools = [];
1112 
1113     assert(log, 'no logger passed to getZpools()');
1114 
1115     log.debug(cmd + ' ' + args.join(' '));
1116     execFile(cmd, args, function (error, stdout, stderr) {
1117         if (error) {
1118             log.error('Unable to get list of zpools');
1119             callback(error, {'stdout': stdout, 'stderr': stderr});
1120         } else {
1121             // strip out any empty values (last one).
1122             raw = stdout.split('\n');
1123             for (idx in raw) {
1124                 if (raw[idx].length > 0) {
1125                     zpools.push(raw[idx]);
1126                 }
1127             }
1128             callback(null, zpools);
1129         }
1130     });
1131 }
1132 
1133 /*
1134  * When you need to access files inside a zoneroot, you need to be careful that
1135  * there are no symlinks in the path. Since we operate from the GZ, these
1136  * symlinks will be evaluated in the GZ context. Eg. a symlink in zone A with
1137  * /var/run -> * /zones/<uuid of zone B>/root/var/run would mean that operating
1138  * on files in zone A's /var/run would actually be touching files in zone B.
1139  *
1140  * To prevent that, only ever modify files inside the zoneroot from the GZ
1141  * *before* first boot. After the zone is booted, it's better to use services
1142  * in the zone to pull values from metadata and write out changes on next boot.
1143  * It's also safe to use zlogin when the zone is running.
1144  *
1145  * This function is intended to be used in those cases we do write things out
1146  * before the zone's first boot but the dataset might have invalid symlinks in
1147  * it even then, so we still need to confirm the paths inside zoneroot before
1148  * using them. It throws an exception if:
1149  *
1150  *  - zoneroot is not an absolute path
1151  *  - fs.lstatSync fails
1152  *  - target path under zoneroot contains symlink
1153  *  - a component leading up to the final one is not a directory
1154  *  - options.type is set to 'file' and target is not a regular file
1155  *  - options.type is set to 'dir' and target references a non-directory
1156  *  - options.type is not one of 'file' or 'dir'
1157  *  - options.enoent_ok is false and target path doesn't exist
1158  *
1159  * if none of those are the case, it returns true.
1160  */
1161 function assertSafeZonePath(zoneroot, target, options)
1162 {
1163     var parts;
1164     var root;
1165     var stat;
1166     var test;
1167 
1168     assert((zoneroot.length > 0 && zoneroot[0] === '/'),
1169         'zoneroot must be an absolute path not: [' + zoneroot + ']');
1170 
1171     parts = trim(target, '/').split('/');
1172     root = trim(zoneroot, '/');
1173     test = '/' + root;
1174 
1175     while (parts.length > 0) {
1176         test = test + '/' + parts.shift();
1177 
1178         try {
1179             stat = fs.lstatSync(test);
1180         } catch (e) {
1181             if (e.code === 'ENOENT') {
1182                 if (!options.hasOwnProperty('enoent_ok')
1183                     || options.enoent_ok === false) {
1184 
1185                     throw e;
1186                 } else {
1187                     // enoent is ok, return true.  This is mostly used when
1188                     // deleting files with rm -f <path>.  It's ok for <path> to
1189                     // not exist (but not ok for any component to be a symlink)
1190                     // there's no point continuing though since ENOENT here
1191                     // means all subpaths also won't exist.
1192                     return true;
1193                 }
1194             } else {
1195                 throw e;
1196             }
1197         }
1198 
1199         if (stat.isSymbolicLink()) {
1200             // it's never ok to have a symlink component
1201             throw new Error(test + ' is a symlink');
1202         }
1203 
1204         // any component other than the last also needs to be a
1205         // directory, last can also be a file.
1206         if (parts.length === 0) {
1207             // last, dir or file
1208             if (!options.hasOwnProperty('type') || options.type === 'dir') {
1209                 if (!stat.isDirectory()) {
1210                     throw new Error(test + ' is not a directory');
1211                 }
1212             } else if (options.type === 'file') {
1213                 if (!stat.isFile()) {
1214                     throw new Error(test + ' is not a file');
1215                 }
1216             } else {
1217                 throw new Error('this function does not know about '
1218                     + options.type);
1219             }
1220         } else if (!stat.isDirectory()) {
1221             // not last component, only dir is acceptable
1222             throw new Error(test + ' is not a directory');
1223         }
1224     }
1225     // if we didn't throw, this is valid.
1226     return true;
1227 }
1228 
1229 function validateProperty(brand, prop, value, action, data, errors, log)
1230 {
1231     var allowed;
1232     var k;
1233 
1234     assert(log, 'no logger passed to validateProperty()');
1235 
1236     if (!data.hasOwnProperty('zpools')) {
1237         data.zpools = [];
1238     }
1239 
1240     if (BRAND_OPTIONS[brand].hasOwnProperty('allowed_properties')) {
1241         allowed = BRAND_OPTIONS[brand].allowed_properties;
1242     } else {
1243         allowed = {};
1244     }
1245 
1246     if (!errors.hasOwnProperty('bad_values')) {
1247         errors.bad_values = [];
1248     }
1249     if (!errors.hasOwnProperty('bad_properties')) {
1250         errors.bad_properties = [];
1251     }
1252 
1253     if (!allowed.hasOwnProperty(prop)) {
1254         // thie BRAND_OPTIONS doesn't have this property at all
1255         if (errors.bad_properties.indexOf(prop) === -1) {
1256             errors.bad_properties.push(prop);
1257         }
1258     } else if (!Array.isArray(allowed[prop])
1259         || allowed[prop].indexOf(action) === -1) {
1260 
1261         // here we've ether got no actions allowed for this value,
1262         // or just not this one
1263         if (errors.bad_properties.indexOf(prop) === -1) {
1264             errors.bad_properties.push(prop);
1265         }
1266     }
1267 
1268     if (PAYLOAD_PROPERTIES.hasOwnProperty(prop)) {
1269         switch (PAYLOAD_PROPERTIES[prop].type) {
1270         case 'uuid':
1271             if (typeof (value) === 'string' && !isUUID(value)
1272                 && errors.bad_values.indexOf(prop) === -1) {
1273 
1274                 errors.bad_values.push(prop);
1275             }
1276             break;
1277         case 'boolean':
1278             if (value === 1 || value === '1') {
1279                 log.warn('DEPRECATED: payload uses 1 instead of '
1280                     + 'true for ' + prop + ', use "true" instead.');
1281             } else if (typeof (fixBoolean(value)) !== 'boolean'
1282                 && errors.bad_values.indexOf(prop) === -1) {
1283 
1284                 errors.bad_values.push(prop);
1285             }
1286             break;
1287         case 'string':
1288             if (value === undefined || value === null
1289                 || trim(value.toString()) === '') {
1290                 // if set empty/false we'll keep since this is used to unset
1291                 break;
1292             } else if (typeof (value) !== 'string'
1293                 && errors.bad_values.indexOf(prop) === -1) {
1294 
1295                 errors.bad_values.push(prop);
1296             }
1297             break;
1298         case 'integer':
1299             if (value === undefined || value === null
1300                 || trim(value.toString()) === '') {
1301                 // if set empty/false we'll keep since this is used to unset
1302                 break;
1303             } else if (((typeof (value) !== 'string'
1304                 && typeof (value) !== 'number')
1305                 || !value.toString().match(/^[0-9]+$/))
1306                 && errors.bad_values.indexOf(prop) === -1) {
1307 
1308                 if ((['vnc_port', 'spice_port'].indexOf(prop) !== -1)
1309                     && (value.toString() === '-1')) {
1310 
1311                     // these keys allow '-1' as a value, so we succeed here even
1312                     // though we'd otherwise fail.
1313                     break;
1314                 }
1315 
1316                 errors.bad_values.push(prop);
1317             } else if (prop === 'max_swap' && value < MINIMUM_MAX_SWAP) {
1318                 errors.bad_values.push(prop);
1319             }
1320             break;
1321         case 'integer-8bit':
1322             if (value === undefined || value === null
1323                 || trim(value.toString()) === '') {
1324                 // if set empty/false we'll keep since this is used to unset
1325                 break;
1326             } else if (((typeof (value) !== 'string'
1327                 && typeof (value) !== 'number')
1328                 || !value.toString().match(/^[0-9]+$/))
1329                 && errors.bad_values.indexOf(prop) === -1
1330                 ) {
1331 
1332                 errors.bad_values.push(prop);
1333                 break;
1334             }
1335             if (value < 0 || value > 255) {
1336                 errors.bad_values.push(prop);
1337             }
1338             break;
1339         case 'zpool':
1340             if ((typeof (value) !== 'string'
1341                 || data.zpools.indexOf(value) === -1)
1342                 && errors.bad_values.indexOf(prop) === -1) {
1343 
1344                 errors.bad_values.push(prop);
1345             }
1346             break;
1347         case 'object':
1348             if (typeof (value) !== 'object'
1349                 && errors.bad_values.indexOf(prop) === -1) {
1350 
1351                 errors.bad_values.push(prop);
1352             }
1353             break;
1354         case 'flat-object':
1355             if (typeof (value) !== 'object'
1356                 && errors.bad_values.indexOf(prop) === -1) {
1357 
1358                 errors.bad_values.push(prop);
1359             }
1360             for (k in value) {
1361                 if (typeof (value[k]) !== 'string'
1362                     && typeof (value[k]) !== 'number'
1363                     && typeof (value[k]) !== 'boolean') {
1364 
1365                     if (errors.bad_values.indexOf(prop) === -1) {
1366                         errors.bad_values.push(prop);
1367                     }
1368                     break;
1369                 }
1370             }
1371             break;
1372         case 'list':
1373             if (typeof (value) === 'string') {
1374                 // really any string could be valid (a one element list)
1375                 break;
1376             } else if (Array.isArray(value)) {
1377                 for (k in value) {
1378                     if (typeof (value[k]) !== 'string'
1379                         && typeof (value[k]) !== 'number') {
1380 
1381                         // TODO: log something more useful here telling them
1382                         // the type is invalid.
1383                         if (errors.bad_values.indexOf(prop) === -1) {
1384                             errors.bad_values.push(prop);
1385                         }
1386                         break;
1387                     }
1388                     // if this is an array, it can't have commas in the
1389                     // values. (since we might stringify the list and
1390                     // we'd end up with something different.
1391                     if (value[k].toString().indexOf(',') !== -1
1392                         && errors.bad_values.indexOf(prop) === -1) {
1393 
1394                         errors.bad_values.push(prop);
1395                     }
1396                 }
1397             } else {
1398                 // not a valid type
1399                 if (errors.bad_values.indexOf(prop) === -1) {
1400                     errors.bad_values.push(prop);
1401                 }
1402             }
1403             break;
1404         case 'object-array':
1405             if (!Array.isArray(value)) {
1406                 if (errors.bad_values.indexOf(prop) === -1) {
1407                     errors.bad_values.push(prop);
1408                 }
1409                 break;
1410             }
1411             for (k in value) {
1412                 if (typeof (value[k]) !== 'object') {
1413                     if (errors.bad_values.indexOf(prop) === -1) {
1414                         errors.bad_values.push(prop);
1415                     }
1416                     break;
1417                 }
1418             }
1419             break;
1420         default:
1421             // don't know what type of prop this is, so it's invalid
1422             if (errors.bad_properties.indexOf(prop) === -1) {
1423                 errors.bad_properties.push(prop);
1424             }
1425             break;
1426         }
1427     }
1428 }
1429 
1430 /*
1431  * image properties:
1432  *
1433  *  size (optional, only used by zvols)
1434  *  type ('zvol' or 'zone-dataset')
1435  *  uuid
1436  *  zpool
1437  *
1438  */
1439 function validateImage(image, log, callback)
1440 {
1441     var args;
1442     var cmd = '/usr/sbin/imgadm';
1443 
1444     args = ['get', '-P', image.zpool, image.uuid];
1445 
1446     log.debug(cmd + ' ' + args.join(' '));
1447 
1448     // on any error we fail closed (assume the image does not exist)
1449     execFile(cmd, args, function (error, stdout, stderr) {
1450         var data;
1451         var e;
1452 
1453         if (error) {
1454             error.stdout = stdout;
1455             error.stderr = stderr;
1456             error.whatFailed = 'EEXECFILE';
1457             log.error(error);
1458             callback(error);
1459             return;
1460         }
1461 
1462         try {
1463             data = JSON.parse(stdout.toString());
1464         } catch (err) {
1465             data = {};
1466         }
1467 
1468         if (data.hasOwnProperty('manifest')) {
1469             if (data.manifest.type !== image.type) {
1470                 // image is wrong type
1471                 e = new Error('image ' + image.uuid + ' is type '
1472                     + data.manifest.type + ', must be ' + image.type);
1473                 e.whatFailed = 'EBADTYPE';
1474                 log.error(e);
1475                 callback(e);
1476                 return;
1477             }
1478             log.info('image ' + image.uuid + ' found in imgadm');
1479 
1480             // If image_size is missing, add it. If it's wrong, error.
1481             if (data.manifest.hasOwnProperty('image_size')) {
1482                 if (image.hasOwnProperty('size')) {
1483                     if (image.size !== data.manifest.image_size) {
1484                         e = new Error('incorrect image_size value for image'
1485                             + ' ' + image.uuid + ' passed: '
1486                             + image.size + ' should be: '
1487                             + data.manifest.image_size);
1488                         e.whatFailed = 'EBADSIZE';
1489                         log.error(e);
1490                         callback(e);
1491                         return;
1492                     }
1493                 } else {
1494                     // image doesn't have size, manifest does, add it.
1495                     image.size = data.manifest.image_size;
1496                 }
1497             }
1498             // everything ok
1499             callback();
1500         } else {
1501             e = new Error('cannot find \'manifest\' for image '
1502                 + image.uuid);
1503             e.whatFailed = 'ENOENT';
1504             log.error(e);
1505             callback(e);
1506             return;
1507         }
1508     });
1509 }
1510 
1511 // Ensure if image_uuid is passed either at top level or for disks.*.image_uuid
1512 // that image_uuid exists on the system according to imgadm.
1513 //
1514 // NOTE: if image_size is missing from payload, but found in imgadm it is added
1515 // to the payload here.
1516 //
1517 function validateImages(payload, errors, log, callback)
1518 {
1519     var check_images = [];
1520     var disk_idx;
1521     var pool;
1522 
1523     if (payload.hasOwnProperty('image_uuid') && isUUID(payload.image_uuid)) {
1524         if (payload.hasOwnProperty('zpool')) {
1525             pool = payload.zpool;
1526         } else {
1527             pool = 'zones';
1528         }
1529 
1530         check_images.push({
1531             'property': 'image_uuid',
1532             'target': payload,
1533             'type': 'zone-dataset',
1534             'uuid': payload.image_uuid,
1535             'zpool': pool
1536         });
1537     }
1538 
1539     ['disks', 'add_disks'].forEach(function (d) {
1540         if (payload.hasOwnProperty(d)) {
1541             disk_idx = 0;
1542             payload[d].forEach(function (disk) {
1543                 if (disk.hasOwnProperty('image_uuid')) {
1544                     if (disk.hasOwnProperty('zpool')) {
1545                         pool = disk.zpool;
1546                     } else {
1547                         pool = 'zones';
1548                     }
1549                     check_images.push({
1550                         'property_prefix': d + '.' + disk_idx,
1551                         'property': d + '.' + disk_idx + '.image_uuid',
1552                         'target': disk,
1553                         'type': 'zvol',
1554                         'uuid': disk.image_uuid,
1555                         'zpool': pool
1556                     });
1557                 }
1558                 disk_idx++;
1559             });
1560         }
1561     });
1562 
1563     async.forEachSeries(check_images, function (image, cb) {
1564 
1565         var i;
1566         var idx;
1567 
1568         i = {
1569             uuid: image.uuid,
1570             type: image.type,
1571             zpool: image.zpool
1572         };
1573 
1574         if (image.target.hasOwnProperty('image_size')) {
1575             i.size = image.target.image_size;
1576         }
1577 
1578         validateImage(i, log, function (err) {
1579             if (err) {
1580                 switch (err.whatFailed) {
1581                     case 'EBADSIZE':
1582                         // image.size is wrong (vs. manifest)
1583                         errors.bad_values.push(image.property_prefix
1584                             + '.image_size');
1585                         break;
1586                     case 'ENOENT':
1587                         // image.uuid not found in imgadm
1588                         errors.bad_values.push(image.property);
1589                         break;
1590                     case 'EBADTYPE':
1591                         // image.type is wrong
1592                         errors.bad_values.push(image.property);
1593                         break;
1594                     default:
1595                         // unknown error, fail closed
1596                         errors.bad_values.push(image.property);
1597                         break;
1598                 }
1599             } else {
1600                 // no errors, so check if size was added
1601                 if (i.hasOwnProperty('size')) {
1602                     if (!image.target.hasOwnProperty('image_size')) {
1603                         image.target.image_size = i.size;
1604                         // Remove error that would have been added earlier
1605                         // when we didn't have image_size
1606                         idx = errors.missing_properties.indexOf(
1607                             image.property_prefix + '.image_size');
1608                         if (idx !== -1) {
1609                             errors.missing_properties.splice(idx, 1);
1610                         }
1611                     }
1612                 }
1613             }
1614 
1615             cb();
1616         });
1617     }, function () {
1618         callback();
1619     });
1620 }
1621 
1622 // This is for allowed_ips which accepts IPiv4 addresses or CIDR addresses in
1623 // the form IP/MASK where MASK is 1-32.
1624 function validateIPlist(list) {
1625     var invalid = [];
1626 
1627     list.forEach(function (ip) {
1628         var matches;
1629         if (!net.isIPv4(ip)) {
1630             matches = ip.match(/^([0-9\.]+)\/([0-9]+)$/);
1631             if (matches && net.isIPv4(matches[1])
1632                 && (Number(matches[2]) >= 1) && (Number(matches[2]) <= 32)) {
1633 
1634                 // In this case it wasn't an IPv4, but it was a valid CIDR
1635                 return;
1636             } else {
1637                 invalid.push(ip);
1638             }
1639         }
1640     });
1641 
1642     if (invalid.length !== 0) {
1643         throw new Error('invalid allowed_ips: ' + invalid.join(', '));
1644     }
1645 
1646     if (list.length > 13) {
1647         throw new Error('Maximum of 13 allowed_ips per nic');
1648     }
1649 }
1650 
1651 exports.validate = function (brand, action, payload, options, callback)
1652 {
1653     var errors = {
1654         'bad_values': [],
1655         'bad_properties': [],
1656         'missing_properties': []
1657     };
1658     var log;
1659     var prop;
1660 
1661     // options is optional
1662     if (arguments.length === 4) {
1663         callback = arguments[3];
1664         options = {};
1665     }
1666 
1667     ensureLogging(false);
1668     if (options.hasOwnProperty('log')) {
1669         log = options.log;
1670     } else {
1671         log = VM.log.child({action: 'validate'});
1672     }
1673 
1674     if (!BRAND_OPTIONS.hasOwnProperty(brand)) {
1675         if (!brand) {
1676             brand = 'undefined';
1677         }
1678         callback({'bad_brand': brand});
1679         return;
1680     }
1681 
1682     // wrap the whole thing with getZpools so we have the list of pools if we
1683     // need them.
1684     getZpools(log, function (err, zpools) {
1685         var disk_idx;
1686         var idx;
1687         var prefix;
1688         var required;
1689         var subprop;
1690         var subprop_action = '';
1691         var value;
1692 
1693         if (err) {
1694             /*
1695              * this only happens when the zpool command fails which should be
1696              * very rare, but when it does happen, we continue with an empty
1697              * zpool list in case they don't need to validate zpools. If they
1698              * do, every zpool will be invalid which is also what we want since
1699              * nothing else that uses zpools is likely to work either.
1700              *
1701              */
1702             zpools = [];
1703         }
1704 
1705         // loop through and weed out ones we don't allow for this action.
1706         for (prop in payload) {
1707             validateProperty(brand, prop, payload[prop], action,
1708                 {zpools: zpools}, errors, log);
1709 
1710             // special case for complex properties where we want to check
1711             // foo.*.whatever
1712             if (PAYLOAD_PROPERTIES.hasOwnProperty(prop)
1713                 && PAYLOAD_PROPERTIES[prop].type === 'object-array'
1714                 && Array.isArray(payload[prop])) {
1715 
1716                 if (PAYLOAD_PROPERTIES[prop].hasOwnProperty('check_as')) {
1717                     prefix = PAYLOAD_PROPERTIES[prop].check_as + '.*.';
1718                     if (prop.match(/^add_/)) {
1719                         subprop_action = 'add';
1720                     } else if (prop.match(/^update_/)) {
1721                         subprop_action = 'update';
1722                     }
1723                 } else {
1724                     // here we've got something like 'disks' which is an add
1725                     prefix = prop + '.*.';
1726                     subprop_action = 'add';
1727                 }
1728 
1729                 for (idx in payload[prop]) {
1730                     if (typeof (payload[prop][idx]) === 'object') {
1731                         // subprop will be something like 'nic_tag'
1732                         for (subprop in payload[prop][idx]) {
1733                             value = payload[prop][idx][subprop];
1734                             validateProperty(brand, prefix + subprop, value,
1735                                 subprop_action, {zpools: zpools}, errors, log);
1736                         }
1737                     } else if (errors.bad_values.indexOf(prop) === -1) {
1738                         // this is not an object so bad value in the array
1739                         errors.bad_values.push(prop);
1740                     }
1741                 }
1742             }
1743         }
1744 
1745         // special case: if you have disks you must specify either image_uuid
1746         // and image_size *or* size and block_size is only allowed when you use
1747         // 'size' and image_name when you don't.
1748         if (BRAND_OPTIONS[brand].hasOwnProperty('allowed_properties')
1749             && BRAND_OPTIONS[brand].allowed_properties
1750             .hasOwnProperty('disks')) {
1751 
1752             function validateDiskSource(prop_prefix, disk) {
1753 
1754                 if (disk.hasOwnProperty('media') && disk.media !== 'disk') {
1755                     // we only care about disks here, not cdroms.
1756                     return;
1757                 }
1758 
1759                 if (disk.hasOwnProperty('image_uuid')) {
1760                     // with image_uuid, size is invalid and image_size is
1761                     // required, additionally block_size is not allowed.
1762 
1763                     if (!disk.hasOwnProperty('image_size')) {
1764                         errors.missing_properties.push(prop_prefix
1765                             + '.image_size');
1766                     }
1767                     if (disk.hasOwnProperty('size')) {
1768                         errors.bad_properties.push(prop_prefix + '.size');
1769                     }
1770                     if (disk.hasOwnProperty('block_size')) {
1771                         errors.bad_properties.push(prop_prefix
1772                             + '.block_size');
1773                     }
1774                 } else {
1775                     // without image_uuid, image_size and image_name are invalid
1776                     // and 'size' is required.
1777 
1778                     if (!disk.hasOwnProperty('size')) {
1779                         errors.missing_properties.push(prop_prefix + '.size');
1780                     }
1781                     if (disk.hasOwnProperty('image_name')) {
1782                         errors.bad_properties.push(prop_prefix + '.image_name');
1783                     }
1784                     if (disk.hasOwnProperty('image_size')) {
1785                         errors.bad_properties.push(prop_prefix + '.image_size');
1786                     }
1787                 }
1788             }
1789 
1790             if (payload.hasOwnProperty('disks')) {
1791                 for (disk_idx in payload.disks) {
1792                     validateDiskSource('disks.' + disk_idx,
1793                         payload.disks[disk_idx]);
1794                 }
1795             }
1796             if (payload.hasOwnProperty('add_disks')) {
1797                 for (disk_idx in payload.add_disks) {
1798                     validateDiskSource('add_disks.' + disk_idx,
1799                         payload.add_disks[disk_idx]);
1800                 }
1801             }
1802         }
1803 
1804         if (BRAND_OPTIONS[brand].hasOwnProperty('required_properties')) {
1805             required = BRAND_OPTIONS[brand].required_properties;
1806             for (prop in required) {
1807                 if (required[prop].indexOf(action) !== -1
1808                     && !payload.hasOwnProperty(prop)) {
1809 
1810                     errors.missing_properties.push(prop);
1811                 }
1812             }
1813         }
1814 
1815         // make sure any images in the payload are also valid
1816         // NOTE: if validateImages() finds errors, it adds to 'errors' here.
1817         validateImages(payload, errors, log, function () {
1818 
1819             // we validate disks.*.refreservation here because image_size might
1820             // not be populated yet until we return from validateImages()
1821             ['disks', 'add_disks'].forEach(function (d) {
1822                 var d_idx = 0;
1823                 if (payload.hasOwnProperty(d)) {
1824                     payload[d].forEach(function (disk) {
1825                         if (disk.hasOwnProperty('refreservation')) {
1826                             if (disk.refreservation < 0) {
1827                                 errors.bad_values.push(d + '.' + d_idx
1828                                     + '.refreservation');
1829                             } else if (disk.size
1830                                 && disk.refreservation > disk.size) {
1831 
1832                                 errors.bad_values.push(d + '.' + d_idx
1833                                     + '.refreservation');
1834                             } else if (disk.image_size
1835                                 && disk.refreservation > disk.image_size) {
1836 
1837                                 errors.bad_values.push(d + '.' + d_idx
1838                                     + '.refreservation');
1839                             }
1840                         }
1841                         d_idx++;
1842                     });
1843                 }
1844             });
1845 
1846             if (errors.bad_properties.length > 0 || errors.bad_values.length > 0
1847                 || errors.missing_properties.length > 0) {
1848 
1849                 callback(errors);
1850                 return;
1851             }
1852 
1853             callback();
1854         });
1855     });
1856 };
1857 
1858 function separateCommas(str)
1859 {
1860     return str.split(',');
1861 }
1862 
1863 function unmangleMem(str)
1864 {
1865     return (Number(str) / (1024 * 1024));
1866 }
1867 
1868 function unbase64(str)
1869 {
1870     return new Buffer(str, 'base64').toString('ascii');
1871 }
1872 
1873 function numberify(str)
1874 {
1875     return Number(str);
1876 }
1877 
1878 function startElement(name, attrs, state, log) {
1879     var disk = {};
1880     var key;
1881     var newobj;
1882     var nic = {};
1883     var obj;
1884     var prop;
1885     var stack;
1886     var use;
1887     var where;
1888 
1889     assert(log, 'no logger passed to startElement()');
1890 
1891     if (!state.hasOwnProperty('stack')) {
1892         state.stack = [];
1893     }
1894     obj = state.obj;
1895     stack = state.stack;
1896 
1897     stack.push(name);
1898     where = stack.join('.');
1899 
1900     if (XML_PROPERTIES.hasOwnProperty(where)) {
1901         for (key in XML_PROPERTIES[where]) {
1902             use = XML_PROPERTIES[where][key];
1903             if (attrs.hasOwnProperty(key)) {
1904                 obj[use] = attrs[key];
1905             } else if (attrs.hasOwnProperty('name') && attrs.name === key) {
1906                 // attrs use the whacky {name, type, value} stuff.
1907                 obj[use] = attrs['value'];
1908             }
1909         }
1910     } else if (where === 'zone.rctl') {
1911         stack.push(attrs.name);
1912     } else if (where === 'zone.network') {
1913         // new network device
1914         for (prop in attrs) {
1915             if (XML_PROPERTIES.nic.hasOwnProperty(prop)) {
1916                 use = XML_PROPERTIES.nic[prop];
1917                 if (prop === 'mac-addr') {
1918                     // XXX SmartOS inherited the ridiculous MAC formatting from
1919                     //     Solaris where leading zeros are removed. We should
1920                     //     Fix that in the OS tools.
1921                     nic[use] = fixMac(attrs[prop]);
1922                 } else {
1923                     nic[use] = attrs[prop];
1924                 }
1925             } else {
1926                 log.debug('unknown net prop: ' + prop);
1927             }
1928         }
1929         if (!obj.hasOwnProperty('networks')) {
1930             obj.networks = {};
1931         }
1932         obj.networks[nic.mac] = nic;
1933         stack.push(nic.mac);
1934     } else if (where.match(/zone\.network\...:..:..:..:..:..\.net-attr/)) {
1935         if (XML_PROPERTIES.nic.hasOwnProperty(attrs.name)) {
1936             use = XML_PROPERTIES.nic[attrs.name];
1937             obj.networks[stack[2]][use] = attrs.value;
1938         } else {
1939             log.debug('unknown net prop: ' + attrs.name);
1940         }
1941     } else if (where === 'zone.device') {
1942         // new disk device
1943         for (prop in attrs) {
1944             if (XML_PROPERTIES.disk.hasOwnProperty(prop)) {
1945                 use = XML_PROPERTIES.disk[prop];
1946                 disk[use] = attrs[prop];
1947             } else {
1948                 log.debug('unknown disk prop: ' + prop);
1949             }
1950         }
1951         if (!obj.hasOwnProperty('devices')) {
1952             obj.devices = {};
1953         }
1954         obj.devices[disk.path] = disk;
1955         stack.push(disk.path);
1956     } else if (where.match(/zone\.device\.\/.*\.net-attr/)) {
1957         if (XML_PROPERTIES.disk.hasOwnProperty(attrs.name)) {
1958             use = XML_PROPERTIES.disk[attrs.name];
1959             obj.devices[stack[2]][use] = attrs.value;
1960         } else {
1961             log.debug('unknown disk prop: ' + attrs.name);
1962         }
1963     } else if (where === 'zone.dataset') {
1964         if (!obj.hasOwnProperty('datasets')) {
1965             obj.datasets = [];
1966         }
1967         if (attrs.hasOwnProperty('name')) {
1968             obj.datasets.push(attrs.name);
1969         }
1970     } else if (where === 'zone.filesystem') {
1971         if (!obj.hasOwnProperty('filesystems')) {
1972             obj.filesystems = [];
1973         }
1974         newobj = {};
1975         for (prop in XML_PROPERTIES.filesystem) {
1976             if (attrs.hasOwnProperty(prop)) {
1977                 newobj[XML_PROPERTIES.filesystem[prop]] = attrs[prop];
1978             }
1979         }
1980         obj.filesystems.push(newobj);
1981     } else if (where === 'zone.filesystem.fsoption') {
1982         newobj = obj.filesystems.slice(-1)[0];  // the last element
1983         if (!newobj.hasOwnProperty('options')) {
1984             newobj.options = [];
1985         }
1986         newobj.options.push(attrs.name);
1987     } else {
1988         log.debug('unknown property: ' + where + ': '
1989             + JSON.stringify(attrs));
1990     }
1991 }
1992 
1993 function endElement(name, state) {
1994     // trim stack back above this element
1995     var stack = state.stack;
1996 
1997     while (stack.pop() !== name) {
1998         // do nothing, we just want to consume.
1999         continue;
2000     }
2001 }
2002 
2003 function indexSort(obj, field, pattern)
2004 {
2005     obj.sort(function (a, b) {
2006         var avalue = 0;
2007         var bvalue = 0;
2008         var matches;
2009 
2010         if (a.hasOwnProperty(field)) {
2011             matches = a[field].match(pattern);
2012             if (matches) {
2013                 avalue = Number(matches[1]);
2014             }
2015         }
2016         if (b.hasOwnProperty(field)) {
2017             matches = b[field].match(pattern);
2018             if (matches) {
2019                 bvalue = Number(matches[1]);
2020             }
2021         }
2022 
2023         return avalue - bvalue;
2024     });
2025 }
2026 
2027 function applyTransforms(obj)
2028 {
2029     var p;
2030     var pp;
2031     var subobj;
2032     var transforms = XML_PROPERTY_TRANSFORMS;
2033 
2034     for (p in transforms) {
2035         if (obj.hasOwnProperty(p)) {
2036             if (typeof (transforms[p]) === 'object') {
2037                 // this is a 'complex' property like nic, and has different
2038                 // transforms for the sub-objects
2039                 for (pp in transforms[p]) {
2040                     for (subobj in obj[p]) {
2041                         if (obj[p][subobj].hasOwnProperty(pp)) {
2042                             obj[p][subobj][pp] =
2043                                 transforms[p][pp](obj[p][subobj][pp]);
2044                         }
2045                     }
2046                 }
2047             } else { // function
2048                 obj[p] = transforms[p](obj[p]);
2049             }
2050         }
2051     }
2052 }
2053 
2054 // This function parses the zone XML file at /etc/zones/<zonename>.xml and adds
2055 // the VM properties to a new object.
2056 function getVmobj(zonename, preload_data, options, callback)
2057 {
2058     var filename = '/etc/zones/' + zonename + '.xml';
2059     var log;
2060     var parser = new expat.Parser('UTF-8');
2061 
2062     assert(options.log, 'no logger passed to getVmobj()');
2063     log = options.log;
2064 
2065     fs.readFile(filename, function (error, data) {
2066         var allowed;
2067         var disk;
2068         var dsinfo;
2069         var fields;
2070         var nic;
2071         var obj = {};
2072         var state = {};
2073 
2074         if (error) {
2075             callback(error);
2076             return;
2077         }
2078 
2079         state.obj = obj;
2080         parser.on('startElement', function (name, attrs) {
2081             return startElement(name, attrs, state, log);
2082         });
2083         parser.on('endElement', function (name) {
2084             return endElement(name, state);
2085         });
2086 
2087         if (!parser.parse(data.toString())) {
2088             throw new Error('There are errors in your xml file: '
2089                 + parser.getError());
2090         }
2091 
2092         // now that we know which brand we are, find out what we're allowed.
2093         allowed = BRAND_OPTIONS[obj.brand].allowed_properties;
2094 
2095         // replace obj.networks with array of nics.
2096         obj.nics = [];
2097         for (nic in obj.networks) {
2098             obj.nics.push(obj.networks[nic]);
2099         }
2100         delete obj.networks;
2101 
2102         // replace obj.devices with array of disks.
2103         if (allowed.hasOwnProperty('disks')) {
2104             obj.disks = [];
2105             for (disk in obj.devices) {
2106                 obj.disks.push(obj.devices[disk]);
2107             }
2108         }
2109         delete obj.devices;
2110 
2111         if (!BRAND_OPTIONS.hasOwnProperty(obj.brand)) {
2112             throw new Error('unable to handle brand ' + obj.brand);
2113         }
2114 
2115         if (BRAND_OPTIONS[obj.brand].features.use_vm_autoboot) {
2116             obj.autoboot = obj.vm_autoboot;
2117             delete obj.vm_autoboot;
2118         }
2119 
2120         // apply the XML_PROPERTY_TRANSFORMs
2121         applyTransforms(obj);
2122 
2123         // probe for some fields on disks if this brand of zone supports them.
2124         if (allowed.hasOwnProperty('disks')
2125             && (allowed.disks.indexOf('create') !== -1)) {
2126 
2127             for (disk in obj.disks) {
2128                 disk = obj.disks[disk];
2129 
2130                 if (preload_data.hasOwnProperty('dsinfo')) {
2131                     dsinfo = preload_data.dsinfo;
2132                     if (dsinfo.hasOwnProperty('mountpoints')
2133                         && dsinfo.mountpoints.hasOwnProperty(disk.path)) {
2134 
2135                         disk.zfs_filesystem = dsinfo.mountpoints[disk.path];
2136                         disk.zpool = disk.zfs_filesystem.split('/')[0];
2137                     } else {
2138                         log.trace('no mountpoint data for ' + disk.path);
2139                     }
2140                 }
2141             }
2142         }
2143 
2144         if (obj.hasOwnProperty('transition')) {
2145             fields = rtrim(obj.transition).split(':');
2146             if (fields.length === 3) {
2147                 delete obj.transition;
2148                 obj.state = fields[0];
2149                 obj.transition_to = fields[1];
2150                 obj.transition_expire = fields[2];
2151             } else {
2152                 log.debug('getVmobj() ignoring bad value for '
2153                     + 'transition "' + obj.transition + '"');
2154             }
2155         }
2156 
2157         // sort the disks + nics by index
2158         if (obj.hasOwnProperty('disks')) {
2159             indexSort(obj.disks, 'path', /^.*-disk(\d+)$/);
2160         }
2161         if (obj.hasOwnProperty('nics')) {
2162             indexSort(obj.nics, 'interface', /^net(\d+)$/);
2163         }
2164         if (obj.hasOwnProperty('filesystems')) {
2165             indexSort(obj.filesystems, 'target', /^(.*)$/);
2166         }
2167 
2168         callback(null, obj);
2169     });
2170 }
2171 
2172 function setQuota(dataset, quota, log, callback)
2173 {
2174     var newval;
2175 
2176     assert(log, 'no logger passed to setQuota()');
2177 
2178     if (!dataset) {
2179         callback(new Error('Invalid dataset: "' + dataset + '"'));
2180         return;
2181     }
2182 
2183     if (quota === 0 || quota === '0') {
2184         newval = 'none';
2185     } else {
2186         newval = quota.toString() + 'g';
2187     }
2188 
2189     zfs(['set', 'quota=' + newval, dataset], log, function (err, fds) {
2190         if (err) {
2191             log.error('setQuota() cmd failed: ' + fds.stderr);
2192             callback(new Error(rtrim(fds.stderr)));
2193         } else {
2194             callback();
2195         }
2196     });
2197 }
2198 
2199 function cleanDatasetObject(obj)
2200 {
2201     var number_fields = [
2202         'avail',
2203         'available',
2204         'copies',
2205         'creation',
2206         'filesystem_limit',
2207         'quota',
2208         'recsize',
2209         'recordsize',
2210         'refer',
2211         'referenced',
2212         'refquota',
2213         'refreserv',
2214         'refreservation',
2215         'reserv',
2216         'reservation',
2217         'snapshot_limit',
2218         'usedbychildren',
2219         'usedbydataset',
2220         'usedbyrefreservation',
2221         'usedbysnapshots',
2222         'used',
2223         'userrefs',
2224         'utf8only',
2225         'version',
2226         'volblock',
2227         'volblocksize',
2228         'volsize',
2229         'written'
2230     ];
2231 
2232     // We should always have mountpoint, dataset and type because we force them
2233     // to be included in zfsList()
2234     assert(obj.hasOwnProperty('mountpoint'), 'cleanDatasetObject('
2235         + JSON.stringify(obj) + '): missing mountpoint');
2236     assert(obj.hasOwnProperty('name'), 'cleanDatasetObject('
2237         + JSON.stringify(obj) + '): missing name');
2238     assert(obj.hasOwnProperty('type'), 'cleanDatasetObject('
2239         + JSON.stringify(obj) + '): missing type');
2240 
2241     // convert numeric fields to proper numbers
2242     number_fields.forEach(function (field) {
2243         if (obj.hasOwnProperty(field) && obj[field] !== '-') {
2244             obj[field] = Number(obj[field]);
2245         }
2246     });
2247 
2248     if (obj.type === 'volume') {
2249         obj.mountpoint = '/dev/zvol/rdsk/' + obj.name;
2250     } else if (obj.mountpoint === '-' || obj.mountpoint === 'legacy') {
2251         obj.mountpoint = '/' + obj.name;
2252     }
2253 }
2254 
2255 function addDatasetResult(fields, types, results, line, log)
2256 {
2257     var dataset;
2258     var field;
2259     var lfields;
2260     var obj;
2261     var snapparts;
2262     var snapobj;
2263 
2264     line = trim(line);
2265 
2266     if (line.length === 0) {
2267         return;
2268     }
2269 
2270     lfields = line.split(/\s+/);
2271 
2272     if (lfields.length !== fields.length) {
2273         return;
2274     }
2275 
2276     obj = {};
2277 
2278     for (field in fields) {
2279         obj[fields[field]] = lfields[field];
2280     }
2281 
2282     cleanDatasetObject(obj);
2283 
2284     if (!results.hasOwnProperty('datasets')) {
2285         results.datasets = {};
2286     }
2287     if (!results.hasOwnProperty('mountpoints')) {
2288         results.mountpoints = {};
2289     }
2290     if (types.indexOf('snapshot') !== -1 && obj.type === 'snapshot') {
2291         if (!results.hasOwnProperty('snapshots')) {
2292             results.snapshots = {};
2293         }
2294 
2295         /*
2296          * For snapshots we store the snapname and optionally creation keyed by
2297          * dataset name So that we can include the list of snapshots for a
2298          * dataset on a VM.
2299          */
2300         snapparts = obj.name.split('@');
2301         assert.equal(snapparts.length, 2);
2302         dataset = snapparts[0];
2303         snapobj = {snapname: snapparts[1], dataset: dataset};
2304         if (!results.snapshots.hasOwnProperty(dataset)) {
2305             results.snapshots[dataset] = [];
2306         }
2307         if (obj.hasOwnProperty('creation')) {
2308             snapobj.created_at = obj.creation;
2309         }
2310         results.snapshots[dataset].push(snapobj);
2311     }
2312 
2313     results.datasets[obj.name] = obj;
2314 
2315     /*
2316      * snapshots don't have mountpoint that we care about and we don't count
2317      * 'none' as a mountpoint. If we otherwise have a mountpoint that looks like
2318      * a path, we add a pointer from that to the dataset name.
2319      */
2320     if (obj.type !== 'snapshot' && obj.mountpoint[0] === '/') {
2321         /*
2322          * For zoned filesystems (delegated datasets) we don't use mountpoint as
2323          * this can be changed from within the zone and is therefore not
2324          * reliable. Also, when a delegated dataset is assigned but the zone's
2325          * not been booted, the delegated dataset will not have the 'zoned'
2326          * property.  So we also check if the name ends in /data.
2327          */
2328         if (obj.hasOwnProperty('zoned') && obj.zoned === 'on') {
2329             // don't add zoned datasets to mountpoints
2330             /*jsl:pass*/
2331         } else if (obj.name.split('/')[2] === 'data') {
2332             // name is /data, skip
2333             /*jsl:pass*/
2334         } else {
2335             // here we have what looks like a normal non-zoned dataset that's
2336             // probably a zoneroot, add to mountpoints mapping.
2337             results.mountpoints[obj.mountpoint] = obj.name;
2338         }
2339     }
2340 }
2341 
2342 /*
2343  * Arguments:
2344  *
2345  * 'fields'   - should be an array of fields as listed in the zfs(1m) man page.
2346  * 'types'    - should be one or more of: filesystem, snapshot, volume.
2347  * 'log'      - should be a bunyan logger object.
2348  * 'callback' - will be called with (err, results)
2349  *
2350  * On failure: callback's err will be an Error object, ignore results.
2351  * On success: callback's results is an object with one or more members of:
2352  *
2353  *     results.datasets
2354  *
2355  *         keyed by dataset name containing the values for the requested fields.
2356  *
2357  *         Eg: results.datasets['zones/cores'] === { name: 'zones/cores', ... }
2358  *
2359  *     results.mountpoints
2360  *
2361  *         keyed by mountpoint with value being dataset name.
2362  *
2363  *         Eg: results.mountpoints['/zones/cores'] === 'zones/cores'
2364  *
2365  *     results.snapshots
2366  *
2367  *         keyed by dataset with value being array of snapname and created_at.
2368  *
2369  *         Eg: results.snapshots['/zones/cores'] === ['snap1', ...]
2370  *
2371  * For non-zoned filesystem datasets (these should be the zoneroot datasets),
2372  * you can use mountpoint which comes from zoneadm's "cheap" info and use that
2373  * to get to the dataset and from datasets[dataset] get the info.
2374  *
2375  * For volumes (KVM VM's disks) you can also use mountpoint as we'll set that
2376  * to the block device path and that's available from the devices section of
2377  * the zoneconfig.
2378  *
2379  * For zoned filesystems (delegated datasets) use the dataset name, as the
2380  * mountpoint can be changed from within the zone.
2381  *
2382  */
2383 function zfsList(fields, types, log, callback) {
2384     var args;
2385     var buffer = '';
2386     var lines;
2387     var cmd = '/usr/sbin/zfs';
2388     var req_fields = ['mountpoint', 'name', 'type'];
2389     var results = {};
2390     var zfs_child;
2391 
2392     assert(Array.isArray(types));
2393     assert(Array.isArray(fields));
2394     assert(log, 'no logger passed to zfsList()');
2395 
2396     // add any missing required fields
2397     req_fields.forEach(function (field) {
2398         if (fields.indexOf(field) === -1) {
2399             fields.push(field);
2400         }
2401     });
2402 
2403     args = ['list', '-H', '-p', '-t', types.join(','), '-o', fields.join(',')];
2404 
2405     log.debug(cmd + ' ' + args.join(' '));
2406 
2407     zfs_child = spawn(cmd, args, {'customFds': [-1, -1, -1]});
2408     log.debug('zfs running with pid ' + zfs_child.pid);
2409 
2410     zfs_child.stdout.on('data', function (data) {
2411         var line;
2412 
2413         buffer += data.toString();
2414         lines = buffer.split('\n');
2415         while (lines.length > 1) {
2416             line = lines.shift();
2417 
2418             // Add this line to results
2419             addDatasetResult(fields, types, results, line, log);
2420         }
2421         buffer = lines.pop();
2422     });
2423 
2424     // doesn't take input.
2425     zfs_child.stdin.end();
2426 
2427     zfs_child.on('exit', function (code) {
2428         log.debug('zfs process ' + zfs_child.pid + ' exited with code: '
2429             + code);
2430         if (code === 0) {
2431             callback(null, results);
2432         } else {
2433             callback(new Error('zfs exited prematurely with code: ' + code));
2434         }
2435     });
2436 }
2437 
2438 /*
2439  * This queue is used to handle zfs list requests. We do this because of OS-1834
2440  * in order to only run one 'zfs list' at a time. If we need to get data from
2441  * 'zfs list', the parameters we want to list are pushed onto this queue. If a
2442  * list is already running with the same parameters, we'll return the output
2443  * from that one when it completes to all the consumers. If there's not one
2444  * running, or the parameters are different, this set of parameters will be
2445  * pushed onto the tail of the queue. The queue is processed serially so long
2446  * as there are active requests.
2447  */
2448 zfs_list_queue = async.queue(function (task, callback) {
2449 
2450     var fields = task.fields;
2451     var log = task.log;
2452     var started = Date.now(0);
2453     var types = task.types;
2454 
2455     zfsList(fields, types, log, function (err, data) {
2456         var emitter = zfs_list_in_progress[task];
2457 
2458         delete zfs_list_in_progress[task];
2459         emitter.emit('result', err, data);
2460         emitter.removeAllListeners('result');
2461 
2462         log.debug('zfs list took ' + (Date.now(0) - started) + ' ms');
2463         callback();
2464     });
2465 
2466 }, 1);
2467 
2468 zfs_list_queue.drain = function () {
2469     // We use the global log here because this queue is not tied to one action.
2470     VM.log.trace('zfs_list_queue is empty');
2471 };
2472 
2473 function getZfsList(fields, types, log, callback) {
2474     var sorted_fields;
2475     var sorted_types;
2476     var task;
2477 
2478     sorted_fields = fields.slice().sort();
2479     sorted_types = types.slice().sort();
2480 
2481     task = {types: sorted_types, fields: sorted_fields, log: log};
2482 
2483     try {
2484         zfs_list_in_progress[task].on('result', callback);
2485     } catch (e) {
2486         if ((e instanceof TypeError)
2487             && (!zfs_list_in_progress.hasOwnProperty(task)
2488             || !zfs_list_in_progress[task].hasOwnProperty('on'))) {
2489 
2490             zfs_list_in_progress[task] = new EventEmitter();
2491             zfs_list_in_progress[task].on('result', callback);
2492             zfs_list_in_progress[task].setMaxListeners(0);
2493             zfs_list_queue.push(task);
2494 
2495             // callback() will get called when 'result' is emitted.
2496         } else {
2497             callback(e);
2498         }
2499     }
2500 }
2501 
2502 function loadDatasetInfo(fields, log, callback)
2503 {
2504     var zfs_fields = [];
2505     var zfs_types = [];
2506 
2507     assert(log, 'no logger passed to loadDataset()');
2508 
2509     function addField(name) {
2510         if (zfs_fields.indexOf(name) === -1) {
2511             zfs_fields.push(name);
2512         }
2513     }
2514 
2515     function addType(name) {
2516         if (zfs_types.indexOf(name) === -1) {
2517             zfs_types.push(name);
2518         }
2519     }
2520 
2521     if (!fields || fields.length < 1) {
2522         // Default to grabbing everything we might possibly need.
2523         zfs_fields =  ['name', 'quota', 'volsize', 'mountpoint', 'type',
2524             'compression', 'recsize', 'volblocksize', 'zoned', 'creation',
2525             'refreservation'];
2526         zfs_types = ['filesystem', 'snapshot', 'volume'];
2527     } else {
2528         if (fields.indexOf('disks') !== -1) {
2529             addField('compression');
2530             addField('volsize');
2531             addField('volblocksize');
2532             addField('refreservation');
2533             addType('volume');
2534         }
2535         if (fields.indexOf('snapshots') !== -1) {
2536             addField('creation');
2537             addType('snapshot');
2538             addType('filesystem');
2539             addType('volume');
2540         }
2541         if (fields.indexOf('create_timestamp') !== -1) {
2542             // We might fall back to creation on the dataset for
2543             // create_timestamp if we have no create-timestamp attr.
2544             addField('creation');
2545             addType('filesystem');
2546         }
2547         if ((fields.indexOf('zfs_root_compression') !== -1)
2548             || (fields.indexOf('zfs_data_compression') !== -1)) {
2549 
2550             addField('compression');
2551             addType('filesystem');
2552         }
2553         if ((fields.indexOf('zfs_root_recsize') !== -1)
2554             || (fields.indexOf('zfs_data_recsize') !== -1)) {
2555 
2556             addField('recsize');
2557             addType('filesystem');
2558         }
2559         if (fields.indexOf('quota') !== -1) {
2560             addField('quota');
2561             addType('filesystem');
2562         }
2563         // both zpool and zfs_filesystem come from 'name'
2564         if (fields.indexOf('zpool') !== -1
2565             || fields.indexOf('zfs_filesystem') !== -1) {
2566 
2567             addField('name');
2568             addType('filesystem');
2569         }
2570         if (zfs_fields.length > 0) {
2571             // we have some fields so we need to zfs, make sure we have name,
2572             // mountpoint and type which we always need if we get anything.
2573             addField('name');
2574             addField('mountpoint');
2575             addField('type');
2576 
2577             if (zfs_types.indexOf('filesystem') !== -1) {
2578                 // to differentiate between delegated and root filesystems
2579                 addField('zoned');
2580             }
2581         } else {
2582             log.debug('no need to call zfs');
2583             callback(null, {
2584                 datasets: {},
2585                 mountpoints: {},
2586                 snapshots: {}
2587             });
2588             return;
2589         }
2590     }
2591 
2592     /*
2593      * NOTE:
2594      * in the future, the plan is to have the list of types and fields
2595      * be dynamic based what's actually needed to handle the request.
2596      */
2597 
2598     getZfsList(zfs_fields, zfs_types, log, callback);
2599 }
2600 
2601 function loadJsonConfig(vmobj, cfg, log, callback)
2602 {
2603     var filename;
2604 
2605     assert(log, 'no logger passed to loadJsonConfig()');
2606 
2607     if (vmobj.zonepath) {
2608         filename = vmobj.zonepath + '/config/' + cfg + '.json';
2609         log.trace('loadJsonConfig() loading ' + filename);
2610 
2611         fs.readFile(filename, function (error, data) {
2612             var json = {};
2613 
2614             if (error) {
2615                 if (error.code === 'ENOENT') {
2616                     log.debug('Skipping nonexistent file ' + filename);
2617                 } else {
2618                     log.error(error,
2619                         'loadJsonConfig() failed to load ' + filename);
2620                     callback(error, {});
2621                     return;
2622                 }
2623             } else {
2624                 try {
2625                     json = JSON.parse(data.toString());
2626                 } catch (e) {
2627                     json = {};
2628                 }
2629             }
2630 
2631             callback(null, json);
2632         });
2633     } else {
2634         callback(null, {});
2635     }
2636 }
2637 
2638 /*
2639  * This preloads some data for us that comes from commands which output for
2640  * *all* VMs.  This allows us to just run these (expensive) commands once
2641  * instead of having to run them for each VM.
2642  *
2643  */
2644 function preloadZoneData(uuid, options, callback)
2645 {
2646     var data = {};
2647     var log;
2648 
2649     assert(options.log, 'no logger passed to preloadZoneData()');
2650     log = options.log;
2651 
2652     // NOTE: uuid can be null, in which case we get data for all VMs.
2653 
2654     async.series([
2655         function (cb) {
2656             // We always do this (calls `zoneadm list -vc`) since we always
2657             // need to know which zones exist.
2658             getZoneRecords(uuid, log, function (err, records) {
2659                 if (!err) {
2660                     data.records = records;
2661                 }
2662                 cb(err);
2663             });
2664         }, function (cb) {
2665             var fields;
2666 
2667             if (options.hasOwnProperty('fields')) {
2668                 fields = options.fields;
2669             } else {
2670                 fields = [];
2671             }
2672 
2673             loadDatasetInfo(fields, log, function (err, dsinfo) {
2674                 if (!err) {
2675                     data.dsinfo = dsinfo;
2676                 }
2677                 cb(err);
2678             });
2679         }, function (cb) {
2680             if (options.hasOwnProperty('fields')
2681                 && (options.fields.indexOf('server_uuid') === -1
2682                     && options.fields.indexOf('datacenter_name') === -1
2683                     && options.fields.indexOf('headnode_id') === -1)) {
2684 
2685                 // we don't need any fields that come from sysinfo.
2686                 log.debug('no need to call sysinfo, no sysinfo fields in list');
2687                 data.sysinfo = {};
2688                 cb();
2689                 return;
2690             }
2691 
2692             VM.getSysinfo([], {log: log}, function (err, sysinfo) {
2693                 if (!err) {
2694                     data.sysinfo = sysinfo;
2695                 }
2696                 cb(err);
2697             });
2698         }, function (cb) {
2699             var u;
2700             var uuids = [];
2701 
2702             if (options.hasOwnProperty('fields')
2703                 && options.fields.indexOf('pid') === -1) {
2704 
2705                 log.debug('no need to check PID files, PID not in field list');
2706                 cb();
2707                 return;
2708             }
2709 
2710             // get the PID values from running KVM VMs
2711 
2712             for (u in data.records) {
2713                 uuids.push(u);
2714             }
2715             async.forEachSeries(uuids, function (z_uuid, zcb) {
2716                 var filename;
2717                 var z = data.records[z_uuid];
2718 
2719                 // NOTE: z.state here is equivalent to zone_state not state.
2720                 if (z && BRAND_OPTIONS[z.brand].hasOwnProperty('features')
2721                     && BRAND_OPTIONS[z.brand].features.pid_file
2722                     && z.state === 'running') {
2723 
2724                     // ensure pid_file is safe
2725                     try {
2726                         assertSafeZonePath(path.join(z.zonepath, '/root'),
2727                             BRAND_OPTIONS[z.brand].features.pid_file,
2728                             {type: 'file', enoent_ok: true});
2729                     } catch (e) {
2730                         // We log an error here, but not being able to get
2731                         // the PID for one broken machine should not impact the
2732                         // ability to get a list of all machines, so we just
2733                         // skip adding a PID and log an error here.
2734                         log.error(e, 'Unsafe path for ' + z.uuid + ' cannot '
2735                             + 'check for PID file: ' + e.message);
2736                         zcb();
2737                         return;
2738                     }
2739 
2740                     filename = path.join(z.zonepath, 'root',
2741                         BRAND_OPTIONS[z.brand].features.pid_file);
2742                     log.debug('checking for ' + filename);
2743 
2744                     fs.readFile(filename,
2745                         function (error, filedata) {
2746 
2747                         var pid;
2748 
2749                         if (!error) {
2750                             pid = Number(trim(filedata.toString()));
2751                             if (pid > 0) {
2752                                 z.pid = pid;
2753                                 log.debug('found PID ' + pid + ' for '
2754                                     + z.uuid);
2755                             }
2756                         }
2757                         if (error && error.code === 'ENOENT') {
2758                             // don't return error in this case because it just
2759                             // didn't exist
2760                             log.debug('no PID file for ' + z.uuid);
2761                             zcb();
2762                         } else {
2763                             zcb(error);
2764                         }
2765                     });
2766                 } else {
2767                     zcb();
2768                 }
2769             }, function (err) {
2770                 cb(err);
2771             });
2772         }
2773     ], function (err, res) {
2774         log.trace('leaving preloadZoneData()');
2775         callback(err, data);
2776     });
2777 }
2778 
2779 function getZoneRecords(uuid, log, callback)
2780 {
2781     var args = [];
2782     var buffer = '';
2783     var cmd = '/usr/sbin/zoneadm';
2784     var line_count = 0;
2785     var lines;
2786     var results = {};
2787     var zadm;
2788     var zadm_stderr = '';
2789 
2790     assert(log, 'no logger passed to getZoneRecords()');
2791 
2792     if (uuid) {
2793         // this gives us zone info if uuid is *either* a zonename or uuid
2794         if (isUUID(uuid)) {
2795             args.push('-z');
2796             args.push(uuid);
2797             args.push('-u');
2798             args.push(uuid);
2799         } else {
2800             args.push('-z');
2801             args.push(uuid);
2802         }
2803     }
2804     args.push('list');
2805     args.push('-p');
2806     if (!uuid) {
2807         args.push('-c');
2808     }
2809 
2810     log.debug(cmd + ' ' + args.join(' '));
2811 
2812     zadm = spawn(cmd, args, {'customFds': [-1, -1, -1]});
2813     log.debug('zoneadm running with PID ' + zadm.pid);
2814 
2815     zadm.stderr.on('data', function (data) {
2816         zadm_stderr += data.toString();
2817     });
2818 
2819     zadm.stdout.on('data', function (data) {
2820         var fields;
2821         var line;
2822         var obj;
2823 
2824         buffer += data.toString();
2825         lines = buffer.split('\n');
2826         while (lines.length > 1) {
2827             line = lines.shift();
2828             line_count++;
2829             fields = rtrim(line).split(':');
2830             if (fields.length === 8 && fields[1] !== 'global') {
2831                 obj = {
2832                     'zoneid': Number(fields[0]),
2833                     'zonename': fields[1],
2834                     'state': fields[2],
2835                     'zonepath': fields[3],
2836                     'uuid': fields[4],
2837                     'brand': fields[5],
2838                     'ip_type': fields[6]
2839                 };
2840                 log.trace('loaded: ' + JSON.stringify(obj));
2841                 // XXX zones in some states have no uuid. We should either fix
2842                 //     that or use zonename for those.
2843                 results[obj.uuid] = obj;
2844             } else if (line.replace(/ /g, '').length > 0) {
2845                 log.debug('getZoneRecords(' + uuid + ') ignoring: ' + line);
2846             }
2847         }
2848         buffer = lines.pop();
2849     });
2850 
2851     // doesn't take input.
2852     zadm.stdin.end();
2853 
2854     zadm.on('close', function (code) {
2855         var errmsg;
2856         var new_err;
2857 
2858         log.debug('zoneadm process ' + zadm.pid + ' exited with code: '
2859             + code + ' (' + line_count + ' lines to stdout)');
2860         if (code === 0) {
2861             callback(null, results);
2862         } else {
2863             errmsg = rtrim(zadm_stderr);
2864             new_err = new Error(errmsg);
2865             if (errmsg.match(/No such zone configured$/)) {
2866                 // not existing isn't always a problem (eg. existence check)
2867                 new_err.code = 'ENOENT';
2868             } else {
2869                 log.error({err: new_err, stderr: zadm_stderr},
2870                     'getZoneRecords() zoneadm "' + args.join(',') + '" failed');
2871             }
2872             callback(new_err);
2873             return;
2874         }
2875     });
2876 }
2877 
2878 exports.flatten = function (vmobj, key)
2879 {
2880     var index;
2881     var tokens = key.split('.');
2882 
2883     // NOTE: VM.flatten() currently doesn't produce any logs
2884 
2885     if (tokens.length === 3
2886         && VM.FLATTENABLE_ARRAY_HASH_KEYS.indexOf(tokens[0]) !== -1) {
2887 
2888         if (!vmobj.hasOwnProperty(tokens[0])) {
2889             return undefined;
2890         }
2891         if (!vmobj[tokens[0]].hasOwnProperty(tokens[1])) {
2892             return undefined;
2893         }
2894         return vmobj[tokens[0]][tokens[1]][tokens[2]];
2895     }
2896 
2897     if (tokens.length === 2
2898         && VM.FLATTENABLE_HASH_KEYS.indexOf(tokens[0]) !== -1) {
2899 
2900         if (!vmobj.hasOwnProperty(tokens[0])) {
2901             return undefined;
2902         }
2903         return vmobj[tokens[0]][tokens[1]];
2904     }
2905 
2906     if (tokens.length === 2
2907         && VM.FLATTENABLE_ARRAYS.indexOf(tokens[0]) !== -1) {
2908 
2909         index = Number(tokens[1]);
2910 
2911         if (!vmobj.hasOwnProperty(tokens[0])) {
2912             return undefined;
2913         }
2914 
2915         if (index === NaN || index < 0
2916             || !vmobj[tokens[0]].hasOwnProperty(index)) {
2917 
2918             return undefined;
2919         }
2920         return vmobj[tokens[0]][index];
2921     }
2922 
2923     return vmobj[key];
2924 };
2925 
2926 function getLastModified(vmobj, log)
2927 {
2928     var files = [];
2929     var file;
2930     var stat;
2931     var timestamp = 0;
2932 
2933     assert(log, 'no logger passed to getLastModified()');
2934 
2935     if (vmobj.zonepath) {
2936         files.push(path.join(vmobj.zonepath, '/config/metadata.json'));
2937         files.push(path.join(vmobj.zonepath, '/config/routes.json'));
2938         files.push(path.join(vmobj.zonepath, '/config/tags.json'));
2939     } else {
2940         log.debug('getLastModified() no zonepath!');
2941     }
2942 
2943     if (vmobj.hasOwnProperty('zonename')) {
2944         files.push('/etc/zones/' + vmobj.zonename + '.xml');
2945     } else {
2946         log.debug('getLastModified() no zonename!');
2947     }
2948 
2949     for (file in files) {
2950         file = files[file];
2951         try {
2952             stat = fs.statSync(file);
2953             if (stat.isFile()) {
2954                 if ((timestamp === 0) || (Date.parse(stat.mtime) > timestamp)) {
2955                     timestamp = Date.parse(stat.mtime);
2956                 }
2957             }
2958         } catch (e) {
2959             if (e.code !== 'ENOENT') {
2960                 log.error(e, 'Unable to get timestamp for "' + file + '":'
2961                     + e.message);
2962             }
2963         }
2964     }
2965 
2966     return ((new Date(timestamp)).toISOString());
2967 }
2968 
2969 function loadVM(uuid, data, options, callback)
2970 {
2971     var e;
2972     var info;
2973     var log;
2974 
2975     assert(options.log, 'no logger passed to loadVM()');
2976     log = options.log;
2977 
2978     // XXX need to always have data when we get here
2979     info = data.records[uuid];
2980 
2981     if (!info) {
2982         e = new Error('VM.load() empty info when getting record '
2983             + 'for vm ' + uuid);
2984         log.error(e);
2985         callback(e);
2986         return;
2987     }
2988 
2989     getVmobj(info.zonename, data, options, function (err, vmobj) {
2990         if (err) {
2991             callback(err);
2992             return;
2993         }
2994 
2995         function wantField(field) {
2996             if (options.hasOwnProperty('fields')
2997                 && options.fields.indexOf(field) === -1) {
2998 
2999                 return false;
3000             }
3001 
3002             return true;
3003         }
3004 
3005         // We got some bits from `zoneadm list` as <info> here, and since we
3006         // already got that data, adding it to the object here is cheap. We also
3007         // need some of these properties to be able to get others later, so we
3008         // add them all now. If they're unwanted they'll be removed from the
3009         // final object.
3010         vmobj.uuid = info.uuid;
3011         vmobj.zone_state = info.state;
3012 
3013         // In the case of 'configured' zones, we might only have zonename
3014         // because uuid isn't set yet.  Because of that case, we set uuid
3015         // to zonename if it is in UUID form.
3016         if ((!vmobj.uuid || vmobj.uuid.length === 0)
3017             && isUUID(vmobj.zonename)) {
3018 
3019             vmobj.uuid = vmobj.zonename;
3020         }
3021 
3022         // These ones we never need elsewhere, so don't bother adding if we
3023         // don't need to.
3024         if (wantField('zoneid') && info.zoneid !== '-') {
3025             vmobj.zoneid = info.zoneid;
3026         }
3027 
3028         if (wantField('pid') && info.pid) {
3029             vmobj.pid = info.pid;
3030         }
3031 
3032         // find when we last modified this VM
3033         if (wantField('last_modified')) {
3034             vmobj.last_modified = getLastModified(vmobj, log);
3035         }
3036 
3037         // If we want resolvers, (eg. OS-2194) we always add the array here
3038         // so you can tell that the resolvers are explicitly not set.
3039         if (wantField('resolvers') && !vmobj.hasOwnProperty('resolvers')) {
3040             vmobj.resolvers = [];
3041         }
3042 
3043         // sysinfo has server_uuid and potentially some DC info
3044         if (data.hasOwnProperty('sysinfo')) {
3045             if (wantField('server_uuid')
3046                 && data.sysinfo.hasOwnProperty('UUID')) {
3047 
3048                 vmobj.server_uuid = data.sysinfo.UUID;
3049             }
3050             if (wantField('datacenter_name')
3051                 && data.sysinfo.hasOwnProperty('Datacenter Name')) {
3052 
3053                 vmobj.datacenter_name = data.sysinfo['Datacenter Name'];
3054             }
3055             if (wantField('headnode_id')
3056                 && data.sysinfo.hasOwnProperty('Headnode ID')) {
3057 
3058                 vmobj.headnode_id = data.sysinfo['Headnode ID'];
3059             }
3060         }
3061 
3062         // state could already be set here if it was overriden by a transition
3063         // that's in progress. So we only change if that's not the case.
3064         if (wantField('state')) {
3065             if (!vmobj.hasOwnProperty('state')) {
3066                 if (info.state === 'installed') {
3067                     vmobj.state = 'stopped';
3068                 } else {
3069                     vmobj.state = info.state;
3070                 }
3071             }
3072 
3073             // If the zone has the 'failed' property it doesn't matter what
3074             // other state it might be in, we list its state as 'failed'.
3075             if (vmobj.failed) {
3076                 vmobj.state = 'failed';
3077             }
3078         }
3079 
3080         async.series([
3081             function (cb) {
3082                 if (!wantField('customer_metadata')
3083                     && !wantField('internal_metadata')) {
3084 
3085                     cb();
3086                     return;
3087                 }
3088 
3089                 loadJsonConfig(vmobj, 'metadata', log,
3090                     function (error, metadata) {
3091                     if (error) {
3092                         // when zone_state is 'incomplete' we could be
3093                         // deleting it in which case metadata may already
3094                         // be gone, ignore failure to load mdata when
3095                         // 'incomplete' because of this.
3096                         if (vmobj.zone_state === 'incomplete') {
3097                             log.debug(error, 'zone is in state incomplete '
3098                                 + 'ignoring error: ' + error.message);
3099                         } else {
3100                             cb(error);
3101                             return;
3102                         }
3103                     }
3104 
3105                     if (wantField('customer_metadata')) {
3106                         if (metadata.hasOwnProperty('customer_metadata')) {
3107                             vmobj.customer_metadata
3108                                 = metadata.customer_metadata;
3109                         } else {
3110                             vmobj.customer_metadata = {};
3111                         }
3112                     }
3113 
3114                     if (wantField('internal_metadata')) {
3115                         if (metadata.hasOwnProperty('internal_metadata')) {
3116                             vmobj.internal_metadata
3117                                 = metadata.internal_metadata;
3118                         } else {
3119                             vmobj.internal_metadata = {};
3120                         }
3121                     }
3122 
3123                     cb();
3124                 });
3125             }, function (cb) {
3126                 if (!wantField('tags')) {
3127                     cb();
3128                     return;
3129                 }
3130 
3131                 loadJsonConfig(vmobj, 'tags', log, function (error, tags) {
3132                     if (error) {
3133                         // when zone_state is 'incomplete' we could be deleting
3134                         // it in which case metadata may already be gone, ignore
3135                         // failure to load mdata when 'incomplete' because of
3136                         // this.
3137                         if (vmobj.zone_state === 'incomplete') {
3138                             log.debug(error, 'zone is in state incomplete '
3139                                 + 'ignoring error: ' + error.message);
3140                         } else {
3141                             cb(error);
3142                             return;
3143                         }
3144                     }
3145                     vmobj.tags = tags;
3146                     cb();
3147                 });
3148             }, function (cb) {
3149                 if (!wantField('routes')) {
3150                     cb();
3151                     return;
3152                 }
3153 
3154                 loadJsonConfig(vmobj, 'routes', log, function (error, routes) {
3155                     if (error) {
3156                         // same as tags above, if zone_state is 'incomplete'
3157                         // we could be a file that's already gone
3158                         if (vmobj.zone_state === 'incomplete') {
3159                             log.debug(error, 'zone is in state incomplete '
3160                                 + 'ignoring error: ' + error.message);
3161                         } else {
3162                             cb(error);
3163                             return;
3164                         }
3165                     }
3166                     vmobj.routes = routes;
3167                     cb();
3168                 });
3169             }, function (cb) {
3170                 var dsinfo;
3171                 var dsname;
3172                 var dsobj;
3173                 var d;
3174                 var delegated;
3175                 var disk;
3176                 var ds;
3177                 var filesys;
3178                 var friendly_snap;
3179                 var friendly_snapshots = [];
3180                 var matches;
3181                 var raw_snapshots = [];
3182                 var snap;
3183                 var snap_time;
3184 
3185                 // local alias, data.dsinfo should include all the info about
3186                 // this VM's zoneroot that we care about here.
3187                 dsinfo = data.dsinfo;
3188 
3189                 if (dsinfo.hasOwnProperty('mountpoints')
3190                     && dsinfo.hasOwnProperty('datasets')
3191                     && dsinfo.mountpoints.hasOwnProperty(vmobj.zonepath)) {
3192 
3193                     dsname = dsinfo.mountpoints[vmobj.zonepath];
3194                     dsobj = dsinfo.datasets[dsname];
3195 
3196                     /* dsobj.quota is in bytes, we want GiB for vmobj.quota */
3197                     if (wantField('quota') && dsobj.hasOwnProperty('quota')) {
3198                         vmobj.quota = (dsobj.quota / (1024 * 1024 * 1024));
3199                         log.trace('found quota "' + vmobj.quota + '" for '
3200                             + vmobj.uuid);
3201                     }
3202 
3203                     if (wantField('create_timestamp')
3204                         && !vmobj.hasOwnProperty('create_timestamp')
3205                         && dsobj.hasOwnProperty('creation')) {
3206 
3207                         log.debug('VM has no create_timestamp, using creation '
3208                             + 'from ' + dsobj.name);
3209                         vmobj.create_timestamp =
3210                             (new Date(dsobj.creation * 1000)).toISOString();
3211                     }
3212 
3213                     if (wantField('zfs_root_compression')
3214                         && dsobj.hasOwnProperty('compression')
3215                         && (dsobj.compression !== 'off')) {
3216 
3217                         vmobj.zfs_root_compression = dsobj.compression;
3218                     }
3219 
3220                     if (wantField('zfs_root_recsize')
3221                         && dsobj.hasOwnProperty('recsize')) {
3222 
3223                         vmobj.zfs_root_recsize = dsobj.recsize;
3224                     }
3225 
3226                     // Always add zfs_filesystem if we can because it's needed
3227                     // to find other properties such as delegated_dataset.
3228                     vmobj.zfs_filesystem = dsobj.name;
3229 
3230                     if (wantField('snapshots')
3231                         && dsinfo.hasOwnProperty('snapshots')
3232                         && dsinfo.snapshots
3233                         .hasOwnProperty(vmobj.zfs_filesystem)) {
3234 
3235                         raw_snapshots = raw_snapshots.concat(
3236                             dsinfo.snapshots[vmobj.zfs_filesystem]);
3237                     }
3238 
3239                     log.trace('found dataset "' + vmobj.zfs_filesystem
3240                         + '" for ' + vmobj.uuid);
3241                 } else {
3242                     log.trace('no dsinfo for ' + vmobj.uuid + ': '
3243                         + vmobj.zonepath);
3244                 }
3245 
3246                 // delegated datasets are keyed on the dataset name instead of
3247                 // mountpoint, since mountpoint can change in a zone.
3248                 if (vmobj.hasOwnProperty('zfs_filesystem')) {
3249                     delegated = vmobj.zfs_filesystem + '/data';
3250                     if (dsinfo.datasets.hasOwnProperty(delegated)) {
3251                         dsobj = dsinfo.datasets[delegated];
3252 
3253                         if (dsobj.hasOwnProperty('compression')
3254                             && (dsobj.compression !== 'off')) {
3255 
3256                             vmobj.zfs_data_compression = dsobj.compression;
3257                         }
3258                         if (dsobj.hasOwnProperty('recsize')) {
3259                             vmobj.zfs_data_recsize = dsobj.recsize;
3260                         }
3261 
3262                         // If there are snapshots for this dataset, add them
3263                         if (DISABLED) {
3264                             // XXX currently only support snapshot on
3265                             // zfs_filesystem
3266                             if (dsinfo.hasOwnProperty('snapshots')
3267                                 && dsinfo.snapshots.hasOwnProperty(delegated)) {
3268 
3269                                 raw_snapshots = raw_snapshots
3270                                     .concat(dsinfo.snapshots[delegated]);
3271                             }
3272                         }
3273                     } else {
3274                         log.trace('no dsinfo for delegated dataset: '
3275                             + delegated);
3276                     }
3277 
3278                     vmobj.zpool =
3279                         vmobj.zfs_filesystem.split('/')[0];
3280                 }
3281 
3282                 if (wantField('disks') && vmobj.hasOwnProperty('disks')) {
3283                     for (d in vmobj.disks) {
3284                         d = vmobj.disks[d];
3285                         if (d.hasOwnProperty('path')
3286                             && dsinfo.mountpoints.hasOwnProperty(d.path)) {
3287 
3288                             dsname = dsinfo.mountpoints[d.path];
3289                             dsobj = dsinfo.datasets[dsname];
3290 
3291                             if (dsobj.hasOwnProperty('volsize')) {
3292 
3293                                 /* dsobj.volsize is in bytes, we want MiB */
3294                                 d.size = (dsobj.volsize / (1024 * 1024));
3295                                 log.debug('found size=' + d.size + ' for '
3296                                     + JSON.stringify(d));
3297                             }
3298                             if (dsobj.hasOwnProperty('compression')) {
3299                                 d.compression = dsobj.compression;
3300                             }
3301                             if (dsobj.hasOwnProperty('refreservation')) {
3302                                 /* dsobj.refreservation is in bytes, want MiB */
3303                                 d.refreservation
3304                                     = (dsobj.refreservation / (1024 * 1024));
3305                                 log.debug('found refreservation='
3306                                     + d.refreservation + ' for '
3307                                     + JSON.stringify(d));
3308                             }
3309                             if (dsobj.hasOwnProperty('volblocksize')) {
3310                                 d.block_size = dsobj.volblocksize;
3311                             }
3312 
3313                             // If there are snapshots for this dataset, add them
3314                             // to the list.
3315                             if (DISABLED) {
3316                                 // XXX currently only support snapshots on
3317                                 //     zfs_filesystem
3318                                 if (dsinfo.hasOwnProperty('snapshots')
3319                                     && dsinfo.snapshots.hasOwnProperty(
3320                                         d.zfs_filesystem)) {
3321 
3322                                     raw_snapshots = raw_snapshots.concat(dsinfo
3323                                         .snapshots[d.zfs_filesystem]);
3324                                 }
3325                             }
3326                         } else if (d.hasOwnProperty('path')) {
3327                             d.missing = true;
3328                         } else {
3329                             log.warn('no dsinfo and no path for '
3330                                 + JSON.stringify(d));
3331                         }
3332                     }
3333                 }
3334 
3335                 // snapshots here is the raw list of snapshots, now we need to
3336                 // convert it to the "friendly" list of snapshots.
3337                 if (wantField('snapshots')) {
3338                     for (snap in raw_snapshots) {
3339                         snap = raw_snapshots[snap];
3340 
3341                         matches = snap.snapname.match(/^vmsnap-(.*)$/);
3342                         if (matches && matches[1]) {
3343                             friendly_snap = {name: matches[1]};
3344                             if (snap.hasOwnProperty('created_at')) {
3345                                 snap_time
3346                                     = new Date(snap.created_at * 1000); // in ms
3347                                 friendly_snap.created_at
3348                                     = snap_time.toISOString();
3349                             }
3350                             friendly_snapshots.push(friendly_snap);
3351                         } else {
3352                             log.debug('ignoring unfriendly ' + snap.snapname);
3353                             continue;
3354                         }
3355                     }
3356                     // sort the snapshots with newest first.
3357                     friendly_snapshots.sort(function (a, b) {
3358                         if (a.created_at > b.created_at) {
3359                             return -1;
3360                         }
3361                         if (a.created_at < b.created_at) {
3362                             return 1;
3363                         }
3364                         return 0; // equal
3365                     });
3366                     vmobj.snapshots = friendly_snapshots;
3367                 }
3368 
3369                 if (vmobj.state === 'receiving') {
3370                     vmobj.missing = { 'datasets': [], 'disks': [],
3371                         'filesystems': [] };
3372                     if (!fs.existsSync(vmobj.zonepath)) {
3373                         vmobj.missing.datasets.push(vmobj.zonepath.substr(1));
3374                     }
3375                     for (ds in vmobj.datasets) {
3376                         ds = vmobj.datasets[ds];
3377                         vmobj.missing.datasets.push(ds);
3378                     }
3379                     for (filesys in vmobj.filesystems) {
3380                         filesys = vmobj.filesystems[filesys];
3381                         if (filesys.hasOwnProperty('source')) {
3382                             vmobj.missing.filesystems.push(filesys.source);
3383                         }
3384                     }
3385                     for (disk in vmobj.disks) {
3386                         disk = vmobj.disks[disk];
3387                         if (disk.hasOwnProperty('missing')) {
3388                             vmobj.missing.disks.push(disk.path);
3389                         }
3390                     }
3391                 }
3392 
3393                 cb();
3394             }
3395         ], function (error) {
3396             callback(error, vmobj);
3397         });
3398 
3399     });
3400 }
3401 
3402 exports.load = function (uuid, options, callback)
3403 {
3404     var log;
3405     var load_opts = {};
3406 
3407     // This is a wrapper so that other internal functions here (such as lookup)
3408     // can do smart things like check the quota for each VM with a separate call
3409     // to zfs get.
3410 
3411     // options is optional
3412     if (arguments.length === 2) {
3413         callback = arguments[1];
3414         options = {};
3415     }
3416 
3417     ensureLogging(false);
3418     if (options.hasOwnProperty('log')) {
3419         log = options.log;
3420     } else {
3421         log = VM.log.child({action: 'load', vm: uuid});
3422     }
3423 
3424     load_opts.log = log;
3425     if (options.hasOwnProperty('fields')) {
3426         load_opts.fields = options.fields;
3427     }
3428 
3429     preloadZoneData(uuid, load_opts, function (error, data) {
3430         if (error) {
3431             if (options.missing_ok && error.code === 'ENOENT') {
3432                 // we're expecting the zone to be gone in this case (eg. delete)
3433                 log.debug('VM ' + uuid + ' does not exist (as expected)');
3434             } else {
3435                 log.error(error, 'VM.load() failed to get zone record'
3436                     + ' for ' + uuid);
3437             }
3438             callback(error);
3439         } else {
3440             loadVM(uuid, data, load_opts, function (e, vmobj) {
3441                 if (e) {
3442                     callback(e);
3443                     return;
3444                 }
3445 
3446                 if (load_opts.hasOwnProperty('fields')) {
3447                     // clean out unwanted fields
3448                     Object.keys(vmobj).forEach(function (key) {
3449                         if (options.fields.indexOf(key) === -1) {
3450                             delete vmobj[key];
3451                         }
3452                     });
3453                 }
3454                 callback(null, vmobj);
3455             });
3456         }
3457     });
3458 };
3459 
3460 function fixMac(str)
3461 {
3462     var fixed = [];
3463     var octet;
3464     var octets = str.split(':');
3465 
3466     for (octet in octets) {
3467         if (octets.hasOwnProperty(octet)) {
3468             octet = parseInt(octets[octet], 16);
3469             if (octet === 'nan') {
3470                 octet = 0;
3471             }
3472             fixed.push(sprintf('%02x', octet));
3473         }
3474     }
3475 
3476     return fixed.join(':');
3477 }
3478 
3479 // zonecfg requires removing leading 0's in MACs like 01:02:03:04:05:06
3480 // This function takes a MAC in normal form and puts it in the goofy form
3481 // zonecfg wants.
3482 function ruinMac(mac)
3483 {
3484     var part;
3485     var parts;
3486     var out = [];
3487 
3488     parts = mac.split(':');
3489 
3490     for (part in parts) {
3491         part = ltrim(parts[part], '0');
3492         if (part.length === 0) {
3493             part = '0';
3494         }
3495         out.push(part);
3496     }
3497 
3498     return (out.join(':'));
3499 }
3500 
3501 function matcher(zone, search)
3502 {
3503     var fields;
3504     var found;
3505     var i;
3506     var key;
3507     var parameters_matched = 0;
3508     var regex;
3509     var target;
3510 
3511     function find_match(k, targ) {
3512         var value = VM.flatten(zone, k);
3513 
3514         if (!regex && k.match(/^nics\..*\.mac$/)) {
3515             // Fix for broken SmartOS MAC format
3516             targ = fixMac(targ);
3517         }
3518 
3519         if (regex && (value !== undefined) && value.toString().match(targ)) {
3520             found = true;
3521         } else if ((value !== undefined)
3522             && value.toString() === targ.toString()) {
3523             found = true;
3524         }
3525     }
3526 
3527     for (key in search) {
3528         found = false;
3529         regex = false;
3530 
3531         target = search[key];
3532         if (target[0] === '~') {
3533             regex = true;
3534             target = new RegExp(target.substr(1), 'i');
3535         }
3536 
3537         fields = key.split('.');
3538         if (fields.length === 3 && fields[1] === '*'
3539             && zone.hasOwnProperty(fields[0])
3540             && VM.FLATTENABLE_ARRAY_HASH_KEYS.indexOf(fields[0]) !== -1) {
3541 
3542             // Special case: for eg. nics.*.ip, we want to loop through all nics
3543             for (i = 0; i < zone[fields[0]].length; i++) {
3544                 fields[1] = i;
3545                 find_match(fields.join('.'), target);
3546             }
3547         } else {
3548             find_match(key, target);
3549         }
3550 
3551         if (!found) {
3552             return false;
3553         } else {
3554             parameters_matched++;
3555         }
3556     }
3557 
3558     if (parameters_matched > 0) {
3559         // we would have returned false from the loop had any parameters not
3560         // matched and we had at least one that did.
3561         return true;
3562     }
3563 
3564     return false;
3565 }
3566 
3567 exports.lookup = function (search, options, callback)
3568 {
3569     var log;
3570     var key;
3571     var matches;
3572     var need_fields = [];
3573     var preload_opts = {};
3574     var quick_ok = true;
3575     var results = [];
3576     var transform;
3577 
3578     // options is optional
3579     if (arguments.length === 2) {
3580         callback = arguments[1];
3581         options = {};
3582     }
3583 
3584     ensureLogging(false);
3585     if (options.hasOwnProperty('log')) {
3586         log = options.log;
3587     } else {
3588         log = VM.log.child({action: 'lookup', search: search});
3589     }
3590 
3591     // XXX the 'transform' option is not intended to be public yet and should
3592     // only be used by tools willing to be rewritten if this is removed or
3593     // changed.
3594     if (options.hasOwnProperty('transform')) {
3595         transform = options.transform;
3596     }
3597 
3598     // keep separate variable because we can have some fields we add below that
3599     // we need for searching, but shouldn't be in the output.
3600     if (options.hasOwnProperty('fields')) {
3601         need_fields = options.fields.slice(0);
3602     }
3603 
3604     for (key in search) {
3605         // To be able to search on a field, that field needs to be added to
3606         // the objects, if user requested a set of fields missing the one
3607         // they're searching for, add it.
3608         matches = key.match(/^([^.]+)\./);
3609         if (matches) {
3610             if (need_fields.indexOf(matches[1]) == -1) {
3611                 need_fields.push(matches[1]);
3612             }
3613         } else {
3614             if (need_fields.indexOf(key) == -1) {
3615                 need_fields.push(key);
3616             }
3617         }
3618     }
3619 
3620     // If all the keys we're searching for are in the QUICK_LOOKUP data, we
3621     // don't need the full zone records to locate the VMs we're interested in.
3622     for (key in need_fields) {
3623         if (QUICK_LOOKUP.indexOf(key) === -1) {
3624             quick_ok = false;
3625         }
3626     }
3627 
3628     preload_opts.log = log;
3629     if (options.hasOwnProperty('fields')) {
3630         preload_opts.fields = need_fields;
3631     }
3632 
3633     // This is used when you've specified fields to remove those that might
3634     // have been added as a group but are not wanted, or were added as
3635     // dependencies for looking up wanted fields, or for search.
3636     function filterFields(res) {
3637         res.forEach(function (result) {
3638             Object.keys(result).forEach(function (k) {
3639                 if (options.fields.indexOf(k) === -1) {
3640                     delete result[k];
3641                 }
3642             });
3643         });
3644     }
3645 
3646     preloadZoneData(null, preload_opts, function (err, data) {
3647         var records = data.records;
3648         var uuids = [];
3649 
3650         if (err) {
3651             callback(err);
3652             return;
3653         }
3654 
3655         if (quick_ok) {
3656             var full_results = [];
3657             var load_opts = {};
3658             var match;
3659             var regex;
3660             var source;
3661             var target;
3662             var u;
3663             var z;
3664 
3665             if (err) {
3666                 callback(err);
3667                 return;
3668             }
3669             for (z in records) {
3670                 z = records[z];
3671                 match = true;
3672                 for (key in search) {
3673                     regex = false;
3674                     // force field type to string so that earlier transformed
3675                     // number fields get back their match method and the
3676                     // strict not-equal operator will work on number lookups
3677                     source = '' + z[key];
3678                     target = search[key];
3679                     if (target[0] === '~') {
3680                         target = new RegExp(target.substr(1), 'i');
3681                         regex = true;
3682                     }
3683                     if (regex && !source.match(target)) {
3684                         match = false;
3685                     } else if (!regex && (source !== search[key])) {
3686                         match = false;
3687                     }
3688                 }
3689                 if (match && z.uuid) {
3690                     results.push(z.uuid);
3691                 }
3692             }
3693 
3694             load_opts.log = log;
3695             if (options.hasOwnProperty('fields') && need_fields.length > 0) {
3696                 // we have a specific set of fields we want to grab
3697                 load_opts.fields = need_fields;
3698             } else if (!options.full) {
3699                 // we don't need all the data so what we already got is enough
3700                 if (options.hasOwnProperty('fields')) {
3701                     filterFields(results);
3702                 }
3703 
3704                 callback(null,
3705                     results.filter(function (res) {
3706                         if (typeof (res) === 'object') {
3707                             return (Object.keys(res).length > 0);
3708                         } else {
3709                             return (true);
3710                         }
3711                     })
3712                 );
3713                 return;
3714             }
3715 
3716             function expander(uuid, cb) {
3717                 loadVM(uuid, data, load_opts, function (e, obj) {
3718                     if (e) {
3719                         if (e.code === 'ENOENT') {
3720                             // zone likely was deleted since lookup, ignore
3721                             cb();
3722                         } else {
3723                             cb(e);
3724                         }
3725                     } else {
3726                         if (transform) {
3727                             transform(obj);
3728                         }
3729                         full_results.push(obj);
3730                         cb();
3731                     }
3732                 });
3733             }
3734 
3735             async.forEachSeries(results, expander, function (e) {
3736                 var res_list;
3737 
3738                 if (e) {
3739                     log.error(e, 'VM.lookup failed to expand results: '
3740                         + e.message);
3741                     callback(e);
3742                 } else {
3743                     res_list = full_results;
3744                     if (options.hasOwnProperty('fields')) {
3745                         filterFields(res_list);
3746                     }
3747                     callback(null,
3748                         res_list.filter(function (res) {
3749                             if (typeof (res) === 'object') {
3750                                 return (Object.keys(res).length > 0);
3751                             } else {
3752                                 return (true);
3753                             }
3754                         })
3755                     );
3756                 }
3757             });
3758         } else {
3759             // have to search the hard way (through all the data)
3760             for (u in records) {
3761                 uuids.push(u);
3762             }
3763             // this is parallel!
3764             async.forEach(uuids, function (uuid, cb) {
3765                 var vmobj = records[uuid];
3766                 var l_opts = {log: log};
3767 
3768                 if (options.hasOwnProperty('fields')
3769                     && need_fields.length > 0) {
3770 
3771                     // we have a specific set of fields we want to grab
3772                     l_opts.fields = need_fields;
3773                 }
3774 
3775                 loadVM(vmobj.uuid, data, l_opts, function (error, obj) {
3776                     if (error) {
3777                         if (error.code === 'ENOENT') {
3778                             // zone likely was deleted since lookup, ignore
3779                             cb();
3780                         } else {
3781                             cb(error);
3782                         }
3783                     } else {
3784                         if (transform) {
3785                             transform(obj);
3786                         }
3787                         if (Object.keys(search).length === 0
3788                             || matcher(obj, search)) {
3789 
3790                             results.push(obj);
3791                         }
3792                         cb();
3793                     }
3794                 });
3795             }, function (e) {
3796                 var r;
3797                 var short_results = [];
3798 
3799                 if (e) {
3800                     callback(e);
3801                 } else {
3802                     if (options.full) {
3803                         callback(null, results);
3804                     } else if (options.fields && need_fields.length > 0) {
3805                         if (options.hasOwnProperty('fields')) {
3806                             filterFields(results);
3807                         }
3808                         callback(null,
3809                             results.filter(function (res) {
3810                                 if (typeof (res) === 'object') {
3811                                     return (Object.keys(res).length > 0);
3812                                 } else {
3813                                     return (true);
3814                                 }
3815                             })
3816                         );
3817                     } else {
3818                         for (r in results) {
3819                             short_results.push(results[r].uuid);
3820                         }
3821                         callback(null, short_results);
3822                     }
3823                 }
3824             });
3825         }
3826     });
3827 };
3828 
3829 // create a random new locally administered MAC address
3830 function generateMAC()
3831 {
3832     var data = [(Math.floor(Math.random() * 15) + 1).toString(16) + 2];
3833     for (var i = 0; i < 5; i++) {
3834         var oct = (Math.floor(Math.random() * 255) + 1).toString(16);
3835         if (oct.length == 1) {
3836             oct = '0' + oct;
3837         }
3838         data.push(oct);
3839     }
3840 
3841     return data.join(':');
3842 }
3843 
3844 // return the MAC address based on a VRRP Virtual Router ID
3845 function vrrpMAC(vrid) {
3846     return sprintf('00:00:5e:00:01:%02x', vrid);
3847 }
3848 
3849 // Ensure we've got all the datasets necessary to create this VM
3850 //
3851 // IMPORTANT:
3852 //
3853 // On SmartOS, we assume a provisioner or some other external entity has already
3854 // loaded the dataset into the system. This function just confirms that the
3855 // dataset actually exists.
3856 //
3857 function checkDatasets(payload, log, callback)
3858 {
3859     var checkme = [];
3860     var d;
3861     var disk;
3862 
3863     assert(log, 'no logger passed to checkDatasets()');
3864 
3865     log.debug('Checking for required datasets.');
3866 
3867     // build list of datasets we need to download (downloadme)
3868     for (disk in payload.add_disks) {
3869         if (payload.add_disks.hasOwnProperty(disk)) {
3870             d = payload.add_disks[disk];
3871             if (d.hasOwnProperty('image_uuid')) {
3872                 checkme.push(payload.zpool + '/'
3873                     + d.image_uuid);
3874             }
3875         }
3876     }
3877 
3878     function checker(dataset, cb) {
3879         zfs(['list', '-o', 'name', '-H', dataset], log, function (err, fds) {
3880             if (err) {
3881                 log.error({'err': err, 'stdout': fds.stdout,
3882                     'stderr': fds.stderr}, 'zfs list ' + dataset + ' '
3883                     + 'exited with' + ' code ' + err.code + ': ' + err.message);
3884                 cb(new Error('unable to find dataset: ' + dataset));
3885             } else {
3886                 cb();
3887             }
3888         });
3889     }
3890 
3891     // check that we have all the volumes
3892     async.forEachSeries(checkme, checker, function (err) {
3893         if (err) {
3894             log.error(err, 'checkDatasets() failed to find required '
3895                 + 'volumes');
3896             callback(err);
3897         } else {
3898             // progress(100, 'we have all necessary datasets');
3899             callback();
3900         }
3901     });
3902 }
3903 
3904 function lookupConflicts(macs, ips, vrids, log, callback) {
3905     var conflict = false;
3906     var load_fields;
3907 
3908     load_fields = ['brand', 'state', 'nics', 'uuid', 'zonename', 'zone_state'];
3909 
3910     assert(log, 'no logger passed to lookupConflicts()');
3911 
3912     log.debug('checking for conflicts with '
3913         + JSON.stringify(macs) + ', ' + JSON.stringify(ips) + ' and '
3914         + JSON.stringify(vrids));
3915 
3916     if (macs.length === 0 && ips.length === 0 && vrids.length === 0) {
3917         log.debug('returning from conflict check (nothing to check)');
3918         callback(null, conflict);
3919         return;
3920     }
3921 
3922     preloadZoneData(null, {fields: load_fields, log: log},
3923         function (err, data) {
3924 
3925         var records = data.records;
3926         var uuid;
3927         var uuids = [];
3928 
3929         if (err) {
3930             callback(err);
3931             return;
3932         }
3933 
3934         for (uuid in records) {
3935             uuids.push(uuid);
3936         }
3937 
3938         // this is parallel!
3939         async.forEach(uuids, function (z_uuid, cb) {
3940             loadVM(z_uuid, data, {fields: load_fields, log: log},
3941                 function (error, obj) {
3942 
3943                 var ip;
3944                 var mac;
3945                 var vrid;
3946 
3947                 if (error) {
3948                     if (error.code === 'ENOENT') {
3949                         // zone likely was deleted since lookup, ignore it
3950                         cb();
3951                     } else {
3952                         cb(error);
3953                     }
3954                     return;
3955                 }
3956 
3957                 if (obj.state === 'failed' && obj.zone_state !== 'running') {
3958                     // Ignore zones that are failed unless they're 'running'
3959                     // which they shouldn't be because they get stopped on
3960                     // failure.
3961                     cb();
3962                     return;
3963                 }
3964 
3965                 for (ip in ips) {
3966                     if (ips[ip] !== 'dhcp'
3967                         && matcher(obj, {'nics.*.ip': ips[ip]})) {
3968 
3969                         log.error('Found conflict: ' + obj.uuid
3970                             + ' already has IP ' + ips[ip]);
3971                         conflict = true;
3972                     }
3973                 }
3974                 for (mac in macs) {
3975                     if (matcher(obj, {'nics.*.mac': macs[mac]})) {
3976                         log.error('Found conflict: ' + obj.uuid
3977                             + ' already has MAC ' + macs[mac]);
3978                         conflict = true;
3979                     }
3980                 }
3981                 for (vrid in vrids) {
3982                     if (matcher(obj, {'nics.*.vrrp_vrid': vrids[vrid]})) {
3983                         log.error('Found conflict: ' + obj.uuid
3984                             + ' already has VRID ' + vrids[vrid]);
3985                         conflict = true;
3986                     }
3987                 }
3988                 cb();
3989             });
3990         }, function (e) {
3991             if (e) {
3992                 callback(e);
3993             } else {
3994                 log.debug('returning from conflict check');
3995                 callback(null, conflict);
3996             }
3997         });
3998     });
3999 }
4000 
4001 function lookupInvalidNicTags(nics, log, callback) {
4002     var etherstubs = [];
4003     var nic_tags = {};
4004 
4005     assert(log, 'no logger passed to lookupInvalidNicTags()');
4006 
4007     if (!nics || nics.length === 0) {
4008         callback();
4009         return;
4010     }
4011 
4012     async.parallel([
4013         function (cb) {
4014             dladm.showEtherstub(null, log, function (err, stubs) {
4015                 if (err) {
4016                     cb(err);
4017                 } else {
4018                     etherstubs = stubs;
4019                     cb();
4020                 }
4021             });
4022         }, function (cb) {
4023             VM.getSysinfo([], {log: log}, function (err, sysinfo) {
4024                 if (err) {
4025                     cb(err);
4026                 } else {
4027                     var nic;
4028                     var tag;
4029                     for (nic in sysinfo['Network Interfaces']) {
4030                         nic = sysinfo['Network Interfaces'][nic];
4031                         for (tag in nic['NIC Names']) {
4032                             nic_tags[nic['NIC Names'][tag]] = 1;
4033                         }
4034                     }
4035                     cb();
4036                 }
4037             });
4038         }
4039     ], function (err, results) {
4040         if (err) {
4041             callback(err);
4042             return;
4043         }
4044 
4045         var nic;
4046         for (nic in nics) {
4047             nic = nics[nic];
4048             if (!nic.hasOwnProperty('nic_tag')) {
4049                 continue;
4050             }
4051             if (!nic_tags.hasOwnProperty(nic.nic_tag)
4052                 && (etherstubs.indexOf(nic.nic_tag) === -1)) {
4053                 callback(new Error('Invalid nic tag "' + nic.nic_tag + '"'));
4054                 return;
4055             }
4056         }
4057 
4058         callback();
4059         return;
4060     });
4061 }
4062 
4063 // create a new zvol for a VM
4064 function createVolume(volume, log, callback)
4065 {
4066     var refreserv;
4067     var size;
4068     var snapshot;
4069 
4070     assert(log, 'no logger passed for createVolume()');
4071 
4072     log.debug('creating volume ' + JSON.stringify(volume));
4073 
4074     if (volume.hasOwnProperty('image_size')) {
4075         size = volume.image_size;
4076     } else if (volume.hasOwnProperty('size')) {
4077         size = volume.size;
4078     } else {
4079         callback(new Error('FATAL: createVolume(' + JSON.stringify(volume)
4080             + '): ' + 'has no size or image_size'));
4081         return;
4082     }
4083 
4084     if (volume.hasOwnProperty('refreservation')) {
4085         refreserv = volume.refreservation;
4086     } else {
4087         log.debug('defaulting to refreservation = ' + size);
4088         refreserv = size;
4089     }
4090 
4091     async.series([
4092         function (cb) {
4093             if (volume.hasOwnProperty('image_uuid')) {
4094                 snapshot = volume.zpool + '/' + volume.image_uuid + '@final';
4095                 zfs(['get', '-Ho', 'value', 'name', snapshot], log,
4096                     function (err, fds) {
4097 
4098                     if (err) {
4099                         if (fds.stderr.match('dataset does not exist')) {
4100                             // no @final, so we'll make a new snapshot @<uuid>
4101                             snapshot = volume.zpool + '/' + volume.image_uuid
4102                                 + '@' + volume.uuid;
4103 
4104                             zfs(['snapshot', snapshot], log, function (e) {
4105                                 cb(e);
4106                             });
4107                         } else {
4108                             cb(err);
4109                         }
4110                     } else {
4111                         // @final is here!
4112                         cb();
4113                     }
4114                 });
4115             } else {
4116                 cb();
4117             }
4118         }, function (cb) {
4119             var args;
4120             var target;
4121 
4122             target = volume.zpool + '/' + volume.uuid;
4123             if (volume.hasOwnProperty('image_uuid')) {
4124                 // This volume is from a template/dataset/image so we create
4125                 // it as a clone of a the @final snapshot on the original.
4126                 // we already set 'snapshot' to the correct location above.
4127                 args = ['clone', '-F'];
4128                 if (volume.hasOwnProperty('compression')) {
4129                     args.push('-o', 'compression='
4130                         + volume.compression);
4131                 }
4132                 if (volume.hasOwnProperty('block_size')) {
4133                     args.push('-o', 'volblocksize='
4134                         + volume.block_size);
4135                 }
4136                 args.push('-o', 'refreservation=' + refreserv + 'M');
4137                 args.push(snapshot, target);
4138                 zfs(args, log, function (e) {
4139                     if (e) {
4140                         cb(e);
4141                     } else {
4142                         volume.path = '/dev/zvol/rdsk/' + target;
4143                         cb();
4144                     }
4145                 });
4146             } else {
4147                 // This volume is not from a template/dataset/image so we create
4148                 // a blank new zvol for it.
4149                 args = ['create'];
4150                 if (volume.hasOwnProperty('compression')) {
4151                     args.push('-o', 'compression='
4152                         + volume.compression);
4153                 }
4154                 if (volume.hasOwnProperty('block_size')) {
4155                     args.push('-o', 'volblocksize='
4156                         + volume.block_size);
4157                 }
4158                 args.push('-o', 'refreservation=' + refreserv + 'M', '-V',
4159                     size + 'M', target);
4160                 zfs(args, log, function (err, fds) {
4161                     if (err) {
4162                         cb(err);
4163                     } else {
4164                         volume.path = '/dev/zvol/rdsk/' + target;
4165                         cb();
4166                     }
4167                 });
4168             }
4169         }
4170     ], function (err, results) {
4171         callback(err);
4172     });
4173 }
4174 
4175 // Create all the volumes for a given VM property set
4176 function createVolumes(payload, log, callback)
4177 {
4178     var createme = [];
4179     var d;
4180     var disk;
4181     var disk_idx = 0;
4182     var used_disk_indexes = [];
4183 
4184     assert(log, 'no logger passed to createVolumes()');
4185 
4186     log.debug('creating volumes: ' + JSON.stringify(payload.add_disks));
4187 
4188     if (payload.hasOwnProperty('used_disk_indexes')) {
4189         used_disk_indexes = payload.used_disk_indexes;
4190     }
4191 
4192     for (disk in payload.add_disks) {
4193         if (payload.add_disks.hasOwnProperty(disk)) {
4194             d = payload.add_disks[disk];
4195 
4196             // we don't create CDROM devices or disk devices which have the
4197             // nocreate: true property.
4198             if (d.media !== 'cdrom' && !d.nocreate) {
4199                 // skip to the next unused one.
4200                 while (used_disk_indexes.indexOf(disk_idx) !== -1) {
4201                     disk_idx++;
4202                 }
4203 
4204                 d.index = disk_idx;
4205                 d.uuid = payload.uuid + '-disk' + disk_idx;
4206                 used_disk_indexes.push(Number(disk_idx));
4207                 if (!d.hasOwnProperty('zpool')) {
4208                     d.zpool = payload.zpool;
4209                 }
4210                 createme.push(d);
4211             }
4212         }
4213     }
4214 
4215     function loggedCreateVolume(volume, cb) {
4216         return createVolume(volume, log, cb);
4217     }
4218 
4219     // create all the volumes we found that we need.
4220     async.forEachSeries(createme, loggedCreateVolume, function (err) {
4221         if (err) {
4222             callback(err);
4223         } else {
4224             callback();
4225         }
4226     });
4227 }
4228 
4229 function writeAndRename(log, name, destfile, file_data, callback)
4230 {
4231     var tempfile = destfile + '.new';
4232 
4233     log.debug('writing ' + name + ' to ' + tempfile);
4234 
4235     fs.writeFile(tempfile, file_data, function (err) {
4236         if (err) {
4237             callback(err);
4238             return;
4239         }
4240 
4241         log.debug('wrote ' + name + ' to ' + tempfile);
4242         log.debug('renaming from ' + tempfile + ' to ' + destfile);
4243 
4244         fs.rename(tempfile, destfile, function (_err) {
4245             if (_err) {
4246                 callback(_err);
4247                 return;
4248             }
4249 
4250             log.debug('renamed from ' + tempfile + ' to ' + destfile);
4251             callback();
4252         });
4253     });
4254 }
4255 
4256 // writes a Zone's metadata JSON to /zones/<uuid>/config/metadata.json
4257 // and /zones/<uuid>/config/tags.json.
4258 function updateMetadata(vmobj, payload, log, callback)
4259 {
4260     var cmdata = {};
4261     var imdata = {};
4262     var key;
4263     var mdata = {};
4264     var mdata_filename;
4265     var tags = {};
4266     var tags_filename;
4267     var zonepath;
4268 
4269     assert(log, 'no logger passed to updateMetadata()');
4270 
4271     if (vmobj.hasOwnProperty('zonepath')) {
4272         zonepath = vmobj.zonepath;
4273     } else if (vmobj.hasOwnProperty('zpool')
4274         && vmobj.hasOwnProperty('zonename')) {
4275 
4276         zonepath = '/' + vmobj.zpool + '/' + vmobj.zonename;
4277     } else {
4278         callback(new Error('unable to find zonepath for '
4279             + JSON.stringify(vmobj)));
4280         return;
4281     }
4282 
4283     // paths are under zonepath but not zoneroot
4284     mdata_filename = zonepath + '/config/metadata.json';
4285     tags_filename = zonepath + '/config/tags.json';
4286 
4287     // customer_metadata
4288     for (key in vmobj.customer_metadata) {
4289         if (vmobj.customer_metadata.hasOwnProperty(key)) {
4290             cmdata[key] = vmobj.customer_metadata[key];
4291             if (payload.hasOwnProperty('remove_customer_metadata')
4292                 && payload.remove_customer_metadata.indexOf(key) !== -1) {
4293 
4294                 // in the remove_* list, don't load it.
4295                 delete cmdata[key];
4296             }
4297         }
4298     }
4299 
4300     for (key in payload.set_customer_metadata) {
4301         if (payload.set_customer_metadata.hasOwnProperty(key)) {
4302             cmdata[key] = payload.set_customer_metadata[key];
4303         }
4304     }
4305 
4306     // internal_metadata
4307     for (key in vmobj.internal_metadata) {
4308         if (vmobj.internal_metadata.hasOwnProperty(key)) {
4309             imdata[key] = vmobj.internal_metadata[key];
4310             if (payload.hasOwnProperty('remove_internal_metadata')
4311                 && payload.remove_internal_metadata.indexOf(key) !== -1) {
4312 
4313                 // in the remove_* list, don't load it.
4314                 delete imdata[key];
4315             }
4316         }
4317     }
4318 
4319     for (key in payload.set_internal_metadata) {
4320         if (payload.set_internal_metadata.hasOwnProperty(key)) {
4321             imdata[key] = payload.set_internal_metadata[key];
4322         }
4323     }
4324 
4325     // same thing for tags
4326     for (key in vmobj.tags) {
4327         if (vmobj.tags.hasOwnProperty(key)) {
4328             tags[key] = vmobj.tags[key];
4329             if (payload.hasOwnProperty('remove_tags')
4330                 && payload.remove_tags.indexOf(key) !== -1) {
4331 
4332                 // in the remove_* list, don't load it.
4333                 delete tags[key];
4334             }
4335         }
4336     }
4337 
4338     for (key in payload.set_tags) {
4339         if (payload.set_tags.hasOwnProperty(key)) {
4340             tags[key] = payload.set_tags[key];
4341         }
4342     }
4343 
4344     mdata = {'customer_metadata': cmdata, 'internal_metadata': imdata};
4345 
4346     async.series([
4347         function (next) {
4348             writeAndRename(log, 'metadata', mdata_filename,
4349                 JSON.stringify(mdata, null, 2), next);
4350         },
4351         function (next) {
4352             writeAndRename(log, 'tags', tags_filename,
4353                 JSON.stringify(tags, null, 2), next);
4354         }
4355     ], callback);
4356 }
4357 
4358 function saveMetadata(payload, log, callback)
4359 {
4360     var protovm = {};
4361 
4362     assert(log, 'no logger passed to saveMetadata()');
4363 
4364     if (!payload.hasOwnProperty('zonepath')
4365         || !payload.hasOwnProperty('zpool')
4366         || !payload.hasOwnProperty('zonename')) {
4367 
4368         callback(new Error('saveMetadata payload is missing zone '
4369             + 'properties.'));
4370         return;
4371     }
4372 
4373     protovm.zonepath = payload.zonepath;
4374     protovm.zpool = payload.zpool;
4375     protovm.zonename = payload.zonename;
4376     protovm.customer_metadata = {};
4377     protovm.tags = {};
4378 
4379     if (payload.hasOwnProperty('tags')) {
4380         payload.set_tags = payload.tags;
4381         delete payload.tags;
4382     }
4383     if (payload.hasOwnProperty('customer_metadata')) {
4384         payload.set_customer_metadata = payload.customer_metadata;
4385         delete payload.customer_metadata;
4386     }
4387     if (payload.hasOwnProperty('internal_metadata')) {
4388         payload.set_internal_metadata = payload.internal_metadata;
4389         delete payload.internal_metadata;
4390     }
4391 
4392     updateMetadata(protovm, payload, log, callback);
4393 }
4394 
4395 // writes a zone's metadata JSON to /zones/<uuid>/config/routes.json
4396 function updateRoutes(vmobj, payload, log, callback)
4397 {
4398     var filename;
4399     var key;
4400     var routes = {};
4401     var zonepath;
4402 
4403     assert(log, 'no logger passed to updateRoutes()');
4404 
4405     if (vmobj.hasOwnProperty('zonepath')) {
4406         zonepath = vmobj.zonepath;
4407     } else if (vmobj.hasOwnProperty('zpool')
4408         && vmobj.hasOwnProperty('zonename')) {
4409 
4410         zonepath = '/' + vmobj.zpool + '/' + vmobj.zonename;
4411     } else {
4412         callback(new Error('unable to find zonepath for '
4413             + JSON.stringify(vmobj)));
4414         return;
4415     }
4416 
4417     // paths are under zonepath but not zoneroot
4418     filename = zonepath + '/config/routes.json';
4419 
4420     for (key in vmobj.routes) {
4421         if (vmobj.routes.hasOwnProperty(key)) {
4422             routes[key] = vmobj.routes[key];
4423             if (payload.hasOwnProperty('remove_routes')
4424                 && payload.remove_routes.indexOf(key) !== -1) {
4425 
4426                 // in the remove_* list, don't load it.
4427                 delete routes[key];
4428             }
4429         }
4430     }
4431 
4432     for (key in payload.set_routes) {
4433         if (payload.set_routes.hasOwnProperty(key)) {
4434             routes[key] = payload.set_routes[key];
4435         }
4436     }
4437 
4438     fs.writeFile(filename, JSON.stringify(routes, null, 2),
4439         function (err) {
4440             if (err) {
4441                 callback(err);
4442             } else {
4443                 log.debug('wrote routes to ' + filename);
4444                 callback();
4445             }
4446         });
4447 }
4448 
4449 function saveRoutes(payload, log, callback)
4450 {
4451     var protovm = {};
4452 
4453     assert(log, 'no logger passed to saveRoutes()');
4454 
4455     if (!payload.hasOwnProperty('zonepath')
4456         || !payload.hasOwnProperty('zpool')
4457         || !payload.hasOwnProperty('zonename')) {
4458 
4459         callback(new Error('saveRoutes payload is missing zone '
4460             + 'properties.'));
4461         return;
4462     }
4463 
4464     protovm.zonepath = payload.zonepath;
4465     protovm.zpool = payload.zpool;
4466     protovm.zonename = payload.zonename;
4467 
4468     if (payload.hasOwnProperty('routes')) {
4469         payload.set_routes = payload.routes;
4470         delete payload.routes;
4471     }
4472 
4473     updateRoutes(protovm, payload, log, callback);
4474 }
4475 
4476 function createVM(payload, log, callback)
4477 {
4478     assert(log, 'no logger passed to createVM()');
4479 
4480     async.series([
4481         function (cb) {
4482             if (!payload.create_only) {
4483                 // progress(2, 'checking required datasets');
4484                 checkDatasets(payload, log, cb);
4485             } else {
4486                 cb();
4487             }
4488         }, function (cb) {
4489             if (!payload.create_only) {
4490                 // progress(29, 'creating volumes');
4491                 createVolumes(payload, log, cb);
4492             } else {
4493                 cb();
4494             }
4495         }, function (cb) {
4496             // progress(51, 'creating zone container');
4497             createZone(payload, log, cb);
4498         }
4499     ], function (err, results) {
4500         if (err) {
4501             callback(err);
4502         } else {
4503             callback(null, results);
4504         }
4505     });
4506 }
4507 
4508 function fixZoneinitMetadataSock(zoneroot, log, callback)
4509 {
4510     var mdata_00;
4511 
4512     // ensure we're safe to touch these files, zone should not be running here
4513     // so this just guards against malicious datasets.
4514     ['/var/zoneinit/includes', '/root/zoneinit.d'].forEach(function (dir) {
4515         assertSafeZonePath(zoneroot, dir, {type: 'dir', enoent_ok: true});
4516     });
4517 
4518     function replaceData(filename, cb) {
4519         fs.readFile(filename, 'utf8', function (error, data) {
4520             if (error) {
4521                 log.error(error, 'failed to load 00-mdata.sh for replacement');
4522                 cb(error);
4523                 return;
4524             }
4525 
4526             data = data.replace(/\/var\/run\/smartdc\/metadata.sock/g,
4527                 '/.zonecontrol/metadata.sock');
4528 
4529             log.trace('writing [' + data + '] to ' + filename);
4530             fs.writeFile(filename, data, 'utf8', function (err) {
4531                 if (err) {
4532                     log.error(err, 'failed to write ' + filename);
4533                 }
4534                 cb(err);
4535             });
4536         });
4537     }
4538 
4539     // try /var/zoneinit/includes/00-mdata.sh first, since that's in new images
4540     mdata_00 = path.join(zoneroot, '/var/zoneinit/includes/00-mdata.sh');
4541     fs.exists(mdata_00, function (exists1) {
4542         if (exists1) {
4543             log.info('fixing socket in /var/zoneinit/includes/00-mdata.sh');
4544             replaceData(mdata_00, callback);
4545         } else {
4546             // didn't exist, so try location it exists in older images eg. 1.6.3
4547             mdata_00 = path.join(zoneroot, '/root/zoneinit.d/00-mdata.sh');
4548             fs.exists(mdata_00, function (exists2) {
4549                 if (exists2) {
4550                     log.info('fixing socket in /root/zoneinit.d/00-mdata.sh');
4551                     replaceData(mdata_00, callback);
4552                 } else {
4553                     log.info('no 00-mdata.sh to cleanup in zoneinit');
4554                     callback();
4555                 }
4556             });
4557         }
4558     });
4559 }
4560 
4561 function fixMdataFetchStart(zonepath, log, callback)
4562 {
4563     // svccfg validates zonepath
4564     var mdata_fetch_start = '/lib/svc/method/mdata-fetch';
4565 
4566     svccfg(zonepath, ['-s', 'svc:/smartdc/mdata:fetch', 'setprop', 'start/exec',
4567         '=', mdata_fetch_start], log, function (error, stdio) {
4568 
4569         if (error) {
4570             log.error(error, 'failed to set mdata:fetch start method');
4571         } else {
4572             log.info('successfully set mdata:fetch start method');
4573         }
4574 
4575         callback(error);
4576     });
4577 }
4578 
4579 function cleanupMessyDataset(zonepath, brand, log, callback)
4580 {
4581     var command;
4582     var zoneroot = path.join(zonepath, '/root');
4583 
4584     assert(log, 'no logger passed to cleanupMessyDataset()');
4585 
4586     try {
4587         ['/var/adm', '/var/svc/log', '/var/svc/manifest', '/root/zoneinit.d']
4588             .forEach(function (dir) {
4589 
4590             // This will ensure these are safe if they exist.
4591             assertSafeZonePath(zoneroot, dir, {type: 'dir', enoent_ok: true});
4592         });
4593     } catch (e) {
4594         log.error(e, 'Unable to cleanup dataset: ' + e.message);
4595         callback(e);
4596         return;
4597     }
4598 
4599     // We've verified the directories here exist, and have no symlinks in the
4600     // path (or don't exist) so rm -f <dir>/<file> should be safe regardless of
4601     // the type of <file>
4602 
4603     command = 'rm -f '
4604         + zoneroot + '/var/adm/utmpx '
4605         + zoneroot + '/var/adm/wtmpx '
4606         + zoneroot + '/var/svc/log/*.log '
4607         + zoneroot + '/var/svc/mdata '
4608         + zoneroot + '/var/svc/manifest/mdata.xml ';
4609 
4610     if (! BRAND_OPTIONS[brand].features.zoneinit) {
4611         // eg. joyent-minimal (don't need zoneinit)
4612         command = command + zoneroot + '/root/zoneinit.xml '
4613             + zoneroot + '/root/zoneinit '
4614             + '&& rm -rf ' + zoneroot + '/root/zoneinit.d ';
4615     }
4616 
4617     command = command + '&& touch ' + zoneroot + '/var/adm/wtmpx';
4618     log.debug(command);
4619     exec(command, function (error, stdout, stderr) {
4620         log.debug({err: error, stdout: stdout, stderr: stderr},
4621             'returned from cleaning up dataset');
4622         if (error || !BRAND_OPTIONS[brand].features.zoneinit) {
4623             // either we already failed or this zone doesn't use zoneinit so
4624             // we don't need to bother fixing zoneinit's scripts.
4625             callback(error);
4626         } else {
4627             fixZoneinitMetadataSock(zoneroot, log, function (err) {
4628                 // See OS-2314, currently we assume all zones w/ zoneinit also
4629                 // have broken mdata:fetch when images are created from them.
4630                 // Attempt to fix that too.
4631                 fixMdataFetchStart(zonepath, log, callback);
4632             });
4633         }
4634     });
4635 }
4636 
4637 // Helper for unlinking and replacing a file that you've already confirmed
4638 // has no symlinks. Throws error when fs.writeFileSync does, or when
4639 // fs.unlinkSync throws non ENOENT.
4640 function replaceFile(zoneroot, filename, data) {
4641     // first delete, in case file itself is a link
4642     try {
4643         fs.unlinkSync(path.join(zoneroot, filename));
4644     } catch (e) {
4645         if (e.code !== 'ENOENT') {
4646             throw e;
4647         }
4648     }
4649 
4650     fs.writeFileSync(path.join(zoneroot, filename), data);
4651 }
4652 
4653 // NOTE: we write these out initially before the zone is started, but after that
4654 // rely on mdata-fetch in the zone to do the updates since we can't safely write
4655 // these files in the zones.
4656 function writeZoneNetfiles(payload, log, callback)
4657 {
4658     var hostname;
4659     var n;
4660     var nic;
4661     var primary_found = false;
4662     var zoneroot;
4663 
4664     assert(log, 'no logger passed to writeZoneNetfiles()');
4665     assert(payload.hasOwnProperty('zonepath'), 'no .zonepath in payload');
4666 
4667     zoneroot = payload.zonepath + '/root';
4668 
4669     try {
4670         assertSafeZonePath(zoneroot, '/etc', {type: 'dir', enoent_ok: true});
4671     } catch (e) {
4672         log.error(e, 'Unable to write zone net files: ' + e.message);
4673         callback(e);
4674         return;
4675     }
4676 
4677     log.info('Writing network files to zone root');
4678 
4679     try {
4680         for (nic in payload.add_nics) {
4681             if (payload.add_nics.hasOwnProperty(nic)) {
4682                 n = payload.add_nics[nic];
4683 
4684                 if (n.ip != 'dhcp') {
4685                     replaceFile(zoneroot, '/etc/hostname.'
4686                         + n.interface, n.ip + ' netmask ' + n.netmask
4687                         + ' up' + '\n');
4688                 }
4689 
4690                 if (n.hasOwnProperty('primary') && !primary_found) {
4691                     // only allow one primary network
4692                     primary_found = true;
4693                     if (n.hasOwnProperty('gateway')) {
4694                         replaceFile(zoneroot, '/etc/defaultrouter',
4695                             n.gateway + '\n');
4696                     }
4697                     if (n.ip == 'dhcp') {
4698                         replaceFile(zoneroot, '/etc/dhcp.' + n.interface, '');
4699                     }
4700                 }
4701             }
4702         }
4703 
4704         // It's possible we don't have zonename or hostname set because of the
4705         // ordering of adding the UUID. In any case, we'll have at least a uuid
4706         // here.
4707         if (payload.hasOwnProperty('hostname')) {
4708             hostname = payload.hostname;
4709         } else if (payload.hasOwnProperty('zonename')) {
4710             hostname = payload.zonename;
4711         } else {
4712             hostname = payload.uuid;
4713         }
4714 
4715         replaceFile(zoneroot, '/etc/nodename', hostname + '\n');
4716     } catch (e) {
4717         log.error(e, 'Unable to write zone networking files: ' + e.message);
4718         callback(e);
4719         return;
4720     }
4721 
4722     callback();
4723 }
4724 
4725 /*
4726  * NOTE: once we no longer support old datasets that need the 'zoneconfig' file,
4727  * this function and calls to it can be removed.
4728  *
4729  * This writes out the zoneconfig file that is used by the zoneinit service in
4730  * joyent branded zones' datasets.
4731  *
4732  */
4733 function writeZoneconfig(payload, log, callback)
4734 {
4735     var data;
4736     var hostname;
4737     var n;
4738     var nic;
4739     var zoneroot;
4740 
4741     assert(log, 'no logger passed to writeZoneconfig()');
4742     assert(payload.hasOwnProperty('zonepath'), 'no .zonepath in payload');
4743 
4744     zoneroot = payload.zonepath + '/root';
4745 
4746     log.info('Writing config for zoneinit');
4747 
4748     if (payload.hasOwnProperty('hostname')) {
4749         hostname = payload.hostname;
4750     } else {
4751         hostname = payload.zonename;
4752     }
4753 
4754     data = 'TEMPLATE_VERSION=0.0.1\n'
4755         + 'ZONENAME=' + payload.zonename + '\n'
4756         + 'HOSTNAME=' + hostname + '.' + payload.dns_domain + '\n'
4757         + 'TMPFS=' + payload.tmpfs + 'm\n';
4758 
4759     if (payload.hasOwnProperty('add_nics') && payload.add_nics[0]) {
4760 
4761         if (payload.add_nics[0] && payload.add_nics[0].ip != 'dhcp') {
4762             data = data + 'PUBLIC_IP=' + payload.add_nics[0].ip + '\n';
4763         }
4764         if (payload.add_nics[1] && payload.add_nics[1].ip != 'dhcp') {
4765             data = data + 'PRIVATE_IP=' + payload.add_nics[1].ip + '\n';
4766         } else if (payload.add_nics[0] && payload.add_nics[0].ip != 'dhcp') {
4767             // zoneinit uses private_ip for /etc/hosts, we want to
4768             // make that same as public, if there's no actual private.
4769             data = data + 'PRIVATE_IP=' + payload.add_nics[0].ip + '\n';
4770         }
4771     }
4772 
4773     if (payload.hasOwnProperty('resolvers')) {
4774         // zoneinit appends to resolv.conf rather than overwriting, so just
4775         // add to the zoneconfig and let zoneinit handle it
4776         data = data + 'RESOLVERS="' + payload.resolvers.join(' ') + '"\n';
4777     }
4778 
4779     for (nic in payload.add_nics) {
4780         if (payload.add_nics.hasOwnProperty(nic)) {
4781             n = payload.add_nics[nic];
4782             data = data + n.interface.toUpperCase() + '_MAC=' + n.mac + '\n'
4783                 + n.interface.toUpperCase() + '_INTERFACE='
4784                 + n.interface.toUpperCase() + '\n';
4785 
4786             if (n.ip != 'dhcp') {
4787                 data = data + n.interface.toUpperCase() + '_IP=' + n.ip + '\n'
4788                     + n.interface.toUpperCase() + '_NETMASK='
4789                     + n.netmask + '\n';
4790             }
4791         }
4792     }
4793 
4794     try {
4795         assertSafeZonePath(zoneroot, '/var/svc/log/system-zoneinit:default.log',
4796             {type: 'file', enoent_ok: true});
4797         assertSafeZonePath(zoneroot, '/root/zoneconfig',
4798             {type: 'file', enoent_ok: true});
4799 
4800         replaceFile(zoneroot, '/var/svc/log/system-zoneinit:default.log', '');
4801 
4802         log.debug('writing zoneconfig ' + JSON.stringify(data) + ' to '
4803             + zoneroot);
4804 
4805         replaceFile(zoneroot, '/root/zoneconfig', data);
4806         callback();
4807     } catch (e) {
4808         log.error(e, 'Unable to write zoneconfig files: ' + e.message);
4809         callback(e);
4810         return;
4811     }
4812 }
4813 
4814 function zonecfg(args, log, callback)
4815 {
4816     var cmd = '/usr/sbin/zonecfg';
4817 
4818     assert(log, 'no logger passed to zonecfg()');
4819 
4820     log.debug(cmd + ' ' + args.join(' '));
4821     execFile(cmd, args, function (error, stdout, stderr) {
4822         if (error) {
4823             callback(error, {'stdout': stdout, 'stderr': stderr});
4824         } else {
4825             callback(null, {'stdout': stdout, 'stderr': stderr});
4826         }
4827     });
4828 }
4829 
4830 function zonecfgFile(data, args, log, callback)
4831 {
4832     var tmpfile = '/tmp/zonecfg.' + process.pid + '.tmp';
4833 
4834     assert(log, 'no logger passed to zonecfgFile()');
4835 
4836     fs.writeFile(tmpfile, data, function (err, result) {
4837         if (err) {
4838             // On failure we don't delete the tmpfile so we can debug it.
4839             callback(err);
4840         } else {
4841             args.push('-f');
4842             args.push(tmpfile);
4843 
4844             zonecfg(args, log, function (e, fds) {
4845                 if (e) {
4846                     // keep temp file around for investigation
4847                     callback(e, fds);
4848                 } else {
4849                     fs.unlink(tmpfile, function () {
4850                         callback(null, fds);
4851                     });
4852                 }
4853             });
4854         }
4855     });
4856 }
4857 
4858 function zoneadm(args, log, callback)
4859 {
4860     var cmd = '/usr/sbin/zoneadm';
4861 
4862     assert(log, 'no logger passed to zoneadm()');
4863 
4864     log.debug(cmd + ' ' + args.join(' '));
4865     execFile(cmd, args, function (error, stdout, stderr) {
4866         if (error) {
4867             callback(error, {'stdout': stdout, 'stderr': stderr});
4868         } else {
4869             callback(null, {'stdout': stdout, 'stderr': stderr});
4870         }
4871     });
4872 }
4873 
4874 function zfs(args, log, callback)
4875 {
4876     var cmd = '/usr/sbin/zfs';
4877 
4878     assert(log, 'no logger passed to zfs()');
4879 
4880     log.debug(cmd + ' ' + args.join(' '));
4881     execFile(cmd, args, function (error, stdout, stderr) {
4882         if (error) {
4883             callback(error, {'stdout': stdout, 'stderr': stderr});
4884         } else {
4885             callback(null, {'stdout': stdout, 'stderr': stderr});
4886         }
4887     });
4888 }
4889 
4890 exports.getSysinfo = function (args, options, callback)
4891 {
4892     var cmd = '/usr/bin/sysinfo';
4893     var log;
4894 
4895     // we used to allow just one argument (callback) and we also allow 2 args
4896     // (args, callback) so that options is optional.
4897     if (arguments.length === 1) {
4898         callback = arguments[0];
4899         args = [];
4900         options = {};
4901     }
4902     if (arguments.length === 2) {
4903         callback = arguments[1];
4904         options = {};
4905     }
4906 
4907     ensureLogging(false);
4908     if (options.hasOwnProperty('log')) {
4909         log = options.log;
4910     } else {
4911         log = VM.log.child({action: 'getSysinfo'});
4912     }
4913 
4914     log.debug(cmd + ' ' + args.join(' '));
4915     execFile(cmd, args, function (error, stdout, stderr) {
4916         var sysinfo;
4917 
4918         if (error) {
4919             callback(error, {'stdout': stdout, 'stderr': stderr});
4920         } else {
4921             try {
4922                 sysinfo = JSON.parse(stdout.toString());
4923             } catch (e) {
4924                 sysinfo = {};
4925             }
4926             callback(null, sysinfo);
4927         }
4928     });
4929 };
4930 
4931 /*
4932  * This watches zone transitions and calls callback when specified
4933  * state is reached.  Optionally you can set a timeout which will
4934  * call your callback when the timeout occurs whether the transition
4935  * has happened or not.
4936  *
4937  * payload needs to have at least .zonename and .uuid
4938  *
4939  */
4940 exports.waitForZoneState = function (payload, state, options, callback)
4941 {
4942     var log;
4943     var sysevent_state;
4944     var timeout;
4945     var timeout_secs = PROVISION_TIMEOUT;
4946     var watcher;
4947 
4948     // options is optional
4949     if (arguments.length === 3) {
4950         callback = arguments[2];
4951         options = {};
4952     }
4953 
4954     ensureLogging(false);
4955     if (options.hasOwnProperty('log')) {
4956         log = options.log;
4957     } else {
4958         log = VM.log.child({action: 'waitForZoneState', vm: payload.uuid});
4959     }
4960 
4961     if (options.hasOwnProperty('timeout')) {
4962         timeout_secs = options.timeout;
4963     }
4964 
4965     sysevent_state = state;
4966     if (state === 'installed') {
4967         // Apparently the zone status 'installed' equals sysevent status
4968         // 'uninitialized'
4969         sysevent_state = 'uninitialized';
4970     }
4971 
4972     function done() {
4973         if (timeout) {
4974             clearTimeout(timeout);
4975             timeout = null;
4976         }
4977     }
4978 
4979     function handler(err, obj) {
4980         if (err) {
4981             done();
4982             callback(err);
4983             return;
4984         }
4985         log.trace('handler got: ' + JSON.stringify(obj));
4986         if (obj.zonename !== payload.zonename) {
4987             return;
4988         }
4989 
4990         if (obj.newstate === sysevent_state) {
4991             // Load again to confirm
4992             VM.lookup({'zonename': obj.zonename},
4993                 {fields: ['zone_state'], log: log},
4994                 function (error, res) {
4995                     var handler_retry;
4996 
4997                     if (error) {
4998                         watcher.cleanup();
4999                         done();
5000                         callback(error);
5001                         return;
5002                     }
5003 
5004                     if (res.length !== 1) {
5005                         watcher.cleanup();
5006                         done();
5007                         callback(new Error('lookup could no find VM '
5008                             + obj.zonename));
5009                         return;
5010                     }
5011 
5012                     if (res[0].hasOwnProperty('zone_state')
5013                         && res[0].zone_state === state) {
5014 
5015                         // found the state we're looking for, success!
5016                         log.debug('saw zone go to ' + obj.newstate + ' ('
5017                             + state + ') calling callback()');
5018                         watcher.cleanup();
5019                         done();
5020                         callback();
5021                     } else if (timeout) {
5022                         // we saw a state change to a state we don't care about
5023                         // so if we've not timed out try reloading again in a
5024                         // second.
5025                         if (!handler_retry) {
5026                             handler_retry = setTimeout(function () {
5027                                 if (timeout) {
5028                                     // try again if wait timeout is still set
5029                                     handler(null, obj);
5030                                 }
5031                                 handler_retry = null;
5032                             }, 1000);
5033                             log.debug('zone state after lookup: '
5034                                 + res[0].zone_state + ', still waiting');
5035                         } else {
5036                             log.debug('zone in wrong state but we already'
5037                                 + ' have a handler running');
5038                         }
5039                     } else {
5040                         // no timeout set and we're not at the correct state
5041                         log.error('failed to reach state: ' + state);
5042                         callback(new Error('failed to reach state: ' + state));
5043                     }
5044                 }
5045             );
5046         }
5047     }
5048 
5049     watcher = watchZoneTransitions(handler, log);
5050 
5051     timeout = setTimeout(function () {
5052         var err;
5053 
5054         done();
5055         watcher.cleanup();
5056         err = new Error('timed out waiting for zone to transition to ' + state);
5057         err.code = 'ETIMEOUT';
5058         callback(err);
5059     }, timeout_secs * 1000);
5060 
5061     // after we've started the watcher (if we checked before there'd be a race)
5062     // we check whether we're already in the target state, if we are close it
5063     // down and return.
5064     VM.load(payload.uuid, {fields: ['zone_state'], log: log},
5065         function (err, obj) {
5066 
5067         if (err) {
5068             watcher.cleanup();
5069             done();
5070             callback(err);
5071         } else if (obj.hasOwnProperty('zone_state')
5072             && obj.zone_state === state) {
5073 
5074             watcher.cleanup();
5075             done();
5076             log.info('VM is in state ' + state);
5077             callback(); // at correct state!
5078         }
5079     });
5080 };
5081 
5082 // handler() will be called with an object describing the transition for any
5083 // transitions seen (after any filtering).  The only filtering here is to remove
5084 // duplicate events.  Other filtering should be done by the caller.
5085 function watchZoneTransitions(handler, log) {
5086     var buffer = '';
5087     var chunks;
5088     var cleanup;
5089     var watcher;
5090     var watcher_pid;
5091 
5092     assert(log, 'no logger passed to watchZoneTransitions()');
5093 
5094     if (!zoneevent) {
5095 
5096         zoneevent = new EventEmitter();
5097 
5098         log.debug('/usr/vm/sbin/zoneevent');
5099         watcher = spawn('/usr/vm/sbin/zoneevent', [],
5100             {'customFds': [-1, -1, -1]});
5101         log.debug('zoneevent running with pid ' + watcher.pid);
5102         watcher_pid = watcher.pid;
5103 
5104         watcher.stdout.on('data', function (data) {
5105             var chunk;
5106             var obj;
5107             var prev_msg;
5108 
5109             buffer += data.toString();
5110             chunks = buffer.split('\n');
5111             while (chunks.length > 1) {
5112                 chunk = chunks.shift();
5113                 obj = JSON.parse(chunk);
5114 
5115                 if (obj === prev_msg) {
5116                     // Note: sometimes sysevent emits multiple events for the
5117                     // same status, we only want the first one here because just
5118                     // because sysevent does it, doesn't make it right.
5119                     log.debug('duplicate zoneevent message! '
5120                         + JSON.stringify(obj));
5121                 } else if (zoneevent) {
5122                     zoneevent.emit('zoneevent', null, obj);
5123                 }
5124             }
5125             buffer = chunks.pop();
5126         });
5127 
5128         // doesn't take input.
5129         watcher.stdin.end();
5130 
5131         watcher.on('exit', function (code) {
5132             log.warn('zoneevent watcher ' + watcher_pid + ' exited: ',
5133                 JSON.stringify(code));
5134             // tell all the listeners of this zoneevent (if there are any) that
5135             // we exited.  Then null it out so next time we'll make a new one.
5136             zoneevent.emit('zoneevent', new Error('zoneevent watcher exited '
5137                 + 'prematurely with code: ' + code));
5138             zoneevent = null;
5139         });
5140     }
5141 
5142     cleanup = function () {
5143         var listeners;
5144 
5145         if (zoneevent) {
5146             listeners = zoneevent.listeners('zoneevent');
5147 
5148             log.debug('cleanup called w/ listeners: '
5149                 + util.inspect(listeners));
5150             zoneevent.removeListener('zoneevent', handler);
5151             if (zoneevent.listeners('zoneevent').length === 0) {
5152                 log.debug('zoneevent watcher ' + watcher_pid
5153                     + ' cleanup called');
5154                 zoneevent = null;
5155                 if (watcher) {
5156                     watcher.stdout.destroy(); // so we don't send more 'data'
5157                     watcher.stderr.destroy();
5158                     watcher.removeAllListeners('exit'); // so don't fail on kill
5159                     watcher.kill();
5160                     watcher = null;
5161                 }
5162             }
5163         } else if (watcher) {
5164             watcher.stdout.destroy(); // so we don't send more 'data'
5165             watcher.stderr.destroy();
5166             watcher.removeAllListeners('exit'); // so don't fail on our kill
5167             watcher.kill();
5168             watcher = null;
5169         }
5170     };
5171 
5172     zoneevent.on('zoneevent', handler);
5173 
5174     return ({'cleanup': cleanup});
5175 }
5176 
5177 function fixPayloadMemory(payload, vmobj, log)
5178 {
5179     var brand;
5180     var max_locked;
5181     var max_phys;
5182     var min_overhead;
5183     var ram;
5184 
5185     assert(log, 'no logger passed to fixPayloadMemory()');
5186 
5187     if (vmobj.hasOwnProperty('brand')) {
5188         brand = vmobj.brand;
5189     } else if (payload.hasOwnProperty('brand')) {
5190         brand = payload.brand;
5191     }
5192 
5193     if (BRAND_OPTIONS[brand].features.default_memory_overhead
5194         && payload.hasOwnProperty('ram')
5195         && !payload.hasOwnProperty('max_physical_memory')) {
5196 
5197         // For now we add overhead to the memory caps for KVM zones, this
5198         // is for the qemu process itself.  Since customers don't have direct
5199         // access to zone memory, this exists mostly to protect against bugs.
5200         payload.max_physical_memory = (payload.ram
5201             + BRAND_OPTIONS[brand].features.default_memory_overhead);
5202     } else if (payload.hasOwnProperty('ram')
5203         && !payload.hasOwnProperty('max_physical_memory')) {
5204 
5205         payload.max_physical_memory = payload.ram;
5206     }
5207 
5208     if (payload.hasOwnProperty('max_physical_memory')) {
5209         if (!payload.hasOwnProperty('max_locked_memory')) {
5210             if (vmobj.hasOwnProperty('max_locked_memory')
5211                 && vmobj.hasOwnProperty('max_physical_memory')) {
5212 
5213                 // we don't have a new value, so first try to keep the same
5214                 // delta that existed before btw. max_phys and max_locked
5215                 payload.max_locked_memory = payload.max_physical_memory
5216                     - (vmobj.max_physical_memory - vmobj.max_locked_memory);
5217             } else {
5218                 // existing obj doesn't have max_locked, add one now
5219                 payload.max_locked_memory = payload.max_physical_memory;
5220             }
5221         }
5222 
5223         if (!payload.hasOwnProperty('max_swap')) {
5224             if (vmobj.hasOwnProperty('max_swap')
5225                 && vmobj.hasOwnProperty('max_physical_memory')) {
5226 
5227                 // we don't have a new value, so first try to keep the same
5228                 // delta that existed before btw. max_phys and max_swap
5229                 if (vmobj.max_swap === MINIMUM_MAX_SWAP
5230                     && vmobj.max_swap <= MINIMUM_MAX_SWAP
5231                     && payload.max_physical_memory >= MINIMUM_MAX_SWAP) {
5232                     // in this case we artificially inflated before to meet
5233                     // minimum tie back to ram.
5234                     payload.max_swap = payload.max_physical_memory;
5235                 } else {
5236                     payload.max_swap = payload.max_physical_memory
5237                         + (vmobj.max_swap - vmobj.max_physical_memory);
5238                 }
5239             } else {
5240                 // existing obj doesn't have max_swap, add one now
5241                 payload.max_swap = payload.max_physical_memory;
5242             }
5243 
5244             // never add a max_swap less than MINIMUM_MAX_SWAP
5245             if (payload.max_swap < MINIMUM_MAX_SWAP) {
5246                 payload.max_swap = MINIMUM_MAX_SWAP;
5247             }
5248         }
5249     }
5250 
5251     // if we're updating tmpfs it must be lower than our new max_physical or
5252     // if we're not also changing max_physical, it must be lower than the
5253     // current one.
5254     if (payload.hasOwnProperty('tmpfs')) {
5255         if (payload.hasOwnProperty('max_physical_memory')
5256             && (Number(payload.tmpfs)
5257                 > Number(payload.max_physical_memory))) {
5258 
5259             payload.tmpfs = payload.max_physical_memory;
5260         } else if (Number(payload.tmpfs)
5261             > Number(vmobj.max_physical_memory)) {
5262 
5263             payload.tmpfs = vmobj.max_physical_memory;
5264         }
5265     }
5266 
5267     if (payload.hasOwnProperty('max_physical_memory')
5268         && BRAND_OPTIONS[brand].features.use_tmpfs
5269         && !payload.hasOwnProperty('tmpfs')) {
5270 
5271         if (vmobj.hasOwnProperty('max_physical_memory')
5272             && vmobj.hasOwnProperty('tmpfs')) {
5273 
5274             // change tmpfs to be the same ratio of ram as before
5275             payload.tmpfs = ((vmobj.tmpfs / vmobj.max_physical_memory)
5276                 * payload.max_physical_memory);
5277             payload.tmpfs = Number(payload.tmpfs).toFixed();
5278         } else {
5279             // tmpfs must be < max_physical_memory, if not: pretend it was
5280             payload.tmpfs = payload.max_physical_memory;
5281         }
5282     }
5283 
5284     // now that we've possibly adjusted target values, lower/raise values to
5285     // satisify max/min.
5286 
5287     min_overhead = BRAND_OPTIONS[brand].features.min_memory_overhead;
5288     if (min_overhead) {
5289         ram = payload.hasOwnProperty('ram') ? payload.ram : vmobj.ram;
5290         max_phys = payload.hasOwnProperty('max_physical_memory')
5291             ? payload.max_physical_memory : vmobj.max_physical_memory;
5292         max_locked = payload.hasOwnProperty('max_locked_memory')
5293             ? payload.max_locked_memory : vmobj.max_locked_memory;
5294 
5295         if ((ram + min_overhead) > max_phys) {
5296             payload.max_physical_memory = (ram + min_overhead);
5297         }
5298         if ((ram + min_overhead) > max_locked) {
5299             payload.max_locked_memory = (ram + min_overhead);
5300         }
5301     }
5302 
5303     if (payload.hasOwnProperty('max_locked_memory')) {
5304         if (payload.hasOwnProperty('max_physical_memory')) {
5305             if (payload.max_locked_memory > payload.max_physical_memory) {
5306                 log.warn('max_locked_memory (' + payload.max_locked_memory
5307                     + ') > max_physical_memory (' + payload.max_physical_memory
5308                     + ') clamping to ' + payload.max_physical_memory);
5309                 payload.max_locked_memory = payload.max_physical_memory;
5310             }
5311         } else if (vmobj.hasOwnProperty('max_physical_memory')) {
5312             // new payload doesn't have a max_physical, so clamp to vmobj's
5313             if (payload.max_locked_memory > vmobj.max_physical_memory) {
5314                 log.warn('max_locked_memory (' + payload.max_locked_memory
5315                     + ') > vm.max_physical_memory (' + vmobj.max_physical_memory
5316                     + ') clamping to ' + vmobj.max_physical_memory);
5317                 payload.max_locked_memory = vmobj.max_physical_memory;
5318             }
5319         }
5320     }
5321 
5322     if (payload.hasOwnProperty('max_swap')) {
5323         if (payload.hasOwnProperty('max_physical_memory')) {
5324             if (payload.max_swap < payload.max_physical_memory) {
5325                 log.warn('max_swap (' + payload.max_swap
5326                     + ') < max_physical_memory (' + payload.max_physical_memory
5327                     + ') raising to ' + payload.max_physical_memory);
5328                 payload.max_swap = payload.max_physical_memory;
5329             }
5330         } else if (vmobj.hasOwnProperty('max_physical_memory')) {
5331             // new payload doesn't have a max_physical, so raise to vmobj's
5332             if (payload.max_swap < vmobj.max_physical_memory) {
5333                 log.warn('max_swap (' + payload.max_swap
5334                     + ') < vm.max_physical_memory (' + vmobj.max_physical_memory
5335                     + ') raising to ' + vmobj.max_physical_memory);
5336                 payload.max_swap = vmobj.max_physical_memory;
5337             }
5338         }
5339     }
5340 }
5341 
5342 // generate a new UUID if payload doesn't have one (also ensures that this uuid
5343 // does not already belong to a zone).
5344 function createZoneUUID(payload, log, callback)
5345 {
5346     var uuid;
5347 
5348     assert(log, 'no logger passed to createZoneUUID()');
5349 
5350     if (payload.hasOwnProperty('uuid')) {
5351         // Ensure that the uuid is not already used.
5352         getZoneRecords(null, log, function (err, records) {
5353             if (err) {
5354                 callback(err);
5355             } else {
5356                 if (records.hasOwnProperty(payload.uuid)) {
5357                     callback(new Error('vm with UUID ' + payload.uuid
5358                         + ' already exists.'));
5359                 } else {
5360                     callback(null, payload.uuid);
5361                 }
5362             }
5363         });
5364     } else {
5365         log.debug('/usr/bin/uuid -v 4');
5366         execFile('/usr/bin/uuid', ['-v', '4'], function (err, stdout, stderr) {
5367             if (err) {
5368                 callback(err);
5369                 return;
5370             }
5371 
5372             // chomp trailing spaces and newlines
5373             uuid = stdout.toString().replace(/\s+$/g, '');
5374             payload.uuid = uuid;
5375             log.info('generated uuid ' + uuid + ' for new VM');
5376             getZoneRecords(null, log, function (e, records) {
5377                 if (e) {
5378                     callback(e);
5379                 } else {
5380                     if (records.hasOwnProperty(payload.uuid)) {
5381                         callback(new Error('vm with UUID ' + payload.uuid
5382                             + 'already exists.'));
5383                     } else {
5384                         callback(null, payload.uuid);
5385                     }
5386                 }
5387             });
5388         });
5389     }
5390 }
5391 
5392 function applyZoneDefaults(payload, log)
5393 {
5394     var allowed;
5395     var disk;
5396     var disks;
5397     var n;
5398     var nic;
5399     var nics;
5400     var zvol;
5401 
5402     assert(log, 'no logger passed to applyZoneDefaults()');
5403 
5404     log.debug('applying zone defaults');
5405 
5406     if (!payload.hasOwnProperty('owner_uuid')) {
5407         // We assume that this all-zero uuid can be treated as 'admin'
5408         payload.owner_uuid = '00000000-0000-0000-0000-000000000000';
5409     }
5410 
5411     if (!payload.hasOwnProperty('autoboot')) {
5412         payload.autoboot = true;
5413     }
5414 
5415     if (!payload.hasOwnProperty('brand')) {
5416         payload.brand = 'joyent';
5417     }
5418 
5419     if (!payload.hasOwnProperty('zpool')) {
5420         payload.zpool = 'zones';
5421     }
5422 
5423     if (!payload.hasOwnProperty('dns_domain')) {
5424         payload.dns_domain = 'local';
5425     }
5426 
5427     if (!payload.hasOwnProperty('cpu_shares')) {
5428         payload.cpu_shares = 100;
5429     } else {
5430         if (payload.cpu_shares > 65535) {
5431             log.info('capping cpu_shares at 64k (was: '
5432                 + payload.cpu_shares + ')');
5433             payload.cpu_shares = 65535; // max is 64K
5434         }
5435     }
5436 
5437     if (!payload.hasOwnProperty('zfs_io_priority')) {
5438         payload.zfs_io_priority = 100;
5439     }
5440 
5441     if (!payload.hasOwnProperty('max_lwps')) {
5442         payload.max_lwps = 2000;
5443     }
5444 
5445     // We need to set the RAM here because we use it as the default for
5446     // the max_physical_memory below. If we've set max_phys and we're not
5447     // KVM, we'll use that instead of ram anyway.
5448     if (!payload.hasOwnProperty('ram')) {
5449         payload.ram = 256;
5450     }
5451 
5452     fixPayloadMemory(payload, {}, log);
5453 
5454     allowed = BRAND_OPTIONS[payload.brand].allowed_properties;
5455     if (allowed.hasOwnProperty('vcpus') && !payload.hasOwnProperty('vcpus')) {
5456         payload.vcpus = 1;
5457     }
5458 
5459     if (BRAND_OPTIONS[payload.brand].features.use_tmpfs
5460         && (!payload.hasOwnProperty('tmpfs')
5461             || (Number(payload.tmpfs) > Number(payload.max_physical_memory)))) {
5462 
5463         payload.tmpfs = payload.max_physical_memory;
5464     }
5465 
5466     if (!payload.hasOwnProperty('limit_priv')) {
5467         // note: the limit privs are going to be added to the brand and
5468         // shouldn't need to be set here by default when that's done.
5469         if (BRAND_OPTIONS[payload.brand].features.limit_priv) {
5470             payload.limit_priv
5471                 = BRAND_OPTIONS[payload.brand].features.limit_priv.join(',');
5472         } else {
5473             payload.limit_priv = 'default';
5474         }
5475     }
5476 
5477     if (!payload.hasOwnProperty('quota')) {
5478         payload.quota = '10'; // in GiB
5479     }
5480 
5481     if (!payload.hasOwnProperty('billing_id')) {
5482         payload.billing_id = '00000000-0000-0000-0000-000000000000';
5483     }
5484 
5485     if (payload.hasOwnProperty('add_disks')) {
5486         // update
5487         disks = payload.add_disks;
5488     } else if (payload.hasOwnProperty('disks')) {
5489         disks = payload.disks;
5490     } else {
5491         // no disks at all
5492         disks = [];
5493     }
5494 
5495     for (disk in disks) {
5496         if (disks.hasOwnProperty(disk)) {
5497             zvol = disks[disk];
5498             if (!zvol.hasOwnProperty('model')
5499                 && payload.hasOwnProperty('disk_driver')) {
5500 
5501                 zvol.model = payload.disk_driver;
5502             }
5503             if (!zvol.hasOwnProperty('media')) {
5504                 zvol.media = 'disk';
5505             }
5506         }
5507     }
5508 
5509     if (payload.hasOwnProperty('add_nics')) {
5510         // update
5511         nics = payload.add_nics;
5512     } else if (payload.hasOwnProperty('nics')) {
5513         nics = payload.nics;
5514     } else {
5515         // no disks at all
5516         nics = [];
5517     }
5518 
5519     for (nic in nics) {
5520         if (nics.hasOwnProperty(nic)) {
5521             n = nics[nic];
5522             if (!n.hasOwnProperty('model')
5523                 && payload.hasOwnProperty('nic_driver')) {
5524 
5525                 n.model = payload.nic_driver;
5526             }
5527         }
5528     }
5529 }
5530 
5531 function validRecordSize(candidate)
5532 {
5533     if (candidate < 512) {
5534         // too low
5535         return (false);
5536     } else if (candidate > 131072) {
5537         // too high
5538         return (false);
5539     } else if ((candidate & (candidate - 1)) !== 0) {
5540         // not a power of 2
5541         return (false);
5542     }
5543 
5544     return (true);
5545 }
5546 
5547 // This function gets called for both create and update to check that payload
5548 // properties are reasonable. If vmobj is null, create is assumed, otherwise
5549 // update is assumed.
5550 function checkPayloadProperties(payload, vmobj, log, callback)
5551 {
5552     var array_fields = [
5553         'add_nics', 'update_nics', 'remove_nics',
5554         'add_disks', 'update_disks', 'remove_disks',
5555         'add_filesystems', 'update_filesystems', 'remove_filesystems'
5556     ];
5557     var changed_nics = [];
5558     var current_ips = [];
5559     var current_macs = [];
5560     var current_primary_ips = [];
5561     var current_vrids = [];
5562     var disk;
5563     var dst;
5564     var field;
5565     var filesys;
5566     var i;
5567     var ips = [];
5568     var is_nic = false;
5569     var live_ok;
5570     var mac;
5571     var macs = [];
5572     var m;
5573     var n;
5574     var nic;
5575     var nics_result = {};
5576     var nics_result_ordered = [];
5577     var nic_fields = ['add_nics', 'update_nics'];
5578     var only_vrrp_nics = true;
5579     var primary_nics;
5580     var prop;
5581     var props;
5582     var ram;
5583     var route;
5584     var routes_result = {};
5585     var brand;
5586     var vrids = [];
5587     var zvol;
5588 
5589     assert(log, 'no logger passed to checkPayloadProperties()');
5590 
5591     if (vmobj) {
5592         brand = vmobj.brand;
5593     } else if (payload.hasOwnProperty('brand')) {
5594         brand = payload.brand;
5595     } else {
5596         callback(new Error('unable to determine brand for VM'));
5597     }
5598 
5599     /* check types of fields that should be arrays */
5600     for (field in array_fields) {
5601         field = array_fields[field];
5602         if (payload.hasOwnProperty(field) && ! Array.isArray(payload[field])) {
5603             callback(new Error(field + ' must be an array.'));
5604             return;
5605         }
5606     }
5607 
5608     if (!vmobj) {
5609         // This is a CREATE
5610 
5611         // These should have already been enforced
5612         if (payload.max_locked_memory > payload.max_physical_memory) {
5613             callback(new Error('max_locked_memory must be <= '
5614                 + 'max_physical_memory'));
5615             return;
5616         }
5617         if (payload.max_swap < payload.max_physical_memory) {
5618             callback(new Error('max_swap must be >= max_physical_memory'));
5619             return;
5620         }
5621 
5622         // We used to use zone_path instead of zonepath, so accept that too.
5623         if (payload.hasOwnProperty('zone_path')
5624             && !payload.hasOwnProperty('zonepath')) {
5625 
5626             payload.zonepath = payload.zone_path;
5627             delete payload.zone_path;
5628         }
5629     } else {
5630         // This is an UPDATE
5631 
5632         // can't update disks of a running VM
5633         if (payload.hasOwnProperty('add_disks')
5634             || payload.hasOwnProperty('remove_disks')) {
5635 
5636             if ((vmobj.state !== 'stopped')
5637                 || (vmobj.state === 'provisioning'
5638                 && vmobj.zone_state !== 'installed')) {
5639 
5640                 callback(new Error('updates to disks are only allowed when '
5641                     + 'state is "stopped", currently: ' + vmobj.state + ' ('
5642                     + vmobj.zone_state + ')'));
5643                 return;
5644             }
5645         }
5646 
5647         // For update_disks we can update refreservation and compression values
5648         // while running. If there are other parameters to update though we'll
5649         // reject.
5650         if (payload.hasOwnProperty('update_disks')) {
5651             if ((vmobj.state !== 'stopped')
5652                 || (vmobj.state === 'provisioning'
5653                 && vmobj.zone_state !== 'installed')) {
5654 
5655                 live_ok = true;
5656 
5657                 payload.update_disks.forEach(function (d) {
5658                     var key;
5659                     var keys = Object.keys(d);
5660 
5661                     while ((keys.length > 0) && live_ok) {
5662                         key = keys.pop();
5663                         if ([
5664                             'compression',
5665                             'path',
5666                             'refreservation'
5667                             ].indexOf(key) === -1) {
5668 
5669                             // this key is not allowed!
5670                             live_ok = false;
5671                         }
5672                     }
5673                 });
5674 
5675                 if (!live_ok) {
5676                     callback(new Error('at least one specified update to disks '
5677                         + 'is only allowed when state is "stopped", currently: '
5678                         + vmobj.state + ' (' + vmobj.zonestate + ')'));
5679                     return;
5680                 }
5681             }
5682         }
5683 
5684         // if there's a min_overhead we ensure values are higher than ram.
5685         if (BRAND_OPTIONS[brand].features.min_memory_overhead) {
5686             if (payload.hasOwnProperty('ram')) {
5687                 ram = payload.ram;
5688             } else {
5689                 ram = vmobj.ram;
5690             }
5691 
5692             // ensure none of these is < ram
5693             if (payload.hasOwnProperty('max_physical_memory')
5694                 && payload.max_physical_memory < ram) {
5695 
5696                 callback(new Error('vm.max_physical_memory ('
5697                     + payload.max_physical_memory + ') cannot be lower than'
5698                     + ' vm.ram (' + ram + ')'));
5699                 return;
5700             }
5701             if (payload.hasOwnProperty('max_locked_memory')
5702                 && payload.max_locked_memory < ram) {
5703 
5704                 callback(new Error('vm.max_locked_memory ('
5705                     + payload.max_locked_memory + ') cannot be lower than'
5706                     + ' vm.ram (' + ram + ')'));
5707                 return;
5708             }
5709             // This should not be allowed anyway because max_swap will be raised
5710             // to match max_physical_memory if you set it lower.
5711             if (payload.hasOwnProperty('max_swap')) {
5712                 if (payload.max_swap < ram) {
5713                     callback(new Error('vm.max_swap ('
5714                         + payload.max_swap + ') cannot be lower than'
5715                         + ' vm.ram (' + ram + ')'));
5716                     return;
5717                 } else if (payload.max_swap < MINIMUM_MAX_SWAP) {
5718                     callback(new Error('vm.max_swap ('
5719                         + payload.max_swap + ') cannot be lower than '
5720                         + MINIMUM_MAX_SWAP + 'MiB'));
5721                     return;
5722                 }
5723             }
5724         }
5725 
5726         /*
5727          * keep track of current IPs/MACs so we can make sure they're not being
5728          * duplicated.
5729          *
5730          */
5731         for (nic in vmobj.nics) {
5732             nic = vmobj.nics[nic];
5733             if (nic.hasOwnProperty('ip') && nic.ip !== 'dhcp') {
5734                 current_ips.push(nic.ip);
5735             }
5736             if (nic.hasOwnProperty('mac')) {
5737                 current_macs.push(nic.mac);
5738             }
5739             if (nic.hasOwnProperty('vrrp_vrid')) {
5740                 current_vrids.push(nic.vrrp_vrid);
5741             }
5742             if (nic.hasOwnProperty('vrrp_primary_ip')) {
5743                 current_primary_ips.push(nic.vrrp_primary_ip);
5744             }
5745 
5746             if (nic.hasOwnProperty('mac') || nic.hasOwnProperty('vrrp_vrid')) {
5747                 mac = nic.hasOwnProperty('mac') ? nic.mac
5748                     : vrrpMAC(nic.vrrp_vrid);
5749                 if (!nics_result.hasOwnProperty(mac)) {
5750                     nics_result[mac] = nic;
5751                     nics_result_ordered.push(nic);
5752                 }
5753             }
5754         }
5755 
5756         // Keep track of route additions / deletions, to make sure that
5757         // we're not setting link-local routes against nics that don't exist
5758         for (route in vmobj.routes) {
5759             routes_result[route] = vmobj.routes[route];
5760         }
5761     }
5762 
5763     if (payload.hasOwnProperty('add_disks')) {
5764         for (disk in payload.add_disks) {
5765             if (payload.add_disks.hasOwnProperty(disk)) {
5766                 zvol = payload.add_disks[disk];
5767 
5768                 // path is only allowed in 2 cases when adding a disk:
5769                 //
5770                 // 1) for cdrom devices
5771                 // 2) when nocreate is specified
5772                 //
5773                 if (zvol.hasOwnProperty('path')) {
5774                     if (zvol.media !== 'cdrom' && !zvol.nocreate) {
5775                         callback(new Error('you cannot specify a path for a '
5776                             + 'disk unless you set nocreate=true'));
5777                         return;
5778                     }
5779                 }
5780 
5781                 // NOTE: We'll have verified the .zpool argument is a valid
5782                 // zpool using VM.validate() if it's set.
5783 
5784                 if (zvol.hasOwnProperty('block_size')
5785                     && !validRecordSize(zvol.block_size)) {
5786 
5787                     callback(new Error('invalid .block_size(' + zvol.block_size
5788                         + '), must be 512-131072 and a power of 2.'));
5789                     return;
5790                 }
5791 
5792                 if (zvol.hasOwnProperty('block_size')
5793                     && zvol.hasOwnProperty('image_uuid')) {
5794 
5795                     callback(new Error('setting both .block_size and '
5796                         + '.image_uuid on a volume is invalid'));
5797                 }
5798 
5799                 if (zvol.hasOwnProperty('compression')) {
5800                     if (VM.COMPRESSION_TYPES.indexOf(zvol.compression) === -1) {
5801                         callback(new Error('invalid compression setting for '
5802                             + 'disk, must be one of: '
5803                             + VM.COMPRESSION_TYPES.join(', ')));
5804                     }
5805                 }
5806 
5807                 if (!zvol.hasOwnProperty('model')
5808                     || zvol.model === 'undefined') {
5809 
5810                     if (vmobj && vmobj.hasOwnProperty('disk_driver')) {
5811                         zvol.model = vmobj.disk_driver;
5812                         log.debug('set model to ' + zvol.model
5813                             + ' from disk_driver');
5814                     } else if (vmobj && vmobj.hasOwnProperty('disks')
5815                         && vmobj.disks.length > 0 && vmobj.disks[0].model) {
5816 
5817                         zvol.model = vmobj.disks[0].model;
5818                         log.debug('set model to ' + zvol.model + ' from disk0');
5819                     } else {
5820                         callback(new Error('missing .model option for '
5821                             + 'disk: ' + JSON.stringify(zvol)));
5822                         return;
5823                     }
5824                 } else if (VM.DISK_MODELS.indexOf(zvol.model) === -1) {
5825                     callback(new Error('"' + zvol.model + '"'
5826                         + ' is not a valid disk model. Valid are: '
5827                         + VM.DISK_MODELS.join(',')));
5828                     return;
5829                 }
5830             }
5831         }
5832     }
5833 
5834     if (payload.hasOwnProperty('update_disks')) {
5835         for (disk in payload.update_disks) {
5836             if (payload.update_disks.hasOwnProperty(disk)) {
5837                 zvol = payload.update_disks[disk];
5838 
5839                 if (zvol.hasOwnProperty('compression')) {
5840                     if (VM.COMPRESSION_TYPES.indexOf(zvol.compression) === -1) {
5841                         callback(new Error('invalid compression type for '
5842                             + 'disk, must be one of: '
5843                             + VM.COMPRESSION_TYPES.join(', ')));
5844                     }
5845                 }
5846 
5847                 if (zvol.hasOwnProperty('block_size')) {
5848                     callback(new Error('cannot change .block_size for a disk '
5849                         + 'after creation'));
5850                     return;
5851                 }
5852             }
5853         }
5854     }
5855 
5856     // If we're receiving, we might not have the filesystem yet
5857     if (!payload.hasOwnProperty('transition')
5858         || payload.transition.transition !== 'receiving') {
5859 
5860         for (filesys in payload.filesystems) {
5861             filesys = payload.filesystems[filesys];
5862             if (!fs.existsSync(filesys.source)) {
5863                 callback(new Error('missing requested filesystem: '
5864                     + filesys.source));
5865                 return;
5866             }
5867         }
5868     }
5869 
5870     if (payload.hasOwnProperty('default_gateway')
5871         && payload.default_gateway !== '') {
5872 
5873         log.warn('DEPRECATED: default_gateway should no longer be used, '
5874             + 'instead set one NIC primary and use nic.gateway.');
5875     }
5876 
5877     primary_nics = 0;
5878     for (field in nic_fields) {
5879         field = nic_fields[field];
5880         if (payload.hasOwnProperty(field)) {
5881             for (nic in payload[field]) {
5882                 if (payload[field].hasOwnProperty(nic)) {
5883                     n = payload[field][nic];
5884 
5885                     // MAC will always conflict in update, since that's the key
5886                     if (field === 'add_nics' && n.hasOwnProperty('mac')) {
5887                         if ((macs.indexOf(n.mac) !== -1)
5888                             || current_macs.indexOf(n.mac) !== -1) {
5889 
5890                             callback(new Error('Cannot add multiple NICs with '
5891                                 + 'the same MAC: ' + n.mac));
5892                             return;
5893                         }
5894                         macs.push(n.mac);
5895                     }
5896 
5897                     if (field === 'add_nics' || field === 'update_nics') {
5898                         if (n.hasOwnProperty('primary')) {
5899                             if (n.primary !== true) {
5900                                 callback(new Error('invalid value for NIC\'s '
5901                                     + 'primary flag: ' + n.primary + ' (must be'
5902                                     + ' true)'));
5903                                 return;
5904                             }
5905                             primary_nics++;
5906                         }
5907                         changed_nics.push(n);
5908                     }
5909 
5910                     if (n.hasOwnProperty('ip') && n.ip != 'dhcp') {
5911                         if (ips.indexOf(n.ip) !== -1
5912                             || current_ips.indexOf(n.ip) !== -1) {
5913 
5914                             callback(new Error('Cannot add multiple NICs with '
5915                                 + 'the same IP: ' + n.ip));
5916                             return;
5917                         }
5918                         ips.push(n.ip);
5919                     }
5920 
5921                     if (n.hasOwnProperty('vrrp_vrid')) {
5922                         if (current_vrids.indexOf(n.vrrp_vrid) !== -1
5923                             || vrids.indexOf(n.vrrp_vrid) !== -1) {
5924                             callback(new Error('Cannot add multiple NICs with '
5925                                 + 'the same VRID: ' + n.vrrp_vrid));
5926                             return;
5927                         }
5928                         vrids.push(n.vrrp_vrid);
5929                     }
5930 
5931                     if (field === 'add_nics'
5932                         && n.hasOwnProperty('vrrp_vrid')
5933                         && n.hasOwnProperty('mac')) {
5934                         callback(
5935                             new Error('Cannot set both mac and vrrp_vrid'));
5936                         return;
5937                     }
5938 
5939                     if (n.hasOwnProperty('vrrp_primary_ip')) {
5940                         current_primary_ips.push(n.vrrp_primary_ip);
5941                     }
5942 
5943                     if (BRAND_OPTIONS[brand].features.model_required
5944                         && field === 'add_nics'
5945                         && (!n.hasOwnProperty('model') || !n.model
5946                         || n.model === 'undefined' || n.model.length === 0)) {
5947 
5948 
5949                         if (vmobj && vmobj.hasOwnProperty('nic_driver')) {
5950                             n.model = vmobj.nic_driver;
5951                             log.debug('set model to ' + n.model
5952                                 + ' from nic_driver');
5953                         } else if (vmobj && vmobj.hasOwnProperty('nics')
5954                             && vmobj.nics.length > 0 && vmobj.nics[0].model) {
5955 
5956                             n.model = vmobj.nics[0].model;
5957                             log.debug('set model to ' + n.model + ' from nic0');
5958                         } else {
5959                             callback(new Error('missing .model option for NIC: '
5960                                 + JSON.stringify(n)));
5961                             return;
5962                         }
5963                     }
5964 
5965                     if (field === 'add_nics' && n.ip !== 'dhcp'
5966                         && (!n.hasOwnProperty('netmask')
5967                         || !net.isIPv4(n.netmask))) {
5968 
5969                         callback(new Error('invalid or missing .netmask option '
5970                             + 'for NIC: ' + JSON.stringify(n)));
5971                         return;
5972                     }
5973 
5974                     if ((field === 'add_nics' || field === 'update_nics')
5975                         && n.hasOwnProperty('ip') && n.ip !== 'dhcp'
5976                         && !net.isIPv4(n.ip)) {
5977 
5978                         callback(new Error('invalid IP for NIC: '
5979                             + JSON.stringify(n)));
5980                         return;
5981                     }
5982 
5983                     if (field === 'add_nics' && (!n.hasOwnProperty('nic_tag')
5984                         || !n.nic_tag.match(/^[a-zA-Z0-9\_]+$/))) {
5985 
5986                         callback(new Error('invalid or missing .nic_tag option '
5987                             + 'for NIC: ' + JSON.stringify(n)));
5988                         return;
5989                     }
5990 
5991                     if (field === 'update_nics' && n.hasOwnProperty('model')
5992                         && (!n.model || n.model === 'undefined'
5993                         || n.model.length === 0)) {
5994 
5995                         callback(new Error('invalid .model option for NIC: '
5996                             + JSON.stringify(n)));
5997                         return;
5998                     }
5999 
6000                     if (field === 'update_nics' && n.hasOwnProperty('netmask')
6001                         && (!n.netmask || !net.isIPv4(n.netmask))) {
6002 
6003                         callback(new Error('invalid .netmask option for NIC: '
6004                             + JSON.stringify(n)));
6005                         return;
6006                     }
6007 
6008                     if (field === 'update_nics' && n.hasOwnProperty('nic_tag')
6009                         && !n.nic_tag.match(/^[a-zA-Z0-9\_]+$/)) {
6010 
6011                         callback(new Error('invalid .nic_tag option for NIC: '
6012                             + JSON.stringify(n)));
6013                         return;
6014                     }
6015 
6016                     if (n.hasOwnProperty('mac')
6017                         || n.hasOwnProperty('vrrp_vrid')) {
6018                         mac = n.hasOwnProperty('mac') ? n.mac
6019                             : vrrpMAC(n.vrrp_vrid);
6020                         if (nics_result.hasOwnProperty(mac)) {
6021                             var p;
6022                             for (p in n) {
6023                                 nics_result[mac][p] = n[p];
6024                             }
6025 
6026                             nics_result_ordered.forEach(function (on) {
6027                                 if (on.hasOwnProperty('mac') && on.mac == mac) {
6028                                     for (p in n) {
6029                                         on[p] = n[p];
6030                                     }
6031                                 }
6032                             });
6033                         } else {
6034                             nics_result[mac] = n;
6035                             nics_result_ordered.push(n);
6036                         }
6037                     }
6038 
6039                     if ((field === 'add_nics' || field === 'update_nics')
6040                         && n.hasOwnProperty('allowed_ips')) {
6041                         try {
6042                             validateIPlist(n.allowed_ips);
6043                         } catch (ipListErr) {
6044                             callback(ipListErr);
6045                             return;
6046                         }
6047                     }
6048                 }
6049             }
6050         }
6051     }
6052 
6053     if (payload.hasOwnProperty('remove_nics')) {
6054         for (m in payload.remove_nics) {
6055             m = payload.remove_nics[m];
6056             n = nics_result[m];
6057             if (!n) {
6058                 continue;
6059             }
6060             if (n.hasOwnProperty('ip') && n.ip != 'dhcp') {
6061                 i = ips.indexOf(n.ip);
6062                 if (i !== -1) {
6063                     ips.splice(i, 1);
6064                 }
6065                 i = current_ips.indexOf(n.ip);
6066                 if (i !== -1) {
6067                     current_ips.splice(i, 1);
6068                 }
6069             }
6070             delete nics_result[m];
6071 
6072             for (i in nics_result_ordered) {
6073                 n = nics_result_ordered[i];
6074                 if (n.hasOwnProperty('mac') && n.mac == m) {
6075                     nics_result_ordered.splice(i, 1);
6076                     break;
6077                 }
6078             }
6079         }
6080     }
6081 
6082     // nics_result now has the state of the nics after the update - now check
6083     // properties that depend on each other or on other nics
6084     for (n in nics_result) {
6085         n = nics_result[n];
6086         if (n.hasOwnProperty('vrrp_vrid')) {
6087             if (n.hasOwnProperty('ip')
6088                 && current_primary_ips.indexOf(n.ip) !== -1) {
6089                 callback(
6090                     new Error(
6091                         'Cannot set vrrp_primary_ip to the IP of a VRRP nic'));
6092                 return;
6093             }
6094 
6095             if (!n.hasOwnProperty('vrrp_primary_ip')) {
6096                 callback(new Error(
6097                     'vrrp_vrid set but not vrrp_primary_ip'));
6098                 return;
6099             }
6100         } else {
6101             only_vrrp_nics = false;
6102         }
6103     }
6104 
6105     if (only_vrrp_nics && Object.keys(nics_result).length !== 0) {
6106         callback(new Error('VM cannot contain only VRRP nics'));
6107         return;
6108     }
6109 
6110     for (i in current_primary_ips) {
6111         i = current_primary_ips[i];
6112         if ((current_ips.indexOf(i) === -1)
6113             && (ips.indexOf(i) === -1)) {
6114             callback(new Error(
6115                 'vrrp_primary_ip must belong to the same VM'));
6116             return;
6117         }
6118     }
6119 
6120     // Since we always need a primary nic, don't allow a value other than true
6121     // for primary flag. Also ensure we're not trying to set primary for more
6122     // than one nic.
6123     if (primary_nics > 1) {
6124         callback(new Error('payload specifies more than 1 primary NIC'));
6125         return;
6126     }
6127 
6128     if (payload.hasOwnProperty('vga')
6129         && VM.VGA_TYPES.indexOf(payload.vga) === -1) {
6130 
6131         callback(new Error('Invalid VGA type: "' + payload.vga
6132             + '", supported types are: ' + VM.VGA_TYPES.join(',')));
6133         return;
6134     }
6135 
6136     function validLocalRoute(r) {
6137         var nicIdx = r.match(/nics\[(\d+)\]/);
6138         if (!nicIdx) {
6139             is_nic = false;
6140             return false;
6141         }
6142         is_nic = true;
6143 
6144         if (nics_result_ordered.length === 0) {
6145             return false;
6146         }
6147 
6148         nicIdx = Number(nicIdx[1]);
6149         if (!nics_result_ordered[nicIdx]
6150             || !nics_result_ordered[nicIdx].hasOwnProperty('ip')
6151             || nics_result_ordered[nicIdx].ip === 'dhcp') {
6152             return false;
6153         }
6154 
6155         return true;
6156     }
6157 
6158     props = [ 'routes', 'set_routes' ];
6159     for (prop in props) {
6160         prop = props[prop];
6161         if (payload.hasOwnProperty(prop)) {
6162             for (dst in payload[prop]) {
6163                 var src = payload[prop][dst];
6164 
6165                 if (!net.isIPv4(dst) && !isCIDR(dst)) {
6166                     callback(new Error('Invalid route destination: "' + dst
6167                         + '" (must be IP address or CIDR)'));
6168                     return;
6169                 }
6170 
6171                 if (!net.isIPv4(src) && !validLocalRoute(src)) {
6172                     callback(new Error(
6173                         is_nic ? 'Route gateway: "' + src
6174                             + '" refers to non-existent or DHCP nic'
6175                         : 'Invalid route gateway: "' + src
6176                             + '" (must be IP address or nic)'));
6177                     return;
6178                 }
6179 
6180                 routes_result[dst] = src;
6181             }
6182         }
6183     }
6184 
6185     if (payload.hasOwnProperty('remove_routes')) {
6186         for (dst in payload.remove_routes) {
6187             dst = payload.remove_routes[dst];
6188             delete routes_result[dst];
6189         }
6190     }
6191 
6192     // Now that we've applied all updates to routes, make sure that all
6193     // link-local routes refer to a nic that still exists
6194     for (dst in routes_result) {
6195         if (!net.isIPv4(routes_result[dst])
6196             && !validLocalRoute(routes_result[dst])) {
6197             callback(new Error('Route gateway: "' + routes_result[dst]
6198                 + '" refers to non-existent or DHCP nic'));
6199             return;
6200         }
6201     }
6202 
6203     // Ensure password is not too long
6204     if (payload.hasOwnProperty('vnc_password')
6205         && payload.vnc_password.length > 8) {
6206 
6207         callback(new Error('VNC password is too long, maximum length is 8 '
6208             + 'characters.'));
6209         return;
6210     }
6211 
6212     props = ['zfs_root_recsize', 'zfs_data_recsize'];
6213     for (prop in props) {
6214         prop = props[prop];
6215         if (payload.hasOwnProperty(prop)) {
6216             if (payload[prop] === 0 || payload[prop] === '') {
6217                 // this is the default, so set it back to that.
6218                 payload[prop] = 131072;
6219             } else if (!validRecordSize(payload[prop])) {
6220                 callback(new Error('invalid ' + prop + ' (' + payload[prop]
6221                     + '), must be 512-131072 and a power of 2. '
6222                     + '(0 to disable)'));
6223                 return;
6224             }
6225         }
6226     }
6227     props = ['zfs_root_compression', 'zfs_data_compression'];
6228     for (prop in props) {
6229         prop = props[prop];
6230 
6231         if (payload.hasOwnProperty(prop)) {
6232             if (VM.COMPRESSION_TYPES.indexOf(payload[prop]) === -1) {
6233                 callback(new Error('invalid compression type for '
6234                     + payload[prop] + ', must be one of: '
6235                     + VM.COMPRESSION_TYPES.join(', ')));
6236             }
6237         }
6238     }
6239 
6240     // Ensure MACs and IPs are not already used on this vm
6241     // NOTE: can't check other nodes yet.
6242 
6243     async.series([
6244         function (cb) {
6245             lookupConflicts(macs, ips, vrids, log, function (error, conflict) {
6246                 if (error) {
6247                     cb(error);
6248                 } else {
6249                     if (conflict) {
6250                         cb(new Error('Conflict detected with another '
6251                             + 'vm, please check the MAC, IP, and VRID'));
6252                     } else {
6253                         log.debug('no conflicts');
6254                         cb();
6255                     }
6256                 }
6257             });
6258         }, function (cb) {
6259             lookupInvalidNicTags(changed_nics, log, function (e) {
6260                 if (e) {
6261                     cb(e);
6262                 } else {
6263                     cb();
6264                 }
6265             });
6266         }, function (cb) {
6267             // We only allow adding firewall rules on create
6268             if (vmobj) {
6269                 log.debug('update: not validating firewall data');
6270                 cb();
6271                 return;
6272             }
6273 
6274             if (!payload.hasOwnProperty('firewall')) {
6275                 log.debug('no firewall data in payload: not validating');
6276                 cb();
6277                 return;
6278             }
6279             validateFirewall(payload, log, cb);
6280         }
6281     ], function (err) {
6282         log.trace('leaving checkPayloadProperties()');
6283         callback(err);
6284     });
6285 }
6286 
6287 function createDelegatedDataset(payload, log, callback)
6288 {
6289     var args;
6290     var ds;
6291     var zcfg = '';
6292 
6293     assert(log, 'no logger passed to createDelegatedDataset()');
6294 
6295     if (payload.delegate_dataset) {
6296         log.info('creating delegated dataset.');
6297         if (!payload.hasOwnProperty('zfs_filesystem')) {
6298             callback(new Error('payload missing zfs_filesystem'));
6299             return;
6300         }
6301         ds = path.join(payload.zfs_filesystem, '/data');
6302 
6303         args = ['create'];
6304         if (payload.hasOwnProperty('zfs_data_compression')) {
6305             args.push('-o', 'compression=' + payload.zfs_data_compression);
6306         }
6307         if (payload.hasOwnProperty('zfs_data_recsize')) {
6308             args.push('-o', 'recsize=' + payload.zfs_data_recsize);
6309         }
6310         args.push(ds);
6311 
6312         zfs(args, log, function (err) {
6313             if (err) {
6314                 callback(err);
6315                 return;
6316             }
6317 
6318             zcfg = zcfg + 'add dataset; set name=' + ds + '; end\n';
6319             zonecfg(['-u', payload.uuid, zcfg], log, function (e, fds) {
6320                 if (e) {
6321                     log.error({'err': e, stdout: fds.stdout,
6322                         stderr: fds.stderr}, 'unable to add delegated dataset '
6323                         + ds + ' to ' + payload.uuid);
6324                     callback(e);
6325                 } else {
6326                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
6327                         'added delegated dataset ' + ds);
6328                     callback();
6329                 }
6330             });
6331         });
6332     } else {
6333         callback();
6334     }
6335 }
6336 
6337 function buildAddRemoveList(vmobj, payload, type, key, updatable)
6338 {
6339     var add = [];
6340     var add_key;
6341     var field;
6342     var newobj;
6343     var oldobj;
6344     var plural = type + 's';
6345     var remove = [];
6346     var remove_key;
6347     var update_key;
6348 
6349     // initialize some plurals
6350     add_key = 'add_' + plural;
6351     remove_key = 'remove_' + plural;
6352     update_key = 'update_' + plural;
6353 
6354     // There's no way to update properties on a disk or nic with zonecfg
6355     // currently.  Yes, really.  So any disks/nics that should be updated, we
6356     // remove then add with the new properties.
6357     if (payload.hasOwnProperty(update_key)) {
6358         for (newobj in payload[update_key]) {
6359             newobj = payload[update_key][newobj];
6360             for (oldobj in vmobj[plural]) {
6361                 oldobj = vmobj[plural][oldobj];
6362 
6363                 if (oldobj[key] === newobj[key]) {
6364                     // This is the one to update: remove and add.
6365                     remove.push(oldobj[key]);
6366 
6367                     // only some fields make sense to update.
6368                     for (field in updatable) {
6369                         field = updatable[field];
6370                         if (newobj.hasOwnProperty(field)) {
6371                             oldobj[field] = newobj[field];
6372                         }
6373                     }
6374 
6375                     add.push(oldobj);
6376                 }
6377             }
6378         }
6379     }
6380 
6381     if (payload.hasOwnProperty(remove_key)) {
6382         for (newobj in payload[remove_key]) {
6383             newobj = payload[remove_key][newobj];
6384             remove.push(newobj);
6385         }
6386     }
6387 
6388     if (payload.hasOwnProperty(add_key)) {
6389         for (newobj in payload[add_key]) {
6390             newobj = payload[add_key][newobj];
6391             add.push(newobj);
6392         }
6393     }
6394 
6395     return ({'add': add, 'remove': remove});
6396 }
6397 
6398 function buildDiskZonecfg(vmobj, payload)
6399 {
6400     var add = [];
6401     var disk;
6402     var lists;
6403     var remove = [];
6404     var zcfg = '';
6405 
6406     lists = buildAddRemoveList(vmobj, payload, 'disk', 'path',
6407         UPDATABLE_DISK_PROPS);
6408     remove = lists.remove;
6409     add = lists.add;
6410 
6411     // remove is a list of disk paths, add a remove for each now.
6412     for (disk in remove) {
6413         disk = remove[disk];
6414         zcfg = zcfg + 'remove -F device match=' + disk + '\n';
6415     }
6416 
6417     for (disk in add) {
6418         disk = add[disk];
6419 
6420         zcfg = zcfg + 'add device\n'
6421             + 'set match=' + disk.path + '\n'
6422             + 'add property (name=boot, value="'
6423             + (disk.boot ? 'true' : 'false') + '")\n'
6424             + 'add property (name=model, value="' + disk.model + '")\n';
6425 
6426         if (disk.hasOwnProperty('media')) {
6427             zcfg = zcfg
6428                 + 'add property (name=media, value="'
6429                 + disk.media + '")\n';
6430         }
6431 
6432         if (disk.hasOwnProperty('image_size')) {
6433             zcfg = zcfg
6434                 + 'add property (name=image-size, value="'
6435                 + disk.image_size + '")\n';
6436         } else if (disk.hasOwnProperty('size')) {
6437             zcfg = zcfg + 'add property (name=size, value="'
6438                 + disk.size + '")\n';
6439         }
6440 
6441         if (disk.hasOwnProperty('image_uuid')) {
6442             zcfg = zcfg
6443                 + 'add property (name=image-uuid, value="'
6444                 + disk.image_uuid + '")\n';
6445         }
6446 
6447         if (disk.hasOwnProperty('image_name')) {
6448             zcfg = zcfg + 'add property (name=image-name, value="'
6449                 + disk.image_name + '")\n';
6450         }
6451 
6452         zcfg = zcfg + 'end\n';
6453     }
6454 
6455     return zcfg;
6456 }
6457 
6458 function buildNicZonecfg(vmobj, payload)
6459 {
6460     var add;
6461     var lists;
6462     var matches;
6463     var n;
6464     var new_primary;
6465     var nic;
6466     var nic_idx = 0;
6467     var remove;
6468     var updated_primary;
6469     var used_nic_indexes = [];
6470     var zcfg = '';
6471 
6472     if (vmobj.hasOwnProperty('nics')) {
6473         // check whether we're adding or updating to set the primary flag. If we
6474         // are also find the existing NIC with the primary flag. If that's not
6475         // being removed, update it to remove the primary flag.
6476         if (payload.hasOwnProperty('add_nics')) {
6477             for (nic in payload.add_nics) {
6478                 nic = payload.add_nics[nic];
6479                 if (nic.hasOwnProperty('primary')) {
6480                     new_primary = nic.mac;
6481                 }
6482             }
6483         }
6484         if (payload.hasOwnProperty('update_nics')) {
6485             for (nic in payload.update_nics) {
6486                 nic = payload.update_nics[nic];
6487                 if (nic.hasOwnProperty('primary')) {
6488                     new_primary = nic.mac;
6489                 }
6490             }
6491         }
6492         if (new_primary) {
6493             // find old primary
6494             for (nic in vmobj.nics) {
6495                 nic = vmobj.nics[nic];
6496                 if (nic.hasOwnProperty('primary') && nic.mac !== new_primary) {
6497                     // we have a new primary, so un-primary the old.
6498                     if (payload.hasOwnProperty('remove_nics')
6499                         && payload.remove_nics.indexOf(nic.mac) !== -1) {
6500 
6501                         // we're removing the old primary so: done.
6502                         break;
6503                     } else if (payload.hasOwnProperty('update_nics')) {
6504                         updated_primary = false;
6505                         for (n in payload.update_nics) {
6506                             n = payload.update_nics[n];
6507                             if (n.mac === nic.mac) {
6508                                 n.primary = false;
6509                                 updated_primary = true;
6510                             }
6511                         }
6512                         if (!updated_primary) {
6513                             payload.update_nics.push({'mac': nic.mac,
6514                                 'primary': false});
6515                         }
6516                     } else {
6517                         // just add a new update to unset the
6518                         payload.update_nics =
6519                             [ {'mac': nic.mac, 'primary': false} ];
6520                     }
6521                 }
6522             }
6523         }
6524     }
6525 
6526     lists = buildAddRemoveList(vmobj, payload, 'nic', 'mac',
6527         UPDATABLE_NIC_PROPS);
6528     remove = lists.remove;
6529     add = lists.add;
6530 
6531     // create a list of used indexes so we can find the free ones
6532     if (vmobj.hasOwnProperty('nics')) {
6533         for (n in vmobj.nics) {
6534             if (vmobj.nics[n].hasOwnProperty('interface')) {
6535                 matches = vmobj.nics[n].interface.match(/^net(\d+)$/);
6536                 if (matches) {
6537                     used_nic_indexes.push(Number(matches[1]));
6538                 }
6539             }
6540         }
6541     }
6542 
6543     // assign next available interface for nics without one
6544     for (nic in add) {
6545         nic = add[nic];
6546         if (!nic.hasOwnProperty('interface')) {
6547             while (used_nic_indexes.indexOf(nic_idx) !== -1) {
6548                 nic_idx++;
6549             }
6550             nic.interface = 'net' + nic_idx;
6551             used_nic_indexes.push(Number(nic_idx));
6552         }
6553 
6554         // Changing the VRID changes the MAC address too, since the VRID is
6555         // encoded in the MAC. This can't be done until after
6556         // buildAddRemoveList above, since mac is used as the key to figure
6557         // out which nic is which
6558         if (nic.hasOwnProperty('vrrp_vrid')) {
6559             nic.mac = vrrpMAC(nic.vrrp_vrid);
6560         }
6561     }
6562 
6563     // remove is a list of nic macs, add a remove for each now.
6564     for (nic in remove) {
6565         nic = remove[nic];
6566         zcfg = zcfg + 'remove net mac-addr=' + ruinMac(nic) + '\n';
6567     }
6568 
6569     // properties that don't require any validation - add them if they're
6570     // present:
6571     var nicProperties = ['ip', 'netmask', 'network_uuid', 'model',
6572         'dhcp_server', 'allow_dhcp_spoofing', 'blocked_outgoing_ports',
6573         'allow_ip_spoofing', 'allow_mac_spoofing', 'allow_restricted_traffic',
6574         'allow_unfiltered_promisc', 'vrrp_vrid', 'vrrp_primary_ip'];
6575 
6576     for (nic in add) {
6577         nic = add[nic];
6578 
6579         zcfg = zcfg
6580             + 'add net\n'
6581             + 'set physical=' + nic.interface + '\n'
6582             + 'set mac-addr=' + ruinMac(nic.mac) + '\n';
6583 
6584         if (nic.hasOwnProperty('nic_tag')) {
6585             zcfg = zcfg + 'set global-nic=' + nic.nic_tag + '\n';
6586         }
6587 
6588         if (nic.hasOwnProperty('gateway') && nic.gateway.length > 0) {
6589             zcfg = zcfg + 'add property (name=gateway, value="'
6590                 + nic.gateway + '")\n';
6591         }
6592 
6593         if (nic.hasOwnProperty('primary') && nic.primary) {
6594             zcfg = zcfg + 'add property (name=primary, value="true")\n';
6595         }
6596 
6597         if (nic.hasOwnProperty('vlan_id') && (nic.vlan_id !== '0')) {
6598             zcfg = zcfg + 'set vlan-id=' + nic.vlan_id + '\n';
6599         }
6600 
6601         if (nic.hasOwnProperty('allowed_ips')) {
6602             zcfg = zcfg
6603                 + 'add property (name=allowed_ips, value="'
6604                 + nic.allowed_ips.join(',') + '")\n';
6605         }
6606 
6607         for (var prop in nicProperties) {
6608             prop = nicProperties[prop];
6609             if (nic.hasOwnProperty(prop)) {
6610                 zcfg = zcfg + 'add property (name=' + prop + ', value="'
6611                     + nic[prop] + '")\n';
6612             }
6613         }
6614 
6615         zcfg = zcfg + 'end\n';
6616     }
6617 
6618     return zcfg;
6619 }
6620 
6621 function buildFilesystemZonecfg(vmobj, payload)
6622 {
6623     var add = [];
6624     var filesystem;
6625     var lists;
6626     var opt;
6627     var remove = [];
6628     var zcfg = '';
6629 
6630     lists = buildAddRemoveList(vmobj, payload, 'filesystem', 'target', []);
6631     remove = lists.remove;
6632     add = lists.add;
6633 
6634     // remove is a list of disk paths, add a remove for each now.
6635     for (filesystem in remove) {
6636         filesystem = remove[filesystem];
6637         zcfg = zcfg + 'remove fs match=' + filesystem + '\n';
6638     }
6639 
6640     for (filesystem in add) {
6641         filesystem = add[filesystem];
6642 
6643         zcfg = zcfg + 'add fs\n' + 'set dir=' + filesystem.target + '\n'
6644             + 'set special=' + filesystem.source + '\n' + 'set type='
6645             + filesystem.type + '\n';
6646         if (filesystem.hasOwnProperty('raw')) {
6647             zcfg = zcfg + 'set raw=' + filesystem.raw + '\n';
6648         }
6649         if (filesystem.hasOwnProperty('options')) {
6650             for (opt in filesystem.options) {
6651                 opt = filesystem.options[opt];
6652                 zcfg = zcfg + 'add options "' + opt + '"\n';
6653             }
6654         }
6655         zcfg = zcfg + 'end\n';
6656     }
6657 
6658     return zcfg;
6659 }
6660 
6661 function buildZonecfgUpdate(vmobj, payload, log)
6662 {
6663     var brand;
6664     var tmp;
6665     var zcfg = '';
6666 
6667     assert(log, 'no logger passed to buildZonecfgUpdate()');
6668 
6669     log.debug({vmobj: vmobj, payload: payload},
6670         'parameters to buildZonecfgUpdate()');
6671 
6672     if (vmobj && vmobj.hasOwnProperty('brand')) {
6673         brand = vmobj.brand;
6674     } else {
6675         brand = payload.brand;
6676     }
6677 
6678     // Global properties can just be set, no need to clear anything first.
6679     if (payload.hasOwnProperty('cpu_shares')) {
6680         zcfg = zcfg + 'set cpu-shares=' + payload.cpu_shares.toString() + '\n';
6681     }
6682     if (payload.hasOwnProperty('zfs_io_priority')) {
6683         zcfg = zcfg + 'set zfs-io-priority='
6684             + payload.zfs_io_priority.toString() + '\n';
6685     }
6686     if (payload.hasOwnProperty('max_lwps')) {
6687         zcfg = zcfg + 'set max-lwps=' + payload.max_lwps.toString() + '\n';
6688     }
6689     if (payload.hasOwnProperty('limit_priv')) {
6690         zcfg = zcfg + 'set limitpriv="' + payload.limit_priv + '"\n';
6691     }
6692 
6693     if (!BRAND_OPTIONS[brand].features.use_vm_autoboot
6694         && payload.hasOwnProperty('autoboot')) {
6695 
6696         // kvm autoboot is managed by the vm-autoboot attr instead
6697         zcfg = zcfg + 'set autoboot=' + payload.autoboot.toString() + '\n';
6698     }
6699 
6700     // Capped Memory properties are special
6701     if (payload.hasOwnProperty('max_physical_memory')
6702         || payload.hasOwnProperty('max_locked_memory')
6703         || payload.hasOwnProperty('max_swap')) {
6704 
6705         // Capped memory parameters need either an add or select first.
6706         if (vmobj.hasOwnProperty('max_physical_memory')
6707             || vmobj.hasOwnProperty('max_locked_memory')
6708             || vmobj.hasOwnProperty('max_swap')) {
6709 
6710             // there's already a capped-memory section, use that.
6711             zcfg = zcfg + 'select capped-memory; ';
6712         } else {
6713             zcfg = zcfg + 'add capped-memory; ';
6714         }
6715 
6716         if (payload.hasOwnProperty('max_physical_memory')) {
6717             zcfg = zcfg + 'set physical='
6718                 + payload.max_physical_memory.toString() + 'm; ';
6719         }
6720         if (payload.hasOwnProperty('max_locked_memory')) {
6721             zcfg = zcfg + 'set locked='
6722                 + payload.max_locked_memory.toString() + 'm; ';
6723         }
6724         if (payload.hasOwnProperty('max_swap')) {
6725             zcfg = zcfg + 'set swap='
6726                 + payload.max_swap.toString() + 'm; ';
6727         }
6728 
6729         zcfg = zcfg + 'end\n';
6730     }
6731 
6732     // Capped CPU is special
6733     if (payload.hasOwnProperty('cpu_cap')) {
6734         if (vmobj.hasOwnProperty('cpu_cap')) {
6735             zcfg = zcfg + 'select capped-cpu; ';
6736         } else {
6737             zcfg = zcfg + 'add capped-cpu; ';
6738         }
6739 
6740         zcfg = zcfg + 'set ncpus='
6741             + (Number(payload.cpu_cap) * 0.01).toString() + '; end\n';
6742     }
6743 
6744     // set to empty string so property is removed when not true or when not
6745     // false if that's the default for the property.
6746     if (payload.hasOwnProperty('do_not_inventory')) {
6747         if (payload.do_not_inventory !== true) {
6748             // removing sets false as that's the default.
6749             payload.do_not_inventory = '';
6750         }
6751     }
6752 
6753     if (payload.hasOwnProperty('archive_on_delete')) {
6754         if (payload.archive_on_delete !== true) {
6755             // removing sets false as that's the default.
6756             payload.archive_on_delete = '';
6757         }
6758     }
6759 
6760     if (payload.hasOwnProperty('firewall_enabled')) {
6761         if (payload.firewall_enabled !== true) {
6762             // removing sets false as that's the default.
6763             payload.firewall_enabled = '';
6764         }
6765     }
6766 
6767     if (payload.hasOwnProperty('restart_init')) {
6768         if (payload.restart_init === true) {
6769             // removing sets true as that's the default.
6770             payload.restart_init = '';
6771         }
6772     }
6773 
6774     // Attributes
6775     function setAttr(attr, attr_name, value) {
6776         if (!value) {
6777             value = payload[attr_name];
6778         }
6779 
6780         if (payload.hasOwnProperty(attr_name)) {
6781             if ((typeof (value) !== 'boolean')
6782                 && (!value || trim(value.toString()) === '')) {
6783 
6784                 // empty values we either remove or ignore.
6785                 if (vmobj.hasOwnProperty(attr_name)) {
6786                     zcfg = zcfg + 'remove attr name=' + attr + ';';
6787                     // else do nothing, we don't add empty values.
6788                 }
6789             } else {
6790                 if (attr_name === 'resolvers'
6791                     && vmobj.hasOwnProperty('resolvers')
6792                     && vmobj.resolvers.length === 0) {
6793 
6794                     // special case for resolvers: we always have 'resolvers'
6795                     // in the object, but if it's empty we don't have it in the
6796                     // zonecfg. Add instead of the usual update.
6797                     zcfg = zcfg + 'add attr; set name="' + attr + '"; '
6798                         + 'set type=string; ';
6799                 } else if (vmobj.hasOwnProperty(attr_name)) {
6800                     zcfg = zcfg + 'select attr name=' + attr + '; ';
6801                 } else {
6802                     zcfg = zcfg + 'add attr; set name="' + attr + '"; '
6803                         + 'set type=string; ';
6804                 }
6805                 zcfg = zcfg + 'set value="' + value.toString() + '"; end\n';
6806             }
6807         }
6808     }
6809     setAttr('billing-id', 'billing_id');
6810     setAttr('owner-uuid', 'owner_uuid');
6811     setAttr('package-name', 'package_name');
6812     setAttr('package-version', 'package_version');
6813     setAttr('tmpfs', 'tmpfs');
6814     setAttr('hostname', 'hostname');
6815     setAttr('dns-domain', 'dns_domain');
6816     setAttr('default-gateway', 'default_gateway');
6817     setAttr('do-not-inventory', 'do_not_inventory');
6818     setAttr('archive-on-delete', 'archive_on_delete');
6819     setAttr('firewall-enabled', 'firewall_enabled');
6820     setAttr('restart-init', 'restart_init');
6821     setAttr('init-name', 'init_name');
6822     setAttr('disk-driver', 'disk_driver');
6823     setAttr('nic-driver', 'nic_driver');
6824 
6825     if (payload.hasOwnProperty('resolvers')) {
6826         setAttr('resolvers', 'resolvers', payload.resolvers.join(','));
6827     }
6828     if (payload.hasOwnProperty('alias')) {
6829         tmp = '';
6830         if (payload.alias) {
6831             tmp = new Buffer(payload.alias).toString('base64');
6832         }
6833         setAttr('alias', 'alias', tmp);
6834     }
6835 
6836     if (BRAND_OPTIONS[brand].features.use_vm_autoboot) {
6837         setAttr('vm-autoboot', 'autoboot');
6838     }
6839 
6840     // XXX Used on KVM but can be passed in for 'OS' too. We only setAttr on KVM
6841     if (BRAND_OPTIONS[brand].features.type === 'KVM') {
6842         setAttr('ram', 'ram');
6843     }
6844 
6845     // NOTE: Thanks to normalizePayload() we'll only have these when relevant
6846     setAttr('vcpus', 'vcpus');
6847     setAttr('boot', 'boot');
6848     setAttr('cpu-type', 'cpu_type');
6849     setAttr('vga', 'vga');
6850     setAttr('vnc-port', 'vnc_port');
6851     setAttr('spice-port', 'spice_port');
6852     setAttr('virtio-txtimer', 'virtio_txtimer');
6853     setAttr('virtio-txburst', 'virtio_txburst');
6854 
6855     // We use base64 here for these next five options:
6856     //
6857     //  vnc_password
6858     //  spice_password
6859     //  spice_opts
6860     //  qemu_opts
6861     //  qemu_extra_opts
6862     //
6863     // since these can contain characters zonecfg doesn't like.
6864     //
6865     if (payload.hasOwnProperty('vnc_password')) {
6866         if (payload.vnc_password === ''
6867             && (vmobj.hasOwnProperty('vnc_password')
6868             && vmobj.vnc_password !== '')) {
6869 
6870             log.warn('Warning: VNC password was removed for VM '
6871                 + vmobj.uuid + ' but VM needs to be restarted for change to'
6872                 + 'take effect.');
6873         }
6874         if (payload.vnc_password.length > 0
6875             && !vmobj.hasOwnProperty('vnc_password')) {
6876 
6877             log.warn('Warning: VNC password was added to VM '
6878                 + vmobj.uuid + ' but VM needs to be restarted for change to'
6879                 + 'take effect.');
6880         }
6881 
6882         setAttr('vnc-password', 'vnc_password',
6883             new Buffer(payload.vnc_password).toString('base64'));
6884     }
6885     if (payload.hasOwnProperty('spice_password')) {
6886         if (payload.spice_password === ''
6887             && (vmobj.hasOwnProperty('spice_password')
6888             && vmobj.spice_password !== '')) {
6889 
6890             log.warn('Warning: SPICE password was removed for VM '
6891                 + vmobj.uuid + ' but VM needs to be restarted for change to'
6892                 + 'take effect.');
6893         }
6894         if (payload.spice_password.length > 0
6895             && !vmobj.hasOwnProperty('spice_password')) {
6896 
6897             log.warn('Warning: SPICE password was added to VM '
6898                 + vmobj.uuid + ' but VM needs to be restarted for change to'
6899                 + 'take effect.');
6900         }
6901 
6902         setAttr('spice-password', 'spice_password',
6903             new Buffer(payload.spice_password).toString('base64'));
6904     }
6905     if (payload.hasOwnProperty('spice_opts')) {
6906         setAttr('spice-opts', 'spice_opts',
6907             new Buffer(payload.spice_opts).toString('base64'));
6908     }
6909     if (payload.hasOwnProperty('qemu_opts')) {
6910         setAttr('qemu-opts', 'qemu_opts',
6911             new Buffer(payload.qemu_opts).toString('base64'));
6912     }
6913     if (payload.hasOwnProperty('qemu_extra_opts')) {
6914         setAttr('qemu-extra-opts', 'qemu_extra_opts',
6915             new Buffer(payload.qemu_extra_opts).toString('base64'));
6916     }
6917 
6918     // Handle disks
6919     if (payload.hasOwnProperty('disks')
6920         || payload.hasOwnProperty('add_disks')
6921         || payload.hasOwnProperty('update_disks')
6922         || payload.hasOwnProperty('remove_disks')) {
6923 
6924         zcfg = zcfg + buildDiskZonecfg(vmobj, payload);
6925     }
6926 
6927     if (payload.hasOwnProperty('fs_allowed')) {
6928         if (payload.fs_allowed === '') {
6929             zcfg = zcfg + 'clear fs-allowed\n';
6930         } else {
6931             zcfg = zcfg + 'set fs-allowed="' + payload.fs_allowed + '"\n';
6932         }
6933     }
6934 
6935     if (payload.hasOwnProperty('filesystems')
6936         || payload.hasOwnProperty('add_filesystems')
6937         || payload.hasOwnProperty('update_filesystems')
6938         || payload.hasOwnProperty('add_filesystems')) {
6939 
6940         zcfg = zcfg + buildFilesystemZonecfg(vmobj, payload);
6941     }
6942 
6943     zcfg = zcfg + buildNicZonecfg(vmobj, payload);
6944 
6945     return zcfg;
6946 }
6947 
6948 // Checks that QMP is responding to query-status and if so passes the boolean
6949 // value of the hwsetup parameter to the callback.
6950 //
6951 // vmobj must have:
6952 //
6953 // zonepath
6954 //
6955 function checkHWSetup(vmobj, log, callback)
6956 {
6957     var q;
6958     var socket;
6959 
6960     assert(log, 'no logger passed to checkHWSetup()');
6961 
6962     q = new Qmp(log);
6963     socket = vmobj.zonepath + '/root/tmp/vm.qmp';
6964 
6965     q.connect(socket, function (error) {
6966         if (error) {
6967             log.error(error, 'q.connect(): Error: ' + error.message);
6968             callback(error);
6969             return;
6970         }
6971         q.command('query-status', null, function (e, result) {
6972             if (e) {
6973                 log.error(e, 'q.command(query-status): Error: ' + e.message);
6974                 callback(e);
6975                 return;
6976             }
6977             q.disconnect();
6978             callback(null, result.hwsetup ? true : false);
6979             return;
6980         });
6981     });
6982 }
6983 
6984 // cb (if set) will be called with an Error if we can't setup the interval loop
6985 // otherwise when the loop is shut down.
6986 //
6987 // vmobj must have:
6988 //
6989 //  brand
6990 //  state
6991 //  uuid
6992 //  zonepath
6993 //  zoneroot
6994 //
6995 function markProvisionedWhenHWSetup(vmobj, options, cb)
6996 {
6997     var ival_handle;
6998     var log;
6999     var loop_interval = 3; // seconds
7000     var zoneroot;
7001 
7002     log = options.log;
7003     assert(log, 'no logger passed to markProvisionedWenHWSetup()');
7004     assert(vmobj.hasOwnProperty('zonepath'), 'no zonepath in vmobj');
7005 
7006     zoneroot = path.join(vmobj.zoneroot, '/root');
7007 
7008     if (!BRAND_OPTIONS[vmobj.brand].features.wait_for_hwsetup) {
7009         // do nothing for zones where we don't wait for hwsetup
7010         cb(new Error('brand ' + vmobj.brand + ' does not support hwsetup'));
7011         return (null);
7012     }
7013 
7014     // Ensure the dataset doesn't have unsafe links as /var or /var/svc
7015     // Since we're checking the 'file' provision_success, this also guarantees
7016     // that if it already exists, it's not a symlink.
7017     try {
7018         assertSafeZonePath(zoneroot, '/var/svc/provision_success',
7019             {type: 'file', enoent_ok: true});
7020     } catch (e) {
7021         cb(e);
7022         return (null);
7023     }
7024 
7025     if (!options) {
7026         options = {};
7027     }
7028 
7029     // if caller wants they can change the interval
7030     if (options.hasOwnProperty('interval')) {
7031         loop_interval = options.interval;
7032     }
7033 
7034     log.debug('setting hwsetup interval ' + vmobj.uuid);
7035     ival_handle = setInterval(function () {
7036         VM.load(vmobj.uuid, {fields: ['transition_expire', 'uuid'], log: log},
7037             function (err, obj) {
7038 
7039             var timeout_remaining;
7040             var ival = ival_handle;
7041 
7042             function done() {
7043                 if (ival_handle) {
7044                     log.debug('clearing hwsetup interval ' + vmobj.uuid);
7045                     clearInterval(ival);
7046                     ival = null;
7047                 } else {
7048                     log.debug('done but no hwsetup interval ' + vmobj.uuid);
7049                 }
7050             }
7051 
7052             if (err) {
7053                 // If the VM was deleted between calls, nothing much we can do.
7054                 log.error(err, 'Unable to load ' + vmobj.uuid + ' '
7055                     + err.message);
7056                 done();
7057                 cb(err);
7058                 return;
7059             }
7060 
7061             // we only do anything if we're still waiting for provisioning
7062             if (vmobj.state !== 'provisioning') {
7063                 done();
7064                 cb();
7065                 return;
7066             }
7067 
7068             timeout_remaining =
7069                 (Number(obj.transition_expire) - Date.now(0)) / 1000;
7070 
7071             if (timeout_remaining <= 0) {
7072                 // IMPORTANT: this may run multiple times, must be idempotent
7073 
7074                 log.warn('Marking VM ' + vmobj.uuid + ' as "failed" because'
7075                     + ' timeout expired and we are still "provisioning"');
7076                 VM.markVMFailure(vmobj, {log: log}, function (mark_err) {
7077                     log.warn(mark_err, 'zoneinit failed, zone is '
7078                         + 'being stopped for manual investigation.');
7079                     done();
7080                     cb();
7081                 });
7082                 return;
7083             }
7084 
7085             checkHWSetup(vmobj, log, function (check_err, result) {
7086                 if (check_err) {
7087                     log.debug(check_err, 'checkHWSetup Error: '
7088                         + check_err.message);
7089                     return;
7090                 }
7091 
7092                 if (result) {
7093                     log.debug('QMP says VM ' + vmobj.uuid
7094                         + ' completed hwsetup');
7095                     VM.unsetTransition(vmobj, {log: log}, function (unset_err) {
7096                         var provisioning;
7097                         var provision_success;
7098 
7099                         provisioning = path.join(vmobj.zonepath,
7100                             '/root/var/svc/provisioning');
7101                         provision_success = path.join(vmobj.zonepath,
7102                             '/root/var/svc/provision_success');
7103 
7104                         if (unset_err) {
7105                             log.error(unset_err);
7106                         } else {
7107                             log.debug('cleared transition to provisioning on'
7108                                 + ' ' + vmobj.uuid);
7109                         }
7110 
7111                         fs.rename(provisioning, provision_success,
7112                             function (e) {
7113 
7114                             if (e) {
7115                                 if (e.code === 'ENOENT') {
7116                                     log.debug(e);
7117                                 } else {
7118                                     log.error(e);
7119                                 }
7120                             }
7121 
7122                             done();
7123                             cb();
7124                             return;
7125                         });
7126                     });
7127                 }
7128             });
7129         });
7130     }, loop_interval * 1000);
7131 
7132     return (ival_handle);
7133 }
7134 
7135 function archiveVM(uuid, options, callback)
7136 {
7137     var archive_dirname;
7138     var dirmode;
7139     var log;
7140     var patterns_to_archive = [];
7141     var vmobj;
7142 
7143     /*jsl:ignore*/
7144     dirmode = 0755;
7145     /*jsl:end*/
7146 
7147     if (options.hasOwnProperty('log')) {
7148         log = options.log;
7149     } else {
7150         log = VM.log;
7151     }
7152 
7153     log.debug('attempting to archive debug data for VM ' + uuid);
7154 
7155     async.series([
7156         function (cb) {
7157             // ensure directory exists
7158             archive_dirname = path.join('/zones/archive', uuid);
7159 
7160             fs.mkdir(archive_dirname, dirmode, function (e) {
7161                 log.debug(e, 'attempted to create ' + archive_dirname);
7162                 cb(e);
7163                 return;
7164             });
7165         }, function (cb) {
7166             VM.load(uuid, {log: log}, function (err, obj) {
7167                 if (err) {
7168                     cb(err);
7169                     return;
7170                 }
7171                 vmobj = obj;
7172                 cb();
7173             });
7174         }, function (cb) {
7175             // write vmobj to archive
7176             var filename;
7177 
7178             filename = path.join(archive_dirname, 'vm.json');
7179 
7180             fs.writeFile(filename, JSON.stringify(vmobj, null, 2) + '\n',
7181                 function (err, result) {
7182 
7183                 if (err) {
7184                     log.error(err, 'failed to create ' + filename + ': '
7185                         + err.message);
7186                 } else {
7187                     log.info('archived data to ' + filename);
7188                 }
7189 
7190                 cb(); // ignore error
7191             });
7192         }, function (cb) {
7193             var cmdline = '/usr/sbin/zfs list -t all -o name | grep '
7194                 + vmobj.zonename + ' | xargs zfs get -pH all >'
7195                 + path.join(archive_dirname, 'zfs.dump');
7196 
7197             log.debug(cmdline);
7198             exec(cmdline, function (e, stdout, stderr) {
7199                 if (e) {
7200                     e.stdout = stdout;
7201                     e.stderr = stderr;
7202                     log.error({err: e}, 'failed to create '
7203                         + path.join(archive_dirname, 'zfs.dump'));
7204                     cb(e);
7205                     return;
7206                 }
7207                 log.info('archived data to ' + path.join(archive_dirname,
7208                     'zfs.dump'));
7209                 cb();
7210             });
7211         }, function (cb) {
7212             patterns_to_archive.push({
7213                 src: path.join('/etc/zones/', vmobj.zonename + '.xml'),
7214                 dst: path.join(archive_dirname, 'zone.xml')
7215             });
7216             patterns_to_archive.push({
7217                 src: path.join(vmobj.zonepath, 'config'),
7218                 dst: archive_dirname,
7219                 targ: path.join(archive_dirname, 'config')
7220             });
7221             patterns_to_archive.push({
7222                 src: path.join(vmobj.zonepath, 'cores'),
7223                 dst: archive_dirname,
7224                 targ: path.join(archive_dirname, 'cores')
7225             });
7226 
7227             if (vmobj.brand === 'kvm') {
7228                 patterns_to_archive.push({
7229                     src: path.join(vmobj.zonepath, 'root/tmp/vm*.log*'),
7230                     dst: path.join(archive_dirname, 'vmlogs'),
7231                     create_dst_dir: true
7232                 });
7233                 patterns_to_archive.push({
7234                     src: path.join(vmobj.zonepath, 'root/startvm'),
7235                     dst: archive_dirname,
7236                     targ: path.join(archive_dirname, 'startvm')
7237                 });
7238             } else {
7239                 patterns_to_archive.push({
7240                     src: path.join(vmobj.zonepath, 'root/var/svc/log/*'),
7241                     dst: path.join(archive_dirname, 'svclogs'),
7242                     create_dst_dir: true
7243                 });
7244                 patterns_to_archive.push({
7245                     src: path.join(vmobj.zonepath, 'root/var/adm/messages*'),
7246                     dst: path.join(archive_dirname, 'admmsgs'),
7247                     create_dst_dir: true
7248                 });
7249             }
7250 
7251             async.forEachSeries(patterns_to_archive, function (pattern, c) {
7252 
7253                 function cpPattern(p, cp_cb) {
7254                     var cmdline = '/usr/bin/cp -RP ' + p.src + ' ' + p.dst;
7255                     var targ = p.targ || p.dst;
7256 
7257                     log.debug(cmdline);
7258                     exec(cmdline, function (e, stdout, stderr) {
7259                         if (e) {
7260                             e.stdout = stdout;
7261                             e.stderr = stderr;
7262                             log.error({err: e}, 'failed to archive data to '
7263                                 + targ);
7264                         } else {
7265                             log.info('archived data to ' + targ);
7266                         }
7267                         // we don't return errors here because on error copying
7268                         // one pattern we still want to grab the others.
7269                         cp_cb();
7270                     });
7271                 }
7272 
7273                 if (pattern.create_dst_dir) {
7274                     fs.mkdir(pattern.dst, dirmode, function (e) {
7275                         if (!e) {
7276                             log.info('created ' + pattern.dst);
7277                         } else {
7278                             log.error({err: e}, 'failed to create '
7279                                 + pattern.dst);
7280                         }
7281                         cpPattern(pattern, c);
7282                     });
7283                 } else {
7284                     cpPattern(pattern, c);
7285                 }
7286             }, function (e) {
7287                 log.info('finished archiving VM ' + vmobj.uuid);
7288                 cb(e);
7289             });
7290         }
7291     ], function () {
7292         // XXX we ignore errors as failures to archive will not block VM delete.
7293         callback();
7294     });
7295 }
7296 
7297 // vmobj argument should have:
7298 //
7299 // transition_to
7300 // uuid
7301 // zonename
7302 //
7303 exports.markVMFailure = function (vmobj, options, cb)
7304 {
7305     var log;
7306 
7307     // options is optional
7308     if (arguments.length === 2) {
7309         cb = arguments[1];
7310         options = {};
7311     }
7312 
7313     if (!vmobj || !vmobj.hasOwnProperty('uuid')
7314         || !vmobj.hasOwnProperty('zonename')) {
7315 
7316         cb(new Error('markVMFailure needs uuid + zonename'));
7317         return;
7318     }
7319 
7320     ensureLogging(true);
7321     if (options.hasOwnProperty('log')) {
7322         log = options.log;
7323     } else {
7324         log = VM.log.child({action: 'markVMFailure', vm: vmobj.uuid});
7325     }
7326 
7327     function dumpDebugInfo(zonename, callback) {
7328         var errors = {};
7329 
7330         async.series([
7331             function (ptree_cb) {
7332                 // note: if the zone is not running this returns empty but still
7333                 // exits 0
7334                 execFile('/usr/bin/ptree', ['-z', zonename],
7335                     function (ptree_err, ptree_stdout, ptree_stderr) {
7336 
7337                         if (ptree_err) {
7338                             log.error(ptree_err, 'unable to get ptree from '
7339                                 + zonename + ': ' + ptree_stderr);
7340                             errors.ptree_err = ptree_err;
7341                         } else {
7342                             log.warn('processes running in ' + zonename
7343                                 + ' at fail time:\n' + ptree_stdout);
7344                         }
7345 
7346                         ptree_cb(); // don't fail on error here.
7347                     }
7348                 );
7349             }, function (svcs_cb) {
7350                 execFile('/usr/bin/svcs', ['-xv', '-z', zonename],
7351                     function (svcs_err, svcs_stdout, svcs_stderr) {
7352 
7353                         if (svcs_err) {
7354                             log.error(svcs_err, 'unable to get svcs from '
7355                                 + zonename + ': ' + svcs_stderr);
7356                             errors.svcs_err = svcs_err;
7357                         } else {
7358                             log.warn('svcs -xv output for ' + zonename
7359                                 + ' at fail time:\n' + svcs_stdout);
7360                         }
7361 
7362                         svcs_cb(); // don't fail on error here.
7363                     }
7364                 );
7365             }, function (kstat_cb) {
7366                 execFile('/usr/bin/kstat', ['-n', zonename.substr(0, 30)],
7367                     function (kstat_err, kstat_stdout, kstat_stderr) {
7368 
7369                         if (kstat_err) {
7370                             log.error(kstat_err, 'unable to get kstats from '
7371                                 + zonename + ': ' + kstat_stderr);
7372                             errors.kstat_err = kstat_err;
7373                         } else {
7374                             log.warn('kstat output for ' + zonename
7375                                 + ' at fail time:\n' + kstat_stdout);
7376                         }
7377 
7378                         kstat_cb(); // don't fail on error here.
7379                     }
7380                 );
7381             }
7382         ], function () {
7383             callback(errors);
7384         });
7385     }
7386 
7387     dumpDebugInfo(vmobj.zonename, function (debug_err) {
7388         var zcfg;
7389 
7390         // note: we don't treat failure to dump debug info as a fatal error.
7391         log.warn(debug_err, 'zone setup failed, zone is being stopped '
7392             + 'for manual investigation.');
7393 
7394         // Mark the zone as 'failed'
7395         zcfg = 'remove -F attr name=failed; add attr; set name=failed; '
7396             + 'set value="provisioning"; set type=string; end';
7397 
7398         zonecfg(['-u', vmobj.uuid, zcfg], log, function (zonecfg_err, fds) {
7399 
7400             if (zonecfg_err) {
7401                 log.error({err: zonecfg_err, stdout: fds.stdout,
7402                     stderr: fds.stderr}, 'Unable to set failure flag on '
7403                     + vmobj.uuid + ': ' + zonecfg_err.message);
7404             } else {
7405                 log.debug({stdout: fds.stdout, stderr: fds.stderr},
7406                     'set failure flag on ' + vmobj.uuid);
7407             }
7408 
7409             // attempt to remove transition
7410             VM.unsetTransition(vmobj, {log: log}, function (unset_err) {
7411                 if (unset_err) {
7412                     log.error(unset_err);
7413                 }
7414 
7415                 VM.stop(vmobj.uuid, {force: true, log: log},
7416                     function (stop_err) {
7417 
7418                     // only log errors because there's nothing to do
7419 
7420                     if (stop_err) {
7421                         log.error(stop_err, 'failed to stop VM '
7422                             + vmobj.uuid + ': ' + stop_err.message);
7423                     }
7424 
7425                     cb();
7426                 });
7427             });
7428         });
7429     });
7430 };
7431 
7432 function svccfg(zonepath, args, log, callback)
7433 {
7434     var cmd = '/usr/sbin/svccfg';
7435     var exec_options = {};
7436     var zoneroot = path.join(zonepath, '/root');
7437 
7438     assert(log, 'no logger passed to svccfg()');
7439 
7440     try {
7441         assertSafeZonePath(zoneroot, '/etc/svc/repository.db',
7442             {type: 'file', enoent_ok: false});
7443     } catch (e) {
7444         log.error(e, 'Error validating /etc/svc/repository.db: ' + e.message);
7445         callback(e);
7446         return;
7447     }
7448 
7449     exec_options = {
7450         env: {
7451             'SVCCFG_CONFIGD_PATH': '/lib/svc/bin/svc.configd',
7452             'SVCCFG_REPOSITORY':
7453                 path.join(zonepath, 'root', '/etc/svc/repository.db')
7454         }
7455     };
7456 
7457     log.debug({'command': cmd + ' ' + args.join(' '),
7458         'exec_options': exec_options}, 'modifying svc repo in ' + zonepath);
7459     execFile(cmd, args, exec_options, function (error, stdout, stderr) {
7460         if (error) {
7461             callback(error, {'stdout': stdout, 'stderr': stderr});
7462         } else {
7463             callback(null, {'stdout': stdout, 'stderr': stderr});
7464         }
7465     });
7466 }
7467 
7468 // This calls cb() when /var/svc/provisioning is gone. When this calls cb()
7469 // with an Error object, the provision is considered failed so this should
7470 // only happen when something timed out that is unrelated to the user.
7471 //
7472 // This returns a function that can be called with no arguments to cancel
7473 // all timers and actions pending from this function.  It will also then not
7474 // call the cb().
7475 //
7476 // IMPORTANT: this is only exported to be used by vmadmd. Do not use elsewhere!
7477 //
7478 // vmobj fields:
7479 //
7480 //  state
7481 //  transition_expire
7482 //  uuid
7483 //  zonepath
7484 //
7485 exports.waitForProvisioning = function (vmobj, options, cb)
7486 {
7487     var dirname = path.join(vmobj.zonepath, 'root', '/var/svc');
7488     var filename = path.join(dirname, 'provisioning');
7489     var ival_h;
7490     var log;
7491     var timeout;
7492     var timeout_remaining = PROVISION_TIMEOUT; // default to whole thing
7493     var watcher;
7494 
7495     // options is optional
7496     if (arguments.length === 2) {
7497         cb = arguments[1];
7498         options = {};
7499     }
7500 
7501     ensureLogging(true);
7502     if (options.hasOwnProperty('log')) {
7503         log = options.log;
7504     } else {
7505         log = VM.log.child({action: 'waitForProvisioning', vm: vmobj.uuid});
7506     }
7507 
7508     function done() {
7509         if (timeout) {
7510             log.debug('clearing provision timeout for ' + vmobj.uuid);
7511             clearTimeout(timeout);
7512             timeout = null;
7513         }
7514         if (watcher) {
7515             log.debug('closing /var/svc/provisioning watcher for '
7516                 + vmobj.uuid);
7517             watcher.close();
7518             watcher = null;
7519         }
7520         if (ival_h) {
7521             log.debug('closing hwsetup check interval for ' + vmobj.uuid);
7522             clearInterval(ival_h);
7523             ival_h = null;
7524         }
7525     }
7526 
7527     if ((vmobj.state === 'provisioning')
7528         && (vmobj.hasOwnProperty('transition_expire'))) {
7529 
7530         timeout_remaining =
7531             (Number(vmobj.transition_expire) - Date.now(0)) / 1000;
7532 
7533         // Always give it at least 1 second's chance.
7534         if (timeout_remaining < 1) {
7535             timeout_remaining = 1;
7536         }
7537     } else {
7538         // don't know what to do here we're not provisioning.
7539         log.warn('waitForProvisioning called when ' + vmobj.uuid
7540             + ' was not provisioning');
7541         cb();
7542         return (null);
7543     }
7544 
7545     log.debug({
7546         'transition_expire': Number(vmobj.transition_expire),
7547         'now': Date.now(0)
7548     }, 'waiting ' + timeout_remaining + ' sec(s) for provisioning');
7549 
7550     log.debug('setting provision timeout for ' + vmobj.uuid);
7551     timeout = setTimeout(function () {
7552         log.warn('Marking VM ' + vmobj.uuid + ' as a "failure" because we '
7553             + 'hit waitForProvisioning() timeout.');
7554         VM.markVMFailure(vmobj, {log: log}, function (err) {
7555             var errstr = 'timed out waiting for /var/svc/provisioning to move'
7556                 + ' for ' + vmobj.uuid;
7557             if (err) {
7558                 log.warn(err, 'markVMFailure(): ' + err.message);
7559             }
7560             log.error(errstr);
7561             done();
7562             cb(new Error(errstr));
7563         });
7564     }, (timeout_remaining * 1000));
7565 
7566     // this starts a loop that will move provisioning -> provision_success when
7567     // the hardware of the VM has been initialized the first time.
7568     if (BRAND_OPTIONS[vmobj.brand].features.wait_for_hwsetup) {
7569         ival_h = markProvisionedWhenHWSetup(vmobj, {log: log}, function (err) {
7570             if (err) {
7571                 log.error(err, 'error in markProvisionedWhenHWSetup()');
7572             }
7573             done();
7574             cb(err);
7575         });
7576         return (done);
7577     }
7578 
7579     watcher = fs.watch(filename, function (evt, file) {
7580         // We only care about 'rename' which also fires when the file is
7581         // deleted.
7582         log.debug('watcher.event(' + vmobj.uuid + '): ' + evt);
7583         if (evt === 'rename') {
7584             fs.exists(filename, function (exists) {
7585                 if (exists) {
7586                     // somehow we still have /var/svc/provisioning!
7587                     log.warn('Marking VM ' + vmobj.uuid + ' as a "failure"'
7588                         + ' because we still have /var/svc/provisioning after '
7589                         + 'rename');
7590                     VM.markVMFailure(vmobj, {log: log}, function (err) {
7591                         if (err) {
7592                             log.warn(err, 'markVMFailure(): ' + err.message);
7593                         }
7594                         done();
7595                         cb(new Error('/var/svc/provisioning exists after '
7596                             + 'rename!'));
7597                     });
7598                     return;
7599                 }
7600 
7601                 // So long as /var/svc/provisioning is gone, we don't care what
7602                 // replaced it.  Success or failure of user script doesn't
7603                 // matter for the state, it's provisioned now. Caller should
7604                 // now clear the transition.
7605                 done();
7606                 cb();
7607                 return;
7608             });
7609         }
7610     });
7611 
7612     log.debug('created watcher for ' + vmobj.uuid);
7613     return (done);
7614 };
7615 
7616 // create and install a 'joyent' or 'kvm' brand zone.
7617 function installZone(payload, log, callback)
7618 {
7619     var load_fields;
7620     var receiving = false;
7621     var reprovisioning = false;
7622     var vmobj;
7623     var zoneinit = {};
7624 
7625     assert(log, 'no logger passed to installZone()');
7626 
7627     log.debug('installZone()');
7628 
7629     load_fields = [
7630         'brand',
7631         'firewall_enabled',
7632         'missing',
7633         'nics',
7634         'owner_uuid',
7635         'routes',
7636         'state',
7637         'tags',
7638         'transition_to',
7639         'transition_expire',
7640         'uuid',
7641         'zonename',
7642         'zonepath'
7643     ];
7644 
7645     if (payload.reprovisioning) {
7646         log.debug('installZone(): reprovisioning');
7647         reprovisioning = true;
7648     }
7649 
7650     async.series([
7651         function (cb) {
7652 
7653             VM.load(payload.uuid, {fields: load_fields, log: log},
7654                 function (err, obj) {
7655 
7656                 if (err) {
7657                     cb(err);
7658                     return;
7659                 }
7660                 vmobj = obj;
7661                 cb();
7662             });
7663         }, function (cb) {
7664             var thing;
7665             var missing = false;
7666             var msg;
7667             var things = ['datasets', 'filesystems', 'disks'];
7668 
7669             if (vmobj.state === 'receiving') {
7670                 receiving = true;
7671                 msg = 'zone is still missing:';
7672                 for (thing in things) {
7673                     thing = things[thing];
7674                     if (vmobj.missing[thing].length !== 0) {
7675                         msg = msg + ' ' + vmobj.missing[thing].length + ' '
7676                             + thing + ',';
7677                         missing = true;
7678                     }
7679                 }
7680                 msg = rtrim(msg, ',');
7681 
7682                 if (missing) {
7683                     cb(new Error('Unable to complete install for '
7684                         + vmobj.uuid + ' ' + msg));
7685                     return;
7686                 }
7687             }
7688             cb();
7689         }, function (cb) {
7690             // Install the zone.
7691             // This will create the dataset and mark the zone 'installed'.
7692             var args;
7693 
7694             if (reprovisioning) {
7695                 // reprovisioning we do *most* of install, but not this.
7696                 cb();
7697                 return;
7698             }
7699 
7700             args = ['-z', vmobj.zonename, 'install', '-q',
7701                 payload.quota.toString()];
7702 
7703             // For both OS and KVM VMs you can pass an image_uuid at the
7704             // top-level. This will be your zone's root dataset. On KVM the user
7705             // is never exposed to this. It's used there for something like
7706             // SPICE.
7707             if (payload.hasOwnProperty('image_uuid')) {
7708                 args.push('-t', payload.image_uuid, '-x', 'nodataset');
7709             }
7710 
7711             zoneadm(args, log, function (err, fds) {
7712                 if (err) {
7713                     log.error({err: err, stdout: fds.stdout,
7714                         stderr: fds.stderr}, 'zoneadm failed to install: '
7715                         + err.message);
7716                     cb(err);
7717                 } else {
7718                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
7719                         'zoneadm installed zone');
7720                     cb();
7721                 }
7722             });
7723         }, function (cb) {
7724             // Apply compression if set
7725             var args = [];
7726             if (payload.hasOwnProperty('zfs_root_compression')) {
7727                 args = ['set', 'compression='
7728                     + payload.zfs_root_compression, payload.zfs_filesystem];
7729                 zfs(args, log, function (err) {
7730                     cb(err);
7731                 });
7732             } else {
7733                 cb();
7734             }
7735         }, function (cb) {
7736             // Apply recsize if set
7737             var args = [];
7738             if (payload.hasOwnProperty('zfs_root_recsize')) {
7739                 args = ['set', 'recsize=' + payload.zfs_root_recsize,
7740                     payload.zfs_filesystem];
7741                 zfs(args, log, function (err) {
7742                     cb(err);
7743                 });
7744             } else {
7745                 cb();
7746             }
7747         }, function (cb) {
7748             // Some zones can have an additional 'data' dataset delegated to
7749             // them for use in the zone.  This will set that up.  If the option
7750             // is not set, the following does nothing.
7751             if (!receiving && !reprovisioning) {
7752                 createDelegatedDataset(payload, log, function (err) {
7753                     if (err) {
7754                         cb(err);
7755                     } else {
7756                         cb();
7757                     }
7758                 });
7759             } else {
7760                 cb();
7761             }
7762         }, function (cb) {
7763             // Write out the zone's metadata
7764             // Note: we don't do this when receiving because dataset will
7765             // already contain metadata and we don't want to wipe that out.
7766             if (!receiving && !reprovisioning) {
7767                 saveMetadata(payload, log, function (err) {
7768                     if (err) {
7769                         log.error(err, 'unable to save metadata: '
7770                             + err.message);
7771                         cb(err);
7772                     } else {
7773                         cb();
7774                     }
7775                 });
7776             } else {
7777                 cb();
7778             }
7779         }, function (cb) {
7780             // Write out the zone's routes
7781             // Note: we don't do this when receiving because dataset will
7782             // already contain routes and we don't want to wipe that out.
7783             if (!receiving && !reprovisioning) {
7784                 saveRoutes(payload, log, function (err) {
7785                     if (err) {
7786                         log.error(err, 'unable to save routes: '
7787                             + err.message);
7788                         cb(err);
7789                     } else {
7790                         cb();
7791                     }
7792                 });
7793             } else {
7794                 cb();
7795             }
7796         }, function (cb) {
7797             // if we were receiving, we're done receiving now
7798             if (receiving) {
7799                 VM.unsetTransition(vmobj, {log: log}, cb);
7800             } else {
7801                 cb();
7802             }
7803         }, function (cb) {
7804             // var zoneinit is in installZone() scope
7805 
7806             // when receiving zoneinit is never run.
7807             if (receiving) {
7808                 cb();
7809                 return;
7810             }
7811 
7812             getZoneinitJSON(vmobj.zonepath, log, function (zoneinit_err, data) {
7813 
7814                 if (zoneinit_err) {
7815                     // NOTE: not existing is not going to give us a zoneinit_err
7816                     log.warn(zoneinit_err, 'error in getZoneinitJSON');
7817                     cb(zoneinit_err);
7818                     return;
7819                 }
7820 
7821                 if (data) {
7822                     zoneinit = data;
7823                 } else {
7824                     zoneinit = {};
7825                 }
7826 
7827                 cb();
7828             });
7829         }, function (cb) {
7830             // var_svc_provisioning is at installZone() scope
7831 
7832             // If we're not receiving, we're provisioning a new VM and in that
7833             // case we write the /var/svc/provisioning file which should exist
7834             // until something in the zone decides provisioning is complete. At
7835             // that point it will be moved to either:
7836             //
7837             //    /var/svc/provision_success
7838             //    /var/svc/provision_failure
7839             //
7840             // to indicate that the provisioning setup has been completed.
7841 
7842             if (receiving) {
7843                 cb();
7844                 return;
7845             }
7846 
7847             fs.writeFile(path.join(vmobj.zonepath, 'root',
7848                 '/var/svc/provisioning'), '', function (err, result) {
7849 
7850                 if (err) {
7851                     log.error(err, 'failed to create '
7852                         + '/var/svc/provisioning: ' + err.message);
7853                 } else {
7854                     log.debug('created /var/svc/provisioning in '
7855                         + path.join(vmobj.zonepath, 'root'));
7856                 }
7857 
7858                 cb(err);
7859             });
7860         }, function (cb) {
7861             // For joyent and joyent-minimal at least, set the timeout for the
7862             // svc start method to the value specified in the payload, or a
7863             // default.
7864 
7865             var timeout;
7866 
7867             if (BRAND_OPTIONS[vmobj.brand].features.update_mdata_exec_timeout) {
7868 
7869                 if (payload.hasOwnProperty('mdata_exec_timeout')) {
7870                     timeout = payload.mdata_exec_timeout;
7871                 } else {
7872                     timeout = DEFAULT_MDATA_TIMEOUT;
7873                 }
7874 
7875                 svccfg(vmobj.zonepath, [
7876                     '-s', 'svc:/smartdc/mdata:execute',
7877                     'setprop', 'start/timeout_seconds', '=', 'count:', timeout
7878                     ], log, function (error, stdio) {
7879 
7880                     if (error) {
7881                         log.error(error, 'failed to set mdata:exec timeout');
7882                         cb(error);
7883                         return;
7884                     }
7885 
7886                     cb();
7887                 });
7888             } else {
7889                 cb();
7890             }
7891 
7892         }, function (cb) {
7893             // This writes out the 'zoneconfig' file used by zoneinit to root's
7894             // home directory in the zone.
7895             if (! receiving
7896                 && BRAND_OPTIONS[vmobj.brand].features.zoneinit
7897                 && (! zoneinit.hasOwnProperty('features')
7898                 || zoneinit.features.zoneconfig)) {
7899 
7900                 // No 'features' means old dataset.  If we have old dataset or
7901                 // one that really wants a zoneconfig, write it out.
7902 
7903                 writeZoneconfig(payload, log, function (err) {
7904                     cb(err);
7905                 });
7906             } else {
7907                 cb();
7908             }
7909         }, function (cb) {
7910             if (BRAND_OPTIONS[vmobj.brand].features.write_zone_netfiles
7911                 && !receiving) {
7912 
7913                 writeZoneNetfiles(payload, log, function (err) {
7914                     cb(err);
7915                 });
7916             } else {
7917                 cb();
7918             }
7919         }, function (cb) {
7920             if (vmobj.hasOwnProperty('zonepath')
7921                 && BRAND_OPTIONS[vmobj.brand].features.cleanup_dataset
7922                 && !receiving) {
7923 
7924                 cleanupMessyDataset(vmobj.zonepath, vmobj.brand, log,
7925                     function (err) {
7926 
7927                     cb(err);
7928                 });
7929             } else {
7930                 cb();
7931             }
7932         }, function (cb) {
7933             // Firewall data has not changed when reprovisioning, so we don't
7934             // re-run addFirewallData()
7935             if (reprovisioning) {
7936                 cb();
7937                 return;
7938             }
7939 
7940             // Add firewall data if it was included
7941             addFirewallData(payload, vmobj, log, cb);
7942         }, function (cb) {
7943 
7944             var cancel;
7945             var calledback = false;
7946             var prov_wait = true;
7947             // var_svc_provisioning is at installZone() scope
7948 
7949             // The vm is now ready to start, we'll start if autoboot is set. If
7950             // not, we also don't want to wait for 'provisioning'.
7951             if (!payload.autoboot) {
7952                 cb();
7953                 return;
7954             }
7955 
7956             // In these cases we never wait for provisioning -> running
7957             if (payload.nowait || receiving || vmobj.state !== 'provisioning') {
7958                 prov_wait = false;
7959             }
7960 
7961             // most VMs support the /var/svc/provision{ing,_success,_failure}
7962             // files. For those, if !nowait, we wait for the file to change
7963             // from provisioning -> either provision_success, or
7964             // provision_failure.
7965 
7966             if (prov_wait) {
7967                 // wait for /var/svc/provisioning -> provision_success/failure
7968                 cancel = VM.waitForProvisioning(vmobj, {log: log},
7969                     function (err) {
7970 
7971                     log.debug(err, 'waited for provisioning');
7972 
7973                     if (!err) {
7974                         log.info('provisioning complete: '
7975                             + '/var/svc/provisioning is gone');
7976                         // this will clear the provision transition
7977                         VM.unsetTransition(vmobj, {log: log},
7978                             function (unset_err) {
7979 
7980                             if (unset_err) {
7981                                 log.error(unset_err, 'error unsetting '
7982                                     + 'transition: ' + unset_err.message);
7983                             }
7984                             // this and the cb in the VM.start callback might
7985                             // both run if we don't check this.
7986                             if (!calledback) {
7987                                 calledback = true;
7988                                 cb(unset_err);
7989                             }
7990                         });
7991                     } else {
7992                         // failed but might not be able to cb if VM.start's
7993                         // callback already did.
7994                         log.error(err, 'error waiting for provisioning: '
7995                             + err.message);
7996                         // this and the cb in the VM.start callback might
7997                         // both run if we don't check this.
7998                         if (!calledback) {
7999                             calledback = true;
8000                             cb(err);
8001                         }
8002                     }
8003                 });
8004             }
8005 
8006             VM.start(payload.uuid, {}, {log: log}, function (err, res) {
8007                 if (err) {
8008                     // we failed to start so we'll never see provisioning, so
8009                     // cancel that and return the error.
8010                     if (cancel) {
8011                         log.info('cancelling VM.waitForProvisioning');
8012                         cancel();
8013                     }
8014                     // this and the cb in the VM.waitForProvisioning
8015                     // callback might both run if we don't check this.
8016                     if (!calledback) {
8017                         calledback = true;
8018                         cb(err);
8019                     }
8020                     return;
8021                 }
8022                 // if we're waiting for 'provisioning' VM.waitForProvisioning's
8023                 // callback will call cb().  If we're not going to wait, we call
8024                 // it here.
8025                 if (!prov_wait) {
8026                     // this and the cb in the VM.waitForProvisioning
8027                     // callback might both run if we don't check this.
8028                     if (!calledback) {
8029                         calledback = true;
8030                         cb();
8031                     }
8032                 }
8033             });
8034         }], function (error) {
8035             callback(error);
8036         }
8037     );
8038 }
8039 
8040 function getZoneinitJSON(rootpath, log, cb)
8041 {
8042     var filename;
8043     var zoneroot;
8044 
8045     assert(log, 'no logger passed to getZoneinitJSON()');
8046 
8047     zoneroot = path.join('/', rootpath, 'root');
8048     filename = path.join(zoneroot, '/var/zoneinit/zoneinit.json');
8049 
8050     try {
8051         assertSafeZonePath(zoneroot, '/var/zoneinit/zoneinit.json',
8052             {type: 'file', enoent_ok: true});
8053     } catch (e) {
8054         log.error(e, 'Error validating /var/zoneinit/zoneinit.json: '
8055             + e.message);
8056         cb(e);
8057         return;
8058     }
8059 
8060     fs.readFile(filename, function (error, data) {
8061         var zoneinit;
8062 
8063         if (error && (error.code === 'ENOENT')) {
8064             // doesn't exist, leave empty
8065             log.debug('zoneinit.json does not exist.');
8066             cb();
8067         } else if (error) {
8068             // error reading: fail.
8069             cb(error);
8070         } else {
8071             // success try to load json
8072             try {
8073                 zoneinit = JSON.parse(data.toString());
8074                 log.debug({'zoneinit_json': zoneinit},
8075                     'parsed zoneinit.json');
8076                 cb(null, zoneinit);
8077             } catch (e) {
8078                 cb(e);
8079             }
8080         }
8081     });
8082 }
8083 
8084 function getDatasetMountpoint(dataset, log, callback)
8085 {
8086     var args;
8087     var cmd = '/usr/sbin/zfs';
8088     var mountpoint;
8089 
8090     assert(log, 'no logger passed to getDatasetMountpoint()');
8091 
8092     args = ['get', '-H', '-o', 'value', 'mountpoint', dataset];
8093 
8094     log.debug(cmd + ' ' + args.join(' '));
8095     execFile(cmd, args, function (error, stdout, stderr) {
8096         if (error) {
8097             log.error(error, 'zfs get failed with: ' + stderr);
8098             callback(error);
8099         } else {
8100             mountpoint = stdout.replace(/\n/g, '');
8101             log.debug('mountpoint: "' + mountpoint + '"');
8102             callback(null, mountpoint);
8103         }
8104     });
8105 }
8106 
8107 // TODO: pull data out of the massive zfs list we pulled earlier
8108 function checkDatasetProvisionable(payload, log, callback)
8109 {
8110     var dataset;
8111 
8112     assert(log, 'no logger passed to checkDatasetProvisionable()');
8113 
8114     if (BRAND_OPTIONS[payload.brand].features.var_svc_provisioning) {
8115         // when the brand always supports /var/svc/provisioning we don't have to
8116         // worry about the dataset not supporting it.
8117         callback(true);
8118         return;
8119     }
8120 
8121     if (!payload.hasOwnProperty('zpool')
8122         || !payload.hasOwnProperty('image_uuid')) {
8123 
8124         log.error('missing properties required to find dataset: '
8125             + JSON.stringify(payload));
8126         callback(false);
8127         return;
8128     }
8129 
8130     dataset = payload.zpool + '/' + payload.image_uuid;
8131 
8132     getDatasetMountpoint(dataset, log, function (dataset_err, mountpoint) {
8133         if (dataset_err) {
8134             log.error('unable to find mount point for ' + dataset);
8135             callback(false);
8136             return;
8137         }
8138 
8139         getZoneinitJSON(dataset, log, function (zoneinit_err, zoneinit) {
8140             var filename_1_6_x;
8141             var filename_1_8_x;
8142 
8143             if (zoneinit_err) {
8144                 log.error(zoneinit_err, 'getZoneinitJSON() failed, assuming '
8145                     + 'not provisionable.');
8146                 callback(false);
8147                 return;
8148             } else if (!zoneinit) {
8149                 log.debug('no data from getZoneinitJSON(), using {}');
8150                 zoneinit = {};
8151             }
8152 
8153             if (zoneinit.hasOwnProperty('features')) {
8154                 if (zoneinit.features.var_svc_provisioning) {
8155                     log.info('zoneinit.features.var_svc_provisioning is '
8156                         + 'set.');
8157                     callback(true);
8158                     return;
8159                 }
8160                 // we have features but not var_svc_provisioning === true means
8161                 // we can't provision. Fall through and return false.
8162             } else {
8163                 // Didn't load zoneinit features, so check for datasets that
8164                 // have // 04-mdata.sh.  For 1.6.x and earlier datasets this was
8165                 // in /root but in 1.8.0 and 1.8.1 it is in /var/zoneinit.  For
8166                 // 1.8.2 and later we'll not get here as the zoneinit.json will
8167                 // exist and we'll use that.
8168                 filename_1_6_x = path.join(mountpoint, 'root',
8169                     '/root/zoneinit.d/04-mdata.sh');
8170                 filename_1_8_x = path.join(mountpoint, 'root',
8171                     '/var/zoneinit/includes/04-mdata.sh');
8172 
8173                 if (fs.existsSync(filename_1_6_x)) {
8174                     log.info(filename_1_6_x + ' exists');
8175                     callback(true);
8176                     return;
8177                 } else {
8178                     log.debug(filename_1_6_x + ' does not exist');
8179                     if (fs.existsSync(filename_1_8_x)) {
8180                         log.info(filename_1_8_x + ' exists');
8181                         callback(true);
8182                         return;
8183                     } else {
8184                         log.debug(filename_1_8_x + ' does not exist');
8185                         // this was our last chance.
8186                         // Fall through and return false.
8187                     }
8188                 }
8189             }
8190 
8191             callback(false);
8192             return;
8193         });
8194     });
8195 }
8196 
8197 // create and install a 'joyent' or 'kvm' brand zone.
8198 function createZone(payload, log, callback)
8199 {
8200     var create_time;
8201     var n;
8202     var now = new Date;
8203     var primary_found;
8204     var provision_timeout = PROVISION_TIMEOUT;
8205     var t;
8206     var vm_version;
8207     var zcfg;
8208 
8209     assert(log, 'no logger passed to createZone()');
8210 
8211     log.debug('createZone()');
8212 
8213     payload.zfs_filesystem = payload.zpool + '/' + payload.zonename;
8214     payload.zonepath = '/' + payload.zfs_filesystem;
8215 
8216     // we add create-timestamp in all cases except where we're receiving since
8217     // in that case we want to preserve the original create-timestamp.
8218     if (!payload.hasOwnProperty('transition')
8219         || (payload.transition.transition !== 'receiving')
8220         || !payload.hasOwnProperty('create_timestamp')) {
8221 
8222         create_time = now.toISOString();
8223     } else {
8224         create_time = payload.create_timestamp;
8225     }
8226 
8227     // we add vm-version (property v) in all cases except where we're receiving
8228     // since in that case we want to preserve the original version.
8229     if (!payload.hasOwnProperty('transition')
8230         || (payload.transition.transition !== 'receiving')
8231         || !payload.hasOwnProperty('v')) {
8232 
8233         vm_version = 1;
8234     } else {
8235         vm_version = payload.v;
8236     }
8237 
8238     // set the properties that can't be updated later here.
8239     zcfg = 'create -b\n'
8240         + 'set zonepath=' + payload.zonepath + '\n'
8241         + 'set brand=' + payload.brand + '\n'
8242         + 'set uuid=' + payload.uuid + '\n'
8243         + 'set ip-type=exclusive\n'
8244         + 'add attr; set name="vm-version"; set type=string; set value="'
8245         + vm_version + '"; end\n'
8246         + 'add attr; set name="create-timestamp"; set type=string; set value="'
8247         + create_time + '"; end\n';
8248 
8249     if (payload.hasOwnProperty('transition')) {
8250         // IMPORTANT: this is for internal use only and should not be documented
8251         // as an option for create's payload.  Used for receive.
8252         t = payload.transition;
8253         zcfg = zcfg
8254             + buildTransitionZonecfg(t.transition, t.target, t.timeout) + '\n';
8255     } else {
8256         // Assume this is really a new VM, add transition called 'provisioning'
8257         // only if the machine is going to be booting.
8258         if (!payload.hasOwnProperty('autoboot') || payload.autoboot) {
8259             zcfg = zcfg + buildTransitionZonecfg('provisioning', 'running',
8260                 provision_timeout * 1000) + '\n';
8261         }
8262     }
8263 
8264     // We call the property 'dataset-uuid' even though the property name is
8265     // image_uuid because existing VMs in the wild will be using dataset-uuid
8266     // already, and we are the point where the image becomes a dataset anyway.
8267     if (payload.hasOwnProperty('image_uuid')) {
8268         zcfg = zcfg + 'add attr; set name="dataset-uuid"; set type=string; '
8269             + 'set value="' + payload.image_uuid + '"; end\n';
8270     }
8271 
8272     if (BRAND_OPTIONS[payload.brand].features.use_vm_autoboot) {
8273         // we always set autoboot=false for VM zones, since we want vmadmd to
8274         // boot them and not the zones tools.  Use vm-autoboot to control VMs
8275         zcfg = zcfg + 'set autoboot=false\n';
8276     }
8277 
8278     // ensure that we have a primary nic, even if one wasn't specified
8279     if (payload.hasOwnProperty('add_nics') && payload.add_nics.length != 0) {
8280         primary_found = false;
8281 
8282         for (n in payload.add_nics) {
8283             n = payload.add_nics[n];
8284             if (n.hasOwnProperty('primary') && n.primary) {
8285                 primary_found = true;
8286                 break;
8287             }
8288         }
8289         if (!primary_found) {
8290             payload.add_nics[0].primary = true;
8291         }
8292     }
8293 
8294     // Passing an empty first parameter here, tells buildZonecfgUpdate that
8295     // we're talking about a new machine.
8296     zcfg = zcfg + buildZonecfgUpdate({}, payload, log);
8297 
8298     // include the zonecfg in the debug output to help track down problems.
8299     log.debug(zcfg);
8300 
8301     // send the zonecfg data we just generated as a file to zonecfg,
8302     // this will create the zone.
8303     zonecfgFile(zcfg, ['-z', payload.zonename], log, function (err, fds) {
8304         if (err || payload.create_only) {
8305             log.error({err: err, zcfg: zcfg, stdout: fds.stdout,
8306                 stderr: fds.stderr}, 'failed to modify zonecfg');
8307             callback(err);
8308         } else {
8309             log.debug({stdout: fds.stdout, stderr: fds.stderr},
8310                 'modified zonecfg');
8311             installZone(payload, log, callback);
8312         }
8313     });
8314 }
8315 
8316 function normalizeNics(payload, vmobj)
8317 {
8318     var n;
8319     var nic;
8320 
8321     // ensure all NICs being created/added have a MAC, remove the 'index' if it
8322     // is passed (that's deprecated), rename 'interface' to 'physical'.
8323     if (payload.hasOwnProperty('add_nics')) {
8324         for (n in payload.add_nics) {
8325             if (payload.add_nics.hasOwnProperty(n)) {
8326                 nic = payload.add_nics[n];
8327 
8328                 if (!nic.hasOwnProperty('mac')
8329                     && !nic.hasOwnProperty('vrrp_vrid')) {
8330                     nic.mac = generateMAC();
8331                 }
8332                 delete nic.index;
8333                 if (nic.hasOwnProperty('interface')) {
8334                     nic.physical = nic.interface;
8335                     delete nic.interface;
8336                 }
8337 
8338                 // nics.*.primary only supports true value, unset false. We also
8339                 // handle the case here why they used the deprecated '1' value.
8340                 // We will have already warned them, but still support for now.
8341                 if (nic.hasOwnProperty('primary')) {
8342                     if (nic.primary || nic.primary === '1'
8343                         || nic.primary === 1) {
8344 
8345                         nic.primary = true;
8346                     } else {
8347                         delete nic.primary;
8348                     }
8349                 }
8350             }
8351         }
8352     }
8353 }
8354 
8355 /*
8356  * This is called for both create and update, everything here should be safe for
8357  * both.  The vmobj will be set if it's an update.
8358  *
8359  */
8360 function normalizePayload(payload, vmobj, log, callback)
8361 {
8362     var action;
8363     var allowed;
8364     var brand;
8365     var property;
8366 
8367     assert(log, 'no logger passed to normalizePayload()');
8368 
8369     // fix type of arguments that should be numbers, do this here so that fixing
8370     // memory works correctly later using math.
8371     for (property in payload) {
8372         if (payload.hasOwnProperty(property)) {
8373             if (PAYLOAD_PROPERTIES.hasOwnProperty(property)
8374                 && PAYLOAD_PROPERTIES[property].type === 'integer'
8375                 && payload[property] !== undefined) {
8376                 // undefined is a special case since we use that to unset props
8377 
8378                 payload[property] = Number(payload[property]);
8379                 if (isNaN(payload[property])) {
8380                     callback(new Error('Invalid value for ' + property + ': '
8381                         + JSON.stringify(payload[property]) + ':'
8382                         + typeof (payload[property])));
8383                     return;
8384                 }
8385             }
8386         }
8387     }
8388 
8389     if (payload.hasOwnProperty('quota') && payload.quota === undefined) {
8390         // when unsetting quota we set to 0
8391         payload.quota = 0;
8392     }
8393 
8394     if (vmobj) {
8395         /* update */
8396         fixPayloadMemory(payload, vmobj, log);
8397         action = 'update';
8398     } else {
8399         /* this also calls fixPayloadMemory() */
8400         applyZoneDefaults(payload, log);
8401 
8402         if (payload.hasOwnProperty('create_only')
8403             && payload.transition.transition === 'receiving') {
8404 
8405             action = 'receive';
8406         } else {
8407             action = 'create';
8408         }
8409     }
8410 
8411     // Should always have a brand after we applied defaults.
8412     if (vmobj && vmobj.hasOwnProperty('brand')) {
8413         brand = vmobj.brand;
8414     } else if (payload.hasOwnProperty('brand')) {
8415         brand = payload.brand;
8416     } else {
8417         callback(new Error('Unable to determine brand for payload'));
8418         return;
8419     }
8420 
8421     // Historically we supported dataset_uuid for joyent+joyent-minimal and
8422     // zone_dataset_uuid for kvm. Now we just support image_uuid so give a
8423     // deprecation warning and translate if old version specified. This needs
8424     // to happen before VM.validate because image_uuid is required for most
8425     // VMs.
8426     allowed = BRAND_OPTIONS[brand].allowed_properties;
8427     if ((allowed.hasOwnProperty('dataset_uuid')
8428             && payload.hasOwnProperty('dataset_uuid'))
8429         || (allowed.hasOwnProperty('zone_dataset_uuid')
8430             && payload.hasOwnProperty('zone_dataset_uuid'))) {
8431 
8432         property = (payload.hasOwnProperty('dataset_uuid') ? 'dataset_uuid'
8433             : 'zone_dataset_uuid');
8434 
8435         if (payload.hasOwnProperty('image_uuid')) {
8436             log.warn('DEPRECATED option ' + property + ' found, '
8437                 + 'ignoring. In the future use image_uuid only.');
8438         } else {
8439             log.warn('DEPRECATED option ' + property + ' found, '
8440                 + 'ignoring. In the future use image_uuid instead.');
8441             payload.image_uuid = payload[property];
8442             delete payload.dataset_uuid;
8443         }
8444     }
8445 
8446     // after ZoneDefaults have been applied, we should always have zone. Now
8447     // we validate the payload properties and remove any that are invalid. If
8448     // there are bad values we'll just fail.
8449     VM.validate(brand, action, payload, {log: log}, function (errors) {
8450         var bad_prop;
8451         var compound_props = ['disks', 'nics', 'filesystems'];
8452         var matches;
8453         var obj;
8454         var prop;
8455 
8456         if (errors) {
8457             if (errors.hasOwnProperty('bad_brand')) {
8458                 callback(new Error('Invalid brand while validating payload: '
8459                     + JSON.stringify(brand)));
8460                 return;
8461             }
8462             if (errors.bad_values.length > 0) {
8463                 callback(new Error('Invalid value(s) for: '
8464                     + errors.bad_values.join(',')));
8465                 return;
8466             }
8467             if (errors.missing_properties.length > 0) {
8468                 callback(new Error('Missing required properties: '
8469                     + errors.missing_properties.join(',')));
8470                 return;
8471             }
8472             for (bad_prop in errors.bad_properties) {
8473                 bad_prop = errors.bad_properties[bad_prop];
8474                 log.warn('Warning, invalid ' + action + ' property: ['
8475                     + bad_prop + '] removing from payload.');
8476 
8477                 // for bad properties like nics.*.allow_unfiltered_promisc we
8478                 // need to remove it from add_nics, update_nics, etc.
8479                 for (prop in compound_props) {
8480                     prop = compound_props[prop];
8481 
8482                     matches = new RegExp('^' + prop
8483                         + '\\.\\*\\.(.*)$').exec(bad_prop);
8484                     if (matches) {
8485                         if (payload.hasOwnProperty(prop)) {
8486                             for (obj in payload[prop]) {
8487                                 delete payload[prop][obj][matches[1]];
8488                             }
8489                         }
8490                         if (payload.hasOwnProperty('add_' + prop)) {
8491                             for (obj in payload['add_' + prop]) {
8492                                 delete payload['add_' + prop][obj][matches[1]];
8493                             }
8494                         }
8495                         if (payload.hasOwnProperty('update_' + prop)) {
8496                             for (obj in payload['update_' + prop]) {
8497                                 delete payload['update_'
8498                                     + prop][obj][matches[1]];
8499                             }
8500                         }
8501                     }
8502                 }
8503 
8504                 delete payload[bad_prop];
8505             }
8506         }
8507 
8508         // By the time we got here all the properties in the payload are allowed
8509 
8510         // Now we make sure we've got a zonename (use uuid if not already set)
8511         if (!payload.hasOwnProperty('zonename')
8512             || payload.zonename === undefined) {
8513 
8514             payload.zonename = payload.uuid;
8515         }
8516 
8517         // You use 'disks' and 'nics' when creating, but the underlying
8518         // functions expect add_disks and add_nics, so we rename them now that
8519         // we've confirmed we've got the correct thing for this action.
8520         if (payload.hasOwnProperty('disks')) {
8521             if (payload.hasOwnProperty('add_disks')) {
8522                 callback(new Error('Cannot specify both "disks" and '
8523                     + '"add_disks"'));
8524                 return;
8525             }
8526             payload.add_disks = payload.disks;
8527             delete payload.disks;
8528         }
8529         if (payload.hasOwnProperty('nics')) {
8530             if (payload.hasOwnProperty('add_nics')) {
8531                 callback(new Error('Cannot specify both "nics" and '
8532                     + '"add_nics"'));
8533                 return;
8534             }
8535             payload.add_nics = payload.nics;
8536             delete payload.nics;
8537         }
8538         if (payload.hasOwnProperty('filesystems')) {
8539             if (payload.hasOwnProperty('add_filesystems')) {
8540                 callback(new Error('Cannot specify both "filesystems" and '
8541                     + '"add_filesystems"'));
8542                 return;
8543             }
8544             payload.add_filesystems = payload.filesystems;
8545             delete payload.filesystems;
8546         }
8547 
8548         // if there's a zfs_root_* and no zfs_data_*, normally the properties
8549         // would fall through, we don't want that.
8550         if (payload.hasOwnProperty('zfs_root_compression')
8551             && !payload.hasOwnProperty('zfs_data_compression')) {
8552 
8553             if (vmobj && vmobj.hasOwnProperty('zfs_data_compression')) {
8554                 // keep existing value.
8555                 payload.zfs_data_compression = vmobj.zfs_data_compression;
8556             } else {
8557                 // keep default value.
8558                 payload.zfs_data_compression = 'off';
8559             }
8560         }
8561         if (payload.hasOwnProperty('zfs_root_recsize')
8562             && !payload.hasOwnProperty('zfs_data_recsize')) {
8563 
8564             if (vmobj && vmobj.hasOwnProperty('zfs_data_recsize')) {
8565                 // keep existing value.
8566                 payload.zfs_data_recsize = vmobj.zfs_data_recsize;
8567             } else {
8568                 // keep default value.
8569                 payload.zfs_data_recsize = 131072;
8570             }
8571         }
8572 
8573         // this will ensure we've got a MAC, etc.
8574         normalizeNics(payload, vmobj);
8575 
8576         // Fix types for boolean fields in case someone put in 'false'/'true'
8577         // instead of false/true
8578         for (property in payload) {
8579             if (payload.hasOwnProperty(property)) {
8580                 if (PAYLOAD_PROPERTIES.hasOwnProperty(property)
8581                     && PAYLOAD_PROPERTIES[property].type === 'boolean') {
8582 
8583                     payload[property] = fixBooleanLoose(payload[property]);
8584                 }
8585             }
8586         }
8587 
8588         // We used to support zfs_storage_pool_name, but zpool is better.
8589         if (payload.hasOwnProperty('zfs_storage_pool_name')) {
8590             if (payload.hasOwnProperty('zpool')) {
8591                 log.warn('DEPRECATED option zfs_storage_pool_name found, '
8592                     + 'ignoring!');
8593             } else {
8594                 log.warn('DEPRECATED option zfs_storage_pool_name found, '
8595                     + 'replacing with zpool!');
8596                 payload.zpool = payload.zfs_storage_pool_name;
8597                 delete payload.zfs_storage_pool_name;
8598             }
8599         }
8600 
8601         // When creating a VM with SPICE you need the image_uuid, if you don't
8602         // pass that, we'll remove any SPICE options.
8603         if (action === 'create'
8604             && !payload.hasOwnProperty('image_uuid')) {
8605 
8606             if (payload.hasOwnProperty('spice_opts')
8607                 || payload.hasOwnProperty('spice_password')
8608                 || payload.hasOwnProperty('spice_port')) {
8609 
8610                 log.warn('Creating with SPICE options requires '
8611                     + 'image_uuid, REMOVING spice_*');
8612                 delete payload.spice_opts;
8613                 delete payload.spice_password;
8614                 delete payload.spice_port;
8615             }
8616         }
8617 
8618         checkPayloadProperties(payload, vmobj, log, function (e) {
8619             if (e) {
8620                 callback(e);
8621             } else {
8622                 callback();
8623             }
8624         });
8625     });
8626 }
8627 
8628 function buildTransitionZonecfg(transition, target, timeout)
8629 {
8630     var cmdline;
8631 
8632     cmdline = 'add attr; set name=transition; set value="'
8633         + transition + ':' + target + ':' + (Date.now(0) + timeout).toString()
8634         + '"; set type=string; end';
8635 
8636     return cmdline;
8637 }
8638 
8639 // vmobj should have:
8640 //
8641 //  uuid
8642 //  transition_to (if set)
8643 //
8644 exports.unsetTransition = function (vmobj, options, callback)
8645 {
8646     var log;
8647 
8648     // options is optional
8649     if (arguments.length === 2) {
8650         callback = arguments[1];
8651         options = {};
8652     }
8653 
8654     ensureLogging(true);
8655     if (options.hasOwnProperty('log')) {
8656         log = options.log;
8657     } else {
8658         log = VM.log.child({action: 'unsetTransition', vm: vmobj.uuid});
8659     }
8660 
8661     zonecfg(['-u', vmobj.uuid, 'remove -F attr name=transition'], log,
8662         function (err, fds) {
8663 
8664         if (err) {
8665             // log at info because this might be because already removed
8666             log.info({err: err, stdout: fds.stdout, stderr: fds.stderr},
8667                 'unable to remove transition for zone ' + vmobj.uuid);
8668         } else {
8669             log.debug({stdout: fds.stdout, stderr: fds.stderr},
8670                 'removed transition for zone ' + vmobj.uuid);
8671         }
8672 
8673         zonecfg(['-u', vmobj.uuid, 'info attr name=transition'], log,
8674             function (info_err, info_fds) {
8675 
8676             if (info_err) {
8677                 log.error({err: info_err, stdout: info_fds.stdout,
8678                     stderr: info_fds.stderr},
8679                     'failed to confirm transition removal');
8680                 callback(info_err);
8681                 return;
8682             }
8683 
8684             if (info_fds.stdout !== 'No such attr resource.\n') {
8685                 log.error({stdout: info_fds.stdout, stderr: info_fds.stderr},
8686                     'unknown error checking transition after removal');
8687                 callback(new Error('transition does not appear to have been '
8688                     + 'removed zonecfg said: ' + JSON.stringify(info_fds)));
8689                 return;
8690             }
8691 
8692             // removed the transition, now attempt to start if we're rebooting.
8693             if (vmobj.transition_to && vmobj.transition_to === 'start') {
8694                 log.debug('VM ' + vmobj.uuid + ' was stopping for reboot, '
8695                     + 'transitioning to start.');
8696                 VM.start(vmobj.uuid, {}, {log: log}, function (e) {
8697                     if (e) {
8698                         log.error(e, 'failed to start when clearing '
8699                             + 'transition');
8700                     }
8701                     callback();
8702                 });
8703             } else {
8704                 callback();
8705             }
8706         });
8707     });
8708 };
8709 
8710 //
8711 // vmobj fields used:
8712 //
8713 // transition
8714 // uuid
8715 //
8716 function setTransition(vmobj, transition, target, timeout, log, callback)
8717 {
8718     assert(log, 'no logger passed to setTransition()');
8719 
8720     if (!timeout) {
8721         callback(new Error('setTransition() requires timeout argument.'));
8722         return;
8723     }
8724 
8725     async.series([
8726         function (cb) {
8727             // unset an existing transition
8728             if (vmobj.hasOwnProperty('transition')) {
8729                 VM.unsetTransition(vmobj, {log: log}, cb);
8730             } else {
8731                 cb();
8732             }
8733         }, function (cb) {
8734             var zcfg;
8735 
8736             zcfg = buildTransitionZonecfg(transition, target, timeout);
8737             zonecfg(['-u', vmobj.uuid, zcfg], log, function (err, fds) {
8738                 if (err) {
8739                     log.error({err: err, stdout: fds.stdout,
8740                         stderr: fds.stderr}, 'failed to set transition='
8741                         + transition + ' for VM ' + vmobj.uuid);
8742                 } else {
8743                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
8744                         'set transition=' + transition + ' for vm '
8745                         + vmobj.uuid);
8746                 }
8747 
8748                 cb(err);
8749             });
8750         }
8751     ], function (error) {
8752         callback(error);
8753     });
8754 }
8755 
8756 function receiveVM(json, log, callback)
8757 {
8758     var payload = {};
8759 
8760     assert(log, 'no logger passed to receiveVM()');
8761 
8762     try {
8763         payload = JSON.parse(json);
8764     } catch (e) {
8765         callback(e);
8766         return;
8767     }
8768 
8769     payload.create_only = true;
8770 
8771     // adding transition here is considered to be *internal only* not for
8772     // consumer use and not to be documented as a property you can use with
8773     // create.
8774     payload.transition =
8775         {'transition': 'receiving', 'target': 'stopped', 'timeout': 86400};
8776 
8777     // We delete tags and metadata here becasue this exists in the root
8778     // dataset which we will be copying, so it would be duplicated here.
8779     delete payload.customer_metadata;
8780     delete payload.internal_metadata;
8781     delete payload.tags;
8782 
8783     // On receive we need to make sure that we don't create new disks so we
8784     // mark them all as nocreate. We also can't set the block_size of imported
8785     // volumes, so we remove that.
8786     if (payload.hasOwnProperty('disks')) {
8787         var disk_idx;
8788 
8789         for (disk_idx in payload.disks) {
8790             payload.disks[disk_idx].nocreate = true;
8791 
8792             if (payload.disks[disk_idx].image_uuid) {
8793                 delete payload.disks[disk_idx].block_size;
8794             }
8795         }
8796     }
8797 
8798     VM.create(payload, {log: log}, function (err, result) {
8799         if (err) {
8800             callback(err);
8801         }
8802 
8803         // don't include the special transition in the payload we write out.
8804         delete payload.transition;
8805 
8806         fs.writeFile('/etc/zones/' + payload.uuid + '-receiving.json',
8807             JSON.stringify(payload, null, 2), function (e) {
8808 
8809             if (e) {
8810                 callback(e);
8811                 return;
8812             }
8813 
8814             // ready for datasets
8815             callback(null, result);
8816         });
8817     });
8818 }
8819 
8820 function receiveStdinChunk(type, log, callback)
8821 {
8822     var child;
8823     var chunk_name = '';
8824     var chunk_size = 0;
8825     var json = '';
8826     var remaining = '';
8827 
8828     assert(log, 'no logger passed to receiveStdinChunk()');
8829 
8830     /*
8831      * XXX
8832      *
8833      * node 0.6.x removed support for arbitrary file descriptors which
8834      * means we can only handle stdin for now since we need to pass this
8835      * descriptor directly to the child.  0.8.x is supposed to reintroduce
8836      * this functionality.  When we do, this should be changed to open
8837      * the file and set fd to the descriptor, and we should be able to
8838      * get rid of vmunbundle.
8839      *
8840      */
8841 
8842     if (type === 'JSON') {
8843         log.info('/usr/vm/sbin/vmunbundle json');
8844         child = spawn('/usr/vm/sbin/vmunbundle', ['json'],
8845             {customFds: [0, -1, -1]});
8846     } else if (type === 'DATASET') {
8847         log.info('/usr/vm/sbin/vmunbundle dataset');
8848         child = spawn('/usr/vm/sbin/vmunbundle', ['dataset'],
8849             {customFds: [0, -1, -1]});
8850     } else {
8851         callback(new Error('Unsupported chunk type ' + type));
8852     }
8853 
8854     child.stderr.on('data', function (data) {
8855         var idx;
8856         var line;
8857         var matches;
8858 
8859         remaining += data.toString();
8860 
8861         idx = remaining.indexOf('\n');
8862         while (idx > -1) {
8863             line = trim(remaining.substring(0, idx));
8864             remaining = remaining.substring(idx + 1);
8865 
8866             log.debug('VMUNBUNDLE: ' + line);
8867             matches = line.match(/Size: ([\d]+)/);
8868             if (matches) {
8869                 chunk_size = Number(matches[1]);
8870             }
8871             matches = line.match(/Name: \[(.*)\]/);
8872             if (matches) {
8873                 chunk_name = matches[1];
8874             }
8875 
8876             idx = remaining.indexOf('\n');
8877         }
8878     });
8879 
8880     child.stdout.on('data', function (data) {
8881         json += data.toString();
8882         log.debug('json size is ' + json.length);
8883     });
8884 
8885     child.on('close', function (code) {
8886         log.debug('vmunbundle process exited with code ' + code);
8887         if (code === 3) {
8888             log.debug('vmbundle: end of bundle.');
8889             callback(null, 'EOF');
8890             return;
8891         } else if (code !== 0) {
8892             callback(new Error('vmunbundle exited with code ' + code));
8893             return;
8894         }
8895 
8896         // if it was a dataset, we've now imported it.
8897         // if it was json, we've now got it in the json var.
8898 
8899         if (type === 'DATASET') {
8900             log.info('Imported dataset ' + chunk_name);
8901             // delete 'sending' snapshot
8902             zfs(['destroy', '-F', chunk_name + '@sending'], log,
8903                 function (err, fds) {
8904                     if (err) {
8905                         log.warn(err, 'Failed to destroy ' + chunk_name
8906                             + '@sending: ' + err.message);
8907                     }
8908                     callback();
8909                 }
8910             );
8911         } else if (type === 'JSON' && chunk_name === 'JSON'
8912             && json.length <= chunk_size && json.length > 0) {
8913 
8914             receiveVM(json, log, function (e, result) {
8915                 if (e) {
8916                     callback(e);
8917                     return;
8918                 }
8919                 log.info('Receive returning: ' + JSON.stringify(result));
8920                 callback(null, result);
8921             });
8922         } else {
8923             log.debug('type: [' + type + ']');
8924             log.debug('chunk_name: [' + chunk_name + ']');
8925             log.debug('chunk_size: [' + chunk_size + ']');
8926             log.debug('json.length: [' + json.length + ']');
8927             log.warn('Failed to get ' + type + '!');
8928             callback(new Error('Failed to get ' + type + '!'));
8929         }
8930     });
8931 }
8932 
8933 exports.receive = function (target, options, callback)
8934 {
8935     var log;
8936 
8937     // options is optional
8938     if (arguments.length === 2) {
8939         callback = arguments[1];
8940         options = {};
8941     }
8942 
8943     ensureLogging(true);
8944 
8945     // We don't know anything about this VM yet, so we don't create a
8946     // VM.log.child.
8947     if (options.hasOwnProperty('log')) {
8948         log = options.log;
8949     } else {
8950         log = VM.log;
8951     }
8952 
8953     log.info('Receiving VM from: ' + JSON.stringify(target));
8954 
8955     if (target.hasOwnProperty('host') && target.hasOwnProperty('port')) {
8956         // network receive not yet supported either.
8957         callback(new Error('cannot receive from ' + JSON.stringify(target)));
8958         return;
8959     } else if (typeof (target) !== 'string' || target !== '-') {
8960         callback(new Error('cannot receive from ' + JSON.stringify(target)));
8961         return;
8962     }
8963 
8964     receiveStdinChunk('JSON', log, function (error, result) {
8965         var eof = false;
8966 
8967         if (error) {
8968             callback(error);
8969             return;
8970         }
8971         if (result && result === 'EOF') {
8972             callback(new Error('unable to find JSON in stdin.'));
8973         } else if (result && result.hasOwnProperty('uuid')) {
8974             // VM started receive, now need datasets
8975 
8976             // We have JSON, so we can log better now if we need one
8977             if (!options.hasOwnProperty('log')) {
8978                 log = VM.log.child({action: 'receive', vm: result.uuid});
8979             }
8980 
8981             log.info('Receiving VM ' + result.uuid);
8982             log.debug('now looking for datasets');
8983 
8984             async.whilst(
8985                 function () { return !eof; },
8986                 function (cb) {
8987                     receiveStdinChunk('DATASET', log, function (err, res) {
8988                         if (err) {
8989                             cb(err);
8990                             return;
8991                         }
8992                         if (res === 'EOF') {
8993                             eof = true;
8994                         }
8995                         cb();
8996                     });
8997                 }, function (err) {
8998                     if (err) {
8999                         callback(err);
9000                         return;
9001                     }
9002                     // no error so we read all the datasets, try an install.
9003                     log.info('receive calling VM.install: ' + eof);
9004                     VM.install(result.uuid, {log: log}, function (e) {
9005                         if (e) {
9006                             log.warn(e, 'couldn\'t install VM: '
9007                                 + e.message);
9008                         }
9009                         callback(e, result);
9010                     });
9011                 }
9012             );
9013         } else {
9014             callback(new Error('unable to receive JSON'));
9015         }
9016     });
9017 };
9018 
9019 exports.reprovision = function (uuid, payload, options, callback)
9020 {
9021     var log;
9022     var provision_timeout = PROVISION_TIMEOUT;
9023     var set_transition = false;
9024     var snapshot;
9025     var vmobj;
9026 
9027     // options is optional
9028     if (arguments.length === 3) {
9029         callback = arguments[2];
9030         options = {};
9031     }
9032 
9033     ensureLogging(true);
9034     if (options.hasOwnProperty('log')) {
9035         log = options.log;
9036     } else {
9037         log = VM.log.child({action: 'reprovision', vm: uuid});
9038     }
9039 
9040     log.info('Reprovisioning VM ' + uuid + ', original payload:\n'
9041             + JSON.stringify(payload, null, 2));
9042 
9043     async.waterfall([
9044         function (cb) {
9045             VM.load(uuid, {
9046                 fields: [
9047                     'brand',
9048                     'datasets',
9049                     'hostname',
9050                     'nics',
9051                     'quota',
9052                     'state',
9053                     'uuid',
9054                     'zfs_filesystem',
9055                     'zone_state',
9056                     'zonename',
9057                     'zonepath',
9058                     'zpool'
9059                 ],
9060                 log: log
9061             }, function (err, obj) {
9062                 if (err) {
9063                     cb(err);
9064                     return;
9065                 }
9066                 vmobj = obj;
9067                 log.debug('Loaded VM is: ' + JSON.stringify(vmobj, null, 2));
9068                 cb();
9069             });
9070         }, function (cb) {
9071             if (BRAND_OPTIONS[vmobj.brand].hasOwnProperty('features')
9072                 && BRAND_OPTIONS[vmobj.brand].features.reprovision
9073                 && BRAND_OPTIONS[vmobj.brand].features.brand_install_script) {
9074 
9075                 cb();
9076             } else {
9077                 cb(new Error('brand "' + vmobj.brand + '" does not yet support'
9078                     + ' reprovision'));
9079             }
9080         }, function (cb) {
9081             // only support image_uuid at top level (for non-KVM currently)
9082             if (!payload.hasOwnProperty('image_uuid')) {
9083                 cb(new Error('payload is missing image_uuid'));
9084             } else {
9085                 cb();
9086             }
9087         }, function (cb) {
9088             if (vmobj.hasOwnProperty('datasets') && vmobj.datasets.length > 1) {
9089                 cb(new Error('cannot support reprovision with multiple '
9090                     + 'delegated datasets'));
9091                 return;
9092             } else if (vmobj.hasOwnProperty('datasets')
9093                 && vmobj.datasets.length === 1
9094                 && vmobj.datasets[0] !== vmobj.zfs_filesystem + '/data') {
9095 
9096                 cb(new Error('cannot support reprovision with non-standard "'
9097                     + vmobj.datasets[0] + '" dataset'));
9098                 return;
9099             }
9100             cb();
9101         }, function (cb) {
9102             // TODO: change here when we support zvols/KVM, add size
9103             // & change type
9104 
9105             validateImage({
9106                 type: 'zone-dataset',
9107                 uuid: payload.image_uuid,
9108                 zpool: vmobj.zpool
9109             }, log, function (e) {
9110                 cb(e);
9111             });
9112         }, function (cb) {
9113             // ensure we're stopped before reprovision starts
9114             if (vmobj.zone_state !== 'installed') {
9115                 VM.stop(uuid, {log: log}, function (e) {
9116                     if (e) {
9117                         log.error(e, 'unable to stop VM ' + uuid + ': '
9118                             + e.message);
9119                     }
9120                     cb(e);
9121                 });
9122             } else {
9123                 cb();
9124             }
9125         }, function (cb) {
9126             // Set transition to provisioning now, we're going for it.
9127             setTransition(vmobj, 'provisioning', 'running',
9128                 (provision_timeout * 1000), log, function (err) {
9129                     if (err) {
9130                         cb(err);
9131                     } else {
9132                         set_transition = true;
9133                         cb();
9134                     }
9135                 });
9136         }, function (cb) {
9137             // we validated any delegated dataset above, so we just need to
9138             // remove the 'zoned' flag if we've got one.
9139             if (!vmobj.hasOwnProperty('datasets')
9140                 || vmobj.datasets.length === 0) {
9141 
9142                 cb();
9143                 return;
9144             }
9145             zfs(['set', 'zoned=off', vmobj.datasets[0]], log,
9146                 function (err, fds) {
9147 
9148                 if (err) {
9149                     log.error({err: err, stdout: fds.stdout,
9150                         stderr: fds.stderr}, 'Unable to turn off "zoned" for '
9151                         + vmobj.datasets[0]);
9152                 }
9153                 cb(err);
9154             });
9155         }, function (cb) {
9156             // if we have a delegated dataset, rename zones/<uuid>/data
9157             //     -> zones/<uuid>-reprovisioning-data
9158             if (!vmobj.hasOwnProperty('datasets')
9159                 || vmobj.datasets.length === 0) {
9160 
9161                 cb();
9162                 return;
9163             }
9164             zfs(['rename', '-f', vmobj.datasets[0], vmobj.zfs_filesystem
9165                 + '-reprovisioning-data'], log, function (err, fds) {
9166 
9167                 if (err) {
9168                     log.error({err: err, stdout: fds.stdout,
9169                         stderr: fds.stderr}, 'Unable to (temporarily) rename '
9170                         + vmobj.datasets[0]);
9171                 }
9172                 cb(err);
9173             });
9174         }, function (cb) {
9175             // unmount <zonepath>/cores so dataset is not busy
9176             zfs(['umount', vmobj.zonepath + '/cores'], log,
9177                 function (err, fds) {
9178 
9179                 if (err) {
9180                     if (trim(fds.stderr).match(/not a mountpoint$/)) {
9181                         log.info('ignoring failure to umount cores which '
9182                             + 'wasn\'t mounted');
9183                         cb();
9184                         return;
9185                     } else {
9186                         log.error({err: err, stdout: fds.stdout,
9187                             stderr: fds.stderr}, 'Unable to umount '
9188                             + vmobj.zonepath + '/cores');
9189                     }
9190                 }
9191                 cb(err);
9192             });
9193         }, function (cb) {
9194             // rename <zfs_filesystem> dataset out of the way
9195             zfs(['rename', '-f', vmobj.zfs_filesystem, vmobj.zfs_filesystem
9196                 + '-reprovisioning-root'], log, function (err, fds) {
9197 
9198                 if (err) {
9199                     log.error({err: err, stdout: fds.stdout,
9200                         stderr: fds.stderr}, 'Unable to (temporarily) rename '
9201                         + vmobj.zfs_filesystem);
9202                 }
9203                 cb(err);
9204             });
9205         }, function (cb) {
9206             var snapname = vmobj.zpool + '/' + payload.image_uuid + '@final';
9207 
9208             // ensure we've got our snapshot
9209             zfs(['get', '-Ho', 'value', 'type', snapname], log,
9210                 function (err, fds) {
9211 
9212                 if (!err) {
9213                     // snapshot already exists, use it
9214                     log.debug('snapshot "' + snapname + '" exists');
9215                     snapshot = snapname;
9216                     cb();
9217                     return;
9218                 }
9219 
9220                 if (fds.stderr.match(/dataset does not exist/)) {
9221                     // we'll use a different one. (falls throught to next func)
9222                     cb();
9223                 } else {
9224                     cb(err);
9225                 }
9226             });
9227         }, function (cb) {
9228             var snapname;
9229 
9230             if (snapshot) {
9231                 // already know which one to use, don't create one
9232                 cb();
9233                 return;
9234             }
9235 
9236             snapname = vmobj.zpool + '/' + payload.image_uuid
9237                 + '@' + vmobj.uuid;
9238 
9239             // ensure we've got a snapshot
9240             zfs(['get', '-Ho', 'value', 'type', snapname], log,
9241                 function (err, fds) {
9242 
9243                 if (!err) {
9244                     // snapshot already exists, use it
9245                     log.debug('snapshot "' + snapname + '" exists');
9246                     snapshot = snapname;
9247                     cb();
9248                     return;
9249                 }
9250 
9251                 if (fds.stderr.match(/dataset does not exist/)) {
9252                     zfs(['snapshot', snapname], log, function (e, snap_fds) {
9253                         if (e) {
9254                             e.stdout = snap_fds.stdout;
9255                             e.stderr = snap_fds.stderr;
9256                             log.error(e, 'Failed to create snapshot: '
9257                                 + e.message);
9258                         } else {
9259                             log.debug('created snapshot "' + snapname + '"');
9260                             snapshot = snapname;
9261                         }
9262                         cb(e);
9263                     });
9264                 } else {
9265                     cb(err);
9266                     return;
9267                 }
9268             });
9269         }, function (cb) {
9270             var args;
9271 
9272             // clone the new image creating a new dataset for zoneroot
9273             assert(snapshot);
9274 
9275             args = ['clone'];
9276             if (vmobj.hasOwnProperty('quota') && vmobj.quota > 0) {
9277                 args.push('-o');
9278                 args.push('quota=' + vmobj.quota + 'G');
9279             }
9280             args.push(snapshot);
9281             args.push(vmobj.zfs_filesystem);
9282 
9283             zfs(args, log, function (err, fds) {
9284                 if (err) {
9285                     log.error({err: err, stdout: fds.stdout,
9286                         stderr: fds.stderr}, 'Unable to create new clone of '
9287                         + payload.image_uuid);
9288                 }
9289                 cb(err);
9290             });
9291         }, function (cb) {
9292             var cmd;
9293 
9294             // copy zones/<uuid>-reprovisioning-root/config to
9295             // zones/<uuid>/config so we keep metadata and ipf rules.
9296             try {
9297                 fs.mkdirSync(vmobj.zonepath + '/config');
9298             } catch (e) {
9299                 if (e.code !== 'EEXIST') {
9300                     e.message = 'Unable to recreate ' + vmobj.zonepath
9301                         + '/config: ' + e.message;
9302                     cb(e);
9303                     return;
9304                 }
9305             }
9306 
9307             cmd = 'cp -pPR '
9308                 + vmobj.zonepath + '-reprovisioning-root/config/* '
9309                 + vmobj.zonepath + '/config/';
9310 
9311             log.debug(cmd);
9312             exec(cmd, function (error, stdout, stderr) {
9313                 log.debug({'stdout': stdout, 'stderr': stderr}, 'cp results');
9314                 if (error) {
9315                     error.stdout = stdout;
9316                     error.stderr = stderr;
9317                     cb(error);
9318                     return;
9319                 } else {
9320                     cb();
9321                 }
9322             });
9323         }, function (cb) {
9324             // destroy <zonepath>-reprovisioning-root, since it's no longer used
9325             zfs(['destroy', '-r', vmobj.zfs_filesystem
9326                 + '-reprovisioning-root'], log, function (err, fds) {
9327 
9328                 if (err) {
9329                     log.error({err: err, stdout: fds.stdout,
9330                         stderr: fds.stderr}, 'Unable to destroy '
9331                         + vmobj.zfs_filesystem + '-reprovisioning-root: '
9332                         + err.message);
9333                 }
9334                 cb(err);
9335             });
9336         }, function (cb) {
9337             // remount /zones/<uuid>/cores
9338             zfs(['mount', vmobj.zpool + '/cores/' + uuid], log,
9339                 function (err, fds) {
9340 
9341                 if (err) {
9342                     log.error({err: err, stdout: fds.stdout,
9343                         stderr: fds.stderr}, 'Unable to mount ' + vmobj.zonepath
9344                         + '/cores: ' + err.message);
9345                 }
9346                 cb(err);
9347             });
9348         }, function (cb) {
9349             var args = ['-r', '-R', vmobj.zonepath, '-z', vmobj.zonename];
9350             var cmd = BRAND_OPTIONS[vmobj.brand].features.brand_install_script;
9351 
9352             // We run the brand's install script here with the -r flag which
9353             // tells it to do everything that's relevant to reprovision.
9354 
9355             log.debug(cmd + ' ' + args.join(' '));
9356             execFile(cmd, args, function (error, stdout, stderr) {
9357                 var new_err;
9358 
9359                 if (error) {
9360                     new_err = new Error('Error running brand install script '
9361                         + cmd);
9362                     // error's message includes stderr.
9363                     log.error({err: error, stdout: stdout},
9364                         'brand install script exited with code ' + error.code);
9365                     cb(new_err);
9366                 } else {
9367                     log.debug(cmd + ' stderr:\n' + stderr);
9368                     cb();
9369                 }
9370             });
9371         }, function (cb) {
9372             // rename zones/<uuid>-reprovision-data -> zones/<uuid>/data
9373             if (!vmobj.hasOwnProperty('datasets')
9374                 || vmobj.datasets.length === 0) {
9375 
9376                 cb();
9377                 return;
9378             }
9379             zfs(['rename', '-f', vmobj.zfs_filesystem + '-reprovisioning-data',
9380                 vmobj.datasets[0]], log, function (err, fds) {
9381 
9382                 if (err) {
9383                     log.error({err: err, stdout: fds.stdout,
9384                         stderr: fds.stderr}, 'Unable to (temporarily) rename '
9385                         + vmobj.zfs_filesystem);
9386                 }
9387                 cb(err);
9388             });
9389         }, function (cb) {
9390             // set zoned=on for zones/<uuid>/data
9391             if (!vmobj.hasOwnProperty('datasets')
9392                 || vmobj.datasets.length === 0) {
9393 
9394                 cb();
9395                 return;
9396             }
9397             zfs(['set', 'zoned=on', vmobj.datasets[0]], log,
9398                 function (err, fds) {
9399 
9400                 if (err) {
9401                     log.error({err: err, stdout: fds.stdout,
9402                         stderr: fds.stderr}, 'Unable to set "zoned" for: '
9403                         + vmobj.datasets[0]);
9404                 }
9405                 cb(err);
9406             });
9407         }, function (cb) {
9408             // update zone's image_uuid field
9409             var zcfg = 'select attr name=dataset-uuid; set value="'
9410                 + payload.image_uuid + '"; end';
9411             zonecfg(['-u', uuid, zcfg], log, function (err, fds) {
9412                 if (err) {
9413                     log.error({err: err, stdout: fds.stdout,
9414                         stderr: fds.stderr}, 'unable to set image_uuid on VM '
9415                         + uuid);
9416                 }
9417                 cb(err);
9418             });
9419         }, function (cb) {
9420             var p = {
9421                 autoboot: true,
9422                 reprovisioning: true,
9423                 uuid: uuid,
9424                 zonename: vmobj.zonename,
9425                 zonepath: vmobj.zonepath
9426             };
9427 
9428             // NOTE: someday we could allow mdata_exec_timeout in the original
9429             // payload to reprovision and then pass it along here.
9430 
9431             // other fields used by installZone()
9432             [
9433                 'dns_domain',
9434                 'hostname',
9435                 'quota',
9436                 'resolvers',
9437                 'tmpfs',
9438                 'zfs_filesystem',
9439                 'zfs_root_compression',
9440                 'zfs_root_recsize'
9441             ].forEach(function (k) {
9442                 if (vmobj.hasOwnProperty(k)) {
9443                     p[k] = vmobj[k];
9444                 }
9445             });
9446 
9447             // nics needs to be called add_nics here
9448             if (vmobj.hasOwnProperty('nics')) {
9449                 p.add_nics = vmobj.nics;
9450             }
9451 
9452             installZone(p, log, function (err) {
9453                 log.debug(err, 'ran installZone() for reprovision');
9454                 cb(err);
9455             });
9456         }
9457     ], function (err) {
9458         if (err && set_transition) {
9459             // remove transition now, if we failed.
9460             VM.unsetTransition(vmobj, {log: log}, function () {
9461                 // err here is original err, we ignore failure to unset because
9462                 // nothing we can do about that..
9463                 callback(err);
9464             });
9465         } else {
9466             callback(err);
9467         }
9468     });
9469 };
9470 
9471 exports.install = function (uuid, options, callback)
9472 {
9473     var log;
9474 
9475     // options is optional
9476     if (arguments.length === 2) {
9477         callback = arguments[1];
9478         options = {};
9479     }
9480 
9481     ensureLogging(true);
9482     if (options.hasOwnProperty('log')) {
9483         log = options.log;
9484     } else {
9485         log = VM.log.child({action: 'install', vm: uuid});
9486     }
9487 
9488     log.info('Installing VM ' + uuid);
9489 
9490     fs.readFile('/etc/zones/' + uuid + '-receiving.json',
9491         function (err, data) {
9492             var payload;
9493 
9494             if (err) {
9495                 callback(err);
9496                 return;
9497             }
9498 
9499             try {
9500                 payload = JSON.parse(data.toString());
9501             } catch (e) {
9502                 callback(e);
9503                 return;
9504             }
9505 
9506             // installZone takes a payload
9507             installZone(payload, log, callback);
9508         }
9509     );
9510 
9511 };
9512 
9513 function getAllDatasets(vmobj)
9514 {
9515     var datasets = [];
9516     var disk;
9517 
9518     if (vmobj.hasOwnProperty('zfs_filesystem')) {
9519         datasets.push(vmobj.zfs_filesystem);
9520     }
9521 
9522     for (disk in vmobj.disks) {
9523         disk = vmobj.disks[disk];
9524         if (disk.hasOwnProperty('zfs_filesystem')) {
9525             datasets.push(disk.zfs_filesystem);
9526         }
9527     }
9528 
9529     return datasets;
9530 }
9531 
9532 //
9533 // Headers are 512 bytes and look like:
9534 //
9535 // MAGIC-VMBUNDLE\0
9536 // <VERSION>\0 -- ASCII #s
9537 // <CHECKSUM>\0 -- ASCII (not yet used)
9538 // <OBJ-NAME>\0 -- max length: 256
9539 // <OBJ-SIZE>\0 -- ASCII # of bytes
9540 // <PADDED-SIZE>\0 -- ASCII # of bytes, must be multiple of 512
9541 // ...\0
9542 //
9543 function chunkHeader(name, size, padding)
9544 {
9545     var header = new Buffer(512);
9546     var pos = 0;
9547 
9548     header.fill(0);
9549     pos += addString(header, 'MAGIC-VMBUNDLE', pos);
9550     pos += addString(header, sprintf('%d', 1), pos);
9551     pos += addString(header, 'CHECKSUM', pos);
9552     pos += addString(header, name, pos);
9553     pos += addString(header, sprintf('%d', size), pos);
9554     pos += addString(header, sprintf('%d', size + padding), pos);
9555 
9556     return (header);
9557 }
9558 
9559 // add the string to buffer at pos, returning pos of new end of the buffer.
9560 function addString(buf, str, pos)
9561 {
9562     var len = str.length;
9563     buf.write(str, pos);
9564     return (len + 1);
9565 }
9566 
9567 function sendJSON(target, json, log, cb)
9568 {
9569     var header;
9570     var pad;
9571     var padding = 0;
9572 
9573     assert(log, 'no logger passed for sendJSON()');
9574 
9575     if (target === 'stdout') {
9576         if ((json.length % 512) != 0) {
9577             padding = 512 - (json.length % 512);
9578         }
9579         header = chunkHeader('JSON', json.length, padding);
9580         process.stdout.write(header);
9581         process.stdout.write(json, 'ascii');
9582         if (padding > 0) {
9583             pad = new Buffer(padding);
9584             pad.fill(0);
9585             process.stdout.write(pad);
9586         }
9587         cb();
9588     } else {
9589         log.error('Don\'t know how to send JSON to '
9590             + JSON.stringify(target));
9591         cb(new Error('Don\'t know how to send JSON to '
9592             + JSON.stringify(target)));
9593     }
9594 }
9595 
9596 function sendDataset(target, dataset, log, callback)
9597 {
9598     var header;
9599 
9600     assert(log, 'no logger passed for sendDataset()');
9601 
9602     if (target === 'stdout') {
9603 
9604         async.series([
9605             function (cb) {
9606                 // delete any existing 'sending' snapshot
9607                 zfs(['destroy', '-F', dataset + '@sending'], log,
9608                     function (err, fds) {
9609                         // We don't expect this to succeed, since that means
9610                         // something left an @sending around. Warn if succeeds.
9611                         if (!err) {
9612                             log.warn('Destroyed pre-existing ' + dataset
9613                                 + '@sending');
9614                         }
9615                         cb();
9616                     }
9617                 );
9618             }, function (cb) {
9619                 zfs(['snapshot', dataset + '@sending'], log,
9620                     function (err, fds) {
9621 
9622                     cb(err);
9623                 });
9624             }, function (cb) {
9625                 header = chunkHeader(dataset, 0, 0);
9626                 process.stdout.write(header);
9627                 cb();
9628             }, function (cb) {
9629                 var child;
9630 
9631                 child = spawn('/usr/sbin/zfs',
9632                     ['send', '-p', dataset + '@sending'],
9633                     {customFds: [-1, 1, -1]});
9634                 child.stderr.on('data', function (data) {
9635                     var idx;
9636                     var lines = trim(data.toString()).split('\n');
9637 
9638                     for (idx in lines) {
9639                         log.debug('zfs send: ' + trim(lines[idx]));
9640                     }
9641                 });
9642                 child.on('close', function (code) {
9643                     log.debug('zfs send process exited with code '
9644                         + code);
9645                     cb();
9646                 });
9647             }, function (cb) {
9648                 zfs(['destroy', '-F', dataset + '@sending'], log,
9649                     function (err, fds) {
9650                         if (err) {
9651                             log.warn(err, 'Unable to destroy ' + dataset
9652                                 + '@sending: ' + err.message);
9653                         }
9654                         cb(err);
9655                     }
9656                 );
9657             }
9658         ], function (err) {
9659             if (err) {
9660                 log.error(err, 'Failed to send dataset: ' + err.message);
9661             } else {
9662                 log.info('Successfully sent dataset');
9663             }
9664             callback(err);
9665         });
9666     } else {
9667         log.error('Don\'t know how to send datasets to '
9668             + JSON.stringify(target));
9669         callback(new Error('Don\'t know how to send datasets to '
9670             + JSON.stringify(target)));
9671     }
9672 }
9673 
9674 exports.send = function (uuid, target, options, callback)
9675 {
9676     var datasets;
9677     var log;
9678     var vmobj;
9679 
9680     // options is optional
9681     if (arguments.length === 3) {
9682         callback = arguments[2];
9683         options = {};
9684     }
9685 
9686     ensureLogging(true);
9687     if (options.hasOwnProperty('log')) {
9688         log = options.log;
9689     } else {
9690         log = VM.log.child({action: 'send', vm: uuid});
9691     }
9692 
9693     target = 'stdout';
9694 
9695     log.info('Sending VM ' + uuid + ' to: ' + JSON.stringify(target));
9696     async.series([
9697         function (cb) {
9698             // make sure we *can* send first, to avoid wasting cycles
9699             if (target === 'stdout' && tty.isatty(1)) {
9700                 log.error('Cannot send VM to a TTY.');
9701                 cb(new Error('Cannot send VM to a TTY.'));
9702             } else {
9703                 cb();
9704             }
9705         }, function (cb) {
9706             // NOTE: for this load we always load all fields, because we need
9707             // to send them all to the target machine.
9708             VM.load(uuid, {log: log}, function (err, obj) {
9709                 if (err) {
9710                     cb(err);
9711                 } else {
9712                     vmobj = obj;
9713                     cb();
9714                 }
9715             });
9716         }, function (cb) {
9717             datasets = getAllDatasets(vmobj);
9718             if (datasets.length < 1) {
9719                 log.error('Cannot send VM with no datasets.');
9720                 cb(new Error('VM has no datasets.'));
9721             } else {
9722                 cb();
9723             }
9724         }, function (cb) {
9725             if (vmobj.state !== 'stopped') {
9726                 // In this case we need to stop it and make sure it stopped.
9727                 VM.stop(uuid, {log: log}, function (e) {
9728                     if (e) {
9729                         log.error(e, 'unable to stop VM ' + uuid + ': '
9730                             + e.message);
9731                         cb(e);
9732                         return;
9733                     }
9734                     VM.load(uuid, {fields: ['zone_state', 'uuid'], log: log},
9735                         function (error, obj) {
9736 
9737                         if (error) {
9738                             log.error(error, 'unable to reload VM ' + uuid
9739                                 + ': ' + error.message);
9740                             return;
9741                         }
9742                         if (obj.zone_state !== 'installed') {
9743                             log.error('after stop attempt, state is '
9744                                 + obj.zone_state + ' != installed');
9745                             cb(new Error('state after stopping is '
9746                                 + obj.zone_state + ' != installed'));
9747                             return;
9748                         }
9749                         cb();
9750                     });
9751                 });
9752             } else {
9753                 // already stopped, good to go!
9754                 cb();
9755             }
9756         }, function (cb) {
9757             // Clean up trash left from broken datasets (see OS-388)
9758             try {
9759                 fs.unlinkSync(vmobj.zonepath + '/SUNWattached.xml');
9760             } catch (err) {
9761                 // DO NOTHING, this file shouldn't have existed anyway.
9762             }
9763             try {
9764                 fs.unlinkSync(vmobj.zonepath + '/SUNWdetached.xml');
9765             } catch (err) {
9766                 // DO NOTHING, this file shouldn't have existed anyway.
9767             }
9768             cb();
9769         }, function (cb) {
9770             // send JSON
9771             var json = JSON.stringify(vmobj, null, 2) + '\n';
9772             sendJSON(target, json, log, cb);
9773         }, function (cb) {
9774             // send datasets
9775             async.forEachSeries(datasets, function (ds, c) {
9776                 sendDataset(target, ds, log, c);
9777             }, function (e) {
9778                 if (e) {
9779                     log.error('Failed to send datasets');
9780                 }
9781                 cb(e);
9782             });
9783         }
9784     ], function (err) {
9785         callback(err);
9786     });
9787 };
9788 
9789 exports.create = function (payload, options, callback)
9790 {
9791     var log;
9792 
9793     // options is optional
9794     if (arguments.length === 2) {
9795         callback = arguments[1];
9796         options = {};
9797     }
9798 
9799     ensureLogging(true);
9800 
9801     if (options.hasOwnProperty('log')) {
9802         log = options.log;
9803     } else {
9804         // default to VM.log until we have a uuid, then we'll switch.
9805         log = VM.log;
9806     }
9807 
9808     log.info('Creating VM, original payload:\n'
9809         + JSON.stringify(payload, null, 2));
9810 
9811     async.waterfall([
9812         function (cb) {
9813             // We get a UUID first so that we can attach as many log messages
9814             // as possible to this uuid.  Since we don't have a UUID here, we
9815             // send VM.log as the logger.  We'll switch to a log.child as soon
9816             // as we have uuid.
9817             createZoneUUID(payload, log, function (e, uuid) {
9818                 // either payload will have .uuid or we'll return error here.
9819                 cb(e);
9820             });
9821         }, function (cb) {
9822             // If we got here, payload now has .uuid and we can start logging
9823             // messages with that uuid if we didn't already have a logger.
9824             if (!options.hasOwnProperty('log')) {
9825                 log = VM.log.child({action: 'create', vm: payload.uuid});
9826             }
9827             cb();
9828         }, function (cb) {
9829             normalizePayload(payload, null, log, function (err) {
9830                 if (err) {
9831                     log.error(err, 'Failed to validate payload: '
9832                         + err.message);
9833                 } else {
9834                     log.debug('normalized payload:\n'
9835                         + JSON.stringify(payload, null, 2));
9836                 }
9837                 cb(err);
9838             });
9839         }, function (cb) {
9840             checkDatasetProvisionable(payload, log, function (provisionable) {
9841                 if (!provisionable) {
9842                     log.error('checkDatasetProvisionable() says dataset is '
9843                         + 'unprovisionable');
9844                     cb(new Error('provisioning dataset ' + payload.image_uuid
9845                         + ' with brand ' + payload.brand
9846                         + ' is not supported'));
9847                     return;
9848                 }
9849                 cb();
9850             });
9851         }, function (cb) {
9852             if (BRAND_OPTIONS[payload.brand].features.type === 'KVM') {
9853                 createVM(payload, log, function (error, result) {
9854                     if (error) {
9855                         cb(error);
9856                     } else {
9857                         cb(null, {'uuid': payload.uuid,
9858                             'zonename': payload.zonename});
9859                     }
9860                 });
9861             } else {
9862                 createZone(payload, log, function (error, result) {
9863                     if (error) {
9864                         cb(error);
9865                     } else {
9866                         cb(null, {'uuid': payload.uuid,
9867                             'zonename': payload.zonename});
9868                     }
9869                 });
9870             }
9871         }
9872     ], function (err, obj) {
9873         callback(err, obj);
9874     });
9875 };
9876 
9877 // delete a zvol
9878 function deleteVolume(volume, log, callback)
9879 {
9880     var args;
9881     var origin;
9882 
9883     assert(log, 'no logger passed to deleteVolume()');
9884 
9885     if (volume.missing) {
9886         // this volume doesn't actually exist, so skip trying to delete.
9887         log.info('volume ' + volume.path + ' doesn\'t exist, skipping '
9888             + 'deletion');
9889         callback();
9890         return;
9891     }
9892 
9893     async.series([
9894         function (cb) {
9895             args = ['get', '-Ho', 'value', 'origin', volume.zfs_filesystem];
9896             zfs(args, log, function (err, fds) {
9897                 if (err && fds.stderr.match('dataset does not exist')) {
9898                     log.info('volume ' + volume.path + ' doesn\'t exist, '
9899                         + 'skipping deletion');
9900                     cb();
9901                 } else {
9902                     origin = trim(fds.stdout);
9903                     log.info('found origin "' + origin + '"');
9904                     cb(err);
9905                 }
9906             });
9907         }, function (cb) {
9908             // use recursive delete to handle possible snapshots on volume
9909             args = ['destroy', '-rF', volume.zfs_filesystem];
9910             zfs(args, log, function (err, fds) {
9911                 // err will be non-null if something broke
9912                 cb(err);
9913             });
9914         }, function (cb) {
9915             // we never delete an @final snapshot, that's the one from recv
9916             // that imgadm left around for us on purpose.
9917             if (!origin || origin.length < 1 || origin == '-'
9918                 || origin.match('@final')) {
9919 
9920                 cb();
9921                 return;
9922             }
9923             args = ['destroy', '-rF', origin];
9924             zfs(args, log, function (err, fds) {
9925                 // err will be non-null if something broke
9926                 cb(err);
9927             });
9928         }
9929     ], function (err) {
9930         callback(err);
9931     });
9932 }
9933 
9934 function deleteZone(uuid, log, callback)
9935 {
9936     var load_fields;
9937     var vmobj;
9938 
9939     assert(log, 'no logger passed to deleteZone()');
9940 
9941     load_fields = [
9942         'archive_on_delete',
9943         'disks',
9944         'uuid',
9945         'zonename'
9946     ];
9947 
9948     async.series([
9949         function (cb) {
9950             VM.load(uuid, {fields: load_fields, log: log}, function (err, obj) {
9951                 if (err) {
9952                     cb(err);
9953                     return;
9954                 }
9955                 vmobj = obj;
9956                 cb();
9957             });
9958         }, function (cb) {
9959             log.debug('archive_on_delete is set to '
9960                 + !!vmobj.archive_on_delete);
9961             if (!vmobj.archive_on_delete) {
9962                 cb();
9963                 return;
9964             }
9965             archiveVM(vmobj.uuid, log, function () {
9966                 cb();
9967             });
9968         // TODO: replace these next two with VM.stop(..{force: true} ?
9969         }, function (cb) {
9970             log.debug('setting autoboot=false');
9971             zonecfg(['-u', uuid, 'set autoboot=false'], log, function (e, fds) {
9972                 if (e) {
9973                     log.warn({err: e, stdout: fds.stdout, stderr: fds.stderr},
9974                         'Error setting autoboot=false');
9975                 } else {
9976                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
9977                         'set autoboot=false');
9978                 }
9979                 cb();
9980             });
9981         }, function (cb) {
9982             log.debug('halting zone');
9983             zoneadm(['-u', uuid, 'halt', '-X'], log, function (e, fds) {
9984                 if (e) {
9985                     log.warn({err: e, stdout: fds.stdout, stderr: fds.stderr},
9986                         'Error halting zone');
9987                 } else {
9988                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
9989                         'halted zone');
9990                 }
9991                 cb();
9992             });
9993         }, function (cb) {
9994             log.debug('uninstalling zone');
9995             zoneadm(['-u', uuid, 'uninstall', '-F'], log, function (e, fds) {
9996                 if (e) {
9997                     log.warn({err: e, stdout: fds.stdout, stderr: fds.stderr},
9998                         'Error uninstalling zone: ' + e.message);
9999                 } else {
10000                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
10001                         'uninstalled zone');
10002                 }
10003                 cb();
10004             });
10005         }, function (cb) {
10006             function loggedDeleteVolume(volume, callbk) {
10007                 return deleteVolume(volume, log, callbk);
10008             }
10009 
10010             if (vmobj && vmobj.hasOwnProperty('disks')) {
10011                 async.forEachSeries(vmobj.disks, loggedDeleteVolume,
10012                     function (err) {
10013                         if (err) {
10014                             log.error(err, 'Unknown error deleting volumes: '
10015                                 + err.message);
10016                             cb(err);
10017                         } else {
10018                             log.info('successfully deleted volumes');
10019                             cb();
10020                         }
10021                     }
10022                 );
10023             } else {
10024                 log.debug('skipping volume destruction for diskless '
10025                     + vmobj.uuid);
10026                 cb();
10027             }
10028         }, function (cb) {
10029             if (vmobj.zonename) {
10030                 log.debug('deleting zone');
10031                 // XXX for some reason -u <uuid> doesn't work with delete
10032                 zonecfg(['-z', vmobj.zonename, 'delete', '-F'], log,
10033                     function (e, fds) {
10034 
10035                     if (e) {
10036                         log.warn({err: e, stdout: fds.stdout,
10037                             stderr: fds.stderr}, 'Error deleting VM');
10038                     } else {
10039                         log.debug({stdout: fds.stdout, stderr: fds.stderr},
10040                             'deleted VM ' + uuid);
10041                     }
10042                     cb();
10043                 });
10044             } else {
10045                 cb();
10046             }
10047         }, function (cb) {
10048             VM.load(uuid, {fields: ['uuid'], log: log, missing_ok: true},
10049                 function (err, obj) {
10050 
10051                 var gone = /^zoneadm:.*: No such zone configured/;
10052                 if (err && err.message.match(gone)) {
10053                     // the zone is gone, that's good.
10054                     log.debug('confirmed VM is gone.');
10055                     cb();
10056                 } else if (err) {
10057                     // there was a non-expected error.
10058                     cb(err);
10059                 } else {
10060                     // the VM still exists!
10061                     err = new Error('VM still exists after delete.');
10062                     err.code = 'EEXIST';
10063                     cb(err);
10064                 }
10065             });
10066         }, function (cb) {
10067             // delete the incoming payload if it exists
10068             fs.unlink('/etc/zones/' + vmobj.uuid + '-receiving.json',
10069                 function (e) {
10070                     // we can't do anyhing if this fails other than log
10071                     if (e && e.code !== 'ENOENT') {
10072                         log.warn(e, 'Failed to delete ' + vmobj.uuid
10073                             + '-receiving.json (' + e.code + '): ' + e.message);
10074                     }
10075                     cb();
10076                 }
10077             );
10078         }
10079     ], function (error) {
10080         callback(error);
10081     });
10082 }
10083 
10084 exports.delete = function (uuid, options, callback)
10085 {
10086     var attemptDelete;
10087     var last_try = 16;
10088     var log;
10089     var next_try = 1;
10090     var tries = 0;
10091 
10092     // options is optional
10093     if (arguments.length === 2) {
10094         callback = arguments[1];
10095         options = {};
10096     }
10097 
10098     ensureLogging(true);
10099 
10100     if (options.hasOwnProperty('log')) {
10101         log = options.log;
10102     } else {
10103         log = VM.log.child({action: 'delete', vm: uuid});
10104     }
10105 
10106     log.info('Deleting VM ' + uuid);
10107 
10108     attemptDelete = function (cb) {
10109         next_try = (next_try * 2);
10110         deleteZone(uuid, log, function (err) {
10111             tries++;
10112             if (err && err.code === 'EEXIST') {
10113                 // zone still existed, try again if we've not tried too much.
10114                 if (next_try <= last_try) {
10115                     log.info('VM.delete(' + tries + '): still there, '
10116                         + 'will try again in: ' + next_try + ' secs');
10117                     setTimeout(function () {
10118                         // try again
10119                         attemptDelete(cb);
10120                     }, next_try * 1000);
10121                 } else {
10122                     log.warn('VM.delete(' + tries + '): still there after'
10123                         + ' ' + next_try + ' seconds, giving up.');
10124                     cb(new Error('delete failed after ' + tries + ' attempts. '
10125                         + '(check the log for details)'));
10126                     return;
10127                 }
10128             } else if (err) {
10129                 // error but not one we can retry from.
10130                 log.error(err, 'VM.delete: FATAL: ' + err.message);
10131                 cb(err);
10132             } else {
10133                 // success!
10134                 log.debug('VM.delete: SUCCESS');
10135                 cb();
10136             }
10137         });
10138     };
10139 
10140     attemptDelete(function (err) {
10141         if (err) {
10142             log.error(err);
10143         }
10144         callback(err);
10145     });
10146 };
10147 
10148 // This function needs vmobj to have:
10149 //
10150 // brand
10151 // never_booted
10152 // uuid
10153 // zonename
10154 //
10155 function startZone(vmobj, log, callback)
10156 {
10157     var set_autoboot = 'set autoboot=true';
10158     var uuid = vmobj.uuid;
10159 
10160     assert(log, 'no logger passed to startZone()');
10161 
10162     log.debug('startZone starting ' + uuid);
10163 
10164     //
10165     // We set autoboot (or vm-autoboot) here because we've just intentionally
10166     // started this vm, so we want it to come up if the host is rebooted.
10167     //
10168     if (BRAND_OPTIONS[vmobj.brand].features.use_vm_autoboot) {
10169         set_autoboot = 'select attr name=vm-autoboot; set value=true; end';
10170     }
10171 
10172     async.series([
10173         function (cb) {
10174             // do the booting
10175             zoneadm(['-u', uuid, 'boot', '-X'], log, function (err, boot_fds) {
10176                 if (err) {
10177                     log.error({err: err, stdout: boot_fds.stdout,
10178                         stderr: boot_fds.stderr}, 'zoneadm failed to boot '
10179                         + 'VM');
10180                 } else {
10181                     log.debug({stdout: boot_fds.stdout,
10182                         stderr: boot_fds.stderr}, 'zoneadm booted VM');
10183                 }
10184                 cb(err);
10185             });
10186         }, function (cb) {
10187             // ensure it booted
10188             VM.waitForZoneState(vmobj, 'running', {timeout: 30, log: log},
10189                 function (err, result) {
10190 
10191                 if (err) {
10192                     if (err.code === 'ETIMEOUT') {
10193                         log.info(err, 'timeout waiting for zone to go to '
10194                             + '"running"');
10195                     } else {
10196                         log.error(err, 'unknown error waiting for zone to go'
10197                             + ' "running"');
10198                     }
10199                 } else {
10200                     // zone got to running
10201                     log.info('VM seems to have switched to "running"');
10202                 }
10203                 cb(err);
10204             });
10205         }, function (cb) {
10206             zonecfg(['-u', uuid, set_autoboot], log,
10207                 function (err, autoboot_fds) {
10208 
10209                 if (err) {
10210                     // The vm is running at this point, erroring out here would
10211                     // do no good, so we just log it.
10212                     log.error({err: err, stdout: autoboot_fds.stdout,
10213                         stderr: autoboot_fds.stderr}, 'startZone(): Failed to '
10214                         + set_autoboot + ' for ' + uuid);
10215                 } else {
10216                     log.debug({stdout: autoboot_fds.stdout,
10217                         stderr: autoboot_fds.stderr}, 'set autoboot');
10218                 }
10219                 cb(err);
10220             });
10221         }, function (cb) {
10222             if (!vmobj.never_booted) {
10223                 cb();
10224                 return;
10225             }
10226             zonecfg(['-u', uuid, 'remove attr name=never-booted' ], log,
10227                 function (err, neverbooted_fds) {
10228                     // Ignore errors here, because we're started.
10229                     if (err) {
10230                         log.warn({err: err, stdout: neverbooted_fds.stdout,
10231                             stderr: neverbooted_fds.stderr}, 'failed to remove '
10232                             + 'never-booted flag');
10233                     } else {
10234                         log.debug({stdout: neverbooted_fds.stdout,
10235                             stderr: neverbooted_fds.stderr}, 'removed '
10236                             + 'never-booted flag');
10237                     }
10238                     cb();
10239                 }
10240             );
10241         }
10242     ], function (err) {
10243         if (!err) {
10244             log.info('Started ' + uuid);
10245         }
10246         callback(err);
10247     });
10248 }
10249 
10250 // build the qemu cmdline and start up a VM
10251 //
10252 // vmobj needs any of the following that are defined:
10253 //
10254 // boot
10255 // brand
10256 // cpu_type
10257 // default_gateway
10258 // disks
10259 // hostname
10260 // internal_metadata
10261 // never_booted
10262 // nics
10263 // qemu_extra_opts
10264 // qemu_opts
10265 // ram
10266 // resolvers
10267 // spice_opts
10268 // spice_password
10269 // spice_port
10270 // state
10271 // uuid
10272 // vcpus
10273 // vga
10274 // virtio_txtimer
10275 // virtio_txburst
10276 // vnc_password
10277 // zone_state
10278 // zonename
10279 // zonepath
10280 //
10281 function startVM(vmobj, extra, log, callback)
10282 {
10283     var check_path;
10284     var cmdargs = [];
10285     var d;
10286     var defaultgw = '';
10287     var disk;
10288     var diskargs = '';
10289     var disk_idx = 0;
10290     var found;
10291     var hostname = vmobj.uuid;
10292     var mdata;
10293     var nic;
10294     var nic_idx = 0;
10295     var primary_found = false;
10296     var qemu_opts = '';
10297     var r;
10298     var script;
10299     var spiceargs;
10300     var uuid = vmobj.uuid;
10301     var virtio_txburst;
10302     var virtio_txtimer;
10303     var vnic_opts;
10304     var zoneroot;
10305 
10306     assert(log, 'no logger passed to startVM');
10307     assert(vmobj.hasOwnProperty('zonepath'), 'missing zonepath');
10308 
10309     log.debug('startVM(' + uuid + ')');
10310 
10311     if (!vmobj.hasOwnProperty('state')) {
10312         callback(new Error('Cannot start VM ' + uuid + ' which has no state'));
10313         return;
10314     }
10315 
10316     if ((vmobj.state !== 'stopped' && vmobj.state !== 'provisioning')
10317         || (vmobj.state === 'provisioning'
10318         && vmobj.zone_state !== 'installed')) {
10319 
10320         callback(new Error('Cannot start VM from state: ' + vmobj.state
10321             + ', must be "stopped"'));
10322         return;
10323     }
10324 
10325     zoneroot = path.join(vmobj.zonepath, '/root');
10326 
10327     // We're going to write to /startvm and /tmp/vm.metadata, we don't care if
10328     // they already exist, but we don't want them to be symlinks.
10329     try {
10330         assertSafeZonePath(zoneroot, '/startvm',
10331             {type: 'file', enoent_ok: true});
10332         assertSafeZonePath(zoneroot, '/tmp/vm.metadata',
10333             {type: 'file', enoent_ok: true});
10334     } catch (e) {
10335         log.error(e, 'Error validating files for startVM(): '
10336             + e.message);
10337         callback(e);
10338         return;
10339     }
10340 
10341     // XXX TODO: validate vmobj data is ok to start
10342 
10343     cmdargs.push('-m', vmobj.ram);
10344     cmdargs.push('-name', vmobj.uuid);
10345     cmdargs.push('-uuid', vmobj.uuid);
10346 
10347     if (vmobj.hasOwnProperty('cpu_type')) {
10348         cmdargs.push('-cpu', vmobj.cpu_type);
10349     } else {
10350         cmdargs.push('-cpu', 'qemu64');
10351     }
10352 
10353     if (vmobj.vcpus > 1) {
10354         cmdargs.push('-smp', vmobj.vcpus);
10355     }
10356 
10357     for (disk in vmobj.disks) {
10358         if (vmobj.disks.hasOwnProperty(disk)) {
10359             disk = vmobj.disks[disk];
10360             if (!disk.media) {
10361                 disk.media = 'disk';
10362             }
10363             diskargs = 'file=' + disk.path + ',if=' + disk.model
10364                 + ',index=' + disk_idx + ',media=' + disk.media;
10365             if (disk.boot) {
10366                 diskargs = diskargs + ',boot=on';
10367             }
10368             cmdargs.push('-drive', diskargs);
10369             disk_idx++;
10370         }
10371     }
10372 
10373     // extra payload can include additional disks that we want to include only
10374     // on this one boot.  It can also contain a boot parameter to control boot
10375     // device.  See qemu http://qemu.weilnetz.de/qemu-doc.html for info on
10376     // -boot options.
10377     if (extra.hasOwnProperty('disks')) {
10378         for (disk in extra.disks) {
10379             if (extra.disks.hasOwnProperty(disk)) {
10380                 disk = extra.disks[disk];
10381 
10382                 // ensure this is either a disk that gets mounted in or a
10383                 // file that's been dropped in to the zonepath
10384                 found = false;
10385                 for (d in vmobj.disks) {
10386                     if (!found && vmobj.disks.hasOwnProperty(d)) {
10387                         d = vmobj.disks[d];
10388                         if (d.path === disk.path) {
10389                             found = true;
10390                         }
10391                     }
10392                 }
10393                 check_path = path.join(vmobj.zonepath, 'root', disk.path);
10394                 if (!found && fs.existsSync(check_path)) {
10395                     found = true;
10396                 }
10397                 if (!found) {
10398                     callback(new Error('Cannot find disk: ' + disk.path));
10399                     return;
10400                 }
10401 
10402                 if (!disk.media) {
10403                     disk.media = 'disk';
10404                 }
10405                 diskargs = 'file=' + disk.path + ',if=' + disk.model
10406                     + ',index=' + disk_idx + ',media=' + disk.media;
10407                 if (disk.boot) {
10408                     diskargs = diskargs + ',boot=on';
10409                 }
10410                 cmdargs.push('-drive', diskargs);
10411                 disk_idx++;
10412             }
10413         }
10414     }
10415 
10416     // helpful values:
10417     // order=nc (network boot, then fallback to disk)
10418     // once=d (boot on disk once and the fallback to default)
10419     // order=c,once=d (boot on CDROM this time, but not subsequent boots)
10420     if (extra.hasOwnProperty('boot')) {
10421         cmdargs.push('-boot', extra.boot);
10422     } else if (vmobj.hasOwnProperty('boot')) {
10423         cmdargs.push('-boot', vmobj.boot);
10424     } else {
10425         // order=cd means try harddisk first (c) and cdrom if that fails (d)
10426         cmdargs.push('-boot', 'order=cd');
10427     }
10428 
10429     if (vmobj.hasOwnProperty('hostname')) {
10430         hostname = vmobj.hostname;
10431     }
10432 
10433     if (vmobj.hasOwnProperty('default_gateway')) {
10434         defaultgw = vmobj['default_gateway'];
10435     }
10436 
10437     /*
10438      * These tunables are set for all virtio vnics on this VM.
10439      */
10440     virtio_txtimer = VIRTIO_TXTIMER_DEFAULT;
10441     virtio_txburst = VIRTIO_TXBURST_DEFAULT;
10442     if (vmobj.hasOwnProperty('virtio_txtimer')) {
10443         virtio_txtimer = vmobj.virtio_txtimer;
10444     }
10445     if (vmobj.hasOwnProperty('virtio_txburst')) {
10446         virtio_txburst = vmobj.virtio_txburst;
10447     }
10448 
10449     for (nic in vmobj.nics) {
10450         if (vmobj.nics.hasOwnProperty(nic)) {
10451             nic = vmobj.nics[nic];
10452 
10453             // for virtio devices, we want to be able to set the txtimer and
10454             // txburst so we use a '-device' instead of a '-net' line.
10455             if (nic.model === 'virtio') {
10456                 cmdargs.push('-device',
10457                     'virtio-net-pci,mac=' + nic.mac
10458                     + ',tx=timer,x-txtimer=' + virtio_txtimer
10459                     + ',x-txburst=' + virtio_txburst
10460                     + ',vlan=' + nic_idx);
10461             } else {
10462                 cmdargs.push('-net',
10463                     'nic,macaddr=' + nic.mac
10464                     + ',vlan=' + nic_idx
10465                     + ',name=' + nic.interface
10466                     + ',model=' + nic.model);
10467             }
10468             vnic_opts = 'vnic,name=' + nic.interface
10469                 + ',vlan=' + nic_idx
10470                 + ',ifname=' + nic.interface;
10471 
10472             if (nic.ip != 'dhcp') {
10473                 vnic_opts = vnic_opts
10474                     + ',ip=' + nic.ip
10475                     + ',netmask=' + nic.netmask;
10476             }
10477 
10478             // The primary network provides the resolvers, default gateway
10479             // and hostname to prevent vm from trying to use settings
10480             // from more than one nic
10481             if (!primary_found) {
10482                 if (nic.hasOwnProperty('primary') && nic.primary) {
10483                     if (nic.hasOwnProperty('gateway') && nic.ip != 'dhcp') {
10484                         vnic_opts += ',gateway_ip=' + nic.gateway;
10485                     }
10486                     primary_found = true;
10487                 } else if (defaultgw && nic.hasOwnProperty('gateway')
10488                     && nic.gateway == defaultgw) {
10489 
10490                     /*
10491                      * XXX this exists here for backward compatibilty.  New VMs
10492                      *     and old VMs that are upgraded should not use
10493                      *     default_gateway.  When we've implemented autoupgrade
10494                      *     this block (and all reference to default_gateway)
10495                      *     can be removed.
10496                      */
10497 
10498                     if (nic.ip != 'dhcp') {
10499                         vnic_opts += ',gateway_ip=' + nic.gateway;
10500                     }
10501                     primary_found = true;
10502                 }
10503 
10504                 if (primary_found && nic.ip != 'dhcp') {
10505                     if (hostname) {
10506                         vnic_opts += ',hostname=' + hostname;
10507                     }
10508                     if (vmobj.hasOwnProperty('resolvers')) {
10509                         for (r in vmobj.resolvers) {
10510                             vnic_opts += ',dns_ip' + r + '='
10511                                 + vmobj.resolvers[r];
10512                         }
10513                     }
10514                 }
10515             }
10516 
10517             cmdargs.push('-net', vnic_opts);
10518             nic_idx++;
10519         }
10520     }
10521 
10522     cmdargs.push('-smbios', 'type=1,manufacturer=Joyent,'
10523         + 'product=SmartDC HVM,version=6.2012Q1,'
10524         + 'serial=' + vmobj.uuid + ',uuid=' + vmobj.uuid + ','
10525         + 'sku=001,family=Virtual Machine');
10526 
10527     cmdargs.push('-pidfile', '/tmp/vm.pid');
10528 
10529     if (vmobj.hasOwnProperty('vga')) {
10530         cmdargs.push('-vga', vmobj.vga);
10531     } else {
10532         cmdargs.push('-vga', 'std');
10533     }
10534 
10535     cmdargs.push('-chardev',
10536         'socket,id=qmp,path=/tmp/vm.qmp,server,nowait');
10537     cmdargs.push('-qmp', 'chardev:qmp');
10538 
10539     // serial0 is for serial console
10540     cmdargs.push('-chardev',
10541         'socket,id=serial0,path=/tmp/vm.console,server,nowait');
10542     cmdargs.push('-serial', 'chardev:serial0');
10543 
10544     // serial1 is used for metadata API
10545     cmdargs.push('-chardev',
10546         'socket,id=serial1,path=/tmp/vm.ttyb,server,nowait');
10547     cmdargs.push('-serial', 'chardev:serial1');
10548 
10549     if (!vmobj.qemu_opts) {
10550         if (vmobj.hasOwnProperty('vnc_password')
10551             && vmobj.vnc_password.length > 0) {
10552 
10553             cmdargs.push('-vnc', 'unix:/tmp/vm.vnc,password');
10554         } else {
10555             cmdargs.push('-vnc', 'unix:/tmp/vm.vnc');
10556         }
10557         if (vmobj.hasOwnProperty('spice_port')
10558             && vmobj.spice_port !== -1) {
10559 
10560             spiceargs = 'sock=/tmp/vm.spice';
10561             if (!vmobj.hasOwnProperty('spice_password')
10562                 || vmobj.spice_password.length <= 0) {
10563 
10564                 spiceargs = spiceargs + ',disable-ticketing';
10565 
10566                 // Otherwise, spice password is set via qmp, so we don't
10567                 // need to do anything here
10568             }
10569             if (vmobj.hasOwnProperty('spice_opts')
10570                 && vmobj.spice_opts.length > 0) {
10571 
10572                 spiceargs = spiceargs + ',' + vmobj.spice_opts;
10573             }
10574             cmdargs.push('-spice', spiceargs);
10575         }
10576         cmdargs.push('-parallel', 'none');
10577         cmdargs.push('-usb');
10578         cmdargs.push('-usbdevice', 'tablet');
10579         cmdargs.push('-k', 'en-us');
10580     } else {
10581         qemu_opts = vmobj.qemu_opts.toString();
10582     }
10583 
10584     if (vmobj.qemu_extra_opts) {
10585         qemu_opts = qemu_opts + ' ' + vmobj.qemu_extra_opts;
10586     }
10587 
10588     // This actually creates the qemu process
10589     script = '#!/usr/bin/bash\n\n'
10590         + 'exec >/tmp/vm.startvm.log 2>&1\n\n'
10591         + 'set -o xtrace\n\n'
10592         + 'if [[ -x /startvm.zone ]]; then\n'
10593         + '    exec /smartdc/bin/qemu-exec /startvm.zone "'
10594         + cmdargs.join('" "')
10595         + '" ' + qemu_opts + '\n'
10596         + 'else\n'
10597         + '    exec /smartdc/bin/qemu-exec /smartdc/bin/qemu-system-x86_64 "'
10598         + cmdargs.join('" "')
10599         + '" ' + qemu_opts + '\n'
10600         + 'fi\n\n'
10601         + 'exit 1\n';
10602 
10603     try {
10604         fs.writeFileSync(vmobj.zonepath + '/root/startvm', script);
10605         fs.chmodSync(vmobj.zonepath + '/root/startvm', '0755');
10606     } catch (e) {
10607         log.warn(e, 'Unable to create /startvm script in ' + vmobj.uuid);
10608         callback(new Error('cannot create /startvm'));
10609         return;
10610     }
10611 
10612     mdata = {
10613         'internal_metadata':
10614             vmobj.internal_metadata ? vmobj.internal_metadata : {}
10615     };
10616     fs.writeFile(path.join(vmobj.zonepath, '/root/tmp/vm.metadata'),
10617         JSON.stringify(mdata, null, 2) + '\n',
10618         function (err) {
10619             if (err) {
10620                 log.debug(err, 'FAILED TO write metadata to '
10621                     + '/tmp/vm.metadata: ' + err.message);
10622                 callback(err);
10623             } else {
10624                 log.debug('wrote metadata to /tmp/vm.metadata');
10625                 startZone(vmobj, log, callback);
10626             }
10627         }
10628     );
10629 }
10630 
10631 // according to usr/src/common/zfs/zfs_namecheck.c allowed characters are:
10632 //
10633 // alphanumeric characters plus the following: [-_.:%]
10634 //
10635 function validSnapshotName(snapname, log)
10636 {
10637     assert(log, 'no logger passed to validSnapshotName()');
10638 
10639     if (snapname.length < 1 || snapname.length > MAX_SNAPNAME_LENGTH) {
10640         log.error('Invalid snapname length: ' + snapname.length
10641             + ' valid range: [1-' + MAX_SNAPNAME_LENGTH + ']');
10642         return (false);
10643     }
10644 
10645     if (snapname.match(/[^a-zA-Z0-9\-\_\.\:\%]/)) {
10646         log.error('Invalid snapshot name: contains invalid characters.');
10647         return (false);
10648     }
10649 
10650     return (true);
10651 }
10652 
10653 function performSnapshotRollback(snapshots, log, callback)
10654 {
10655     assert(log, 'no logger passed to performSnapshotRollback()');
10656 
10657     // NOTE: we assume machine is stopped and snapshots are already validated
10658 
10659     function rollback(snapname, cb) {
10660         var args;
10661 
10662         args = ['rollback', '-r', snapname];
10663         zfs(args, log, function (zfs_err, fds) {
10664             if (zfs_err) {
10665                 log.error({'err': zfs_err, 'stdout': fds.stdout,
10666                     'stderr': fds.stdout}, 'zfs rollback of ' + snapname
10667                     + ' failed.');
10668                 cb(zfs_err);
10669                 return;
10670             }
10671             log.info('rolled back snapshot ' + snapname);
10672             log.debug('zfs destroy stdout: ' + fds.stdout);
10673             log.debug('zfs destroy stderr: ' + fds.stderr);
10674             cb();
10675         });
10676     }
10677 
10678     async.forEachSeries(snapshots, rollback, function (err) {
10679         if (err) {
10680             log.error(err, 'Unable to rollback some datasets.');
10681         }
10682         callback(err);
10683     });
10684 }
10685 
10686 function updateZonecfgTimestamp(vmobj, callback)
10687 {
10688     var file;
10689     var now;
10690 
10691     assert(vmobj.zonename, 'updateZonecfgTimestamp() vmobj must have '
10692         + '.zonename');
10693 
10694     file = path.join('/etc/zones/', vmobj.zonename + '.xml');
10695     now = new Date();
10696 
10697     fs.utimes(file, now, now, callback);
10698 }
10699 
10700 exports.rollback_snapshot = function (uuid, snapname, options, callback)
10701 {
10702     var load_fields;
10703     var log;
10704 
10705     // options is optional
10706     if (arguments.length === 3) {
10707         callback = arguments[2];
10708         options = {};
10709     }
10710 
10711     ensureLogging(true);
10712     if (options.hasOwnProperty('log')) {
10713         log = options.log;
10714     } else {
10715         log = VM.log.child({action: 'rollback_snapshot', vm: uuid});
10716     }
10717 
10718     if (!validSnapshotName(snapname, log)) {
10719         callback(new Error('Invalid snapshot name'));
10720         return;
10721     }
10722 
10723     load_fields = [
10724         'brand',
10725         'snapshots',
10726         'zfs_filesystem',
10727         'state',
10728         'uuid'
10729     ];
10730 
10731     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
10732         var found;
10733         var snap;
10734         var snapshot_list = [];
10735 
10736         if (err) {
10737             callback(err);
10738             return;
10739         }
10740 
10741         if (vmobj.brand === 'kvm') {
10742             callback(new Error('snapshots for KVM VMs currently unsupported'));
10743             return;
10744         }
10745 
10746         found = false;
10747         if (vmobj.hasOwnProperty('snapshots')) {
10748             for (snap in vmobj.snapshots) {
10749                 if (vmobj.snapshots[snap].name === snapname) {
10750                     found = true;
10751                     break;
10752                 }
10753             }
10754         }
10755         if (!found) {
10756             callback(new Error('No snapshot named "' + snapname + '" for '
10757                 + uuid));
10758             return;
10759         }
10760 
10761         snapshot_list = [vmobj.zfs_filesystem + '@vmsnap-' + snapname];
10762 
10763         if (vmobj.state !== 'stopped') {
10764             VM.stop(vmobj.uuid, {'force': true, log: log}, function (stop_err) {
10765                 if (stop_err) {
10766                     log.error(stop_err, 'failed to stop VM ' + vmobj.uuid
10767                         + ': ' + stop_err.message);
10768                     callback(stop_err);
10769                     return;
10770                 }
10771                 performSnapshotRollback(snapshot_list, log,
10772                     function (rollback_err) {
10773 
10774                     if (rollback_err) {
10775                         log.error(rollback_err, 'failed to '
10776                             + 'performSnapshotRollback');
10777                         callback(rollback_err);
10778                         return;
10779                     }
10780                     if (options.do_not_start) {
10781                         callback();
10782                     } else {
10783                         VM.start(vmobj.uuid, {}, {log: log}, callback);
10784                     }
10785                     return;
10786                 });
10787             });
10788         } else {
10789             performSnapshotRollback(snapshot_list, log,
10790                 function (rollback_err) {
10791 
10792                 if (rollback_err) {
10793                     log.error(rollback_err, 'failed to '
10794                         + 'performSnapshotRollback');
10795                     callback(rollback_err);
10796                     return;
10797                 }
10798                 if (options.do_not_start) {
10799                     callback();
10800                 } else {
10801                     VM.start(vmobj.uuid, {}, {log: log}, callback);
10802                 }
10803                 return;
10804             });
10805         }
10806     });
10807 };
10808 
10809 exports.delete_snapshot = function (uuid, snapname, options, callback)
10810 {
10811     var load_fields;
10812     var log;
10813 
10814     // options is optional
10815     if (arguments.length === 3) {
10816         callback = arguments[2];
10817         options = {};
10818     }
10819 
10820     ensureLogging(true);
10821     if (options.hasOwnProperty('log')) {
10822         log = options.log;
10823     } else {
10824         log = VM.log.child({action: 'delete_snapshot', vm: uuid});
10825     }
10826 
10827     if (!validSnapshotName(snapname, log)) {
10828         callback(new Error('Invalid snapshot name'));
10829         return;
10830     }
10831 
10832     load_fields = [
10833         'brand',
10834         'snapshots',
10835         'zfs_filesystem',
10836         'zonepath',
10837         'zonename'
10838     ];
10839 
10840     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
10841         var found;
10842         var mountpath;
10843         var mountpoint;
10844         var snap;
10845         var zoneroot;
10846 
10847         if (err) {
10848             callback(err);
10849             return;
10850         }
10851 
10852         if (vmobj.brand === 'kvm') {
10853             callback(new Error('snapshots for KVM VMs currently unsupported'));
10854             return;
10855         }
10856 
10857         found = false;
10858         if (vmobj.hasOwnProperty('snapshots')) {
10859             for (snap in vmobj.snapshots) {
10860                 if (vmobj.snapshots[snap].name === snapname) {
10861                     found = true;
10862                     break;
10863                 }
10864             }
10865         }
10866         if (!found) {
10867             callback(new Error('No snapshot named "' + snapname + '" for '
10868                 + uuid));
10869             return;
10870         }
10871 
10872         zoneroot = vmobj.zonepath + '/root';
10873         mountpath = '/checkpoints/' + snapname;
10874         mountpoint = zoneroot + '/' + mountpath;
10875 
10876         async.waterfall([
10877             function (cb) {
10878                 // Ensure it's safe for us to be doing something in this dir
10879                 try {
10880                     assertSafeZonePath(zoneroot, mountpath,
10881                         {type: 'dir', enoent_ok: true});
10882                 } catch (e) {
10883                     log.error(e, 'Unsafe mountpoint for checkpoints: '
10884                         + e.message);
10885                     cb(e);
10886                     return;
10887                 }
10888                 cb();
10889             }, function (cb) {
10890                 // umount snapshot
10891                 var argv;
10892                 var cmd = '/usr/sbin/umount';
10893 
10894                 argv = [mountpoint];
10895 
10896                 execFile(cmd, argv, function (e, stdout, stderr) {
10897                     if (e) {
10898                         log.error({err: e}, 'There was an error while '
10899                             + 'unmounting the snapshot: ' + e.message);
10900                         // we treat an error here as fatal only if the error
10901                         // was something other than 'not mounted'
10902                         if (!stderr.match(/ not mounted/)) {
10903                             cb(e);
10904                             return;
10905                         }
10906                     } else {
10907                         log.trace('umounted ' + mountpoint);
10908                     }
10909                     cb();
10910                 });
10911             }, function (cb) {
10912                 // remove the mountpoint directory
10913                 fs.rmdir(mountpoint, function (e) {
10914                     if (e) {
10915                         log.error(e);
10916                     } else {
10917                         log.trace('removed directory ' + mountpoint);
10918                     }
10919                     cb(); // XXX not fatal because might also not exist
10920                 });
10921             }, function (cb) {
10922                 var args;
10923 
10924                 args = ['destroy', vmobj.zfs_filesystem + '@vmsnap-'
10925                     + snapname];
10926 
10927                 zfs(args, log, function (e, fds) {
10928                     if (e) {
10929                         log.error({'err': e, 'stdout': fds.stdout,
10930                             'stderr': fds.stdout}, 'zfs destroy failed.');
10931                         cb(e);
10932                         return;
10933                     }
10934                     log.debug({err: e, stdout: fds.stdout, stderr: fds.stderr},
10935                         'zfs destroy ' + vmobj.zfs_filesystem + '@vmsnap-'
10936                         + snapname);
10937                     cb();
10938                 });
10939             }, function (cb) {
10940                 updateZonecfgTimestamp(vmobj, function (e) {
10941                     if (e) {
10942                         log.warn(e, 'failed to update timestamp after deleting '
10943                             + 'snapshot');
10944                     }
10945                     // don't pass err because there's no recovery possible
10946                     // (the snapshot's gone)
10947                     cb();
10948                 });
10949             }
10950         ], function (error) {
10951             callback(error);
10952         });
10953     });
10954 };
10955 
10956 exports.create_snapshot = function (uuid, snapname, options, callback)
10957 {
10958     var load_fields;
10959     var log;
10960 
10961     // options is optional
10962     if (arguments.length === 3) {
10963         callback = arguments[2];
10964         options = {};
10965     }
10966 
10967     ensureLogging(true);
10968 
10969     if (options.hasOwnProperty('log')) {
10970         log = options.log;
10971     } else {
10972         log = VM.log.child({action: 'create_snapshot', vm: uuid});
10973     }
10974 
10975     if (!validSnapshotName(snapname, log)) {
10976         callback(new Error('Invalid snapshot name'));
10977         return;
10978     }
10979 
10980     load_fields = [
10981         'brand',
10982         'datasets',
10983         'zone_state',
10984         'snapshots',
10985         'zfs_filesystem',
10986         'zonepath',
10987         'zonename'
10988     ];
10989 
10990     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
10991         var full_snapname;
10992         var mountpath;
10993         var mountpoint;
10994         var mount_snapshot = true;
10995         var snap;
10996         var snapshot_list = [];
10997         var zoneroot;
10998 
10999         if (err) {
11000             callback(err);
11001             return;
11002         }
11003 
11004         if (vmobj.brand === 'kvm') {
11005             callback(new Error('snapshots for KVM VMs currently unsupported'));
11006             return;
11007         }
11008 
11009         if (vmobj.hasOwnProperty('datasets') && vmobj.datasets.length > 0) {
11010             callback(new Error('Cannot currently snapshot zones that have '
11011                 + 'datasets'));
11012             return;
11013         }
11014 
11015         if (!vmobj.hasOwnProperty('zfs_filesystem')) {
11016             callback(new Error('vmobj missing zfs_filesystem, cannot create '
11017                 + 'snapshot'));
11018             return;
11019         }
11020 
11021         full_snapname = vmobj.zfs_filesystem + '@vmsnap-' + snapname;
11022 
11023         // Check that name not already used
11024         if (vmobj.hasOwnProperty('snapshots')) {
11025             for (snap in vmobj.snapshots) {
11026                 snap = vmobj.snapshots[snap];
11027 
11028                 if (snap.name === full_snapname) {
11029                     callback(new Error('snapshot with name "' + snapname
11030                         + '" already exists.'));
11031                     return;
11032                 } else {
11033                     log.debug('SKIPPING ' + snap.name);
11034                 }
11035             }
11036         }
11037 
11038         snapshot_list.push(full_snapname);
11039 
11040         // assert snapshot_list.length > 0
11041 
11042         log.info('Taking snapshot "' + snapname + '" of ' + uuid);
11043 
11044         zoneroot = vmobj.zonepath + '/root';
11045         mountpath = '/checkpoints/' + snapname;
11046         mountpoint = zoneroot + '/' + mountpath;
11047 
11048         async.waterfall([
11049             function (cb) {
11050                 // take the snapshot
11051                 var args;
11052                 args = ['snapshot'].concat(snapshot_list);
11053 
11054                 zfs(args, log, function (zfs_err, fds) {
11055                     if (zfs_err) {
11056                         log.error({err: zfs_err, stdout: fds.stdout,
11057                             stderr: fds.stdout}, 'zfs snapshot failed.');
11058                     } else {
11059                         log.debug({err: zfs_err, stdout: fds.stdout,
11060                             stderr: fds.stderr}, 'zfs ' + args.join(' '));
11061                     }
11062                     cb(zfs_err);
11063                 });
11064             }, function (cb) {
11065 
11066                 if (vmobj.zone_state !== 'running') {
11067                     log.info('Not mounting snapshot as zone is in state '
11068                         + vmobj.zone_state + ', must be: running');
11069                     mount_snapshot = false;
11070                     cb();
11071                     return;
11072                 }
11073 
11074                 // Ensure it's safe for us to be doing something in this dir
11075                 try {
11076                     assertSafeZonePath(zoneroot, mountpath,
11077                         {type: 'dir', enoent_ok: true});
11078                 } catch (e) {
11079                     log.error(e, 'Unsafe mountpoint for checkpoints: '
11080                         + e.message);
11081                     cb(e);
11082                     return;
11083                 }
11084                 cb();
11085             }, function (cb) {
11086                 // Make the mountpoint directory and parent
11087                 var newmode;
11088 
11089                 if (mount_snapshot === false) {
11090                     cb();
11091                     return;
11092                 }
11093 
11094                 /*jsl:ignore*/
11095                 newmode = 0755;
11096                 /*jsl:end*/
11097 
11098                 function doMkdir(dir, callbk) {
11099                     fs.mkdir(dir, newmode, function (e) {
11100                         if (e && e.code !== 'EEXIST') {
11101                             log.error({err: e}, 'unable to create mountpoint '
11102                                 + 'for checkpoints: ' + e.message);
11103                             callbk(e);
11104                             return;
11105                         }
11106                         callbk();
11107                     });
11108                 }
11109 
11110                 doMkdir(path.dirname(mountpoint), function (parent_e) {
11111                     if (parent_e) {
11112                         cb(parent_e);
11113                         return;
11114                     }
11115                     doMkdir(mountpoint, function (dir_e) {
11116                         if (dir_e) {
11117                             cb(dir_e);
11118                             return;
11119                         }
11120 
11121                         log.debug('created ' + mountpoint);
11122                         cb();
11123                     });
11124                 });
11125             }, function (cb) {
11126                 var argv;
11127                 var cmd = '/usr/sbin/mount';
11128                 var snapdir;
11129 
11130                 if (mount_snapshot === false) {
11131                     cb();
11132                     return;
11133                 }
11134 
11135                 snapdir = vmobj.zonepath + '/.zfs/snapshot/vmsnap-' + snapname
11136                     + '/root';
11137                 argv = [ '-F', 'lofs', '-o', 'ro,setuid,nodevices', snapdir,
11138                     mountpoint];
11139 
11140                 execFile(cmd, argv, function (e, stdout, stderr) {
11141                     if (e) {
11142                         log.error({err: e}, 'unable to mount snapshot: '
11143                             + e.message);
11144                     }
11145                     // not fatal becase snapshot was already created.
11146                     cb();
11147                 });
11148             }, function (cb) {
11149                 // update timestamp so last_modified gets bumped
11150                 updateZonecfgTimestamp(vmobj, function (e) {
11151                     if (e) {
11152                         log.warn(e,
11153                             'failed to update timestamp after snapshot');
11154                     }
11155                     // ignore error since there's no recovery
11156                     // (snapshot was created)
11157                     cb();
11158                 });
11159             }
11160         ], function (error) {
11161             callback(error);
11162         });
11163     });
11164 };
11165 
11166 exports.start = function (uuid, extra, options, callback)
11167 {
11168     var load_fields;
11169     var log;
11170 
11171     load_fields = [
11172         'brand',
11173         'nics',
11174         'state',
11175         'uuid',
11176         'zone_state',
11177         'zonename',
11178         'zonepath'
11179     ];
11180 
11181     // options is optional
11182     if (arguments.length === 3) {
11183         callback = arguments[2];
11184         options = {};
11185     }
11186 
11187     assert(callback, 'undefined callback!');
11188 
11189     ensureLogging(true);
11190     if (options.hasOwnProperty('log')) {
11191         log = options.log;
11192     } else {
11193         log = VM.log.child({action: 'start', vm: uuid});
11194     }
11195 
11196     log.info('Starting VM ' + uuid);
11197 
11198     VM.load(uuid, {log: log, fields: load_fields}, function (err, vmobj) {
11199         if (err) {
11200             callback(err);
11201         } else {
11202 
11203             if (vmobj.state === 'running') {
11204                 err = new Error('VM ' + vmobj.uuid + ' is already '
11205                     + '\'running\'');
11206                 err.code = 'EALREADYRUNNING';
11207                 callback(err);
11208                 return;
11209             }
11210 
11211             if ((vmobj.state !== 'stopped' && vmobj.state !== 'provisioning')
11212                 || (vmobj.state === 'provisioning'
11213                 && vmobj.zone_state !== 'installed')) {
11214 
11215                 err = new Error('Cannot to start vm from state "' + vmobj.state
11216                     + '", must be "stopped".');
11217                 log.error(err);
11218                 callback(err);
11219                 return;
11220             }
11221 
11222             lookupInvalidNicTags(vmobj.nics, log, function (e) {
11223                 var kvm_load_fields = [
11224                     'boot',
11225                     'brand',
11226                     'cpu_type',
11227                     'default_gateway',
11228                     'disks',
11229                     'hostname',
11230                     'internal_metadata',
11231                     'never_booted',
11232                     'nics',
11233                     'qemu_extra_opts',
11234                     'qemu_opts',
11235                     'ram',
11236                     'resolvers',
11237                     'spice_opts',
11238                     'spice_password',
11239                     'spice_port',
11240                     'state',
11241                     'uuid',
11242                     'vcpus',
11243                     'vga',
11244                     'virtio_txtimer',
11245                     'virtio_txburst',
11246                     'vnc_password',
11247                     'zone_state',
11248                     'zonename',
11249                     'zonepath'
11250                 ];
11251 
11252                 if (e) {
11253                     callback(e);
11254                     return;
11255                 }
11256 
11257                 if (BRAND_OPTIONS[vmobj.brand].features.type === 'KVM') {
11258                     // when we boot KVM we need a lot more fields, so load again
11259                     // in that case to get the fields we need.
11260                     VM.load(uuid, {log: log, fields: kvm_load_fields},
11261                         function (error, obj) {
11262 
11263                         if (error) {
11264                             callback(error);
11265                             return;
11266                         }
11267                         startVM(obj, extra, log, callback);
11268                     });
11269                 } else if (BRAND_OPTIONS[vmobj.brand].features.type === 'OS') {
11270                     startZone(vmobj, log, callback);
11271                 } else {
11272                     err = new Error('no idea how to start a vm with brand: '
11273                         + vmobj.brand);
11274                     log.error(err);
11275                     callback(err);
11276                 }
11277             });
11278         }
11279     });
11280 };
11281 
11282 function setRctl(zonename, rctl, value, log, callback)
11283 {
11284     var args;
11285 
11286     assert(log, 'no logger passed to setRctl()');
11287 
11288     args = ['-n', rctl, '-v', value.toString(), '-r', '-i', 'zone', zonename];
11289     log.debug('/usr/bin/prctl ' + args.join(' '));
11290     execFile('/usr/bin/prctl', args, function (error, stdout, stderr) {
11291         if (error) {
11292             log.error(error, 'setRctl() failed with: ' + stderr);
11293             callback(error);
11294         } else {
11295             callback();
11296         }
11297     });
11298 }
11299 
11300 function resizeTmp(zonename, newsize, log, callback)
11301 {
11302     var args;
11303 
11304     // NOTE: this used to update /etc/vfstab in the zone as well, but was
11305     // changed with OS-920.  Now vfstab is updated by mdata-fetch in the
11306     // zone instead, so that will happen next boot.  We still do the mount
11307     // so the property update happens on the running zone.
11308 
11309     assert(log, 'no logger passed to resizeTmp()');
11310 
11311     args = [zonename, '/usr/sbin/mount', '-F', 'tmpfs', '-o', 'remount,size='
11312         + newsize + 'm', '/tmp'];
11313     log.debug('/usr/sbin/zlogin ' + args.join(' '));
11314     execFile('/usr/sbin/zlogin', args, function (err, mnt_stdout, mnt_stderr) {
11315         if (err) {
11316             log.error({'err': err, 'stdout': mnt_stdout,
11317                 'stderr': mnt_stderr}, 'zlogin for ' + zonename
11318                 + ' exited with code ' + err.code + ' -- ' + err.message);
11319             // error here is not fatal as this should be fixed on reboot
11320         }
11321 
11322         callback();
11323     });
11324 }
11325 
11326 function resizeDisks(disks, updates, log, callback)
11327 {
11328     var d;
11329     var disk;
11330     var resized = 0;
11331     var vols = [];
11332 
11333     assert(log, 'no logger passed to resizeDisks()');
11334 
11335     for (disk in updates) {
11336         disk = updates[disk];
11337         for (d in disks) {
11338             d = disks[d];
11339             if (d.path === disk.path && disk.hasOwnProperty('size')) {
11340                 vols.push({'disk': d, 'new_size': disk.size});
11341             }
11342         }
11343     }
11344 
11345     function resize(vol, cb) {
11346         var args;
11347         var dsk = vol.disk;
11348         var size = vol.new_size;
11349 
11350         if (dsk.hasOwnProperty('zfs_filesystem')) {
11351             if (dsk.size > size) {
11352                 cb(new Error('cannot resize ' + dsk.zfs_filesystem
11353                     + ' new size must be greater than current size. ('
11354                     + dsk.size + ' > ' + dsk.size + ')'));
11355             } else if (dsk.size === size) {
11356                 // no point resizing if the old+new are the same
11357                 cb();
11358             } else {
11359                 args = ['set', 'volsize=' + size + 'M', dsk.zfs_filesystem];
11360                 zfs(args, log, function (err, fds) {
11361                     resized++;
11362                     cb(err);
11363                 });
11364             }
11365         } else {
11366             cb(new Error('could not find zfs_filesystem in '
11367                 + JSON.stringify(dsk)));
11368         }
11369     }
11370 
11371     async.forEachSeries(vols, resize, function (err) {
11372         if (err) {
11373             log.error(err, 'Unable to resize disks');
11374             callback(err);
11375         } else {
11376             callback(null, resized);
11377         }
11378     });
11379 }
11380 
11381 function updateVnicAllowedIPs(uuid, nic, log, callback)
11382 {
11383     var ips = [];
11384 
11385     assert(log, 'no logger passed to updateVnicAllowedIPs()');
11386 
11387     if (!uuid || !nic.interface) {
11388         callback();
11389         return;
11390     }
11391 
11392     if (nic.hasOwnProperty('allow_ip_spoofing') && nic.allow_ip_spoofing) {
11393         dladm.resetLinkProp(uuid, nic.interface, 'allowed-ips', log, callback);
11394         return;
11395     }
11396 
11397     if (nic.hasOwnProperty('ip')) {
11398         ips.push(nic.ip);
11399     }
11400 
11401     if (nic.hasOwnProperty('vrrp_primary_ip')) {
11402         ips.push(nic.vrrp_primary_ip);
11403     }
11404 
11405     if (nic.hasOwnProperty('allowed_ips')) {
11406         ips = ips.concat(nic.allowed_ips);
11407     }
11408 
11409     if (!ips.length === 0) {
11410         dladm.resetLinkProp(uuid, nic.interface, 'allowed-ips', log, callback);
11411     } else {
11412         dladm.setLinkProp(uuid, nic.interface, 'allowed-ips', ips, log,
11413             callback);
11414     }
11415 }
11416 
11417 function updateVnicProperties(uuid, vmobj, payload, log, callback)
11418 {
11419     assert(log, 'no logger passed to updateVnicProperties()');
11420 
11421     if (vmobj.state != 'running') {
11422         log.debug('VM not running: not updating vnic properties');
11423         callback(null);
11424         return;
11425     }
11426 
11427     if (!payload.hasOwnProperty('update_nics')) {
11428         log.debug(
11429             'No update_nics property: not updating vnic properties');
11430         callback(null);
11431         return;
11432     }
11433 
11434     async.forEach(payload.update_nics, function (nic, cb) {
11435         var opt;
11436         var needsUpdate = false;
11437         var needsIPupdate = false;
11438         var spoof_opts = {
11439             'allow_ip_spoofing': 'ip-nospoof',
11440             'allow_mac_spoofing': 'mac-nospoof',
11441             'allow_dhcp_spoofing': 'dhcp-nospoof',
11442             'allow_restricted_traffic': 'restricted'
11443         };
11444         var vm_nic;
11445 
11446         // First, determine if we've changed any of the spoofing opts in this
11447         // update:
11448         for (opt in spoof_opts) {
11449             if (nic.hasOwnProperty(opt)) {
11450                 needsUpdate = true;
11451                 break;
11452             }
11453         }
11454 
11455         if (nic.hasOwnProperty('vrrp_primary_ip')
11456             || nic.hasOwnProperty('allowed_ips')
11457             || nic.hasOwnProperty('allow_ip_spoofing')) {
11458             needsIPupdate = true;
11459         }
11460 
11461         for (vm_nic in vmobj.nics) {
11462             vm_nic = vmobj.nics[vm_nic];
11463             if (vm_nic.mac == nic.mac) {
11464                 break;
11465             }
11466         }
11467 
11468         if (!vm_nic) {
11469             cb(new Error('Unknown NIC: ' + nic.mac));
11470             return;
11471         }
11472 
11473         if (!needsUpdate) {
11474             log.debug('No spoofing / allowed IP opts updated for nic "'
11475                 + nic.mac + '": not updating');
11476             if (needsIPupdate) {
11477                 updateVnicAllowedIPs(uuid, vm_nic, log, cb);
11478             } else {
11479                 cb(null);
11480             }
11481             return;
11482         }
11483 
11484         // Using the updated nic object, figure out what spoofing opts to set
11485         for (opt in spoof_opts) {
11486             if (vm_nic.hasOwnProperty(opt) && fixBoolean(vm_nic[opt])) {
11487                 delete spoof_opts[opt];
11488             }
11489         }
11490 
11491         if (vm_nic.hasOwnProperty('dhcp_server')
11492                 && fixBoolean(vm_nic.dhcp_server)) {
11493             delete spoof_opts.allow_dhcp_spoofing;
11494             delete spoof_opts.allow_ip_spoofing;
11495         }
11496 
11497         if (Object.keys(spoof_opts).length === 0) {
11498             dladm.resetLinkProp(uuid, vm_nic.interface, 'protection', log,
11499                 function (err) {
11500                     if (err) {
11501                         cb(err);
11502                         return;
11503                     }
11504                     if (needsIPupdate) {
11505                         updateVnicAllowedIPs(uuid, vm_nic, log, cb);
11506                         return;
11507                     }
11508                     cb();
11509                     return;
11510                 });
11511         } else {
11512             dladm.setLinkProp(uuid, vm_nic.interface, 'protection',
11513                     Object.keys(spoof_opts).map(function (k) {
11514                         return spoof_opts[k];
11515                     }), log,
11516                 function (err) {
11517                     if (err) {
11518                         cb(err);
11519                         return;
11520                     }
11521                     if (needsIPupdate) {
11522                         updateVnicAllowedIPs(uuid, vm_nic, log, cb);
11523                         return;
11524                     }
11525                     cb();
11526                     return;
11527                 });
11528         }
11529     }, function (err) {
11530         if (err) {
11531             callback(err);
11532         } else {
11533             callback(null);
11534         }
11535     });
11536 }
11537 
11538 // Run a fw.js function that requires all VM records
11539 function firewallVMrun(opts, fn, log, callback)
11540 {
11541     assert(log, 'no logger passed to firewallVMrun()');
11542     VM.lookup({}, {fields: fw.VM_FIELDS, log: log}, function (err, records) {
11543         if (err) {
11544             callback(err);
11545             return;
11546         }
11547 
11548         opts.vms = records;
11549         if (fn.name == 'validatePayload') {
11550             opts.logName = 'VM-create';
11551         } else {
11552             opts.logName = 'VM-' + (fn.name || '');
11553         }
11554 
11555         if (opts.provisioning) {
11556             opts.vms.push(opts.provisioning);
11557             delete opts.provisioning;
11558         }
11559 
11560         fn(opts, callback);
11561         return;
11562     });
11563 }
11564 
11565 function validateFirewall(payload, log, callback)
11566 {
11567     assert(log, 'no logger passed to validateFirewall()');
11568 
11569     log.debug(toValidate, 'Validating firewall payload');
11570     var toValidate = payload.firewall;
11571     toValidate.provisioning = {
11572         'state': 'provisioning'
11573     };
11574 
11575     fw.VM_FIELDS.forEach(function (field) {
11576         if (payload.hasOwnProperty(field)) {
11577             toValidate.provisioning[field] = payload[field];
11578         }
11579     });
11580 
11581     if (payload.hasOwnProperty('add_nics')) {
11582         toValidate.provisioning.nics = payload.add_nics;
11583     }
11584 
11585     // We're not actually writing data to zonepath when validating, and we
11586     // don't actually have a zonepath created yet, so add a key so that the
11587     // payload passes validation
11588     if (!payload.hasOwnProperty('zonepath')) {
11589         toValidate.provisioning.zonepath = true;
11590     }
11591 
11592     log.debug({
11593         firewall: toValidate.firewall,
11594         provisioning: toValidate.provisioning,
11595         payload: payload
11596     }, 'Validating firewall payload');
11597 
11598     firewallVMrun(toValidate, fw.validatePayload, log,
11599         function (err, res) {
11600         if (err) {
11601             log.error(err, 'Error validating firewall payload');
11602             err.message = 'Invalid firewall payload: ' + err.message;
11603         }
11604 
11605         callback(err, res);
11606         return;
11607     });
11608 }
11609 
11610 function addFirewallData(payload, vmobj, log, callback)
11611 {
11612     var firewallOpts = payload.firewall;
11613 
11614     assert(log, 'no logger passed to addFirewallData()');
11615 
11616     if (!payload.hasOwnProperty('firewall')) {
11617         firewallOpts = {};
11618     }
11619     firewallOpts.localVMs = [vmobj];
11620 
11621     log.debug(firewallOpts, 'Adding firewall data');
11622     firewallVMrun(firewallOpts, fw.add, log, function (err, res) {
11623         if (err) {
11624             log.error(err, 'Error adding firewall data');
11625         }
11626 
11627         callback(err, res);
11628         return;
11629     });
11630 }
11631 
11632 function updateFirewallData(payload, vmobj, log, callback)
11633 {
11634     var enablePrefix = 'En';
11635     var enableFn = fw.enable;
11636     var firewallOpts = payload.firewall;
11637 
11638     assert(log, 'no logger passed to updateFirewallData()');
11639 
11640     if (!payload.hasOwnProperty('firewall')) {
11641         firewallOpts = {};
11642     }
11643     firewallOpts.localVMs = [vmobj];
11644 
11645     log.debug(firewallOpts, 'Updating firewall data');
11646     firewallVMrun(firewallOpts, fw.update, log, function (err, res) {
11647         if (err) {
11648             log.error(err, 'Error updating firewall data');
11649         }
11650 
11651         if (!payload.hasOwnProperty('firewall_enabled')) {
11652             callback(err, res);
11653             return;
11654         }
11655 
11656         if (!payload.firewall_enabled) {
11657             enableFn = fw.disable;
11658             enablePrefix = 'Dis';
11659         }
11660 
11661         log.debug('%sabling firewall for VM %s', enablePrefix, vmobj.uuid);
11662         firewallVMrun({ vm: vmobj }, enableFn, log, function (err2, res2) {
11663             if (err2) {
11664                 log.error(err, 'Error %sabling firewall',
11665                     enablePrefix.toLowerCase());
11666             }
11667 
11668             callback(err2, res2);
11669             return;
11670         });
11671     });
11672 }
11673 
11674 function restartMetadataService(vmobj, payload, log, callback) {
11675     var args;
11676 
11677     assert(log, 'no logger passed to restartMetadataService()');
11678 
11679     if (!BRAND_OPTIONS[vmobj.brand].hasOwnProperty('features')
11680         || !BRAND_OPTIONS[vmobj.brand].hasOwnProperty('features')
11681         || !BRAND_OPTIONS[vmobj.brand].features.mdata_restart) {
11682         log.debug('restarting mdata:fetch service not supported for brand '
11683             + vmobj.brand);
11684         callback();
11685         return;
11686     }
11687 
11688     if (vmobj.state !== 'running' || !payload.hasOwnProperty('resolvers')
11689         && !payload.hasOwnProperty('routes')
11690         && !payload.hasOwnProperty('set_routes')
11691         && !payload.hasOwnProperty('remove_routes')) {
11692         callback();
11693         return;
11694     }
11695 
11696     log.debug('restarting metadata service for: ' + vmobj.uuid);
11697 
11698     args = [vmobj.zonename, '/usr/sbin/svcadm', 'restart',
11699         'svc:/smartdc/mdata:fetch'];
11700     log.debug('/usr/sbin/zlogin ' + args.join(' '));
11701     execFile('/usr/sbin/zlogin', args, function (err, svc_stdout, svc_stderr) {
11702         if (err) {
11703             log.error({'err': err, 'stdout': svc_stdout,
11704                 'stderr': svc_stderr}, 'zlogin for ' + vmobj.zonename
11705                 + ' exited with code' + err.code + err.message);
11706             // error here is not fatal as this should be fixed on reboot
11707         }
11708 
11709         callback();
11710     });
11711 }
11712 
11713 function applyUpdates(oldobj, newobj, payload, log, callback)
11714 {
11715     var changed_datasets = false;
11716 
11717     assert(log, 'no logger passed to applyUpdates()');
11718 
11719     // Note: oldobj is the VM *before* the update, newobj *after*
11720     log.debug('applying updates to ' + oldobj.uuid);
11721 
11722     async.series([
11723         function (cb) {
11724             if (payload.hasOwnProperty('update_disks')
11725                 && oldobj.hasOwnProperty('disks')) {
11726 
11727                 resizeDisks(oldobj.disks, payload.update_disks, log,
11728                     function (err, resized) {
11729                         // If any were resized, mark that we changed something
11730                         if (!err && resized > 0) {
11731                             changed_datasets = true;
11732                         }
11733                         cb(err);
11734                     }
11735                 );
11736             } else {
11737                 cb();
11738             }
11739         }, function (cb) {
11740             if (payload.hasOwnProperty('quota')
11741                 && (Number(payload.quota) !== Number(oldobj.quota))) {
11742 
11743                 setQuota(newobj.zfs_filesystem, payload.quota, log,
11744                     function (err) {
11745 
11746                     if (!err) {
11747                         changed_datasets = true;
11748                     }
11749                     cb(err);
11750                 });
11751             } else {
11752                 cb();
11753             }
11754         }, function (cb) {
11755             // NOTE: we've already validated the value
11756             if (payload.hasOwnProperty('zfs_root_recsize')
11757                 && (payload.zfs_root_recsize !== oldobj.zfs_root_recsize)) {
11758 
11759                 zfs(['set', 'recsize=' + payload.zfs_root_recsize,
11760                     newobj.zfs_filesystem], log, function (err, fds) {
11761 
11762                     if (err) {
11763                         log.error(err, 'failed to apply zfs_root_recsize: '
11764                             + fds.stderr);
11765                         cb(new Error(rtrim(fds.stderr)));
11766                     } else {
11767                         cb();
11768                     }
11769                 });
11770             } else {
11771                 cb();
11772             }
11773         }, function (cb) {
11774             // NOTE: we've already validated the value.
11775             if (payload.hasOwnProperty('zfs_data_recsize')
11776                 && oldobj.hasOwnProperty('zfs_data_recsize')
11777                 && newobj.hasOwnProperty('datasets')
11778                 && (newobj.datasets.indexOf(newobj.zfs_filesystem
11779                     + '/data') !== -1)) {
11780 
11781                 zfs(['set', 'recsize=' + payload.zfs_data_recsize,
11782                     newobj.zfs_filesystem + '/data'], log, function (err, fds) {
11783 
11784                     if (err) {
11785                         log.error(err, 'failed to apply zfs_data_recsize: '
11786                             + fds.stderr);
11787                         cb(new Error(rtrim(fds.stderr)));
11788                     } else {
11789                         cb();
11790                     }
11791                 });
11792             } else {
11793                 cb();
11794             }
11795         }, function (cb) {
11796             // NOTE: we've already validated the value
11797             if (payload.hasOwnProperty('zfs_root_compression')
11798                 && (payload.zfs_root_compression !==
11799                     oldobj.zfs_root_compression)) {
11800 
11801                 zfs(['set', 'compression=' + payload.zfs_root_compression,
11802                     newobj.zfs_filesystem], log, function (err, fds) {
11803 
11804                     if (err) {
11805                         log.error(err, 'failed to apply '
11806                             + 'zfs_root_compression: ' + fds.stderr);
11807                         cb(new Error(rtrim(fds.stderr)));
11808                     } else {
11809                         cb();
11810                     }
11811                 });
11812             } else {
11813                 cb();
11814             }
11815         }, function (cb) {
11816             // NOTE: we've already validated the value
11817             if (payload.hasOwnProperty('zfs_data_compression')
11818                 && newobj.hasOwnProperty('datasets')
11819                 && (newobj.datasets.indexOf(newobj.zfs_filesystem
11820                     + '/data') !== -1)) {
11821 
11822                 zfs(['set', 'compression=' + payload.zfs_data_compression,
11823                     newobj.zfs_filesystem + '/data'], log, function (err, fds) {
11824 
11825                     if (err) {
11826                         log.error(err, 'failed to apply '
11827                             + 'zfs_data_compression: ' + fds.stderr);
11828                         cb(new Error(rtrim(fds.stderr)));
11829                     } else {
11830                         cb();
11831                     }
11832                 });
11833             } else {
11834                 cb();
11835             }
11836         }, function (cb) {
11837             var d;
11838             var disk;
11839             var zfs_updates = [];
11840 
11841             if (payload.hasOwnProperty('update_disks')) {
11842                 // loop through the disks we updated and perform any updates.
11843                 for (disk in payload.update_disks) {
11844                     disk = payload.update_disks[disk];
11845 
11846                     if (!disk) {
11847                         continue;
11848                     }
11849 
11850                     for (d in oldobj.disks) {
11851                         d = oldobj.disks[d];
11852                         if (d.path === disk.path
11853                             && d.hasOwnProperty('zfs_filesystem')) {
11854 
11855                             if (disk.hasOwnProperty('compression')) {
11856                                 zfs_updates.push({
11857                                     zfs_filesystem: d.zfs_filesystem,
11858                                     property: 'compression',
11859                                     value: disk.compression
11860                                 });
11861                             }
11862 
11863                             if (disk.hasOwnProperty('refreservation')) {
11864                                 zfs_updates.push({
11865                                     zfs_filesystem: d.zfs_filesystem,
11866                                     property: 'refreservation',
11867                                     value: disk.refreservation + 'M'
11868                                 });
11869                             }
11870                         }
11871                     }
11872                 }
11873                 if (zfs_updates.length > 0) {
11874                     log.debug('applying ' + zfs_updates.length
11875                         + ' zfs updates');
11876                     async.each(zfs_updates, function (props, f_cb) {
11877                         zfs(['set', props.property + '=' + props.value,
11878                             props.zfs_filesystem], log, function (err, fds) {
11879 
11880                             if (err) {
11881                                 log.error(err, 'failed to set ' + props.property
11882                                     + '=' + props.value + ' for '
11883                                     + props.zfs_filesystem);
11884                             }
11885                             f_cb(err);
11886                         });
11887                     }, function (err) {
11888                         log.debug({err: err}, 'end of zfs updates');
11889                         cb(err);
11890                     });
11891                 } else {
11892                     log.debug('no zfs updates to apply');
11893                     cb();
11894                 }
11895             } else {
11896                 cb();
11897             }
11898         }, function (cb) {
11899             var factor;
11900             var keys = [];
11901             var rctl;
11902             var rctls = {
11903                 'cpu_shares': ['zone.cpu-shares'],
11904                 'zfs_io_priority': ['zone.zfs-io-priority'],
11905                 'max_lwps': ['zone.max-lwps'],
11906                 'max_physical_memory': ['zone.max-physical-memory',
11907                     (1024 * 1024)],
11908                 'max_locked_memory': ['zone.max-locked-memory', (1024 * 1024)],
11909                 'max_swap': ['zone.max-swap', (1024 * 1024)],
11910                 'cpu_cap': ['zone.cpu-cap']
11911             };
11912 
11913             if (!BRAND_OPTIONS[oldobj.brand].features.update_rctls) {
11914                 cb();
11915                 return;
11916             }
11917 
11918             for (rctl in rctls) {
11919                 keys.push(rctl);
11920             }
11921 
11922             async.forEachSeries(keys, function (prop, c) {
11923                 rctl = rctls[prop][0];
11924                 if (rctls[prop][1]) {
11925                     factor = rctls[prop][1];
11926                 } else {
11927                     factor = 1;
11928                 }
11929 
11930                 if (payload.hasOwnProperty(prop)) {
11931                     setRctl(newobj.zonename, rctl,
11932                         Number(payload[prop]) * factor, log,
11933                         function (err) {
11934                             if (err) {
11935                                 log.warn(err, 'failed to set rctl: ' + prop);
11936                             }
11937                             c();
11938                         }
11939                     );
11940                 } else {
11941                     c();
11942                 }
11943             }, function (err) {
11944                 cb(err);
11945             });
11946         }, function (cb) {
11947             if ((payload.hasOwnProperty('vnc_password')
11948                 && (oldobj.vnc_password !== newobj.vnc_password))
11949                 || (payload.hasOwnProperty('vnc_port')
11950                     && (oldobj.vnc_port !== newobj.vnc_port))) {
11951 
11952                 // tell vmadmd to refresh_password and port (will restart
11953                 // listener)
11954                 postVmadmd(newobj.uuid, 'reload_display', {}, log,
11955                     function (e) {
11956 
11957                     if (e) {
11958                         cb(new Error('Unable to tell vmadmd to reload VNC: '
11959                             + e.message));
11960                     } else {
11961                         cb();
11962                     }
11963                 });
11964             } else if ((payload.hasOwnProperty('spice_password')
11965                 && (oldobj.spice_password !== newobj.spice_password))
11966                 || (payload.hasOwnProperty('spice_port')
11967                     && (oldobj.spice_port !== newobj.spice_port))) {
11968 
11969                 // tell vmadmd to refresh_password and port (will restart
11970                 // listener)
11971                 postVmadmd(newobj.uuid, 'reload_display', {}, log,
11972                     function (e) {
11973 
11974                     if (e) {
11975                         cb(new Error('Unable to tell vmadmd to reload SPICE: '
11976                             + e.message));
11977                     } else {
11978                         cb();
11979                     }
11980                 });
11981             } else {
11982                 cb();
11983             }
11984         }, function (cb) {
11985             // we do this last, since we need the memory in the zone updated
11986             // first if we're growing this.
11987             if (payload.hasOwnProperty('tmpfs')) {
11988                 resizeTmp(newobj.zonename, payload.tmpfs, log, cb);
11989             } else {
11990                 cb();
11991             }
11992         }, function (cb) {
11993             var now = new Date();
11994 
11995             // If we changed any dataset properties, we touch the zone's xml
11996             // file so that last_modified is correct.
11997             if (changed_datasets && newobj.hasOwnProperty('zonename')) {
11998                 fs.utimes('/etc/zones/' + newobj.zonename + '.xml', now, now,
11999                     function (err) {
12000                         if (err) {
12001                             log.warn(err, 'Unable to "touch" xml file for "'
12002                                 + newobj.zonename + '": ' + err.message);
12003                         } else {
12004                             log.debug('Touched ' + newobj.zonename
12005                                 + '.xml after datasets were modified.');
12006                         }
12007                         // We don't error out if we just couldn't touch because
12008                         // the actual updates above already did happen.
12009                         cb();
12010                     }
12011                 );
12012             } else {
12013                 cb();
12014             }
12015         }
12016 
12017     ], function (err, res) {
12018         log.debug('done applying updates to ' + oldobj.uuid);
12019         callback(err);
12020     });
12021 }
12022 
12023 exports.update = function (uuid, payload, options, callback)
12024 {
12025     var log;
12026     var new_vmobj;
12027     var vmobj;
12028     var unlock;
12029     var lockpath;
12030 
12031     // options parameter is optional
12032     if (arguments.length === 3) {
12033         callback = arguments[2];
12034         options = {};
12035     }
12036 
12037     ensureLogging(true);
12038     if (options.hasOwnProperty('log')) {
12039         log = options.log;
12040     } else {
12041         log = VM.log.child({action: 'update', vm: uuid});
12042     }
12043 
12044     log.info('Updating VM ' + uuid + ' with initial payload:\n'
12045         + JSON.stringify(payload, null, 2));
12046 
12047     async.series([
12048         function (cb) {
12049             lockpath = '/var/run/vm.' + uuid + '.config.lockfile';
12050             log.debug('acquiring lock on ' + lockpath);
12051             lock(lockpath, function (err, _unlock) {
12052                 log.debug('acquired lock on ' + lockpath);
12053                 if (err) {
12054                     cb(err);
12055                     return;
12056                 }
12057                 unlock = _unlock;
12058                 cb();
12059             });
12060         },
12061         function (cb) {
12062             // for update we currently always load the whole vmobj since the
12063             // update functions may need to look at bits from the existing VM.
12064             VM.load(uuid, {log: log}, function (err, obj) {
12065                 if (err) {
12066                     cb(err);
12067                     return;
12068                 }
12069                 vmobj = obj;
12070                 cb();
12071             });
12072         }, function (cb) {
12073             normalizePayload(payload, vmobj, log, function (e) {
12074                 log.debug('Used payload:\n'
12075                     + JSON.stringify(payload, null, 2));
12076                 cb(e);
12077             });
12078         }, function (cb) {
12079             var deletables = [];
12080             var to_remove = [];
12081             var n;
12082 
12083             // destroy remove_disks before we add in case we're recreating with
12084             // an existing name.
12085 
12086             if (payload.hasOwnProperty('remove_disks')) {
12087                 to_remove = payload.remove_disks;
12088                 for (n in vmobj.disks) {
12089                     if (to_remove.indexOf(vmobj.disks[n].path) !== -1) {
12090                         deletables.push(vmobj.disks[n]);
12091                     }
12092                 }
12093             } else {
12094                 // no disks to remove so all done.
12095                 cb();
12096                 return;
12097             }
12098 
12099             function loggedDeleteVolume(volume, callbk) {
12100                 return deleteVolume(volume, log, callbk);
12101             }
12102 
12103             async.forEachSeries(deletables, loggedDeleteVolume,
12104                 function (err) {
12105                     if (err) {
12106                         log.error(err, 'Unknown error deleting volumes: '
12107                             + err.message);
12108                         cb(err);
12109                     } else {
12110                         log.info('successfully deleted volumes');
12111                         cb();
12112                     }
12113                 }
12114             );
12115         }, function (cb) {
12116             var disks = [];
12117             var matches;
12118             var n;
12119             var p;
12120             var used_disk_indexes = [];
12121 
12122             // create any new volumes we need.
12123             if (payload.hasOwnProperty('add_disks')) {
12124                 disks = payload.add_disks;
12125             }
12126 
12127             // create a list of used indexes so we can find the free ones to
12128             // use in createVolume()
12129             if (vmobj.hasOwnProperty('disks')) {
12130                 for (n in vmobj.disks) {
12131                     matches = vmobj.disks[n].path.match(/^.*-disk(\d+)$/);
12132                     if (matches) {
12133                         used_disk_indexes.push(Number(matches[1]));
12134                     }
12135                 }
12136             }
12137 
12138             // add the bits of payload createVolumes() needs.
12139             p = {'add_disks': disks};
12140             p.uuid = uuid;
12141             if (vmobj.hasOwnProperty('zpool')) {
12142                 p.zpool = vmobj.zpool;
12143             }
12144             p.used_disk_indexes = used_disk_indexes;
12145             createVolumes(p, log, function (e) {
12146                 cb(e);
12147             });
12148         }, function (cb) {
12149             updateMetadata(vmobj, payload, log, function (e) {
12150                 cb(e);
12151             });
12152         }, function (cb) {
12153             updateRoutes(vmobj, payload, log, function (e) {
12154                 cb(e);
12155             });
12156         }, function (cb) {
12157             var zcfg;
12158             // generate a payload and send as a file to zonecfg to update
12159             // the zone.
12160             zcfg = buildZonecfgUpdate(vmobj, payload, log);
12161             zonecfgFile(zcfg, ['-u', uuid], log, function (e, fds) {
12162                 if (e) {
12163                     log.error({err: e, stdout: fds.stdout, stderr: fds.stderr},
12164                         'unable to update zonecfg');
12165                 } else {
12166                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
12167                         'updated zonecfg');
12168                 }
12169                 cb(e);
12170             });
12171         }, function (cb) {
12172             restartMetadataService(vmobj, payload, log, function (e) {
12173                 cb(e);
12174             });
12175         }, function (cb) {
12176             updateVnicProperties(uuid, vmobj, payload, log, function (e) {
12177                 cb(e);
12178             });
12179         }, function (cb) {
12180             // Update the firewall data
12181             updateFirewallData(payload, vmobj, log, cb);
12182         }, function (cb) {
12183             // Do another full reload (all fields) so we can compare in
12184             // applyUpdates() and decide what's changed that we need to apply.
12185             VM.load(uuid, {log: log}, function (e, newobj) {
12186                 if (e) {
12187                     cb(e);
12188                 } else {
12189                     new_vmobj = newobj;
12190                     cb();
12191                 }
12192             });
12193         }, function (cb) {
12194             applyUpdates(vmobj, new_vmobj, payload, log, function () {
12195                 cb();
12196             });
12197         }
12198     ], function (e) {
12199         // If we were able to hold the lockfile, and thus have an unlock
12200         // callback, we must call it before returning, whether or not
12201         // there was an error.
12202         if (unlock) {
12203             log.debug('releasing lock on ' + lockpath);
12204             unlock(function (unlock_err) {
12205                 if (unlock_err) {
12206                     log.error(err, 'unlock error! (path ' + lockpath + ')');
12207                 } else {
12208                     log.debug('released lock on ' + lockpath);
12209                 }
12210                 callback(e);
12211             });
12212         } else {
12213             callback(e);
12214         }
12215     });
12216 };
12217 
12218 function kill(uuid, log, callback)
12219 {
12220     var load_fields;
12221     var unset_autoboot = 'set autoboot=false';
12222 
12223     assert(log, 'no logger passed to kill()');
12224 
12225     log.info('Killing VM ' + uuid);
12226 
12227     load_fields = [
12228         'brand',
12229         'state',
12230         'transition_to',
12231         'uuid'
12232     ];
12233 
12234     /* We load here to ensure this vm exists. */
12235     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
12236         if (err) {
12237             callback(err);
12238             return;
12239         }
12240 
12241         if (BRAND_OPTIONS[vmobj.brand].features.use_vm_autoboot) {
12242             unset_autoboot =
12243                 'select attr name=vm-autoboot; set value=false; end';
12244         }
12245 
12246         zoneadm(['-u', uuid, 'halt', '-X'], log, function (e, fds) {
12247             var msg = trim(fds.stderr);
12248 
12249             if (msg.match(/zone is already halted$/)) {
12250                 // remove transition marker, since vm is not running now.
12251                 VM.unsetTransition(vmobj, {log: log}, function () {
12252                     var new_err;
12253 
12254                     new_err = new Error('VM ' + vmobj.uuid + ' is already '
12255                         + 'not \'running\' (currently: ' + vmobj.state + ')');
12256                     new_err.code = 'ENOTRUNNING';
12257                     callback(new_err);
12258                 });
12259             } else if (e) {
12260                 log.error({err: e, stdout: fds.stdout, stderr: fds.stderr},
12261                     'failed to halt VM ' + uuid);
12262                 callback(err, msg);
12263             } else {
12264                 log.debug({stdout: fds.stdout, stderr: fds.stderr},
12265                     'zoneadm halted VM ' + uuid);
12266                 zonecfg(['-u', uuid, unset_autoboot], log,
12267                     function (error, unset_fds) {
12268 
12269                     if (error) {
12270                         // The vm is dead at this point, erroring out here would
12271                         // do no good, so we just log it.
12272                         log.error({err: error, stdout: unset_fds.stdout,
12273                             stderr: unset_fds.stderr}, 'killVM(): Failed to '
12274                             + unset_autoboot);
12275                     } else {
12276                         log.debug({stdout: unset_fds.stdout,
12277                             stderr: unset_fds.stderr}, 'unset autoboot flag');
12278                     }
12279                     if (vmobj.state === 'stopping') {
12280                         // remove transition marker
12281                         VM.unsetTransition(vmobj, {log: log}, function () {
12282                             callback(null, msg);
12283                         });
12284                     } else {
12285                         callback(null, msg);
12286                     }
12287                 });
12288             }
12289         });
12290     });
12291 }
12292 
12293 function postVmadmd(uuid, action, args, log, callback)
12294 {
12295     var arg;
12296     var url_path = '/vm/' + uuid + '?action=' + action;
12297     var req;
12298 
12299     assert(log, 'no logger passed to postVmadmd()');
12300 
12301     if (args) {
12302         for (arg in args) {
12303             if (args.hasOwnProperty(arg)) {
12304                 url_path = url_path + '&' + arg + '=' + args[arg];
12305             }
12306         }
12307     }
12308 
12309     log.debug('HTTP POST ' + url_path);
12310     req = http.request(
12311         { method: 'POST', host: '127.0.0.1', port: '8080', path: url_path },
12312         function (res) {
12313 
12314             log.debug('HTTP STATUS: ' + res.statusCode);
12315             log.debug('HTTP HEADERS: ' + JSON.stringify(res.headers));
12316             res.setEncoding('utf8');
12317             res.on('data', function (chunk) {
12318                 log.debug('HTTP BODY: ' + chunk);
12319             });
12320             res.on('end', function () {
12321                 log.debug('HTTP conversation has completed.');
12322                 callback();
12323             });
12324         }
12325     );
12326     req.on('error', function (e) {
12327         log.error(e, 'HTTP error: ' + e.message);
12328         callback(e);
12329     });
12330     req.end();
12331 }
12332 
12333 // options parameter is *REQUIRED* for VM.stop()
12334 exports.stop = function (uuid, options, callback)
12335 {
12336     var load_fields;
12337     var log;
12338     var unset_autoboot = 'set autoboot=false';
12339     var vmobj;
12340 
12341     load_fields = [
12342         'brand',
12343         'state',
12344         'uuid',
12345         'zonename'
12346     ];
12347 
12348     if (!options) {
12349         options = {};
12350     }
12351 
12352     if (options.hasOwnProperty('force') && options.force) {
12353         ensureLogging(true);
12354         if (options.hasOwnProperty('log')) {
12355             log = options.log;
12356         } else {
12357             log = VM.log.child({action: 'stop-F', vm: uuid});
12358         }
12359         kill(uuid, log, callback);
12360         return;
12361     } else {
12362         ensureLogging(true);
12363         if (options.hasOwnProperty('log')) {
12364             log = options.log;
12365         } else {
12366             log = VM.log.child({action: 'stop', vm: uuid});
12367         }
12368     }
12369 
12370     log.info('Stopping VM ' + uuid);
12371 
12372     if (!options.timeout) {
12373         options.timeout = 180;
12374     }
12375     if (!options.transition_to) {
12376         options.transition_to = 'stopped';
12377     }
12378 
12379     async.series([
12380         function (cb) {
12381             /* We load here to ensure this vm exists. */
12382             VM.load(uuid, {log: log, fields: load_fields}, function (err, obj) {
12383                 var new_err;
12384 
12385                 if (err) {
12386                     log.error(err);
12387                     cb(err);
12388                     return;
12389                 } else {
12390                     vmobj = obj;
12391                     if (vmobj.state !== 'running') {
12392                         new_err = new Error('VM ' + vmobj.uuid + ' is already '
12393                             + 'not \'running\' (currently: ' + vmobj.state
12394                             + ')');
12395                         new_err.code = 'ENOTRUNNING';
12396                         cb(new_err);
12397                     } else {
12398                         cb();
12399                     }
12400                 }
12401             });
12402         }, function (cb) {
12403             // When stopping a VM that uses vm_autoboot, we assume we also do
12404             // the stop through vmadmd.
12405             if (BRAND_OPTIONS[vmobj.brand].features.use_vm_autoboot) {
12406                 async.series([
12407                     function (callbk) {
12408                         setTransition(vmobj, 'stopping', options.transition_to,
12409                             (options.timeout * 1000), log, function (err) {
12410 
12411                             callbk(err);
12412                         });
12413                     }, function (callbk) {
12414                         postVmadmd(vmobj.uuid, 'stop',
12415                             {'timeout': options.timeout}, log, function (err) {
12416 
12417                             if (err) {
12418                                 log.error(err);
12419                                 err.message = 'Unable to post "stop" to vmadmd:'
12420                                     + ' ' + err.message;
12421                             }
12422                             callbk(err);
12423                         });
12424                     }, function (callbk) {
12425 
12426                         // different version for VMs
12427                         unset_autoboot = 'select attr name=vm-autoboot; '
12428                             + 'set value=false; end';
12429 
12430                         zonecfg(['-u', uuid, unset_autoboot], log,
12431                             function (err, fds) {
12432                                 if (err) {
12433                                     // The vm is dead at this point, failing
12434                                     // here would do no good, so we just log it.
12435                                     log.error({err: err, stdout: fds.stdout,
12436                                         stderr: fds.stderr}, 'stop(): Failed to'
12437                                         + ' ' + unset_autoboot + ' for ' + uuid
12438                                         + ': ' + err.message);
12439                                 } else {
12440                                     log.info({stdout: fds.stdout,
12441                                         stderr: fds.stderr}, 'Stopped ' + uuid);
12442                                 }
12443                                 callbk();
12444                             }
12445                         );
12446                     }
12447                 ], function (err) {
12448                     cb(err);
12449                 });
12450             } else { // no vm_autoboot / vmadmd stop
12451                 cb();
12452             }
12453         }, function (cb) {
12454             var args;
12455 
12456             // joyent brand specific stuff
12457             args = [vmobj.zonename, '/usr/sbin/shutdown', '-y', '-g', '0',
12458                 '-i', '5'];
12459 
12460             // not using vm_autoboot means using the 'normal' boot process
12461             if (!BRAND_OPTIONS[vmobj.brand].features.use_vm_autoboot) {
12462                 async.series([
12463                     function (callbk) {
12464                         log.debug('/usr/sbin/zlogin ' + args.join(' '));
12465                         execFile('/usr/sbin/zlogin', args,
12466                             function (err, stdout, stderr) {
12467 
12468                             if (err) {
12469                                 log.error({'err': err, 'stdout': stdout,
12470                                     'stderr': stderr}, 'zlogin for '
12471                                     + vmobj.zonename + ' exited with code'
12472                                     + err.code + ': ' + err.message);
12473                                 callbk(err);
12474                             } else {
12475                                 log.debug('zlogin claims to have worked, '
12476                                     + 'stdout:\n' + stdout + '\nstderr:\n'
12477                                     + stderr);
12478                                 callbk();
12479                             }
12480                         });
12481                     }, function (callbk) {
12482                         zonecfg(['-u', uuid, unset_autoboot], log,
12483                             function (err, fds) {
12484                                 if (err) {
12485                                     // The vm is dead at this point, failing
12486                                     // do no good, so we just log it.
12487                                     log.warn({err: err, stdout: fds.stdout,
12488                                         stderr: fds.stderr}, 'Failed to '
12489                                         + unset_autoboot + ' for ' + uuid);
12490                                 } else {
12491                                     log.info({stdout: fds.stdout,
12492                                         stderr: fds.stderr}, 'Stopped ' + uuid);
12493                                 }
12494                                 callbk();
12495                             }
12496                         );
12497                     }
12498                 ], function (err) {
12499                     cb(err);
12500                 });
12501             } else { // using vmautoboot so won't shutdown from in the zone
12502                 cb();
12503             }
12504         }, function (cb) {
12505             // Verify it's shut down
12506             VM.waitForZoneState(vmobj, 'installed', {log: log},
12507                 function (err, result) {
12508 
12509                 if (err) {
12510                     if (err.code === 'ETIMEOUT') {
12511                         log.info(err, 'timeout waiting for zone to go to '
12512                             + '"installed"');
12513                     } else {
12514                         log.error(err, 'unknown error waiting for zone to go'
12515                             + ' "installed"');
12516                     }
12517                     cb(err);
12518                 } else {
12519                     // zone got to stopped
12520                     log.info('VM seems to have switched to "installed"');
12521                     cb();
12522                 }
12523             });
12524         }
12525     ], function (err) {
12526         callback(err);
12527     });
12528 };
12529 
12530 // sends several query-* commands to QMP to get details for a VM
12531 exports.info = function (uuid, types, options, callback)
12532 {
12533     var load_fields;
12534     var log;
12535 
12536     // options is optional
12537     if (arguments.length === 3) {
12538         callback = arguments[2];
12539         options = {};
12540     }
12541 
12542     ensureLogging(false);
12543     if (options.hasOwnProperty('log')) {
12544         log = options.log;
12545     } else {
12546         log = VM.log.child({action: 'info', vm: uuid});
12547     }
12548 
12549     load_fields = [
12550         'brand',
12551         'state',
12552         'uuid'
12553     ];
12554 
12555     // load to ensure we're a VM
12556     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
12557         var type;
12558 
12559         if (err) {
12560             callback(err);
12561             return;
12562         }
12563 
12564         if (!BRAND_OPTIONS[vmobj.brand].features.runtime_info) {
12565             //  XXX if support is added to other brands, update this message.
12566             callback(new Error('the info command is only supported for KVM '
12567                 + 'VMs'));
12568             return;
12569         }
12570 
12571         if (vmobj.state !== 'running' && vmobj.state !== 'stopping') {
12572             callback(new Error('Unable to get info for vm from state "'
12573                 + vmobj.state + '", must be "running" or "stopping".'));
12574             return;
12575         }
12576 
12577         if (!types) {
12578             types = ['all'];
12579         }
12580 
12581         for (type in types) {
12582             type = types[type];
12583             if (VM.INFO_TYPES.indexOf(type) === -1) {
12584                 callback(new Error('unknown info type: ' + type));
12585                 return;
12586             }
12587         }
12588 
12589         http.get({ host: '127.0.0.1', port: 8080, path: '/vm/' + uuid + '/info'
12590             + '?types=' + types.join(',') }, function (res) {
12591 
12592                 var data = '';
12593 
12594                 if (res.statusCode !== 200) {
12595                     callback(new Error('Unable to get info from vmadmd, query '
12596                         + 'returned ' + res.statusCode + '.'));
12597                 } else {
12598                     res.on('data', function (d) {
12599                         data = data + d.toString();
12600                     });
12601                     res.on('end', function (d) {
12602                         callback(null, JSON.parse(data));
12603                     });
12604                 }
12605             }
12606         ).on('error', function (e) {
12607             log.error(e);
12608             callback(e);
12609         });
12610     });
12611 };
12612 
12613 function reset(uuid, log, callback)
12614 {
12615     var load_fields;
12616 
12617     assert(log, 'no logger passed to reset()');
12618 
12619     log.info('Resetting VM ' + uuid);
12620 
12621     load_fields = [
12622         'brand',
12623         'state',
12624         'uuid'
12625     ];
12626 
12627     /* We load here to ensure this vm exists. */
12628     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
12629         if (err) {
12630             callback(err);
12631             return;
12632         }
12633 
12634         if (vmobj.state !== 'running') {
12635             callback(new Error('Cannot reset vm from state "'
12636                 + vmobj.state + '", must be "running".'));
12637             return;
12638         }
12639 
12640         if (BRAND_OPTIONS[vmobj.brand].features.use_vmadmd) {
12641             postVmadmd(vmobj.uuid, 'reset', {}, log, function (e) {
12642                 if (e) {
12643                     callback(new Error('Unable to post "reset" to '
12644                         + 'vmadmd: ' + e.message));
12645                 } else {
12646                     callback();
12647                 }
12648             });
12649         } else {
12650             zoneadm(['-u', vmobj.uuid, 'reboot', '-X'], log, function (e, fds) {
12651                 if (e) {
12652                     log.warn({err: e, stdout: fds.stdout, stderr: fds.stderr},
12653                         'zoneadm failed to reboot VM ' + vmobj.uuid);
12654                     callback(new Error(rtrim(fds.stderr)));
12655                 } else {
12656                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
12657                         'zoneadm rebooted VM ' + vmobj.uuid);
12658                     callback();
12659                 }
12660             });
12661         }
12662     });
12663 }
12664 
12665 // options is *REQUIRED* for VM.reboot()
12666 exports.reboot = function (uuid, options, callback)
12667 {
12668     var cleanup;
12669     var log;
12670     var reboot_async = false;
12671     var reboot_complete = false;
12672     var vmobj;
12673 
12674     if (options.hasOwnProperty('log')) {
12675         log = options.log;
12676     }
12677 
12678     if (options.hasOwnProperty('force') && options.force) {
12679         ensureLogging(true);
12680         if (!log) {
12681             log = VM.log.child({action: 'reboot-F', vm: uuid});
12682         }
12683         reset(uuid, log, callback);
12684         return;
12685     } else {
12686         ensureLogging(true);
12687         log = VM.log.child({action: 'reboot', vm: uuid});
12688     }
12689 
12690     log.info('Rebooting VM ' + uuid);
12691 
12692     if (!options) {
12693         options = {};
12694     }
12695 
12696     async.series([
12697         function (cb) {
12698             var load_fields = [
12699                 'brand',
12700                 'nics',
12701                 'state',
12702                 'zonename'
12703             ];
12704 
12705             VM.load(uuid, {fields: load_fields, log: log},
12706                 function (err, obj) {
12707 
12708                 if (err) {
12709                     cb(err);
12710                     return;
12711                 }
12712 
12713                 if (obj.state !== 'running') {
12714                     cb(new Error('Cannot reboot vm from state "' + obj.state
12715                         + '", must be "running"'));
12716                     return;
12717                 }
12718 
12719                 vmobj = obj;
12720                 cb();
12721             });
12722         }, function (cb) {
12723             // If nic tags have disappeared out from under us, don't allow a
12724             // reboot that will put us into a bad state
12725             lookupInvalidNicTags(vmobj.nics, log, function (e) {
12726                 if (e) {
12727                     cb(new Error('Cannot reboot vm: ' + e.message));
12728                     return;
12729                 }
12730 
12731                 cb();
12732             });
12733 
12734         }, function (cb) {
12735             var watcherobj;
12736 
12737             if (!reboot_async) {
12738                 watcherobj = watchZoneTransitions(function (err, ze) {
12739                     if (!err && ze.zonename !== vmobj.zonename) {
12740                         // not something we need to handle
12741                         return;
12742                     }
12743 
12744                     if (err) {
12745                         // XXX what should we do here?
12746                         log.error(err);
12747                         return;
12748                     }
12749 
12750                     log.debug(ze); // TODO move to trace
12751 
12752                     if (ze.newstate === 'running'
12753                         && ze.oldstate !== 'running') {
12754 
12755                         if (watcherobj) {
12756                             // cleanup our watcher since we found what we're
12757                             // looking for.
12758                             cleanup();
12759                         }
12760 
12761                         reboot_complete = true;
12762                     }
12763                 }, log);
12764                 cleanup = watcherobj.cleanup;
12765             }
12766 
12767             cb();
12768         }, function (cb) {
12769             var args;
12770 
12771             if (BRAND_OPTIONS[vmobj.brand].features.use_vmadmd) {
12772                 // here we stop the machine and set a transition so vmadmd will
12773                 // start the machine once the stop finished.
12774                 options.transition_to = 'start';
12775                 options.log = log;
12776                 VM.stop(uuid, options, function (err) {
12777                     if (err) {
12778                         cb(err);
12779                     } else {
12780                         cb();
12781                     }
12782                 });
12783             } else {
12784                 // joyent branded zones
12785                 args = [vmobj.zonename, '/usr/sbin/shutdown', '-y', '-g', '0',
12786                     '-i', '6'];
12787                 log.debug('/usr/sbin/zlogin ' + args.join(' '));
12788                 execFile('/usr/sbin/zlogin', args,
12789                     function (err, stdout, stderr) {
12790                     if (err) {
12791                         log.error({'err': err, 'stdout': stdout,
12792                             'stderr': stderr}, 'zlogin for ' + vmobj.zonename
12793                             + ' exited with code' + err.code + ': '
12794                             + err.message);
12795                         cb(err);
12796                     } else {
12797                         cb();
12798                     }
12799                 });
12800             }
12801         }, function (cb) {
12802             var ival;
12803             var ticks = 0;
12804 
12805             if (reboot_async) {
12806                 cb();
12807                 return;
12808             } else {
12809                 ticks = 180 * 10; // (180 * 10) 100ms ticks = 3m
12810                 ival = setInterval(function () {
12811                     if (reboot_complete) {
12812                         log.debug('reboot marked complete, cleaning up');
12813                         clearInterval(ival);
12814                         cleanup();
12815                         cb();
12816                         return;
12817                     }
12818                     ticks--;
12819                     if (ticks <= 0) {
12820                         // timed out
12821                         log.debug('reboot timed out, cleaning up');
12822                         clearInterval(ival);
12823                         cleanup();
12824                         cb(new Error('timed out waiting for zone to reboot'));
12825                         return;
12826                     }
12827                 }, 100);
12828             }
12829         }
12830     ], function (err) {
12831         callback(err);
12832     });
12833 };
12834 
12835 // options is *REQUIRED* for VM.sysrq
12836 exports.sysrq = function (uuid, req, options, callback)
12837 {
12838     var load_fields;
12839     var log;
12840 
12841     ensureLogging(true);
12842 
12843     if (options.hasOwnProperty('log')) {
12844         log = options.log;
12845     } else {
12846         log = VM.log.child({action: 'sysrq-' + req, vm: uuid});
12847     }
12848 
12849     log.info('Sending sysrq "' + req + '" to ' + uuid);
12850 
12851     load_fields = [
12852         'brand',
12853         'state',
12854         'uuid'
12855     ];
12856 
12857     /* We load here to ensure this vm exists. */
12858     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
12859         if (err) {
12860             callback(err);
12861             return;
12862         }
12863 
12864         if (vmobj.state !== 'running' && vmobj.state !== 'stopping') {
12865             callback(new Error('Unable to send request to vm from state "'
12866                 + vmobj.state + '", must be "running" or "stopping".'));
12867             return;
12868         }
12869 
12870         if (BRAND_OPTIONS[vmobj.brand].features.type !== 'KVM') {
12871             callback(new Error('The sysrq command is only supported for KVM.'));
12872             return;
12873         }
12874 
12875         if (VM.SYSRQ_TYPES.indexOf(req) === -1) {
12876             callback(new Error('Invalid sysrq "' + req + '" valid values: '
12877                 + '"' + VM.SYSRQ_TYPES.join('","') + '".'));
12878             return;
12879         }
12880 
12881         postVmadmd(vmobj.uuid, 'sysrq', {'request': req}, log, function (e) {
12882             if (e) {
12883                 callback(new Error('Unable to post "sysrq" to vmadmd: '
12884                     + e.message));
12885             } else {
12886                 callback();
12887             }
12888         });
12889     });
12890 };
12891 
12892 exports.console = function (uuid, options, callback)
12893 {
12894     var load_fields;
12895     var log;
12896 
12897     // options is optional
12898     if (arguments.length === 2) {
12899         callback = arguments[1];
12900         options = {};
12901     }
12902 
12903     ensureLogging(false);
12904     if (options.hasOwnProperty('log')) {
12905         log = options.log;
12906     } else {
12907         log = VM.log.child({action: 'console', vm: uuid});
12908     }
12909 
12910     load_fields = [
12911         'brand',
12912         'state',
12913         'zonename',
12914         'zonepath'
12915     ];
12916 
12917     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
12918         var args;
12919         var child;
12920         var cmd;
12921         var stty;
12922 
12923         if (err) {
12924             callback(err);
12925             return;
12926         }
12927         if (vmobj.state !== 'running') {
12928             callback(new Error('cannot connect to console when state is '
12929                 + '"' + vmobj.state + '" must be "running".'));
12930             return;
12931         }
12932 
12933         if (BRAND_OPTIONS[vmobj.brand].features.zlogin_console) {
12934             cmd = '/usr/sbin/zlogin';
12935             args = ['-C', '-e', '\\035', vmobj.zonename];
12936 
12937             log.debug(cmd + ' ' + args.join(' '));
12938             child = spawn(cmd, args, {customFds: [0, 1, 2]});
12939             child.on('close', function (code) {
12940                 log.debug('zlogin process exited with code ' + code);
12941                 callback();
12942             });
12943         } else if (BRAND_OPTIONS[vmobj.brand].features.serial_console) {
12944             async.series([
12945                 function (cb) {
12946                     cmd = '/usr/bin/stty';
12947                     args = ['-g'];
12948                     stty = '';
12949 
12950                     log.debug(cmd + ' ' + args.join(' '));
12951                     child = spawn(cmd, args, {customFds: [0, -1, -1]});
12952                     child.stdout.on('data', function (data) {
12953                         // log.debug('data: ' + data.toString());
12954                         stty = data.toString();
12955                     });
12956                     child.on('close', function (code) {
12957                         log.debug('stty process exited with code ' + code);
12958                         cb();
12959                     });
12960                 }, function (cb) {
12961                     cmd = '/usr/bin/socat';
12962                     args = ['unix-client:' + vmobj.zonepath
12963                         + '/root/tmp/vm.console', '-,raw,echo=0,escape=0x1d'];
12964 
12965                     log.debug(cmd + ' ' + args.join(' '));
12966                     child = spawn(cmd, args, {customFds: [0, 1, 2]});
12967                     child.on('close', function (code) {
12968                         log.debug('zlogin process exited with code ' + code);
12969                         cb();
12970                     });
12971                 }, function (cb) {
12972                     cmd = '/usr/bin/stty';
12973                     args = [stty];
12974 
12975                     log.debug(cmd + ' ' + args.join(' '));
12976                     child = spawn(cmd, args, {customFds: [0, -1, -1]});
12977                     child.on('close', function (code) {
12978                         log.debug('stty process exited with code ' + code);
12979                         cb();
12980                     });
12981                 }
12982             ], function (e, results) {
12983                 callback(e);
12984             });
12985         } else {
12986             callback(new Error('Cannot get console for brand: ' + vmobj.brand));
12987         }
12988     });
12989 };