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 EventEmitter = require('events').EventEmitter;
  71 var exec = cp.exec;
  72 var execFile = cp.execFile;
  73 var expat = require('/usr/node/node_modules/node-expat');
  74 var fs = require('fs');
  75 var fw = require('/usr/fw/lib/fw');
  76 var http = require('http');
  77 var net = require('net');
  78 var path = require('path');
  79 var Qmp = require('/usr/vm/node_modules/qmp').Qmp;
  80 var spawn = cp.spawn;
  81 var sprintf = require('/usr/node/node_modules/sprintf').sprintf;
  82 var tty = require('tty');
  83 var util = require('util');
  84 
  85 var log_to_file = false;
  86 
  87 // keep the last 512 messages just in case we end up wanting them.
  88 var ringbuffer = new bunyan.RingBuffer({ limit: 512 });
  89 
  90 // zfs_list_queue variables for the serialization of 'zfs list' calls
  91 var zfs_list_in_progress = {};
  92 var zfs_list_queue;
  93 
  94 // global handle for the zoneevent watcher
  95 var zoneevent;
  96 
  97 /*
  98  * zone states from libzonecfg/common/zonecfg_impl.h
  99  *
 100  * #define ZONE_STATE_STR_CONFIGURED       "configured"
 101  * #define ZONE_STATE_STR_INCOMPLETE       "incomplete"
 102  * #define ZONE_STATE_STR_INSTALLED        "installed"
 103  * #define ZONE_STATE_STR_READY            "ready"
 104  * #define ZONE_STATE_STR_MOUNTED          "mounted"
 105  * #define ZONE_STATE_STR_RUNNING          "running"
 106  * #define ZONE_STATE_STR_SHUTTING_DOWN    "shutting_down"
 107  * #define ZONE_STATE_STR_DOWN             "down"
 108  *
 109  */
 110 
 111 exports.FLATTENABLE_ARRAYS = [
 112     'resolvers'
 113 ];
 114 exports.FLATTENABLE_ARRAY_HASH_KEYS = [
 115     'disks',
 116     'nics'
 117 ];
 118 exports.FLATTENABLE_HASH_KEYS = [
 119     'customer_metadata',
 120     'internal_metadata',
 121     'routes',
 122     'tags'
 123 ];
 124 
 125 var DEFAULT_MDATA_TIMEOUT = 300;
 126 var DISABLED = 0;
 127 var MAX_SNAPNAME_LENGTH = 64;
 128 var MINIMUM_MAX_SWAP = 256;
 129 var PROVISION_TIMEOUT = 300;
 130 var STOP_TIMEOUT = 60;
 131 var VM = this;
 132 
 133 VM.log = null;
 134 
 135 // can be (re)set by loader before we start.
 136 exports.logger = null;
 137 exports.loglevel = 'debug';
 138 
 139 // OpenOnErrorFileStream is a bunyan stream that only creates the file when
 140 // there's an error or higher level message or when the global log_to_file
 141 // variable is set. For actions that modify things log_to_file is always set.
 142 // For other actions we shouldn't log in the normal case but where we do want
 143 // logs when something breaks. Thanks to Trent++ for most of this code.
 144 //
 145 // Note: if you want to rotate the logs while this is writing to a file, you
 146 // can first move it. The watcher will notice that the log file was moved and
 147 // reopen a new file with the original name.
 148 
 149 function OpenOnErrorFileStream(filename) {
 150     this.path = filename;
 151     this.write = this.constructor.prototype.write1;
 152     this.end = this.constructor.prototype.end1;
 153     this.emit = this.constructor.prototype.emit1;
 154     this.once = this.constructor.prototype.once1;
 155 
 156     this.newStream = function () {
 157         var self = this;
 158         var watcher;
 159 
 160         self.stream = fs.createWriteStream(self.path,
 161             {flags: 'a', encoding: 'utf8'});
 162 
 163         watcher = fs.watch(self.path, {persistent: false}, function (evt) {
 164             if (evt != 'rename') {
 165                 return;
 166             }
 167             // file was renamed, we want to reopen.
 168             if (self.stream) {
 169                 self.stream.destroySoon();
 170             }
 171             watcher.close();
 172             self.stream = null;
 173         });
 174     };
 175 }
 176 
 177 OpenOnErrorFileStream.prototype.end1 = function () {
 178     // in initial mode we're not writing anything, so nothing to flush
 179     return;
 180 };
 181 
 182 OpenOnErrorFileStream.prototype.emit1 = function () {
 183     return;
 184 };
 185 
 186 // Warning: never emits anything
 187 OpenOnErrorFileStream.prototype.once1 = function () {
 188     return;
 189 };
 190 
 191 // used until first ERROR or higher, then opens file and ensures future writes
 192 // go to .write2()
 193 OpenOnErrorFileStream.prototype.write1 = function (rec) {
 194     var r;
 195     var stream;
 196 
 197     if (rec.level >= bunyan.ERROR || log_to_file) {
 198         if (! this.stream) {
 199             this.newStream();
 200         }
 201 
 202         stream = this.stream;
 203 
 204         this.emit = function () { stream.emit.apply(stream, arguments); };
 205         this.end = function () { stream.end.apply(stream, arguments); };
 206         this.once = function () { stream.once.apply(stream, arguments); };
 207         this.write = this.constructor.prototype.write2;
 208         // dump out logs from ringbuffer too since there was an error so we can
 209         // figure out what's going on.
 210         for (r in ringbuffer.records) {
 211             r = ringbuffer.records[r];
 212             if (r != rec) {
 213                 this.write(r);
 214             }
 215         }
 216 
 217         this.write(rec);
 218     }
 219 
 220     // This write doesn't fail (since it's going to memory or nowhere) so we
 221     // always return true so that callers don't try to wait for 'drain' which
 222     // we'll not emit.
 223     return true;
 224 };
 225 
 226 // used when writing to file
 227 OpenOnErrorFileStream.prototype.write2 = function (rec) {
 228     var str;
 229 
 230     // need to support writing '' so we know when to drain
 231     if (typeof (rec) === 'string' && rec.length < 1) {
 232         str = '';
 233     } else {
 234         str = JSON.stringify(rec, bunyan.safeCycles()) + '\n';
 235     }
 236 
 237     if (! this.stream) {
 238         this.newStream();
 239     }
 240 
 241     return this.stream.write(str);
 242 };
 243 
 244 // This function should be called by any exported function from this module.
 245 // It ensures that a logger is setup. If side_effects is true, we'll start
 246 // writing log messages to the file right away. If not, we'll only start
 247 // logging after we hit a message error or higher. This is intended such that
 248 // things that are expected to change the state or modify VMs on the system:
 249 // eg. create, start, stop, delete should have this set true.  It should be
 250 // set false when the action should not cause changes to the system:
 251 // eg.: load, lookup, info, console, &c.
 252 function ensureLogging(side_effects)
 253 {
 254     side_effects = !!side_effects; // make it boolean (undef === false)
 255 
 256     var filename;
 257     var logname;
 258     var streams = [];
 259 
 260     function start_logging() {
 261         var params = {
 262             name: logname,
 263             streams: streams,
 264             serializers: bunyan.stdSerializers
 265         };
 266 
 267         if (process.env.REQ_ID) {
 268             params.req_id = process.env.REQ_ID;
 269         } else if (process.env.req_id) {
 270             params.req_id = process.env.req_id;
 271         }
 272         VM.log = bunyan.createLogger(params);
 273     }
 274 
 275     // This is here in case an app calls a lookup first and then a create.  The
 276     // logger will get created in no-sideeffects mode for the lookup but when
 277     // the create is called this will force the switch to writing.
 278     if (side_effects) {
 279         log_to_file = true;
 280     }
 281 
 282     if (VM.log) {
 283         // We're already logging, don't break things.
 284         return;
 285     }
 286 
 287     if (VM.hasOwnProperty('logname')) {
 288         logname = VM.logname.replace(/[^a-zA-Z\_]/g, '');
 289     }
 290     if (!logname || logname.length < 1) {
 291         logname = 'VM';
 292     }
 293 
 294     if (VM.hasOwnProperty('logger') && VM.logger) {
 295         // Use concat, in case someone's sneaky and makes more than one logger.
 296         // We don't officially support that yet though.
 297         streams = streams.concat(VM.logger);
 298     }
 299 
 300     // Add the ringbuffer which we'll dump if we switch from not writing to
 301     // writing, and so that they'll show up in dumps.
 302     streams.push({
 303         level: 'trace',
 304         type: 'raw',
 305         stream: ringbuffer
 306     });
 307 
 308     try {
 309         if (!fs.existsSync('/var/log/vm')) {
 310             fs.mkdirSync('/var/log/vm');
 311         }
 312         if (!fs.existsSync('/var/log/vm/logs')) {
 313             fs.mkdirSync('/var/log/vm/logs');
 314         }
 315     } catch (e) {
 316         // We can't ever log to a file in /var/log/vm/logs if we can't create
 317         // it, so we just log to ring buffer (above).
 318         start_logging();
 319         return;
 320     }
 321 
 322     filename = '/var/log/vm/logs/' + Date.now(0) + '-'
 323         + sprintf('%06d', process.pid) + '-' + logname + '.log';
 324 
 325     streams.push({
 326         type: 'raw',
 327         stream: new OpenOnErrorFileStream(filename),
 328         level: VM.loglevel
 329     });
 330 
 331     start_logging();
 332 }
 333 
 334 function ltrim(str, chars)
 335 {
 336     chars = chars || '\\s';
 337     str = str || '';
 338     return str.replace(new RegExp('^[' + chars + ']+', 'g'), '');
 339 }
 340 
 341 function rtrim(str, chars)
 342 {
 343     chars = chars || '\\s';
 344     str = str || '';
 345     return str.replace(new RegExp('[' + chars + ']+$', 'g'), '');
 346 }
 347 
 348 function trim(str, chars)
 349 {
 350     return ltrim(rtrim(str, chars), chars);
 351 }
 352 
 353 function isUUID(str) {
 354     var re = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
 355     if (str && str.length === 36 && str.match(re)) {
 356         return true;
 357     } else {
 358         return false;
 359     }
 360 }
 361 
 362 function fixBoolean(str)
 363 {
 364     if (str === 'true') {
 365         return true;
 366     } else if (str === 'false') {
 367         return false;
 368     } else {
 369         return str;
 370     }
 371 }
 372 
 373 function fixBooleanLoose(str)
 374 {
 375     if (str === 'true' || str === '1' || str === 1) {
 376         return true;
 377     } else if (str === 'false' || str === '0' || str === 0) {
 378         return false;
 379     } else {
 380         return str;
 381     }
 382 }
 383 
 384 function isCIDR(str) {
 385     if (typeof (str) !== 'string') {
 386         return false;
 387     }
 388     var parts = str.split('/');
 389     if (parts.length !== 2 || !net.isIPv4(parts[0])) {
 390         return false;
 391     }
 392 
 393     var size = Number(parts[1]);
 394     if (!size || size < 8 || size > 32) {
 395         return false;
 396     }
 397 
 398     return true;
 399 }
 400 
 401 // IMPORTANT:
 402 //
 403 //  Some of these properties get translated below into backward compatible
 404 //  names.
 405 //
 406 
 407 var UPDATABLE_NIC_PROPS = [
 408     'primary',
 409     'nic_tag',
 410     'vrrp_vrid',
 411     'vrrp_primary_ip',
 412     'blocked_outgoing_ports',
 413     'mac',
 414     'gateway',
 415     'ip',
 416     'model',
 417     'netmask',
 418     'network_uuid',
 419     'dhcp_server',
 420     'allow_dhcp_spoofing',
 421     'allow_ip_spoofing',
 422     'allow_mac_spoofing',
 423     'allow_restricted_traffic',
 424     'allow_unfiltered_promisc',
 425     'vlan_id'
 426 ];
 427 
 428 var UPDATABLE_DISK_PROPS = [
 429     'boot',
 430     'model'
 431 ];
 432 
 433 // Note: this doesn't include 'state' because of 'stopping' which is a virtual
 434 // state and therefore lookups would be wrong (because they'd search on real
 435 // state).
 436 var QUICK_LOOKUP = [
 437     'zoneid',
 438     'zonename',
 439     'zonepath',
 440     'uuid',
 441     'brand',
 442     'ip_type'
 443 ];
 444 
 445 exports.DISK_MODELS = [
 446     'virtio',
 447     'ide',
 448     'scsi'
 449 ];
 450 
 451 exports.VGA_TYPES = [
 452     'cirrus',
 453     'std',
 454     'vmware',
 455     'qxl',
 456     'xenfb'
 457 ];
 458 
 459 exports.INFO_TYPES = [
 460     'all',
 461     'block',
 462     'blockstats',
 463     'chardev',
 464     'cpus',
 465     'kvm',
 466     'pci',
 467     'spice',
 468     'status',
 469     'version',
 470     'vnc'
 471 ];
 472 
 473 exports.SYSRQ_TYPES = [
 474     'nmi',
 475     'screenshot'
 476 ];
 477 
 478 exports.COMPRESSION_TYPES = [
 479     'on',
 480     'off',
 481     'lzjb',
 482     'gzip',
 483     'gzip-1',
 484     'gzip-2',
 485     'gzip-3',
 486     'gzip-4',
 487     'gzip-5',
 488     'gzip-6',
 489     'gzip-7',
 490     'gzip-8',
 491     'gzip-9',
 492     'zle'
 493 ];
 494 
 495 exports.KVM_MEM_OVERHEAD = 1024;
 496 exports.KVM_MIN_MEM_OVERHEAD = 256;
 497 
 498 var XML_PROPERTIES = {
 499     'zone': {
 500         'name': 'zonename',
 501         'zonepath': 'zonepath',
 502         'autoboot': 'autoboot',
 503         'brand': 'brand',
 504         'limitpriv': 'limit_priv',
 505         'fs-allowed': 'fs_allowed'
 506     },
 507     'zone.attr': {
 508         'alias': 'alias',
 509         'archive-on-delete': 'archive_on_delete',
 510         'billing-id': 'billing_id',
 511         'boot': 'boot',
 512         'cpu-type': 'cpu_type',
 513         'create-timestamp': 'create_timestamp',
 514         'dataset-uuid': 'image_uuid',
 515         'default-gateway': 'default_gateway',
 516         'disk-driver': 'disk_driver',
 517         'dns-domain': 'dns_domain',
 518         'do-not-inventory': 'do_not_inventory',
 519         'failed': 'failed',
 520         'firewall-enabled': 'firewall_enabled',
 521         'hostname': 'hostname',
 522         'init-name': 'init_name',
 523         'never-booted': 'never_booted',
 524         'nic-driver': 'nic_driver',
 525         'owner-uuid': 'owner_uuid',
 526         'package-name': 'package_name',
 527         'package-version': 'package_version',
 528         'qemu-extra-opts': 'qemu_extra_opts',
 529         'qemu-opts': 'qemu_opts',
 530         'ram': 'ram',
 531         'restart-init': 'restart_init',
 532         'resolvers': 'resolvers',
 533         'spice-opts': 'spice_opts',
 534         'spice-password': 'spice_password',
 535         'spice-port': 'spice_port',
 536         'tmpfs': 'tmpfs',
 537         'transition': 'transition',
 538         'vcpus': 'vcpus',
 539         'vga': 'vga',
 540         'virtio-txtimer': 'virtio_txtimer',
 541         'virtio-txburst': 'virtio_txburst',
 542         'vm-version': 'v',
 543         'vm-autoboot': 'vm_autoboot',
 544         'vnc-password': 'vnc_password',
 545         'vnc-port': 'vnc_port'
 546     },
 547     'zone.rctl.zone.cpu-shares.rctl-value': {
 548         'limit': 'cpu_shares'
 549     },
 550     'zone.rctl.zone.cpu-cap.rctl-value': {
 551         'limit': 'cpu_cap'
 552     },
 553     'zone.rctl.zone.zfs-io-priority.rctl-value': {
 554         'limit': 'zfs_io_priority'
 555     },
 556     'zone.rctl.zone.max-lwps.rctl-value': {
 557         'limit': 'max_lwps'
 558     },
 559     'zone.rctl.zone.max-physical-memory.rctl-value': {
 560         'limit': 'max_physical_memory'
 561     },
 562     'zone.rctl.zone.max-locked-memory.rctl-value': {
 563         'limit': 'max_locked_memory'
 564     },
 565     'zone.rctl.zone.max-swap.rctl-value': {
 566         'limit': 'max_swap'
 567     },
 568     'nic': {
 569         'ip': 'ip',
 570         'mac-addr': 'mac',
 571         'physical': 'interface',
 572         'vlan-id': 'vlan_id',
 573         'global-nic': 'nic_tag',
 574         'dhcp_server': 'dhcp_server',
 575         'allow_dhcp_spoofing': 'allow_dhcp_spoofing',
 576         'allow_ip_spoofing': 'allow_ip_spoofing',
 577         'allow_mac_spoofing': 'allow_mac_spoofing',
 578         'allow_restricted_traffic': 'allow_restricted_traffic',
 579         'allow_unfiltered_promisc': 'allow_unfiltered_promisc',
 580         'allowed_ips': 'allowed_ips',
 581         'netmask': 'netmask',
 582         'network_uuid': 'network_uuid',
 583         'model': 'model',
 584         'gateway': 'gateway',
 585         'primary': 'primary',
 586         'vrrp_vrid': 'vrrp_vrid',
 587         'vrrp_primary_ip': 'vrrp_primary_ip',
 588         'blocked-outgoing-ports': 'blocked_outgoing_ports'
 589     },
 590     'filesystem': {
 591         'special': 'source',
 592         'directory': 'target',
 593         'type': 'type',
 594         'raw': 'raw'
 595     },
 596     'disk': {
 597         'boot': 'boot',
 598         'image-size': 'image_size',
 599         'image-name': 'image_name',
 600         'image-uuid': 'image_uuid',
 601         'match': 'path',
 602         'media': 'media',
 603         'model': 'model',
 604         'size': 'size'
 605     }
 606 };
 607 
 608 /*
 609  * This allows one to define a function that will be run over the values from
 610  * the zonecfg at the point where we transform that data into a VM object.
 611  *
 612  */
 613 var XML_PROPERTY_TRANSFORMS = {
 614     'alias': unbase64,
 615     'archive_on_delete': fixBoolean,
 616     'autoboot': fixBoolean,
 617     'cpu_cap': numberify,
 618     'cpu_shares': numberify,
 619     'disks': {
 620         'boot': fixBoolean,
 621         'image_size': numberify,
 622         'size': numberify
 623     },
 624     'do_not_inventory': fixBoolean,
 625     'firewall_enabled': fixBoolean,
 626     'max_locked_memory': unmangleMem,
 627     'max_lwps': numberify,
 628     'max_physical_memory': unmangleMem,
 629     'max_swap': unmangleMem,
 630     'never_booted': fixBoolean,
 631     'nics': {
 632         'dhcp_server': fixBoolean,
 633         'allow_dhcp_spoofing': fixBoolean,
 634         'allow_ip_spoofing': fixBoolean,
 635         'allow_mac_spoofing': fixBoolean,
 636         'allow_restricted_traffic': fixBoolean,
 637         'allow_unfiltered_promisc': fixBoolean,
 638         'allowed_ips': separateCommas,
 639         'primary': fixBooleanLoose,
 640         'vrrp_vrid': numberify,
 641         'vlan_id': numberify
 642     },
 643     'qemu_extra_opts': unbase64,
 644     'qemu_opts': unbase64,
 645     'ram': numberify,
 646     'restart_init': fixBoolean,
 647     'resolvers': separateCommas,
 648     'spice_password': unbase64,
 649     'spice_port': numberify,
 650     'spice_opts': unbase64,
 651     'tmpfs': numberify,
 652     'v': numberify,
 653     'vcpus': numberify,
 654     'virtio_txburst': numberify,
 655     'virtio_txtimer': numberify,
 656     'vnc_password': unbase64,
 657     'vnc_port': numberify,
 658     'zfs_io_priority': numberify,
 659     'zoneid': numberify
 660 };
 661 
 662 /*
 663  * This defines all of the possible properties that could be in a create/update
 664  * payload and their types. Each of the entries are required to have at least
 665  * a 'type' property which is one of:
 666  *
 667  *  object-array    -- an array of objects
 668  *  boolean         -- true or false
 669  *  flat-object     -- an object that has only string properties
 670  *  integer         -- integers only
 671  *  list            -- Either comma separated or array list of strings
 672  *  string          -- Simple string
 673  *  uuid            -- A standard 00000000-0000-0000-0000-000000000000 type uuid
 674  *  zpool           -- The name of an existing zpool
 675  *
 676  */
 677 var PAYLOAD_PROPERTIES = {
 678     'add_disks': {'type': 'object-array', 'check_as': 'disks'},
 679     'add_nics': {'type': 'object-array', 'check_as': 'nics'},
 680     'alias': {'type': 'string'},
 681     'archive_on_delete': {'type': 'boolean'},
 682     'autoboot': {'type': 'boolean'},
 683     'billing_id': {'type': 'string'},
 684     'boot': {'type': 'string'},
 685     'brand': {'type': 'string'},
 686     'cpu_cap': {'type': 'integer'},
 687     'cpu_shares': {'type': 'integer'},
 688     'cpu_type': {'type': 'string'},
 689     'create_only': {'type': 'boolean'},
 690     'create_timestamp': {'type': 'string'},
 691     'customer_metadata': {'type': 'flat-object'},
 692     'dataset_uuid': {'type': 'uuid'},
 693     'delegate_dataset': {'type': 'boolean'},
 694     'disks': {'type': 'object-array'},
 695     'disks.*.block_size': {'type': 'integer'},
 696     'disks.*.boot': {'type': 'boolean'},
 697     'disks.*.compression': {'type': 'string'},
 698     'disks.*.image_name': {'type': 'string'},
 699     'disks.*.image_size': {'type': 'integer'},
 700     'disks.*.image_uuid': {'type': 'uuid'},
 701     'disks.*.refreservation': {'type': 'integer'},
 702     'disks.*.size': {'type': 'integer'},
 703     'disks.*.media': {'type': 'string'},
 704     'disks.*.model': {'type': 'string'},
 705     'disks.*.nocreate': {'type': 'boolean'},
 706     'disks.*.path': {'type': 'string'},
 707     'disks.*.zpool': {'type': 'zpool'},
 708     'disk_driver': {'type': 'string'},
 709     'do_not_inventory': {'type': 'boolean'},
 710     'dns_domain': {'type': 'string'},
 711     'filesystems': {'type': 'object-array'},
 712     'filesystems.*.type': {'type': 'string'},
 713     'filesystems.*.source': {'type': 'string'},
 714     'filesystems.*.target': {'type': 'string'},
 715     'filesystems.*.raw': {'type': 'string'},
 716     'filesystems.*.options': {'type': 'list'},
 717     'firewall': {'type': 'object'},
 718     'firewall_enabled': {'type': 'boolean'},
 719     'fs_allowed': {'type': 'list'},
 720     'hostname': {'type': 'string'},
 721     'image_uuid': {'type': 'uuid'},
 722     'init_name': {'type': 'string'},
 723     'internal_metadata': {'type': 'flat-object'},
 724     'limit_priv': {'type': 'list'},
 725     'max_locked_memory': {'type': 'integer'},
 726     'max_lwps': {'type': 'integer'},
 727     'max_physical_memory': {'type': 'integer'},
 728     'max_swap': {'type': 'integer'},
 729     'mdata_exec_timeout': {'type': 'integer'},
 730     'nics': {'type': 'object-array'},
 731     'nics.*.allow_dhcp_spoofing': {'type': 'boolean'},
 732     'nics.*.allow_ip_spoofing': {'type': 'boolean'},
 733     'nics.*.allow_mac_spoofing': {'type': 'boolean'},
 734     'nics.*.allow_restricted_traffic': {'type': 'boolean'},
 735     'nics.*.allow_unfiltered_promisc': {'type': 'boolean'},
 736     'nics.*.allowed_ips': {'type': 'list'},
 737     'nics.*.blocked_outgoing_ports': {'type': 'list'},
 738     'nics.*.dhcp_server': {'type': 'boolean'},
 739     'nics.*.gateway': {'type': 'string'},
 740     'nics.*.interface': {'type': 'string'},
 741     'nics.*.ip': {'type': 'string'},
 742     'nics.*.mac': {'type': 'string'},
 743     'nics.*.model': {'type': 'string'},
 744     'nics.*.netmask': {'type': 'string'},
 745     'nics.*.network_uuid': {'type': 'uuid'},
 746     'nics.*.nic_tag': {'type': 'string'},
 747     'nics.*.primary': {'type': 'boolean'},
 748     'nics.*.vrrp_vrid': {'type': 'integer-8bit'},
 749     'nics.*.vrrp_primary_ip': {'type': 'string'},
 750     'nics.*.vlan_id': {'type': 'integer'},
 751     'nic_driver': {'type': 'string'},
 752     'nowait': {'type': 'boolean'},
 753     'owner_uuid': {'type': 'string'},
 754     'package_name': {'type': 'string'},
 755     'package_version': {'type': 'string'},
 756     'qemu_opts': {'type': 'string'},
 757     'qemu_extra_opts': {'type': 'string'},
 758     'quota': {'type': 'integer'},
 759     'ram': {'type': 'integer'},
 760     'remove_customer_metadata': {'type': 'list'},
 761     'remove_disks': {'type': 'list'},
 762     'remove_internal_metadata': {'type': 'list'},
 763     'remove_nics': {'type': 'list'},
 764     'remove_routes': {'type': 'list'},
 765     'remove_tags': {'type': 'list'},
 766     'restart_init': {'type': 'boolean'},
 767     'resolvers': {'type': 'list'},
 768     'routes': {'type': 'flat-object'},
 769     'set_routes': {'type': 'flat-object'},
 770     'set_tags': {'type': 'flat-object'},
 771     'set_customer_metadata': {'type': 'flat-object'},
 772     'set_internal_metadata': {'type': 'flat-object'},
 773     'spice_opts': {'type': 'string'},
 774     'spice_password': {'type': 'string'},
 775     'spice_port': {'type': 'integer'},
 776     'tags': {'type': 'flat-object'},
 777     'tmpfs': {'type': 'integer'},
 778     'transition': {'type': 'flat-object'},
 779     'update_disks': {'type': 'object-array', 'check_as': 'disks'},
 780     'update_nics': {'type': 'object-array', 'check_as': 'nics'},
 781     'uuid': {'type': 'uuid'},
 782     'v': {'type': 'integer'},
 783     'vcpus': {'type': 'integer'},
 784     'vga': {'type': 'string'},
 785     'virtio_txburst': {'type': 'integer'},
 786     'virtio_txtimer': {'type': 'integer'},
 787     'vnc_password': {'type': 'string'},
 788     'vnc_port': {'type': 'integer'},
 789     'zfs_data_compression': {'type': 'string'},
 790     'zfs_data_recsize': {'type': 'integer'},
 791     'zfs_io_priority': {'type': 'integer'},
 792     'zfs_root_compression': {'type': 'string'},
 793     'zfs_root_recsize': {'type': 'integer'},
 794     'zone_dataset_uuid': {'type': 'uuid'},
 795     'zonename': {'type': 'string'},
 796     'zfs_storage_pool_name': {'type': 'zpool'},
 797     'zpool': {'type': 'zpool'}
 798 };
 799 
 800 // shared between 'joyent' and 'joyent-minimal'
 801 var joyent_allowed = {
 802     'add_nics': ['update'],
 803     'alias': ['create', 'receive', 'update'],
 804     'archive_on_delete': ['create', 'receive', 'update'],
 805     'autoboot': ['create', 'receive', 'update'],
 806     'billing_id': ['create', 'receive', 'update'],
 807     'brand': ['create', 'receive'],
 808     'cpu_cap': ['create', 'receive', 'update'],
 809     'cpu_shares': ['create', 'receive', 'update'],
 810     'create_only': ['receive'],
 811     'create_timestamp': ['receive'],
 812     'customer_metadata': ['create', 'receive'],
 813     'dataset_uuid': ['create', 'receive'],
 814     'delegate_dataset': ['create', 'receive'],
 815     'do_not_inventory': ['create', 'receive', 'update'],
 816     'dns_domain': ['create', 'receive'],
 817     'filesystems': ['create', 'receive'],
 818     'filesystems.*.type': ['add'],
 819     'filesystems.*.source': ['add'],
 820     'filesystems.*.target': ['add'],
 821     'filesystems.*.raw': ['add'],
 822     'filesystems.*.options': ['add'],
 823     'firewall': ['create'],
 824     'firewall_enabled': ['create', 'receive', 'update'],
 825     'fs_allowed': ['create', 'receive', 'update'],
 826     'hostname': ['create', 'receive', 'update'],
 827     'image_uuid': ['create', 'receive'],
 828     'init_name': ['create', 'receive', 'update'],
 829     'internal_metadata': ['create', 'receive'],
 830     'limit_priv': ['create', 'receive', 'update'],
 831     'max_locked_memory': ['create', 'receive', 'update'],
 832     'max_lwps': ['create', 'receive', 'update'],
 833     'max_physical_memory': ['create', 'receive', 'update'],
 834     'max_swap': ['create', 'receive', 'update'],
 835     'mdata_exec_timeout': ['create'],
 836     'nics': ['create', 'receive'],
 837     'nics.*.allow_dhcp_spoofing': ['add', 'update'],
 838     'nics.*.allow_ip_spoofing': ['add', 'update'],
 839     'nics.*.allow_mac_spoofing': ['add', 'update'],
 840     'nics.*.allow_restricted_traffic': ['add', 'update'],
 841     'nics.*.allowed_ips': ['add', 'update'],
 842     'nics.*.blocked_outgoing_ports': ['add', 'update'],
 843     'nics.*.dhcp_server': ['add', 'update'],
 844     'nics.*.gateway': ['add', 'update'],
 845     'nics.*.interface': ['add', 'update'],
 846     'nics.*.ip': ['add', 'update'],
 847     'nics.*.mac': ['add', 'update'],
 848     'nics.*.netmask': ['add', 'update'],
 849     'nics.*.network_uuid': ['add', 'update'],
 850     'nics.*.nic_tag': ['add', 'update'],
 851     'nics.*.vrrp_vrid': ['add', 'update'],
 852     'nics.*.vrrp_primary_ip': ['add', 'update'],
 853     'nics.*.primary': ['add', 'update'],
 854     'nics.*.vlan_id': ['add', 'update'],
 855     'nowait': ['create', 'receive'],
 856     'owner_uuid': ['create', 'receive', 'update'],
 857     'package_name': ['create', 'receive', 'update'],
 858     'package_version': ['create', 'receive', 'update'],
 859     'quota': ['create', 'receive', 'update'],
 860     'ram': ['create', 'receive', 'update'],
 861     'remove_customer_metadata': ['update'],
 862     'remove_internal_metadata': ['update'],
 863     'remove_nics': ['update'],
 864     'remove_routes': ['update'],
 865     'remove_tags': ['update'],
 866     'restart_init': ['create', 'receive', 'update'],
 867     'resolvers': ['create', 'receive', 'update'],
 868     'routes': ['create', 'receive'],
 869     'set_customer_metadata': ['update'],
 870     'set_internal_metadata': ['update'],
 871     'set_routes': ['update'],
 872     'set_tags': ['update'],
 873     'tags': ['create', 'receive'],
 874     'tmpfs': ['create', 'receive', 'update'],
 875     'transition': ['receive'],
 876     'update_nics': ['update'],
 877     'uuid': ['create', 'receive'],
 878     'v': ['receive'],
 879     'zfs_data_compression': ['create', 'receive', 'update'],
 880     'zfs_data_recsize': ['create', 'receive', 'update'],
 881     'zfs_io_priority': ['create', 'receive', 'update'],
 882     'zfs_root_compression': ['create', 'receive', 'update'],
 883     'zfs_root_recsize': ['create', 'receive', 'update'],
 884     'zfs_storage_pool_name': ['create', 'receive'],
 885     'zonename': ['create', 'receive'],
 886     'zpool': ['create', 'receive']
 887 };
 888 
 889 /*
 890  * This defines all of the properties allowed, required and features that a
 891  * brand has. For each of the allowed/required properties you have a list of
 892  * actions for which this is allowed/required. For properties that are lists
 893  * of objects, you can specify the action as 'add' or 'update' for when you're
 894  * adding or updating one of those objects.
 895  *
 896  * Features can currently be one of:
 897  *
 898  * 'cleanup_dataset' -- (boolean) whether to remove trash before booting
 899  * 'default_memory_overhead' -- (integer) memory above 'ram' that's added
 900  * 'limit_priv': (list) list of priviledges for this zone (if not 'default')
 901  * 'mdata_restart' -- (boolean) whether the brand supports restarting its
 902  *                    mdata:fetch service to update properties in the zone
 903  * 'min_memory_overhead' -- (integer) minimum delta between ram + max_physical
 904  * 'model_required' -- (boolean) whether a .model is required on nics and disks
 905  * 'pid_file' -- (pathname) file containing the PID for zones with one process
 906  * 'runtime_info' -- (boolean) whether this zone supports the 'info' command
 907  * 'serial_console' -- (boolean) whether this zone uses serial console
 908  * 'type' -- the type of the VM (OS or KVM), all brands should include this
 909  * 'update_mdata_exec_timeout' (boolean) whether to update mdata:exec timeout
 910  * 'update_rctls' (boolean) whether we can update rctls 'live' for this zone
 911  * 'use_tmpfs' -- (boolean) whether this type of zone uses tmpfs
 912  * 'use_vm_autoboot' -- (boolean) use vm-autoboot instead of autoboot
 913  * 'use_vmadmd' -- (boolean) use vmadmd for some actions instead of direct
 914  * 'var_svc_provisioning' -- (boolean) whether brand uses /var/svc/provisioning
 915  * 'wait_for_hwsetup' -- (boolean) use QMP and provision_success when hwsetup
 916  * 'write_zone_netfiles' -- (boolean) write out files like /etc/hostname.net0
 917  * 'zlogin_console' -- (boolean) use zlogin -C for console (vs. serial_console)
 918  * 'zoneinit' -- (boolean) this brand's setup may be controlled by zoneinit
 919  *
 920  * All of the keys:
 921  *
 922  *  allowed_properties
 923  *  required_properties
 924  *  features
 925  *
 926  * should be defined for each brand. Even if empty.
 927  */
 928 var BRAND_OPTIONS = {
 929     'joyent': {
 930         'allowed_properties': joyent_allowed,
 931         'required_properties': {
 932             'brand': ['create', 'receive'],
 933             'image_uuid': ['create', 'receive']
 934         }, 'features': {
 935             'brand_install_script': '/usr/lib/brand/joyent/jinstall',
 936             'cleanup_dataset': true,
 937             'mdata_restart': true,
 938             'reprovision': true,
 939             'type': 'OS',
 940             'update_mdata_exec_timeout': true,
 941             'update_rctls': true,
 942             'use_tmpfs': true,
 943             'write_zone_netfiles': true,
 944             'zlogin_console': true,
 945             'zoneinit': true
 946         }
 947     }, 'joyent-minimal': {
 948         'allowed_properties': joyent_allowed,
 949         'required_properties': {
 950             'brand': ['create', 'receive'],
 951             'image_uuid': ['create', 'receive']
 952         }, 'features': {
 953             'brand_install_script': '/usr/lib/brand/joyent-minimal/jinstall',
 954             'cleanup_dataset': true,
 955             'mdata_restart': true,
 956             'reprovision': true,
 957             'type': 'OS',
 958             'update_mdata_exec_timeout': true,
 959             'update_rctls': true,
 960             'use_tmpfs': true,
 961             'var_svc_provisioning': true,
 962             'write_zone_netfiles': true,
 963             'zlogin_console': true
 964         }
 965     }, 'sngl': {
 966         'allowed_properties': joyent_allowed,
 967         'required_properties': {
 968             'brand': ['create', 'receive'],
 969             'image_uuid': ['create', 'receive']
 970         }, 'features': {
 971             'cleanup_dataset': true,
 972             'mdata_restart': true,
 973             'type': 'OS',
 974             'update_mdata_exec_timeout': true,
 975             'update_rctls': true,
 976             'use_tmpfs': true,
 977             'write_zone_netfiles': true,
 978             'zlogin_console': true,
 979             'zoneinit': true
 980         }
 981     }, 'kvm': {
 982         'allowed_properties': {
 983             'add_disks': ['update'],
 984             'add_nics': ['update'],
 985             'alias': ['create', 'receive', 'update'],
 986             'archive_on_delete': ['create', 'receive', 'update'],
 987             'autoboot': ['create', 'receive', 'update'],
 988             'billing_id': ['create', 'receive', 'update'],
 989             'boot': ['create', 'receive', 'update'],
 990             'brand': ['create', 'receive'],
 991             'cpu_cap': ['create', 'receive', 'update'],
 992             'cpu_shares': ['create', 'receive', 'update'],
 993             'cpu_type': ['create', 'receive', 'update'],
 994             'create_only': ['receive'],
 995             'create_timestamp': ['receive'],
 996             'customer_metadata': ['create', 'receive'],
 997             'disks': ['create', 'receive'],
 998             'disks.*.block_size': ['add'],
 999             'disks.*.boot': ['add', 'update'],
1000             'disks.*.compression': ['add', 'update'],
1001             'disks.*.image_name': ['add', 'update'],
1002             'disks.*.image_size': ['add'],
1003             'disks.*.image_uuid': ['add'],
1004             'disks.*.refreservation': ['add', 'update'],
1005             'disks.*.size': ['add'],
1006             'disks.*.media': ['add', 'update'],
1007             'disks.*.model': ['add', 'update'],
1008             'disks.*.nocreate': ['add'],
1009             'disks.*.path': ['add', 'update'],
1010             'disks.*.zpool': ['add'],
1011             'disk_driver': ['create', 'receive', 'update'],
1012             'do_not_inventory': ['create', 'receive', 'update'],
1013             'firewall': ['create'],
1014             'firewall_enabled': ['create', 'receive', 'update'],
1015             'hostname': ['create', 'receive', 'update'],
1016             'image_uuid': ['create', 'receive'],
1017             'internal_metadata': ['create', 'receive'],
1018             'limit_priv': ['create', 'receive', 'update'],
1019             'max_locked_memory': ['create', 'receive', 'update'],
1020             'max_lwps': ['create', 'receive', 'update'],
1021             'max_physical_memory': ['create', 'receive', 'update'],
1022             'max_swap': ['create', 'receive', 'update'],
1023             'nics': ['create', 'receive'],
1024             'nics.*.allow_dhcp_spoofing': ['add', 'update'],
1025             'nics.*.allow_ip_spoofing': ['add', 'update'],
1026             'nics.*.allow_mac_spoofing': ['add', 'update'],
1027             'nics.*.allow_restricted_traffic': ['add', 'update'],
1028             'nics.*.allow_unfiltered_promisc': ['add', 'update'],
1029             'nics.*.allowed_ips': ['add', 'update'],
1030             'nics.*.blocked_outgoing_ports': ['add', 'update'],
1031             'nics.*.dhcp_server': ['add', 'update'],
1032             'nics.*.gateway': ['add', 'update'],
1033             'nics.*.interface': ['add', 'update'],
1034             'nics.*.ip': ['add', 'update'],
1035             'nics.*.mac': ['add', 'update'],
1036             'nics.*.model': ['add', 'update'],
1037             'nics.*.netmask': ['add', 'update'],
1038             'nics.*.network_uuid': ['add', 'update'],
1039             'nics.*.nic_tag': ['add', 'update'],
1040             'nics.*.primary': ['add', 'update'],
1041             'nics.*.vlan_id': ['add', 'update'],
1042             'nic_driver': ['create', 'receive', 'update'],
1043             'owner_uuid': ['create', 'receive', 'update'],
1044             'package_name': ['create', 'receive', 'update'],
1045             'package_version': ['create', 'receive', 'update'],
1046             'qemu_opts': ['create', 'receive', 'update'],
1047             'qemu_extra_opts': ['create', 'receive', 'update'],
1048             'quota': ['create', 'receive', 'update'],
1049             'ram': ['create', 'receive', 'update'],
1050             'remove_customer_metadata': ['update'],
1051             'remove_disks': ['update'],
1052             'remove_internal_metadata': ['update'],
1053             'remove_nics': ['update'],
1054             'remove_routes': ['update'],
1055             'remove_tags': ['update'],
1056             'resolvers': ['create', 'receive', 'update'],
1057             'set_customer_metadata': ['update'],
1058             'set_internal_metadata': ['update'],
1059             'set_routes': ['update'],
1060             'set_tags': ['update'],
1061             'spice_opts': ['create', 'receive', 'update'],
1062             'spice_password': ['create', 'receive', 'update'],
1063             'spice_port': ['create', 'receive', 'update'],
1064             'tags': ['create', 'receive'],
1065             'transition': ['receive'],
1066             'update_disks': ['update'],
1067             'update_nics': ['update'],
1068             'uuid': ['create', 'receive'],
1069             'v': ['receive'],
1070             'vcpus': ['create', 'receive', 'update'],
1071             'vga': ['create', 'receive', 'update'],
1072             'virtio_txburst': ['create', 'receive', 'update'],
1073             'virtio_txtimer': ['create', 'receive', 'update'],
1074             'vnc_password': ['create', 'receive', 'update'],
1075             'vnc_port': ['create', 'receive', 'update'],
1076             'zfs_io_priority': ['create', 'receive', 'update'],
1077             'zfs_root_compression': ['create', 'receive', 'update'],
1078             'zfs_root_recsize': ['create', 'receive', 'update'],
1079             'zone_dataset_uuid': ['create', 'receive'],
1080             'zpool': ['create', 'receive']
1081         }, 'required_properties': {
1082             'brand': ['create', 'receive']
1083         }, 'features': {
1084             'default_memory_overhead': VM.KVM_MEM_OVERHEAD,
1085             'limit_priv': ['default', '-file_link_any', '-net_access',
1086                 '-proc_fork', '-proc_info', '-proc_session'],
1087             'min_memory_overhead': VM.KVM_MIN_MEM_OVERHEAD,
1088             'model_required': true,
1089             'pid_file': '/tmp/vm.pid',
1090             'runtime_info': true,
1091             'serial_console': true,
1092             'type': 'KVM',
1093             'use_vm_autoboot': true,
1094             'use_vmadmd': true,
1095             'var_svc_provisioning': true,
1096             'wait_for_hwsetup': true
1097         }
1098     }
1099 };
1100 
1101 var VIRTIO_TXTIMER_DEFAULT = 200000;
1102 var VIRTIO_TXBURST_DEFAULT = 128;
1103 
1104 function getZpools(log, callback)
1105 {
1106     var args = ['list', '-H', '-p', '-o', 'name'];
1107     var cmd = '/usr/sbin/zpool';
1108     var idx;
1109     var raw = [];
1110     var zpools = [];
1111 
1112     assert(log, 'no logger passed to getZpools()');
1113 
1114     log.debug(cmd + ' ' + args.join(' '));
1115     execFile(cmd, args, function (error, stdout, stderr) {
1116         if (error) {
1117             log.error('Unable to get list of zpools');
1118             callback(error, {'stdout': stdout, 'stderr': stderr});
1119         } else {
1120             // strip out any empty values (last one).
1121             raw = stdout.split('\n');
1122             for (idx in raw) {
1123                 if (raw[idx].length > 0) {
1124                     zpools.push(raw[idx]);
1125                 }
1126             }
1127             callback(null, zpools);
1128         }
1129     });
1130 }
1131 
1132 /*
1133  * When you need to access files inside a zoneroot, you need to be careful that
1134  * there are no symlinks in the path. Since we operate from the GZ, these
1135  * symlinks will be evaluated in the GZ context. Eg. a symlink in zone A with
1136  * /var/run -> * /zones/<uuid of zone B>/root/var/run would mean that operating
1137  * on files in zone A's /var/run would actually be touching files in zone B.
1138  *
1139  * To prevent that, only ever modify files inside the zoneroot from the GZ
1140  * *before* first boot. After the zone is booted, it's better to use services
1141  * in the zone to pull values from metadata and write out changes on next boot.
1142  * It's also safe to use zlogin when the zone is running.
1143  *
1144  * This function is intended to be used in those cases we do write things out
1145  * before the zone's first boot but the dataset might have invalid symlinks in
1146  * it even then, so we still need to confirm the paths inside zoneroot before
1147  * using them. It throws an exception if:
1148  *
1149  *  - zoneroot is not an absolute path
1150  *  - fs.lstatSync fails
1151  *  - target path under zoneroot contains symlink
1152  *  - a component leading up to the final one is not a directory
1153  *  - options.type is set to 'file' and target is not a regular file
1154  *  - options.type is set to 'dir' and target references a non-directory
1155  *  - options.type is not one of 'file' or 'dir'
1156  *  - options.enoent_ok is false and target path doesn't exist
1157  *
1158  * if none of those are the case, it returns true.
1159  */
1160 function assertSafeZonePath(zoneroot, target, options)
1161 {
1162     var parts;
1163     var root;
1164     var stat;
1165     var test;
1166 
1167     assert((zoneroot.length > 0 && zoneroot[0] === '/'),
1168         'zoneroot must be an absolute path not: [' + zoneroot + ']');
1169 
1170     parts = trim(target, '/').split('/');
1171     root = trim(zoneroot, '/');
1172     test = '/' + root;
1173 
1174     while (parts.length > 0) {
1175         test = test + '/' + parts.shift();
1176 
1177         try {
1178             stat = fs.lstatSync(test);
1179         } catch (e) {
1180             if (e.code === 'ENOENT') {
1181                 if (!options.hasOwnProperty('enoent_ok')
1182                     || options.enoent_ok === false) {
1183 
1184                     throw e;
1185                 } else {
1186                     // enoent is ok, return true.  This is mostly used when
1187                     // deleting files with rm -f <path>.  It's ok for <path> to
1188                     // not exist (but not ok for any component to be a symlink)
1189                     // there's no point continuing though since ENOENT here
1190                     // means all subpaths also won't exist.
1191                     return true;
1192                 }
1193             } else {
1194                 throw e;
1195             }
1196         }
1197 
1198         if (stat.isSymbolicLink()) {
1199             // it's never ok to have a symlink component
1200             throw new Error(test + ' is a symlink');
1201         }
1202 
1203         // any component other than the last also needs to be a
1204         // directory, last can also be a file.
1205         if (parts.length === 0) {
1206             // last, dir or file
1207             if (!options.hasOwnProperty('type') || options.type === 'dir') {
1208                 if (!stat.isDirectory()) {
1209                     throw new Error(test + ' is not a directory');
1210                 }
1211             } else if (options.type === 'file') {
1212                 if (!stat.isFile()) {
1213                     throw new Error(test + ' is not a file');
1214                 }
1215             } else {
1216                 throw new Error('this function does not know about '
1217                     + options.type);
1218             }
1219         } else if (!stat.isDirectory()) {
1220             // not last component, only dir is acceptable
1221             throw new Error(test + ' is not a directory');
1222         }
1223     }
1224     // if we didn't throw, this is valid.
1225     return true;
1226 }
1227 
1228 function validateProperty(brand, prop, value, action, data, errors, log)
1229 {
1230     var allowed;
1231     var k;
1232 
1233     assert(log, 'no logger passed to validateProperty()');
1234 
1235     if (!data.hasOwnProperty('zpools')) {
1236         data.zpools = [];
1237     }
1238 
1239     if (BRAND_OPTIONS[brand].hasOwnProperty('allowed_properties')) {
1240         allowed = BRAND_OPTIONS[brand].allowed_properties;
1241     } else {
1242         allowed = {};
1243     }
1244 
1245     if (!errors.hasOwnProperty('bad_values')) {
1246         errors.bad_values = [];
1247     }
1248     if (!errors.hasOwnProperty('bad_properties')) {
1249         errors.bad_properties = [];
1250     }
1251 
1252     if (!allowed.hasOwnProperty(prop)) {
1253         // thie BRAND_OPTIONS doesn't have this property at all
1254         if (errors.bad_properties.indexOf(prop) === -1) {
1255             errors.bad_properties.push(prop);
1256         }
1257     } else if (!Array.isArray(allowed[prop])
1258         || allowed[prop].indexOf(action) === -1) {
1259 
1260         // here we've ether got no actions allowed for this value,
1261         // or just not this one
1262         if (errors.bad_properties.indexOf(prop) === -1) {
1263             errors.bad_properties.push(prop);
1264         }
1265     }
1266 
1267     if (PAYLOAD_PROPERTIES.hasOwnProperty(prop)) {
1268         switch (PAYLOAD_PROPERTIES[prop].type) {
1269         case 'uuid':
1270             if (typeof (value) === 'string' && !isUUID(value)
1271                 && errors.bad_values.indexOf(prop) === -1) {
1272 
1273                 errors.bad_values.push(prop);
1274             }
1275             break;
1276         case 'boolean':
1277             if (value === 1 || value === '1') {
1278                 log.warn('DEPRECATED: payload uses 1 instead of '
1279                     + 'true for ' + prop + ', use "true" instead.');
1280             } else if (typeof (fixBoolean(value)) !== 'boolean'
1281                 && errors.bad_values.indexOf(prop) === -1) {
1282 
1283                 errors.bad_values.push(prop);
1284             }
1285             break;
1286         case 'string':
1287             if (value === undefined || value === null
1288                 || trim(value.toString()) === '') {
1289                 // if set empty/false we'll keep since this is used to unset
1290                 break;
1291             } else if (typeof (value) !== 'string'
1292                 && errors.bad_values.indexOf(prop) === -1) {
1293 
1294                 errors.bad_values.push(prop);
1295             }
1296             break;
1297         case 'integer':
1298             if (value === undefined || value === null
1299                 || trim(value.toString()) === '') {
1300                 // if set empty/false we'll keep since this is used to unset
1301                 break;
1302             } else if (((typeof (value) !== 'string'
1303                 && typeof (value) !== 'number')
1304                 || !value.toString().match(/^[0-9]+$/))
1305                 && errors.bad_values.indexOf(prop) === -1) {
1306 
1307                 if ((['vnc_port', 'spice_port'].indexOf(prop) !== -1)
1308                     && (value.toString() === '-1')) {
1309 
1310                     // these keys allow '-1' as a value, so we succeed here even
1311                     // though we'd otherwise fail.
1312                     break;
1313                 }
1314 
1315                 errors.bad_values.push(prop);
1316             } else if (prop === 'max_swap' && value < MINIMUM_MAX_SWAP) {
1317                 errors.bad_values.push(prop);
1318             }
1319             break;
1320         case 'integer-8bit':
1321             if (value === undefined || value === null
1322                 || trim(value.toString()) === '') {
1323                 // if set empty/false we'll keep since this is used to unset
1324                 break;
1325             } else if (((typeof (value) !== 'string'
1326                 && typeof (value) !== 'number')
1327                 || !value.toString().match(/^[0-9]+$/))
1328                 && errors.bad_values.indexOf(prop) === -1
1329                 ) {
1330 
1331                 errors.bad_values.push(prop);
1332                 break;
1333             }
1334             if (value < 0 || value > 255) {
1335                 errors.bad_values.push(prop);
1336             }
1337             break;
1338         case 'zpool':
1339             if ((typeof (value) !== 'string'
1340                 || data.zpools.indexOf(value) === -1)
1341                 && errors.bad_values.indexOf(prop) === -1) {
1342 
1343                 errors.bad_values.push(prop);
1344             }
1345             break;
1346         case 'object':
1347             if (typeof (value) !== 'object'
1348                 && errors.bad_values.indexOf(prop) === -1) {
1349 
1350                 errors.bad_values.push(prop);
1351             }
1352             break;
1353         case 'flat-object':
1354             if (typeof (value) !== 'object'
1355                 && errors.bad_values.indexOf(prop) === -1) {
1356 
1357                 errors.bad_values.push(prop);
1358             }
1359             for (k in value) {
1360                 if (typeof (value[k]) !== 'string'
1361                     && typeof (value[k]) !== 'number'
1362                     && typeof (value[k]) !== 'boolean') {
1363 
1364                     if (errors.bad_values.indexOf(prop) === -1) {
1365                         errors.bad_values.push(prop);
1366                     }
1367                     break;
1368                 }
1369             }
1370             break;
1371         case 'list':
1372             if (typeof (value) === 'string') {
1373                 // really any string could be valid (a one element list)
1374                 break;
1375             } else if (Array.isArray(value)) {
1376                 for (k in value) {
1377                     if (typeof (value[k]) !== 'string'
1378                         && typeof (value[k]) !== 'number') {
1379 
1380                         // TODO: log something more useful here telling them
1381                         // the type is invalid.
1382                         if (errors.bad_values.indexOf(prop) === -1) {
1383                             errors.bad_values.push(prop);
1384                         }
1385                         break;
1386                     }
1387                     // if this is an array, it can't have commas in the
1388                     // values. (since we might stringify the list and
1389                     // we'd end up with something different.
1390                     if (value[k].toString().indexOf(',') !== -1
1391                         && errors.bad_values.indexOf(prop) === -1) {
1392 
1393                         errors.bad_values.push(prop);
1394                     }
1395                 }
1396             } else {
1397                 // not a valid type
1398                 if (errors.bad_values.indexOf(prop) === -1) {
1399                     errors.bad_values.push(prop);
1400                 }
1401             }
1402             break;
1403         case 'object-array':
1404             if (!Array.isArray(value)) {
1405                 if (errors.bad_values.indexOf(prop) === -1) {
1406                     errors.bad_values.push(prop);
1407                 }
1408                 break;
1409             }
1410             for (k in value) {
1411                 if (typeof (value[k]) !== 'object') {
1412                     if (errors.bad_values.indexOf(prop) === -1) {
1413                         errors.bad_values.push(prop);
1414                     }
1415                     break;
1416                 }
1417             }
1418             break;
1419         default:
1420             // don't know what type of prop this is, so it's invalid
1421             if (errors.bad_properties.indexOf(prop) === -1) {
1422                 errors.bad_properties.push(prop);
1423             }
1424             break;
1425         }
1426     }
1427 }
1428 
1429 /*
1430  * image properties:
1431  *
1432  *  size (optional, only used by zvols)
1433  *  type ('zvol' or 'zone-dataset')
1434  *  uuid
1435  *  zpool
1436  *
1437  */
1438 function validateImage(image, log, callback)
1439 {
1440     var args;
1441     var cmd = '/usr/sbin/imgadm';
1442 
1443     args = ['get', '-P', image.zpool, image.uuid];
1444 
1445     log.debug(cmd + ' ' + args.join(' '));
1446 
1447     // on any error we fail closed (assume the image does not exist)
1448     execFile(cmd, args, function (error, stdout, stderr) {
1449         var data;
1450         var e;
1451 
1452         if (error) {
1453             error.stdout = stdout;
1454             error.stderr = stderr;
1455             error.whatFailed = 'EEXECFILE';
1456             log.error(error);
1457             callback(error);
1458             return;
1459         }
1460 
1461         try {
1462             data = JSON.parse(stdout.toString());
1463         } catch (err) {
1464             data = {};
1465         }
1466 
1467         if (data.hasOwnProperty('manifest')) {
1468             if (data.manifest.type !== image.type) {
1469                 // image is wrong type
1470                 e = new Error('image ' + image.uuid + ' is type '
1471                     + data.manifest.type + ', must be ' + image.type);
1472                 e.whatFailed = 'EBADTYPE';
1473                 log.error(e);
1474                 callback(e);
1475                 return;
1476             }
1477             log.info('image ' + image.uuid + ' found in imgadm');
1478 
1479             // If image_size is missing, add it. If it's wrong, error.
1480             if (data.manifest.hasOwnProperty('image_size')) {
1481                 if (image.hasOwnProperty('size')) {
1482                     if (image.size !== data.manifest.image_size) {
1483                         e = new Error('incorrect image_size value for image'
1484                             + ' ' + image.uuid + ' passed: '
1485                             + image.size + ' should be: '
1486                             + data.manifest.image_size);
1487                         e.whatFailed = 'EBADSIZE';
1488                         log.error(e);
1489                         callback(e);
1490                         return;
1491                     }
1492                 } else {
1493                     // image doesn't have size, manifest does, add it.
1494                     image.size = data.manifest.image_size;
1495                 }
1496             }
1497             // everything ok
1498             callback();
1499         } else {
1500             e = new Error('cannot find \'manifest\' for image '
1501                 + image.uuid);
1502             e.whatFailed = 'ENOENT';
1503             log.error(e);
1504             callback(e);
1505             return;
1506         }
1507     });
1508 }
1509 
1510 // Ensure if image_uuid is passed either at top level or for disks.*.image_uuid
1511 // that image_uuid exists on the system according to imgadm.
1512 //
1513 // NOTE: if image_size is missing from payload, but found in imgadm it is added
1514 // to the payload here.
1515 //
1516 function validateImages(payload, errors, log, callback)
1517 {
1518     var check_images = [];
1519     var disk_idx;
1520     var pool;
1521 
1522     if (payload.hasOwnProperty('image_uuid') && isUUID(payload.image_uuid)) {
1523         if (payload.hasOwnProperty('zpool')) {
1524             pool = payload.zpool;
1525         } else {
1526             pool = 'zones';
1527         }
1528 
1529         check_images.push({
1530             'property': 'image_uuid',
1531             'target': payload,
1532             'type': 'zone-dataset',
1533             'uuid': payload.image_uuid,
1534             'zpool': pool
1535         });
1536     }
1537 
1538     ['disks', 'add_disks'].forEach(function (d) {
1539         if (payload.hasOwnProperty(d)) {
1540             disk_idx = 0;
1541             payload[d].forEach(function (disk) {
1542                 if (disk.hasOwnProperty('image_uuid')) {
1543                     if (disk.hasOwnProperty('zpool')) {
1544                         pool = disk.zpool;
1545                     } else {
1546                         pool = 'zones';
1547                     }
1548                     check_images.push({
1549                         'property_prefix': d + '.' + disk_idx,
1550                         'property': d + '.' + disk_idx + '.image_uuid',
1551                         'target': disk,
1552                         'type': 'zvol',
1553                         'uuid': disk.image_uuid,
1554                         'zpool': pool
1555                     });
1556                 }
1557                 disk_idx++;
1558             });
1559         }
1560     });
1561 
1562     async.forEachSeries(check_images, function (image, cb) {
1563 
1564         var i;
1565         var idx;
1566 
1567         i = {
1568             uuid: image.uuid,
1569             type: image.type,
1570             zpool: image.zpool
1571         };
1572 
1573         if (image.target.hasOwnProperty('image_size')) {
1574             i.size = image.target.image_size;
1575         }
1576 
1577         validateImage(i, log, function (err) {
1578             if (err) {
1579                 switch (err.whatFailed) {
1580                     case 'EBADSIZE':
1581                         // image.size is wrong (vs. manifest)
1582                         errors.bad_values.push(image.property_prefix
1583                             + '.image_size');
1584                         break;
1585                     case 'ENOENT':
1586                         // image.uuid not found in imgadm
1587                         errors.bad_values.push(image.property);
1588                         break;
1589                     case 'EBADTYPE':
1590                         // image.type is wrong
1591                         errors.bad_values.push(image.property);
1592                         break;
1593                     default:
1594                         // unknown error, fail closed
1595                         errors.bad_values.push(image.property);
1596                         break;
1597                 }
1598             } else {
1599                 // no errors, so check if size was added
1600                 if (i.hasOwnProperty('size')) {
1601                     if (!image.target.hasOwnProperty('image_size')) {
1602                         image.target.image_size = i.size;
1603                         // Remove error that would have been added earlier
1604                         // when we didn't have image_size
1605                         idx = errors.missing_properties.indexOf(
1606                             image.property_prefix + '.image_size');
1607                         if (idx !== -1) {
1608                             errors.missing_properties.splice(idx, 1);
1609                         }
1610                     }
1611                 }
1612             }
1613 
1614             cb();
1615         });
1616     }, function () {
1617         callback();
1618     });
1619 }
1620 
1621 // This is for allowed_ips which accepts IPiv4 addresses or CIDR addresses in
1622 // the form IP/MASK where MASK is 1-32.
1623 function validateIPlist(list) {
1624     var invalid = [];
1625 
1626     list.forEach(function (ip) {
1627         var matches;
1628         if (!net.isIPv4(ip)) {
1629             matches = ip.match(/^([0-9\.]+)\/([0-9]+)$/);
1630             if (matches && net.isIPv4(matches[1])
1631                 && (Number(matches[2]) >= 1) && (Number(matches[2]) <= 32)) {
1632 
1633                 // In this case it wasn't an IPv4, but it was a valid CIDR
1634                 return;
1635             } else {
1636                 invalid.push(ip);
1637             }
1638         }
1639     });
1640 
1641     if (invalid.length !== 0) {
1642         throw new Error('invalid allowed_ips: ' + invalid.join(', '));
1643     }
1644 
1645     if (list.length > 13) {
1646         throw new Error('Maximum of 13 allowed_ips per nic');
1647     }
1648 }
1649 
1650 exports.validate = function (brand, action, payload, options, callback)
1651 {
1652     var errors = {
1653         'bad_values': [],
1654         'bad_properties': [],
1655         'missing_properties': []
1656     };
1657     var log;
1658     var prop;
1659 
1660     // options is optional
1661     if (arguments.length === 4) {
1662         callback = arguments[3];
1663         options = {};
1664     }
1665 
1666     ensureLogging(false);
1667     if (options.hasOwnProperty('log')) {
1668         log = options.log;
1669     } else {
1670         log = VM.log.child({action: 'validate'});
1671     }
1672 
1673     if (!BRAND_OPTIONS.hasOwnProperty(brand)) {
1674         if (!brand) {
1675             brand = 'undefined';
1676         }
1677         callback({'bad_brand': brand});
1678         return;
1679     }
1680 
1681     // wrap the whole thing with getZpools so we have the list of pools if we
1682     // need them.
1683     getZpools(log, function (err, zpools) {
1684         var disk_idx;
1685         var idx;
1686         var prefix;
1687         var required;
1688         var subprop;
1689         var subprop_action = '';
1690         var value;
1691 
1692         if (err) {
1693             /*
1694              * this only happens when the zpool command fails which should be
1695              * very rare, but when it does happen, we continue with an empty
1696              * zpool list in case they don't need to validate zpools. If they
1697              * do, every zpool will be invalid which is also what we want since
1698              * nothing else that uses zpools is likely to work either.
1699              *
1700              */
1701             zpools = [];
1702         }
1703 
1704         // loop through and weed out ones we don't allow for this action.
1705         for (prop in payload) {
1706             validateProperty(brand, prop, payload[prop], action,
1707                 {zpools: zpools}, errors, log);
1708 
1709             // special case for complex properties where we want to check
1710             // foo.*.whatever
1711             if (PAYLOAD_PROPERTIES.hasOwnProperty(prop)
1712                 && PAYLOAD_PROPERTIES[prop].type === 'object-array'
1713                 && Array.isArray(payload[prop])) {
1714 
1715                 if (PAYLOAD_PROPERTIES[prop].hasOwnProperty('check_as')) {
1716                     prefix = PAYLOAD_PROPERTIES[prop].check_as + '.*.';
1717                     if (prop.match(/^add_/)) {
1718                         subprop_action = 'add';
1719                     } else if (prop.match(/^update_/)) {
1720                         subprop_action = 'update';
1721                     }
1722                 } else {
1723                     // here we've got something like 'disks' which is an add
1724                     prefix = prop + '.*.';
1725                     subprop_action = 'add';
1726                 }
1727 
1728                 for (idx in payload[prop]) {
1729                     if (typeof (payload[prop][idx]) === 'object') {
1730                         // subprop will be something like 'nic_tag'
1731                         for (subprop in payload[prop][idx]) {
1732                             value = payload[prop][idx][subprop];
1733                             validateProperty(brand, prefix + subprop, value,
1734                                 subprop_action, {zpools: zpools}, errors, log);
1735                         }
1736                     } else if (errors.bad_values.indexOf(prop) === -1) {
1737                         // this is not an object so bad value in the array
1738                         errors.bad_values.push(prop);
1739                     }
1740                 }
1741             }
1742         }
1743 
1744         // special case: if you have disks you must specify either image_uuid
1745         // and image_size *or* size and block_size is only allowed when you use
1746         // 'size' and image_name when you don't.
1747         if (BRAND_OPTIONS[brand].hasOwnProperty('allowed_properties')
1748             && BRAND_OPTIONS[brand].allowed_properties
1749             .hasOwnProperty('disks')) {
1750 
1751             function validateDiskSource(prop_prefix, disk) {
1752 
1753                 if (disk.hasOwnProperty('media') && disk.media !== 'disk') {
1754                     // we only care about disks here, not cdroms.
1755                     return;
1756                 }
1757 
1758                 if (disk.hasOwnProperty('image_uuid')) {
1759                     // with image_uuid, size is invalid and image_size is
1760                     // required, additionally block_size is not allowed.
1761 
1762                     if (!disk.hasOwnProperty('image_size')) {
1763                         errors.missing_properties.push(prop_prefix
1764                             + '.image_size');
1765                     }
1766                     if (disk.hasOwnProperty('size')) {
1767                         errors.bad_properties.push(prop_prefix + '.size');
1768                     }
1769                     if (disk.hasOwnProperty('block_size')) {
1770                         errors.bad_properties.push(prop_prefix
1771                             + '.block_size');
1772                     }
1773                 } else {
1774                     // without image_uuid, image_size and image_name are invalid
1775                     // and 'size' is required.
1776 
1777                     if (!disk.hasOwnProperty('size')) {
1778                         errors.missing_properties.push(prop_prefix + '.size');
1779                     }
1780                     if (disk.hasOwnProperty('image_name')) {
1781                         errors.bad_properties.push(prop_prefix + '.image_name');
1782                     }
1783                     if (disk.hasOwnProperty('image_size')) {
1784                         errors.bad_properties.push(prop_prefix + '.image_size');
1785                     }
1786                 }
1787             }
1788 
1789             if (payload.hasOwnProperty('disks')) {
1790                 for (disk_idx in payload.disks) {
1791                     validateDiskSource('disks.' + disk_idx,
1792                         payload.disks[disk_idx]);
1793                 }
1794             }
1795             if (payload.hasOwnProperty('add_disks')) {
1796                 for (disk_idx in payload.add_disks) {
1797                     validateDiskSource('add_disks.' + disk_idx,
1798                         payload.add_disks[disk_idx]);
1799                 }
1800             }
1801         }
1802 
1803         if (BRAND_OPTIONS[brand].hasOwnProperty('required_properties')) {
1804             required = BRAND_OPTIONS[brand].required_properties;
1805             for (prop in required) {
1806                 if (required[prop].indexOf(action) !== -1
1807                     && !payload.hasOwnProperty(prop)) {
1808 
1809                     errors.missing_properties.push(prop);
1810                 }
1811             }
1812         }
1813 
1814         // make sure any images in the payload are also valid
1815         // NOTE: if validateImages() finds errors, it adds to 'errors' here.
1816         validateImages(payload, errors, log, function () {
1817 
1818             // we validate disks.*.refreservation here because image_size might
1819             // not be populated yet until we return from validateImages()
1820             ['disks', 'add_disks'].forEach(function (d) {
1821                 var d_idx = 0;
1822                 if (payload.hasOwnProperty(d)) {
1823                     payload[d].forEach(function (disk) {
1824                         if (disk.hasOwnProperty('refreservation')) {
1825                             if (disk.refreservation < 0) {
1826                                 errors.bad_values.push(d + '.' + d_idx
1827                                     + '.refreservation');
1828                             } else if (disk.size
1829                                 && disk.refreservation > disk.size) {
1830 
1831                                 errors.bad_values.push(d + '.' + d_idx
1832                                     + '.refreservation');
1833                             } else if (disk.image_size
1834                                 && disk.refreservation > disk.image_size) {
1835 
1836                                 errors.bad_values.push(d + '.' + d_idx
1837                                     + '.refreservation');
1838                             }
1839                         }
1840                         d_idx++;
1841                     });
1842                 }
1843             });
1844 
1845             if (errors.bad_properties.length > 0 || errors.bad_values.length > 0
1846                 || errors.missing_properties.length > 0) {
1847 
1848                 callback(errors);
1849                 return;
1850             }
1851 
1852             callback();
1853         });
1854     });
1855 };
1856 
1857 function separateCommas(str)
1858 {
1859     return str.split(',');
1860 }
1861 
1862 function unmangleMem(str)
1863 {
1864     return (Number(str) / (1024 * 1024));
1865 }
1866 
1867 function unbase64(str)
1868 {
1869     return new Buffer(str, 'base64').toString('ascii');
1870 }
1871 
1872 function numberify(str)
1873 {
1874     return Number(str);
1875 }
1876 
1877 function startElement(name, attrs, state, log) {
1878     var disk = {};
1879     var key;
1880     var newobj;
1881     var nic = {};
1882     var obj;
1883     var prop;
1884     var stack;
1885     var use;
1886     var where;
1887 
1888     assert(log, 'no logger passed to startElement()');
1889 
1890     if (!state.hasOwnProperty('stack')) {
1891         state.stack = [];
1892     }
1893     obj = state.obj;
1894     stack = state.stack;
1895 
1896     stack.push(name);
1897     where = stack.join('.');
1898 
1899     if (XML_PROPERTIES.hasOwnProperty(where)) {
1900         for (key in XML_PROPERTIES[where]) {
1901             use = XML_PROPERTIES[where][key];
1902             if (attrs.hasOwnProperty(key)) {
1903                 obj[use] = attrs[key];
1904             } else if (attrs.hasOwnProperty('name') && attrs.name === key) {
1905                 // attrs use the whacky {name, type, value} stuff.
1906                 obj[use] = attrs['value'];
1907             }
1908         }
1909     } else if (where === 'zone.rctl') {
1910         stack.push(attrs.name);
1911     } else if (where === 'zone.network') {
1912         // new network device
1913         for (prop in attrs) {
1914             if (XML_PROPERTIES.nic.hasOwnProperty(prop)) {
1915                 use = XML_PROPERTIES.nic[prop];
1916                 if (prop === 'mac-addr') {
1917                     // XXX SmartOS inherited the ridiculous MAC formatting from
1918                     //     Solaris where leading zeros are removed. We should
1919                     //     Fix that in the OS tools.
1920                     nic[use] = fixMac(attrs[prop]);
1921                 } else {
1922                     nic[use] = attrs[prop];
1923                 }
1924             } else {
1925                 log.debug('unknown net prop: ' + prop);
1926             }
1927         }
1928         if (!obj.hasOwnProperty('networks')) {
1929             obj.networks = {};
1930         }
1931         obj.networks[nic.mac] = nic;
1932         stack.push(nic.mac);
1933     } else if (where.match(/zone\.network\...:..:..:..:..:..\.net-attr/)) {
1934         if (XML_PROPERTIES.nic.hasOwnProperty(attrs.name)) {
1935             use = XML_PROPERTIES.nic[attrs.name];
1936             obj.networks[stack[2]][use] = attrs.value;
1937         } else {
1938             log.debug('unknown net prop: ' + attrs.name);
1939         }
1940     } else if (where === 'zone.device') {
1941         // new disk device
1942         for (prop in attrs) {
1943             if (XML_PROPERTIES.disk.hasOwnProperty(prop)) {
1944                 use = XML_PROPERTIES.disk[prop];
1945                 disk[use] = attrs[prop];
1946             } else {
1947                 log.debug('unknown disk prop: ' + prop);
1948             }
1949         }
1950         if (!obj.hasOwnProperty('devices')) {
1951             obj.devices = {};
1952         }
1953         obj.devices[disk.path] = disk;
1954         stack.push(disk.path);
1955     } else if (where.match(/zone\.device\.\/.*\.net-attr/)) {
1956         if (XML_PROPERTIES.disk.hasOwnProperty(attrs.name)) {
1957             use = XML_PROPERTIES.disk[attrs.name];
1958             obj.devices[stack[2]][use] = attrs.value;
1959         } else {
1960             log.debug('unknown disk prop: ' + attrs.name);
1961         }
1962     } else if (where === 'zone.dataset') {
1963         if (!obj.hasOwnProperty('datasets')) {
1964             obj.datasets = [];
1965         }
1966         if (attrs.hasOwnProperty('name')) {
1967             obj.datasets.push(attrs.name);
1968         }
1969     } else if (where === 'zone.filesystem') {
1970         if (!obj.hasOwnProperty('filesystems')) {
1971             obj.filesystems = [];
1972         }
1973         newobj = {};
1974         for (prop in XML_PROPERTIES.filesystem) {
1975             if (attrs.hasOwnProperty(prop)) {
1976                 newobj[XML_PROPERTIES.filesystem[prop]] = attrs[prop];
1977             }
1978         }
1979         obj.filesystems.push(newobj);
1980     } else if (where === 'zone.filesystem.fsoption') {
1981         newobj = obj.filesystems.slice(-1)[0];  // the last element
1982         if (!newobj.hasOwnProperty('options')) {
1983             newobj.options = [];
1984         }
1985         newobj.options.push(attrs.name);
1986     } else {
1987         log.debug('unknown property: ' + where + ': '
1988             + JSON.stringify(attrs));
1989     }
1990 }
1991 
1992 function endElement(name, state) {
1993     // trim stack back above this element
1994     var stack = state.stack;
1995 
1996     while (stack.pop() !== name) {
1997         // do nothing, we just want to consume.
1998         continue;
1999     }
2000 }
2001 
2002 function indexSort(obj, field, pattern)
2003 {
2004     obj.sort(function (a, b) {
2005         var avalue = 0;
2006         var bvalue = 0;
2007         var matches;
2008 
2009         if (a.hasOwnProperty(field)) {
2010             matches = a[field].match(pattern);
2011             if (matches) {
2012                 avalue = Number(matches[1]);
2013             }
2014         }
2015         if (b.hasOwnProperty(field)) {
2016             matches = b[field].match(pattern);
2017             if (matches) {
2018                 bvalue = Number(matches[1]);
2019             }
2020         }
2021 
2022         return avalue - bvalue;
2023     });
2024 }
2025 
2026 function applyTransforms(obj)
2027 {
2028     var p;
2029     var pp;
2030     var subobj;
2031     var transforms = XML_PROPERTY_TRANSFORMS;
2032 
2033     for (p in transforms) {
2034         if (obj.hasOwnProperty(p)) {
2035             if (typeof (transforms[p]) === 'object') {
2036                 // this is a 'complex' property like nic, and has different
2037                 // transforms for the sub-objects
2038                 for (pp in transforms[p]) {
2039                     for (subobj in obj[p]) {
2040                         if (obj[p][subobj].hasOwnProperty(pp)) {
2041                             obj[p][subobj][pp] =
2042                                 transforms[p][pp](obj[p][subobj][pp]);
2043                         }
2044                     }
2045                 }
2046             } else { // function
2047                 obj[p] = transforms[p](obj[p]);
2048             }
2049         }
2050     }
2051 }
2052 
2053 // This function parses the zone XML file at /etc/zones/<zonename>.xml and adds
2054 // the VM properties to a new object.
2055 function getVmobj(zonename, preload_data, options, callback)
2056 {
2057     var filename = '/etc/zones/' + zonename + '.xml';
2058     var log;
2059     var parser = new expat.Parser('UTF-8');
2060 
2061     assert(options.log, 'no logger passed to getVmobj()');
2062     log = options.log;
2063 
2064     fs.readFile(filename, function (error, data) {
2065         var allowed;
2066         var disk;
2067         var dsinfo;
2068         var fields;
2069         var nic;
2070         var obj = {};
2071         var state = {};
2072 
2073         if (error) {
2074             callback(error);
2075             return;
2076         }
2077 
2078         state.obj = obj;
2079         parser.on('startElement', function (name, attrs) {
2080             return startElement(name, attrs, state, log);
2081         });
2082         parser.on('endElement', function (name) {
2083             return endElement(name, state);
2084         });
2085 
2086         if (!parser.parse(data.toString())) {
2087             throw new Error('There are errors in your xml file: '
2088                 + parser.getError());
2089         }
2090 
2091         // now that we know which brand we are, find out what we're allowed.
2092         allowed = BRAND_OPTIONS[obj.brand].allowed_properties;
2093 
2094         // replace obj.networks with array of nics.
2095         obj.nics = [];
2096         for (nic in obj.networks) {
2097             obj.nics.push(obj.networks[nic]);
2098         }
2099         delete obj.networks;
2100 
2101         // replace obj.devices with array of disks.
2102         if (allowed.hasOwnProperty('disks')) {
2103             obj.disks = [];
2104             for (disk in obj.devices) {
2105                 obj.disks.push(obj.devices[disk]);
2106             }
2107         }
2108         delete obj.devices;
2109 
2110         if (!BRAND_OPTIONS.hasOwnProperty(obj.brand)) {
2111             throw new Error('unable to handle brand ' + obj.brand);
2112         }
2113 
2114         if (BRAND_OPTIONS[obj.brand].features.use_vm_autoboot) {
2115             obj.autoboot = obj.vm_autoboot;
2116             delete obj.vm_autoboot;
2117         }
2118 
2119         // apply the XML_PROPERTY_TRANSFORMs
2120         applyTransforms(obj);
2121 
2122         // probe for some fields on disks if this brand of zone supports them.
2123         if (allowed.hasOwnProperty('disks')
2124             && (allowed.disks.indexOf('create') !== -1)) {
2125 
2126             for (disk in obj.disks) {
2127                 disk = obj.disks[disk];
2128 
2129                 if (preload_data.hasOwnProperty('dsinfo')) {
2130                     dsinfo = preload_data.dsinfo;
2131                     if (dsinfo.hasOwnProperty('mountpoints')
2132                         && dsinfo.mountpoints.hasOwnProperty(disk.path)) {
2133 
2134                         disk.zfs_filesystem = dsinfo.mountpoints[disk.path];
2135                         disk.zpool = disk.zfs_filesystem.split('/')[0];
2136                     } else {
2137                         log.trace('no mountpoint data for ' + disk.path);
2138                     }
2139                 }
2140             }
2141         }
2142 
2143         if (obj.hasOwnProperty('transition')) {
2144             fields = rtrim(obj.transition).split(':');
2145             if (fields.length === 3) {
2146                 delete obj.transition;
2147                 obj.state = fields[0];
2148                 obj.transition_to = fields[1];
2149                 obj.transition_expire = fields[2];
2150             } else {
2151                 log.debug('getVmobj() ignoring bad value for '
2152                     + 'transition "' + obj.transition + '"');
2153             }
2154         }
2155 
2156         // sort the disks + nics by index
2157         if (obj.hasOwnProperty('disks')) {
2158             indexSort(obj.disks, 'path', /^.*-disk(\d+)$/);
2159         }
2160         if (obj.hasOwnProperty('nics')) {
2161             indexSort(obj.nics, 'interface', /^net(\d+)$/);
2162         }
2163         if (obj.hasOwnProperty('filesystems')) {
2164             indexSort(obj.filesystems, 'target', /^(.*)$/);
2165         }
2166 
2167         callback(null, obj);
2168     });
2169 }
2170 
2171 function setQuota(dataset, quota, log, callback)
2172 {
2173     var newval;
2174 
2175     assert(log, 'no logger passed to setQuota()');
2176 
2177     if (!dataset) {
2178         callback(new Error('Invalid dataset: "' + dataset + '"'));
2179         return;
2180     }
2181 
2182     if (quota === 0 || quota === '0') {
2183         newval = 'none';
2184     } else {
2185         newval = quota.toString() + 'g';
2186     }
2187 
2188     zfs(['set', 'quota=' + newval, dataset], log, function (err, fds) {
2189         if (err) {
2190             log.error('setQuota() cmd failed: ' + fds.stderr);
2191             callback(new Error(rtrim(fds.stderr)));
2192         } else {
2193             callback();
2194         }
2195     });
2196 }
2197 
2198 function cleanDatasetObject(obj)
2199 {
2200     var number_fields = [
2201         'avail',
2202         'available',
2203         'copies',
2204         'creation',
2205         'filesystem_limit',
2206         'quota',
2207         'recsize',
2208         'recordsize',
2209         'refer',
2210         'referenced',
2211         'refquota',
2212         'refreserv',
2213         'refreservation',
2214         'reserv',
2215         'reservation',
2216         'snapshot_limit',
2217         'usedbychildren',
2218         'usedbydataset',
2219         'usedbyrefreservation',
2220         'usedbysnapshots',
2221         'used',
2222         'userrefs',
2223         'utf8only',
2224         'version',
2225         'volblock',
2226         'volblocksize',
2227         'volsize',
2228         'written'
2229     ];
2230 
2231     // We should always have mountpoint, dataset and type because we force them
2232     // to be included in zfsList()
2233     assert(obj.hasOwnProperty('mountpoint'), 'cleanDatasetObject('
2234         + JSON.stringify(obj) + '): missing mountpoint');
2235     assert(obj.hasOwnProperty('name'), 'cleanDatasetObject('
2236         + JSON.stringify(obj) + '): missing name');
2237     assert(obj.hasOwnProperty('type'), 'cleanDatasetObject('
2238         + JSON.stringify(obj) + '): missing type');
2239 
2240     // convert numeric fields to proper numbers
2241     number_fields.forEach(function (field) {
2242         if (obj.hasOwnProperty(field) && obj[field] !== '-') {
2243             obj[field] = Number(obj[field]);
2244         }
2245     });
2246 
2247     if (obj.type === 'volume') {
2248         obj.mountpoint = '/dev/zvol/rdsk/' + obj.name;
2249     } else if (obj.mountpoint === '-' || obj.mountpoint === 'legacy') {
2250         obj.mountpoint = '/' + obj.name;
2251     }
2252 }
2253 
2254 function addDatasetResult(fields, types, results, line, log)
2255 {
2256     var dataset;
2257     var field;
2258     var lfields;
2259     var obj;
2260     var snapparts;
2261     var snapobj;
2262 
2263     line = trim(line);
2264 
2265     if (line.length === 0) {
2266         return;
2267     }
2268 
2269     lfields = line.split(/\s+/);
2270 
2271     if (lfields.length !== fields.length) {
2272         return;
2273     }
2274 
2275     obj = {};
2276 
2277     for (field in fields) {
2278         obj[fields[field]] = lfields[field];
2279     }
2280 
2281     cleanDatasetObject(obj);
2282 
2283     if (!results.hasOwnProperty('datasets')) {
2284         results.datasets = {};
2285     }
2286     if (!results.hasOwnProperty('mountpoints')) {
2287         results.mountpoints = {};
2288     }
2289     if (types.indexOf('snapshot') !== -1 && obj.type === 'snapshot') {
2290         if (!results.hasOwnProperty('snapshots')) {
2291             results.snapshots = {};
2292         }
2293 
2294         /*
2295          * For snapshots we store the snapname and optionally creation keyed by
2296          * dataset name So that we can include the list of snapshots for a
2297          * dataset on a VM.
2298          */
2299         snapparts = obj.name.split('@');
2300         assert.equal(snapparts.length, 2);
2301         dataset = snapparts[0];
2302         snapobj = {snapname: snapparts[1], dataset: dataset};
2303         if (!results.snapshots.hasOwnProperty(dataset)) {
2304             results.snapshots[dataset] = [];
2305         }
2306         if (obj.hasOwnProperty('creation')) {
2307             snapobj.created_at = obj.creation;
2308         }
2309         results.snapshots[dataset].push(snapobj);
2310     }
2311 
2312     results.datasets[obj.name] = obj;
2313 
2314     /*
2315      * snapshots don't have mountpoint that we care about and we don't count
2316      * 'none' as a mountpoint. If we otherwise have a mountpoint that looks like
2317      * a path, we add a pointer from that to the dataset name.
2318      */
2319     if (obj.type !== 'snapshot' && obj.mountpoint[0] === '/') {
2320         /*
2321          * For zoned filesystems (delegated datasets) we don't use mountpoint as
2322          * this can be changed from within the zone and is therefore not
2323          * reliable. Also, when a delegated dataset is assigned but the zone's
2324          * not been booted, the delegated dataset will not have the 'zoned'
2325          * property.  So we also check if the name ends in /data.
2326          */
2327         if (obj.hasOwnProperty('zoned') && obj.zoned === 'on') {
2328             // don't add zoned datasets to mountpoints
2329             /*jsl:pass*/
2330         } else if (obj.name.split('/')[2] === 'data') {
2331             // name is /data, skip
2332             /*jsl:pass*/
2333         } else {
2334             // here we have what looks like a normal non-zoned dataset that's
2335             // probably a zoneroot, add to mountpoints mapping.
2336             results.mountpoints[obj.mountpoint] = obj.name;
2337         }
2338     }
2339 }
2340 
2341 /*
2342  * Arguments:
2343  *
2344  * 'fields'   - should be an array of fields as listed in the zfs(1m) man page.
2345  * 'types'    - should be one or more of: filesystem, snapshot, volume.
2346  * 'log'      - should be a bunyan logger object.
2347  * 'callback' - will be called with (err, results)
2348  *
2349  * On failure: callback's err will be an Error object, ignore results.
2350  * On success: callback's results is an object with one or more members of:
2351  *
2352  *     results.datasets
2353  *
2354  *         keyed by dataset name containing the values for the requested fields.
2355  *
2356  *         Eg: results.datasets['zones/cores'] === { name: 'zones/cores', ... }
2357  *
2358  *     results.mountpoints
2359  *
2360  *         keyed by mountpoint with value being dataset name.
2361  *
2362  *         Eg: results.mountpoints['/zones/cores'] === 'zones/cores'
2363  *
2364  *     results.snapshots
2365  *
2366  *         keyed by dataset with value being array of snapname and created_at.
2367  *
2368  *         Eg: results.snapshots['/zones/cores'] === ['snap1', ...]
2369  *
2370  * For non-zoned filesystem datasets (these should be the zoneroot datasets),
2371  * you can use mountpoint which comes from zoneadm's "cheap" info and use that
2372  * to get to the dataset and from datasets[dataset] get the info.
2373  *
2374  * For volumes (KVM VM's disks) you can also use mountpoint as we'll set that
2375  * to the block device path and that's available from the devices section of
2376  * the zoneconfig.
2377  *
2378  * For zoned filesystems (delegated datasets) use the dataset name, as the
2379  * mountpoint can be changed from within the zone.
2380  *
2381  */
2382 function zfsList(fields, types, log, callback) {
2383     var args;
2384     var buffer = '';
2385     var lines;
2386     var cmd = '/usr/sbin/zfs';
2387     var req_fields = ['mountpoint', 'name', 'type'];
2388     var results = {};
2389     var zfs_child;
2390 
2391     assert(Array.isArray(types));
2392     assert(Array.isArray(fields));
2393     assert(log, 'no logger passed to zfsList()');
2394 
2395     // add any missing required fields
2396     req_fields.forEach(function (field) {
2397         if (fields.indexOf(field) === -1) {
2398             fields.push(field);
2399         }
2400     });
2401 
2402     args = ['list', '-H', '-p', '-t', types.join(','), '-o', fields.join(',')];
2403 
2404     log.debug(cmd + ' ' + args.join(' '));
2405 
2406     zfs_child = spawn(cmd, args, {'customFds': [-1, -1, -1]});
2407     log.debug('zfs running with pid ' + zfs_child.pid);
2408 
2409     zfs_child.stdout.on('data', function (data) {
2410         var line;
2411 
2412         buffer += data.toString();
2413         lines = buffer.split('\n');
2414         while (lines.length > 1) {
2415             line = lines.shift();
2416 
2417             // Add this line to results
2418             addDatasetResult(fields, types, results, line, log);
2419         }
2420         buffer = lines.pop();
2421     });
2422 
2423     // doesn't take input.
2424     zfs_child.stdin.end();
2425 
2426     zfs_child.on('exit', function (code) {
2427         log.debug('zfs process ' + zfs_child.pid + ' exited with code: '
2428             + code);
2429         if (code === 0) {
2430             callback(null, results);
2431         } else {
2432             callback(new Error('zfs exited prematurely with code: ' + code));
2433         }
2434     });
2435 }
2436 
2437 /*
2438  * This queue is used to handle zfs list requests. We do this because of OS-1834
2439  * in order to only run one 'zfs list' at a time. If we need to get data from
2440  * 'zfs list', the parameters we want to list are pushed onto this queue. If a
2441  * list is already running with the same parameters, we'll return the output
2442  * from that one when it completes to all the consumers. If there's not one
2443  * running, or the parameters are different, this set of parameters will be
2444  * pushed onto the tail of the queue. The queue is processed serially so long
2445  * as there are active requests.
2446  */
2447 zfs_list_queue = async.queue(function (task, callback) {
2448 
2449     var fields = task.fields;
2450     var log = task.log;
2451     var started = Date.now(0);
2452     var types = task.types;
2453 
2454     zfsList(fields, types, log, function (err, data) {
2455         var emitter = zfs_list_in_progress[task];
2456 
2457         delete zfs_list_in_progress[task];
2458         emitter.emit('result', err, data);
2459         emitter.removeAllListeners('result');
2460 
2461         log.debug('zfs list took ' + (Date.now(0) - started) + ' ms');
2462         callback();
2463     });
2464 
2465 }, 1);
2466 
2467 zfs_list_queue.drain = function () {
2468     // We use the global log here because this queue is not tied to one action.
2469     VM.log.trace('zfs_list_queue is empty');
2470 };
2471 
2472 function getZfsList(fields, types, log, callback) {
2473     var sorted_fields;
2474     var sorted_types;
2475     var task;
2476 
2477     sorted_fields = fields.slice().sort();
2478     sorted_types = types.slice().sort();
2479 
2480     task = {types: sorted_types, fields: sorted_fields, log: log};
2481 
2482     try {
2483         zfs_list_in_progress[task].on('result', callback);
2484     } catch (e) {
2485         if ((e instanceof TypeError)
2486             && (!zfs_list_in_progress.hasOwnProperty(task)
2487             || !zfs_list_in_progress[task].hasOwnProperty('on'))) {
2488 
2489             zfs_list_in_progress[task] = new EventEmitter();
2490             zfs_list_in_progress[task].on('result', callback);
2491             zfs_list_in_progress[task].setMaxListeners(0);
2492             zfs_list_queue.push(task);
2493 
2494             // callback() will get called when 'result' is emitted.
2495         } else {
2496             callback(e);
2497         }
2498     }
2499 }
2500 
2501 function loadDatasetInfo(fields, log, callback)
2502 {
2503     var zfs_fields = [];
2504     var zfs_types = [];
2505 
2506     assert(log, 'no logger passed to loadDataset()');
2507 
2508     function addField(name) {
2509         if (zfs_fields.indexOf(name) === -1) {
2510             zfs_fields.push(name);
2511         }
2512     }
2513 
2514     function addType(name) {
2515         if (zfs_types.indexOf(name) === -1) {
2516             zfs_types.push(name);
2517         }
2518     }
2519 
2520     if (!fields || fields.length < 1) {
2521         // Default to grabbing everything we might possibly need.
2522         zfs_fields =  ['name', 'quota', 'volsize', 'mountpoint', 'type',
2523             'compression', 'recsize', 'volblocksize', 'zoned', 'creation',
2524             'refreservation'];
2525         zfs_types = ['filesystem', 'snapshot', 'volume'];
2526     } else {
2527         if (fields.indexOf('disks') !== -1) {
2528             addField('compression');
2529             addField('volsize');
2530             addField('volblocksize');
2531             addField('refreservation');
2532             addType('volume');
2533         }
2534         if (fields.indexOf('snapshots') !== -1) {
2535             addField('creation');
2536             addType('snapshot');
2537             addType('filesystem');
2538             addType('volume');
2539         }
2540         if (fields.indexOf('create_timestamp') !== -1) {
2541             // We might fall back to creation on the dataset for
2542             // create_timestamp if we have no create-timestamp attr.
2543             addField('creation');
2544             addType('filesystem');
2545         }
2546         if ((fields.indexOf('zfs_root_compression') !== -1)
2547             || (fields.indexOf('zfs_data_compression') !== -1)) {
2548 
2549             addField('compression');
2550             addType('filesystem');
2551         }
2552         if ((fields.indexOf('zfs_root_recsize') !== -1)
2553             || (fields.indexOf('zfs_data_recsize') !== -1)) {
2554 
2555             addField('recsize');
2556             addType('filesystem');
2557         }
2558         if (fields.indexOf('quota') !== -1) {
2559             addField('quota');
2560             addType('filesystem');
2561         }
2562         // both zpool and zfs_filesystem come from 'name'
2563         if (fields.indexOf('zpool') !== -1
2564             || fields.indexOf('zfs_filesystem') !== -1) {
2565 
2566             addField('name');
2567             addType('filesystem');
2568         }
2569         if (zfs_fields.length > 0) {
2570             // we have some fields so we need to zfs, make sure we have name,
2571             // mountpoint and type which we always need if we get anything.
2572             addField('name');
2573             addField('mountpoint');
2574             addField('type');
2575 
2576             if (zfs_types.indexOf('filesystem') !== -1) {
2577                 // to differentiate between delegated and root filesystems
2578                 addField('zoned');
2579             }
2580         } else {
2581             log.debug('no need to call zfs');
2582             callback(null, {
2583                 datasets: {},
2584                 mountpoints: {},
2585                 snapshots: {}
2586             });
2587             return;
2588         }
2589     }
2590 
2591     /*
2592      * NOTE:
2593      * in the future, the plan is to have the list of types and fields
2594      * be dynamic based what's actually needed to handle the request.
2595      */
2596 
2597     getZfsList(zfs_fields, zfs_types, log, callback);
2598 }
2599 
2600 function loadJsonConfig(vmobj, cfg, log, callback)
2601 {
2602     var filename;
2603 
2604     assert(log, 'no logger passed to loadJsonConfig()');
2605 
2606     if (vmobj.zonepath) {
2607         filename = vmobj.zonepath + '/config/' + cfg + '.json';
2608         log.trace('loadJsonConfig() loading ' + filename);
2609 
2610         fs.readFile(filename, function (error, data) {
2611             var json = {};
2612 
2613             if (error) {
2614                 if (error.code === 'ENOENT') {
2615                     log.debug('Skipping nonexistent file ' + filename);
2616                 } else {
2617                     log.error(error,
2618                         'loadJsonConfig() failed to load ' + filename);
2619                     callback(error, {});
2620                     return;
2621                 }
2622             } else {
2623                 try {
2624                     json = JSON.parse(data.toString());
2625                 } catch (e) {
2626                     json = {};
2627                 }
2628             }
2629 
2630             callback(null, json);
2631         });
2632     } else {
2633         callback(null, {});
2634     }
2635 }
2636 
2637 /*
2638  * This preloads some data for us that comes from commands which output for
2639  * *all* VMs.  This allows us to just run these (expensive) commands once
2640  * instead of having to run them for each VM.
2641  *
2642  */
2643 function preloadZoneData(uuid, options, callback)
2644 {
2645     var data = {};
2646     var log;
2647 
2648     assert(options.log, 'no logger passed to preloadZoneData()');
2649     log = options.log;
2650 
2651     // NOTE: uuid can be null, in which case we get data for all VMs.
2652 
2653     async.series([
2654         function (cb) {
2655             // We always do this (calls `zoneadm list -vc`) since we always
2656             // need to know which zones exist.
2657             getZoneRecords(uuid, log, function (err, records) {
2658                 if (!err) {
2659                     data.records = records;
2660                 }
2661                 cb(err);
2662             });
2663         }, function (cb) {
2664             var fields;
2665 
2666             if (options.hasOwnProperty('fields')) {
2667                 fields = options.fields;
2668             } else {
2669                 fields = [];
2670             }
2671 
2672             loadDatasetInfo(fields, log, function (err, dsinfo) {
2673                 if (!err) {
2674                     data.dsinfo = dsinfo;
2675                 }
2676                 cb(err);
2677             });
2678         }, function (cb) {
2679             if (options.hasOwnProperty('fields')
2680                 && (options.fields.indexOf('server_uuid') === -1
2681                     && options.fields.indexOf('datacenter_name') === -1
2682                     && options.fields.indexOf('headnode_id') === -1)) {
2683 
2684                 // we don't need any fields that come from sysinfo.
2685                 log.debug('no need to call sysinfo, no sysinfo fields in list');
2686                 data.sysinfo = {};
2687                 cb();
2688                 return;
2689             }
2690 
2691             VM.getSysinfo([], {log: log}, function (err, sysinfo) {
2692                 if (!err) {
2693                     data.sysinfo = sysinfo;
2694                 }
2695                 cb(err);
2696             });
2697         }, function (cb) {
2698             var u;
2699             var uuids = [];
2700 
2701             if (options.hasOwnProperty('fields')
2702                 && options.fields.indexOf('pid') === -1) {
2703 
2704                 log.debug('no need to check PID files, PID not in field list');
2705                 cb();
2706                 return;
2707             }
2708 
2709             // get the PID values from running KVM VMs
2710 
2711             for (u in data.records) {
2712                 uuids.push(u);
2713             }
2714             async.forEachSeries(uuids, function (z_uuid, zcb) {
2715                 var filename;
2716                 var z = data.records[z_uuid];
2717 
2718                 // NOTE: z.state here is equivalent to zone_state not state.
2719                 if (z && BRAND_OPTIONS[z.brand].hasOwnProperty('features')
2720                     && BRAND_OPTIONS[z.brand].features.pid_file
2721                     && z.state === 'running') {
2722 
2723                     // ensure pid_file is safe
2724                     try {
2725                         assertSafeZonePath(path.join(z.zonepath, '/root'),
2726                             BRAND_OPTIONS[z.brand].features.pid_file,
2727                             {type: 'file', enoent_ok: true});
2728                     } catch (e) {
2729                         // We log an error here, but not being able to get
2730                         // the PID for one broken machine should not impact the
2731                         // ability to get a list of all machines, so we just
2732                         // skip adding a PID and log an error here.
2733                         log.error(e, 'Unsafe path for ' + z.uuid + ' cannot '
2734                             + 'check for PID file: ' + e.message);
2735                         zcb();
2736                         return;
2737                     }
2738 
2739                     filename = path.join(z.zonepath, 'root',
2740                         BRAND_OPTIONS[z.brand].features.pid_file);
2741                     log.debug('checking for ' + filename);
2742 
2743                     fs.readFile(filename,
2744                         function (error, filedata) {
2745 
2746                         var pid;
2747 
2748                         if (!error) {
2749                             pid = Number(trim(filedata.toString()));
2750                             if (pid > 0) {
2751                                 z.pid = pid;
2752                                 log.debug('found PID ' + pid + ' for '
2753                                     + z.uuid);
2754                             }
2755                         }
2756                         if (error && error.code === 'ENOENT') {
2757                             // don't return error in this case because it just
2758                             // didn't exist
2759                             log.debug('no PID file for ' + z.uuid);
2760                             zcb();
2761                         } else {
2762                             zcb(error);
2763                         }
2764                     });
2765                 } else {
2766                     zcb();
2767                 }
2768             }, function (err) {
2769                 cb(err);
2770             });
2771         }
2772     ], function (err, res) {
2773         log.trace('leaving preloadZoneData()');
2774         callback(err, data);
2775     });
2776 }
2777 
2778 function getZoneRecords(uuid, log, callback)
2779 {
2780     var args = [];
2781     var buffer = '';
2782     var cmd = '/usr/sbin/zoneadm';
2783     var line_count = 0;
2784     var lines;
2785     var results = {};
2786     var zadm;
2787     var zadm_stderr = '';
2788 
2789     assert(log, 'no logger passed to getZoneRecords()');
2790 
2791     if (uuid) {
2792         // this gives us zone info if uuid is *either* a zonename or uuid
2793         if (isUUID(uuid)) {
2794             args.push('-z');
2795             args.push(uuid);
2796             args.push('-u');
2797             args.push(uuid);
2798         } else {
2799             args.push('-z');
2800             args.push(uuid);
2801         }
2802     }
2803     args.push('list');
2804     args.push('-p');
2805     if (!uuid) {
2806         args.push('-c');
2807     }
2808 
2809     log.debug(cmd + ' ' + args.join(' '));
2810 
2811     zadm = spawn(cmd, args, {'customFds': [-1, -1, -1]});
2812     log.debug('zoneadm running with PID ' + zadm.pid);
2813 
2814     zadm.stderr.on('data', function (data) {
2815         zadm_stderr += data.toString();
2816     });
2817 
2818     zadm.stdout.on('data', function (data) {
2819         var fields;
2820         var line;
2821         var obj;
2822 
2823         buffer += data.toString();
2824         lines = buffer.split('\n');
2825         while (lines.length > 1) {
2826             line = lines.shift();
2827             line_count++;
2828             fields = rtrim(line).split(':');
2829             if (fields.length === 8 && fields[1] !== 'global') {
2830                 obj = {
2831                     'zoneid': Number(fields[0]),
2832                     'zonename': fields[1],
2833                     'state': fields[2],
2834                     'zonepath': fields[3],
2835                     'uuid': fields[4],
2836                     'brand': fields[5],
2837                     'ip_type': fields[6]
2838                 };
2839                 log.trace('loaded: ' + JSON.stringify(obj));
2840                 // XXX zones in some states have no uuid. We should either fix
2841                 //     that or use zonename for those.
2842                 results[obj.uuid] = obj;
2843             } else if (line.replace(/ /g, '').length > 0) {
2844                 log.debug('getZoneRecords(' + uuid + ') ignoring: ' + line);
2845             }
2846         }
2847         buffer = lines.pop();
2848     });
2849 
2850     // doesn't take input.
2851     zadm.stdin.end();
2852 
2853     zadm.on('close', function (code) {
2854         var errmsg;
2855         var new_err;
2856 
2857         log.debug('zoneadm process ' + zadm.pid + ' exited with code: '
2858             + code + ' (' + line_count + ' lines to stdout)');
2859         if (code === 0) {
2860             callback(null, results);
2861         } else {
2862             errmsg = rtrim(zadm_stderr);
2863             new_err = new Error(errmsg);
2864             if (errmsg.match(/No such zone configured$/)) {
2865                 // not existing isn't always a problem (eg. existence check)
2866                 new_err.code = 'ENOENT';
2867             } else {
2868                 log.error({err: new_err, stderr: zadm_stderr},
2869                     'getZoneRecords() zoneadm "' + args.join(',') + '" failed');
2870             }
2871             callback(new_err);
2872             return;
2873         }
2874     });
2875 }
2876 
2877 exports.flatten = function (vmobj, key)
2878 {
2879     var index;
2880     var tokens = key.split('.');
2881 
2882     // NOTE: VM.flatten() currently doesn't produce any logs
2883 
2884     if (tokens.length === 3
2885         && VM.FLATTENABLE_ARRAY_HASH_KEYS.indexOf(tokens[0]) !== -1) {
2886 
2887         if (!vmobj.hasOwnProperty(tokens[0])) {
2888             return undefined;
2889         }
2890         if (!vmobj[tokens[0]].hasOwnProperty(tokens[1])) {
2891             return undefined;
2892         }
2893         return vmobj[tokens[0]][tokens[1]][tokens[2]];
2894     }
2895 
2896     if (tokens.length === 2
2897         && VM.FLATTENABLE_HASH_KEYS.indexOf(tokens[0]) !== -1) {
2898 
2899         if (!vmobj.hasOwnProperty(tokens[0])) {
2900             return undefined;
2901         }
2902         return vmobj[tokens[0]][tokens[1]];
2903     }
2904 
2905     if (tokens.length === 2
2906         && VM.FLATTENABLE_ARRAYS.indexOf(tokens[0]) !== -1) {
2907 
2908         index = Number(tokens[1]);
2909 
2910         if (!vmobj.hasOwnProperty(tokens[0])) {
2911             return undefined;
2912         }
2913 
2914         if (index === NaN || index < 0
2915             || !vmobj[tokens[0]].hasOwnProperty(index)) {
2916 
2917             return undefined;
2918         }
2919         return vmobj[tokens[0]][index];
2920     }
2921 
2922     return vmobj[key];
2923 };
2924 
2925 function getLastModified(vmobj, log)
2926 {
2927     var files = [];
2928     var file;
2929     var stat;
2930     var timestamp = 0;
2931 
2932     assert(log, 'no logger passed to getLastModified()');
2933 
2934     if (vmobj.zonepath) {
2935         files.push(path.join(vmobj.zonepath, '/config/metadata.json'));
2936         files.push(path.join(vmobj.zonepath, '/config/routes.json'));
2937         files.push(path.join(vmobj.zonepath, '/config/tags.json'));
2938     } else {
2939         log.debug('getLastModified() no zonepath!');
2940     }
2941 
2942     if (vmobj.hasOwnProperty('zonename')) {
2943         files.push('/etc/zones/' + vmobj.zonename + '.xml');
2944     } else {
2945         log.debug('getLastModified() no zonename!');
2946     }
2947 
2948     for (file in files) {
2949         file = files[file];
2950         try {
2951             stat = fs.statSync(file);
2952             if (stat.isFile()) {
2953                 if ((timestamp === 0) || (Date.parse(stat.mtime) > timestamp)) {
2954                     timestamp = Date.parse(stat.mtime);
2955                 }
2956             }
2957         } catch (e) {
2958             if (e.code !== 'ENOENT') {
2959                 log.error(e, 'Unable to get timestamp for "' + file + '":'
2960                     + e.message);
2961             }
2962         }
2963     }
2964 
2965     return ((new Date(timestamp)).toISOString());
2966 }
2967 
2968 function loadVM(uuid, data, options, callback)
2969 {
2970     var e;
2971     var info;
2972     var log;
2973 
2974     assert(options.log, 'no logger passed to loadVM()');
2975     log = options.log;
2976 
2977     // XXX need to always have data when we get here
2978     info = data.records[uuid];
2979 
2980     if (!info) {
2981         e = new Error('VM.load() empty info when getting record '
2982             + 'for vm ' + uuid);
2983         log.error(e);
2984         callback(e);
2985         return;
2986     }
2987 
2988     getVmobj(info.zonename, data, options, function (err, vmobj) {
2989         if (err) {
2990             callback(err);
2991             return;
2992         }
2993 
2994         function wantField(field) {
2995             if (options.hasOwnProperty('fields')
2996                 && options.fields.indexOf(field) === -1) {
2997 
2998                 return false;
2999             }
3000 
3001             return true;
3002         }
3003 
3004         // We got some bits from `zoneadm list` as <info> here, and since we
3005         // already got that data, adding it to the object here is cheap. We also
3006         // need some of these properties to be able to get others later, so we
3007         // add them all now. If they're unwanted they'll be removed from the
3008         // final object.
3009         vmobj.uuid = info.uuid;
3010         vmobj.zone_state = info.state;
3011 
3012         // In the case of 'configured' zones, we might only have zonename
3013         // because uuid isn't set yet.  Because of that case, we set uuid
3014         // to zonename if it is in UUID form.
3015         if ((!vmobj.uuid || vmobj.uuid.length === 0)
3016             && isUUID(vmobj.zonename)) {
3017 
3018             vmobj.uuid = vmobj.zonename;
3019         }
3020 
3021         // These ones we never need elsewhere, so don't bother adding if we
3022         // don't need to.
3023         if (wantField('zoneid') && info.zoneid !== '-') {
3024             vmobj.zoneid = info.zoneid;
3025         }
3026 
3027         if (wantField('pid') && info.pid) {
3028             vmobj.pid = info.pid;
3029         }
3030 
3031         // find when we last modified this VM
3032         if (wantField('last_modified')) {
3033             vmobj.last_modified = getLastModified(vmobj, log);
3034         }
3035 
3036         // If we want resolvers, (eg. OS-2194) we always add the array here
3037         // so you can tell that the resolvers are explicitly not set.
3038         if (wantField('resolvers') && !vmobj.hasOwnProperty('resolvers')) {
3039             vmobj.resolvers = [];
3040         }
3041 
3042         // sysinfo has server_uuid and potentially some DC info
3043         if (data.hasOwnProperty('sysinfo')) {
3044             if (wantField('server_uuid')
3045                 && data.sysinfo.hasOwnProperty('UUID')) {
3046 
3047                 vmobj.server_uuid = data.sysinfo.UUID;
3048             }
3049             if (wantField('datacenter_name')
3050                 && data.sysinfo.hasOwnProperty('Datacenter Name')) {
3051 
3052                 vmobj.datacenter_name = data.sysinfo['Datacenter Name'];
3053             }
3054             if (wantField('headnode_id')
3055                 && data.sysinfo.hasOwnProperty('Headnode ID')) {
3056 
3057                 vmobj.headnode_id = data.sysinfo['Headnode ID'];
3058             }
3059         }
3060 
3061         // state could already be set here if it was overriden by a transition
3062         // that's in progress. So we only change if that's not the case.
3063         if (wantField('state')) {
3064             if (!vmobj.hasOwnProperty('state')) {
3065                 if (info.state === 'installed') {
3066                     vmobj.state = 'stopped';
3067                 } else {
3068                     vmobj.state = info.state;
3069                 }
3070             }
3071 
3072             // If the zone has the 'failed' property it doesn't matter what
3073             // other state it might be in, we list its state as 'failed'.
3074             if (vmobj.failed) {
3075                 vmobj.state = 'failed';
3076             }
3077         }
3078 
3079         async.series([
3080             function (cb) {
3081                 if (!wantField('customer_metadata')
3082                     && !wantField('internal_metadata')) {
3083 
3084                     cb();
3085                     return;
3086                 }
3087 
3088                 loadJsonConfig(vmobj, 'metadata', log,
3089                     function (error, metadata) {
3090                     if (error) {
3091                         // when zone_state is 'incomplete' we could be
3092                         // deleting it in which case metadata may already
3093                         // be gone, ignore failure to load mdata when
3094                         // 'incomplete' because of this.
3095                         if (vmobj.zone_state === 'incomplete') {
3096                             log.debug(error, 'zone is in state incomplete '
3097                                 + 'ignoring error: ' + error.message);
3098                         } else {
3099                             cb(error);
3100                             return;
3101                         }
3102                     }
3103 
3104                     if (wantField('customer_metadata')) {
3105                         if (metadata.hasOwnProperty('customer_metadata')) {
3106                             vmobj.customer_metadata
3107                                 = metadata.customer_metadata;
3108                         } else {
3109                             vmobj.customer_metadata = {};
3110                         }
3111                     }
3112 
3113                     if (wantField('internal_metadata')) {
3114                         if (metadata.hasOwnProperty('internal_metadata')) {
3115                             vmobj.internal_metadata
3116                                 = metadata.internal_metadata;
3117                         } else {
3118                             vmobj.internal_metadata = {};
3119                         }
3120                     }
3121 
3122                     cb();
3123                 });
3124             }, function (cb) {
3125                 if (!wantField('tags')) {
3126                     cb();
3127                     return;
3128                 }
3129 
3130                 loadJsonConfig(vmobj, 'tags', log, function (error, tags) {
3131                     if (error) {
3132                         // when zone_state is 'incomplete' we could be deleting
3133                         // it in which case metadata may already be gone, ignore
3134                         // failure to load mdata when 'incomplete' because of
3135                         // this.
3136                         if (vmobj.zone_state === 'incomplete') {
3137                             log.debug(error, 'zone is in state incomplete '
3138                                 + 'ignoring error: ' + error.message);
3139                         } else {
3140                             cb(error);
3141                             return;
3142                         }
3143                     }
3144                     vmobj.tags = tags;
3145                     cb();
3146                 });
3147             }, function (cb) {
3148                 if (!wantField('routes')) {
3149                     cb();
3150                     return;
3151                 }
3152 
3153                 loadJsonConfig(vmobj, 'routes', log, function (error, routes) {
3154                     if (error) {
3155                         // same as tags above, if zone_state is 'incomplete'
3156                         // we could be a file that's already gone
3157                         if (vmobj.zone_state === 'incomplete') {
3158                             log.debug(error, 'zone is in state incomplete '
3159                                 + 'ignoring error: ' + error.message);
3160                         } else {
3161                             cb(error);
3162                             return;
3163                         }
3164                     }
3165                     vmobj.routes = routes;
3166                     cb();
3167                 });
3168             }, function (cb) {
3169                 var dsinfo;
3170                 var dsname;
3171                 var dsobj;
3172                 var d;
3173                 var delegated;
3174                 var disk;
3175                 var ds;
3176                 var filesys;
3177                 var friendly_snap;
3178                 var friendly_snapshots = [];
3179                 var matches;
3180                 var raw_snapshots = [];
3181                 var snap;
3182                 var snap_time;
3183 
3184                 // local alias, data.dsinfo should include all the info about
3185                 // this VM's zoneroot that we care about here.
3186                 dsinfo = data.dsinfo;
3187 
3188                 if (dsinfo.hasOwnProperty('mountpoints')
3189                     && dsinfo.hasOwnProperty('datasets')
3190                     && dsinfo.mountpoints.hasOwnProperty(vmobj.zonepath)) {
3191 
3192                     dsname = dsinfo.mountpoints[vmobj.zonepath];
3193                     dsobj = dsinfo.datasets[dsname];
3194 
3195                     /* dsobj.quota is in bytes, we want GiB for vmobj.quota */
3196                     if (wantField('quota') && dsobj.hasOwnProperty('quota')) {
3197                         vmobj.quota = (dsobj.quota / (1024 * 1024 * 1024));
3198                         log.trace('found quota "' + vmobj.quota + '" for '
3199                             + vmobj.uuid);
3200                     }
3201 
3202                     if (wantField('create_timestamp')
3203                         && !vmobj.hasOwnProperty('create_timestamp')
3204                         && dsobj.hasOwnProperty('creation')) {
3205 
3206                         log.debug('VM has no create_timestamp, using creation '
3207                             + 'from ' + dsobj.name);
3208                         vmobj.create_timestamp =
3209                             (new Date(dsobj.creation * 1000)).toISOString();
3210                     }
3211 
3212                     if (wantField('zfs_root_compression')
3213                         && dsobj.hasOwnProperty('compression')
3214                         && (dsobj.compression !== 'off')) {
3215 
3216                         vmobj.zfs_root_compression = dsobj.compression;
3217                     }
3218 
3219                     if (wantField('zfs_root_recsize')
3220                         && dsobj.hasOwnProperty('recsize')) {
3221 
3222                         vmobj.zfs_root_recsize = dsobj.recsize;
3223                     }
3224 
3225                     // Always add zfs_filesystem if we can because it's needed
3226                     // to find other properties such as delegated_dataset.
3227                     vmobj.zfs_filesystem = dsobj.name;
3228 
3229                     if (wantField('snapshots')
3230                         && dsinfo.hasOwnProperty('snapshots')
3231                         && dsinfo.snapshots
3232                         .hasOwnProperty(vmobj.zfs_filesystem)) {
3233 
3234                         raw_snapshots = raw_snapshots.concat(
3235                             dsinfo.snapshots[vmobj.zfs_filesystem]);
3236                     }
3237 
3238                     log.trace('found dataset "' + vmobj.zfs_filesystem
3239                         + '" for ' + vmobj.uuid);
3240                 } else {
3241                     log.trace('no dsinfo for ' + vmobj.uuid + ': '
3242                         + vmobj.zonepath);
3243                 }
3244 
3245                 // delegated datasets are keyed on the dataset name instead of
3246                 // mountpoint, since mountpoint can change in a zone.
3247                 if (vmobj.hasOwnProperty('zfs_filesystem')) {
3248                     delegated = vmobj.zfs_filesystem + '/data';
3249                     if (dsinfo.datasets.hasOwnProperty(delegated)) {
3250                         dsobj = dsinfo.datasets[delegated];
3251 
3252                         if (dsobj.hasOwnProperty('compression')
3253                             && (dsobj.compression !== 'off')) {
3254 
3255                             vmobj.zfs_data_compression = dsobj.compression;
3256                         }
3257                         if (dsobj.hasOwnProperty('recsize')) {
3258                             vmobj.zfs_data_recsize = dsobj.recsize;
3259                         }
3260 
3261                         // If there are snapshots for this dataset, add them
3262                         if (DISABLED) {
3263                             // XXX currently only support snapshot on
3264                             // zfs_filesystem
3265                             if (dsinfo.hasOwnProperty('snapshots')
3266                                 && dsinfo.snapshots.hasOwnProperty(delegated)) {
3267 
3268                                 raw_snapshots = raw_snapshots
3269                                     .concat(dsinfo.snapshots[delegated]);
3270                             }
3271                         }
3272                     } else {
3273                         log.trace('no dsinfo for delegated dataset: '
3274                             + delegated);
3275                     }
3276 
3277                     vmobj.zpool =
3278                         vmobj.zfs_filesystem.split('/')[0];
3279                 }
3280 
3281                 if (wantField('disks') && vmobj.hasOwnProperty('disks')) {
3282                     for (d in vmobj.disks) {
3283                         d = vmobj.disks[d];
3284                         if (d.hasOwnProperty('path')
3285                             && dsinfo.mountpoints.hasOwnProperty(d.path)) {
3286 
3287                             dsname = dsinfo.mountpoints[d.path];
3288                             dsobj = dsinfo.datasets[dsname];
3289 
3290                             if (dsobj.hasOwnProperty('volsize')) {
3291 
3292                                 /* dsobj.volsize is in bytes, we want MiB */
3293                                 d.size = (dsobj.volsize / (1024 * 1024));
3294                                 log.debug('found size=' + d.size + ' for '
3295                                     + JSON.stringify(d));
3296                             }
3297                             if (dsobj.hasOwnProperty('compression')) {
3298                                 d.compression = dsobj.compression;
3299                             }
3300                             if (dsobj.hasOwnProperty('refreservation')) {
3301                                 /* dsobj.refreservation is in bytes, want MiB */
3302                                 d.refreservation
3303                                     = (dsobj.refreservation / (1024 * 1024));
3304                                 log.debug('found refreservation='
3305                                     + d.refreservation + ' for '
3306                                     + JSON.stringify(d));
3307                             }
3308                             if (dsobj.hasOwnProperty('volblocksize')) {
3309                                 d.block_size = dsobj.volblocksize;
3310                             }
3311 
3312                             // If there are snapshots for this dataset, add them
3313                             // to the list.
3314                             if (DISABLED) {
3315                                 // XXX currently only support snapshots on
3316                                 //     zfs_filesystem
3317                                 if (dsinfo.hasOwnProperty('snapshots')
3318                                     && dsinfo.snapshots.hasOwnProperty(
3319                                         d.zfs_filesystem)) {
3320 
3321                                     raw_snapshots = raw_snapshots.concat(dsinfo
3322                                         .snapshots[d.zfs_filesystem]);
3323                                 }
3324                             }
3325                         } else if (d.hasOwnProperty('path')) {
3326                             d.missing = true;
3327                         } else {
3328                             log.warn('no dsinfo and no path for '
3329                                 + JSON.stringify(d));
3330                         }
3331                     }
3332                 }
3333 
3334                 // snapshots here is the raw list of snapshots, now we need to
3335                 // convert it to the "friendly" list of snapshots.
3336                 if (wantField('snapshots')) {
3337                     for (snap in raw_snapshots) {
3338                         snap = raw_snapshots[snap];
3339 
3340                         matches = snap.snapname.match(/^vmsnap-(.*)$/);
3341                         if (matches && matches[1]) {
3342                             friendly_snap = {name: matches[1]};
3343                             if (snap.hasOwnProperty('created_at')) {
3344                                 snap_time
3345                                     = new Date(snap.created_at * 1000); // in ms
3346                                 friendly_snap.created_at
3347                                     = snap_time.toISOString();
3348                             }
3349                             friendly_snapshots.push(friendly_snap);
3350                         } else {
3351                             log.debug('ignoring unfriendly ' + snap.snapname);
3352                             continue;
3353                         }
3354                     }
3355                     // sort the snapshots with newest first.
3356                     friendly_snapshots.sort(function (a, b) {
3357                         if (a.created_at > b.created_at) {
3358                             return -1;
3359                         }
3360                         if (a.created_at < b.created_at) {
3361                             return 1;
3362                         }
3363                         return 0; // equal
3364                     });
3365                     vmobj.snapshots = friendly_snapshots;
3366                 }
3367 
3368                 if (vmobj.state === 'receiving') {
3369                     vmobj.missing = { 'datasets': [], 'disks': [],
3370                         'filesystems': [] };
3371                     if (!fs.existsSync(vmobj.zonepath)) {
3372                         vmobj.missing.datasets.push(vmobj.zonepath.substr(1));
3373                     }
3374                     for (ds in vmobj.datasets) {
3375                         ds = vmobj.datasets[ds];
3376                         vmobj.missing.datasets.push(ds);
3377                     }
3378                     for (filesys in vmobj.filesystems) {
3379                         filesys = vmobj.filesystems[filesys];
3380                         if (filesys.hasOwnProperty('source')) {
3381                             vmobj.missing.filesystems.push(filesys.source);
3382                         }
3383                     }
3384                     for (disk in vmobj.disks) {
3385                         disk = vmobj.disks[disk];
3386                         if (disk.hasOwnProperty('missing')) {
3387                             vmobj.missing.disks.push(disk.path);
3388                         }
3389                     }
3390                 }
3391 
3392                 cb();
3393             }
3394         ], function (error) {
3395             callback(error, vmobj);
3396         });
3397 
3398     });
3399 }
3400 
3401 exports.load = function (uuid, options, callback)
3402 {
3403     var log;
3404     var load_opts = {};
3405 
3406     // This is a wrapper so that other internal functions here (such as lookup)
3407     // can do smart things like check the quota for each VM with a separate call
3408     // to zfs get.
3409 
3410     // options is optional
3411     if (arguments.length === 2) {
3412         callback = arguments[1];
3413         options = {};
3414     }
3415 
3416     ensureLogging(false);
3417     if (options.hasOwnProperty('log')) {
3418         log = options.log;
3419     } else {
3420         log = VM.log.child({action: 'load', vm: uuid});
3421     }
3422 
3423     load_opts.log = log;
3424     if (options.hasOwnProperty('fields')) {
3425         load_opts.fields = options.fields;
3426     }
3427 
3428     preloadZoneData(uuid, load_opts, function (error, data) {
3429         if (error) {
3430             if (options.missing_ok && error.code === 'ENOENT') {
3431                 // we're expecting the zone to be gone in this case (eg. delete)
3432                 log.debug('VM ' + uuid + ' does not exist (as expected)');
3433             } else {
3434                 log.error(error, 'VM.load() failed to get zone record'
3435                     + ' for ' + uuid);
3436             }
3437             callback(error);
3438         } else {
3439             loadVM(uuid, data, load_opts, function (e, vmobj) {
3440                 if (e) {
3441                     callback(e);
3442                     return;
3443                 }
3444 
3445                 if (load_opts.hasOwnProperty('fields')) {
3446                     // clean out unwanted fields
3447                     Object.keys(vmobj).forEach(function (key) {
3448                         if (options.fields.indexOf(key) === -1) {
3449                             delete vmobj[key];
3450                         }
3451                     });
3452                 }
3453                 callback(null, vmobj);
3454             });
3455         }
3456     });
3457 };
3458 
3459 function fixMac(str)
3460 {
3461     var fixed = [];
3462     var octet;
3463     var octets = str.split(':');
3464 
3465     for (octet in octets) {
3466         if (octets.hasOwnProperty(octet)) {
3467             octet = parseInt(octets[octet], 16);
3468             if (octet === 'nan') {
3469                 octet = 0;
3470             }
3471             fixed.push(sprintf('%02x', octet));
3472         }
3473     }
3474 
3475     return fixed.join(':');
3476 }
3477 
3478 // zonecfg requires removing leading 0's in MACs like 01:02:03:04:05:06
3479 // This function takes a MAC in normal form and puts it in the goofy form
3480 // zonecfg wants.
3481 function ruinMac(mac)
3482 {
3483     var part;
3484     var parts;
3485     var out = [];
3486 
3487     parts = mac.split(':');
3488 
3489     for (part in parts) {
3490         part = ltrim(parts[part], '0');
3491         if (part.length === 0) {
3492             part = '0';
3493         }
3494         out.push(part);
3495     }
3496 
3497     return (out.join(':'));
3498 }
3499 
3500 function matcher(zone, search)
3501 {
3502     var fields;
3503     var found;
3504     var i;
3505     var key;
3506     var parameters_matched = 0;
3507     var regex;
3508     var target;
3509 
3510     function find_match(k, targ) {
3511         var value = VM.flatten(zone, k);
3512 
3513         if (!regex && k.match(/^nics\..*\.mac$/)) {
3514             // Fix for broken SmartOS MAC format
3515             targ = fixMac(targ);
3516         }
3517 
3518         if (regex && (value !== undefined) && value.toString().match(targ)) {
3519             found = true;
3520         } else if ((value !== undefined)
3521             && value.toString() === targ.toString()) {
3522             found = true;
3523         }
3524     }
3525 
3526     for (key in search) {
3527         found = false;
3528         regex = false;
3529 
3530         target = search[key];
3531         if (target[0] === '~') {
3532             regex = true;
3533             target = new RegExp(target.substr(1), 'i');
3534         }
3535 
3536         fields = key.split('.');
3537         if (fields.length === 3 && fields[1] === '*'
3538             && zone.hasOwnProperty(fields[0])
3539             && VM.FLATTENABLE_ARRAY_HASH_KEYS.indexOf(fields[0]) !== -1) {
3540 
3541             // Special case: for eg. nics.*.ip, we want to loop through all nics
3542             for (i = 0; i < zone[fields[0]].length; i++) {
3543                 fields[1] = i;
3544                 find_match(fields.join('.'), target);
3545             }
3546         } else {
3547             find_match(key, target);
3548         }
3549 
3550         if (!found) {
3551             return false;
3552         } else {
3553             parameters_matched++;
3554         }
3555     }
3556 
3557     if (parameters_matched > 0) {
3558         // we would have returned false from the loop had any parameters not
3559         // matched and we had at least one that did.
3560         return true;
3561     }
3562 
3563     return false;
3564 }
3565 
3566 exports.lookup = function (search, options, callback)
3567 {
3568     var log;
3569     var key;
3570     var matches;
3571     var need_fields = [];
3572     var preload_opts = {};
3573     var quick_ok = true;
3574     var results = [];
3575     var transform;
3576 
3577     // options is optional
3578     if (arguments.length === 2) {
3579         callback = arguments[1];
3580         options = {};
3581     }
3582 
3583     ensureLogging(false);
3584     if (options.hasOwnProperty('log')) {
3585         log = options.log;
3586     } else {
3587         log = VM.log.child({action: 'lookup', search: search});
3588     }
3589 
3590     // XXX the 'transform' option is not intended to be public yet and should
3591     // only be used by tools willing to be rewritten if this is removed or
3592     // changed.
3593     if (options.hasOwnProperty('transform')) {
3594         transform = options.transform;
3595     }
3596 
3597     // keep separate variable because we can have some fields we add below that
3598     // we need for searching, but shouldn't be in the output.
3599     if (options.hasOwnProperty('fields')) {
3600         need_fields = options.fields.slice(0);
3601     }
3602 
3603     for (key in search) {
3604         // To be able to search on a field, that field needs to be added to
3605         // the objects, if user requested a set of fields missing the one
3606         // they're searching for, add it.
3607         matches = key.match(/^([^.]+)\./);
3608         if (matches) {
3609             if (need_fields.indexOf(matches[1]) == -1) {
3610                 need_fields.push(matches[1]);
3611             }
3612         } else {
3613             if (need_fields.indexOf(key) == -1) {
3614                 need_fields.push(key);
3615             }
3616         }
3617     }
3618 
3619     // If all the keys we're searching for are in the QUICK_LOOKUP data, we
3620     // don't need the full zone records to locate the VMs we're interested in.
3621     for (key in need_fields) {
3622         if (QUICK_LOOKUP.indexOf(key) === -1) {
3623             quick_ok = false;
3624         }
3625     }
3626 
3627     preload_opts.log = log;
3628     if (options.hasOwnProperty('fields')) {
3629         preload_opts.fields = need_fields;
3630     }
3631 
3632     // This is used when you've specified fields to remove those that might
3633     // have been added as a group but are not wanted, or were added as
3634     // dependencies for looking up wanted fields, or for search.
3635     function filterFields(res) {
3636         res.forEach(function (result) {
3637             Object.keys(result).forEach(function (k) {
3638                 if (options.fields.indexOf(k) === -1) {
3639                     delete result[k];
3640                 }
3641             });
3642         });
3643     }
3644 
3645     preloadZoneData(null, preload_opts, function (err, data) {
3646         var records = data.records;
3647         var uuids = [];
3648 
3649         if (err) {
3650             callback(err);
3651             return;
3652         }
3653 
3654         if (quick_ok) {
3655             var full_results = [];
3656             var load_opts = {};
3657             var match;
3658             var regex;
3659             var source;
3660             var target;
3661             var u;
3662             var z;
3663 
3664             if (err) {
3665                 callback(err);
3666                 return;
3667             }
3668             for (z in records) {
3669                 z = records[z];
3670                 match = true;
3671                 for (key in search) {
3672                     regex = false;
3673                     // force field type to string so that earlier transformed
3674                     // number fields get back their match method and the
3675                     // strict not-equal operator will work on number lookups
3676                     source = '' + z[key];
3677                     target = search[key];
3678                     if (target[0] === '~') {
3679                         target = new RegExp(target.substr(1), 'i');
3680                         regex = true;
3681                     }
3682                     if (regex && !source.match(target)) {
3683                         match = false;
3684                     } else if (!regex && (source !== search[key])) {
3685                         match = false;
3686                     }
3687                 }
3688                 if (match && z.uuid) {
3689                     results.push(z.uuid);
3690                 }
3691             }
3692 
3693             load_opts.log = log;
3694             if (options.hasOwnProperty('fields') && need_fields.length > 0) {
3695                 // we have a specific set of fields we want to grab
3696                 load_opts.fields = need_fields;
3697             } else if (!options.full) {
3698                 // we don't need all the data so what we already got is enough
3699                 if (options.hasOwnProperty('fields')) {
3700                     filterFields(results);
3701                 }
3702 
3703                 callback(null,
3704                     results.filter(function (res) {
3705                         if (typeof (res) === 'object') {
3706                             return (Object.keys(res).length > 0);
3707                         } else {
3708                             return (true);
3709                         }
3710                     })
3711                 );
3712                 return;
3713             }
3714 
3715             function expander(uuid, cb) {
3716                 loadVM(uuid, data, load_opts, function (e, obj) {
3717                     if (e) {
3718                         if (e.code === 'ENOENT') {
3719                             // zone likely was deleted since lookup, ignore
3720                             cb();
3721                         } else {
3722                             cb(e);
3723                         }
3724                     } else {
3725                         if (transform) {
3726                             transform(obj);
3727                         }
3728                         full_results.push(obj);
3729                         cb();
3730                     }
3731                 });
3732             }
3733 
3734             async.forEachSeries(results, expander, function (e) {
3735                 var res_list;
3736 
3737                 if (e) {
3738                     log.error(e, 'VM.lookup failed to expand results: '
3739                         + e.message);
3740                     callback(e);
3741                 } else {
3742                     res_list = full_results;
3743                     if (options.hasOwnProperty('fields')) {
3744                         filterFields(res_list);
3745                     }
3746                     callback(null,
3747                         res_list.filter(function (res) {
3748                             if (typeof (res) === 'object') {
3749                                 return (Object.keys(res).length > 0);
3750                             } else {
3751                                 return (true);
3752                             }
3753                         })
3754                     );
3755                 }
3756             });
3757         } else {
3758             // have to search the hard way (through all the data)
3759             for (u in records) {
3760                 uuids.push(u);
3761             }
3762             // this is parallel!
3763             async.forEach(uuids, function (uuid, cb) {
3764                 var vmobj = records[uuid];
3765                 var l_opts = {log: log};
3766 
3767                 if (options.hasOwnProperty('fields')
3768                     && need_fields.length > 0) {
3769 
3770                     // we have a specific set of fields we want to grab
3771                     l_opts.fields = need_fields;
3772                 }
3773 
3774                 loadVM(vmobj.uuid, data, l_opts, function (error, obj) {
3775                     if (error) {
3776                         if (error.code === 'ENOENT') {
3777                             // zone likely was deleted since lookup, ignore
3778                             cb();
3779                         } else {
3780                             cb(error);
3781                         }
3782                     } else {
3783                         if (transform) {
3784                             transform(obj);
3785                         }
3786                         if (Object.keys(search).length === 0
3787                             || matcher(obj, search)) {
3788 
3789                             results.push(obj);
3790                         }
3791                         cb();
3792                     }
3793                 });
3794             }, function (e) {
3795                 var r;
3796                 var short_results = [];
3797 
3798                 if (e) {
3799                     callback(e);
3800                 } else {
3801                     if (options.full) {
3802                         callback(null, results);
3803                     } else if (options.fields && need_fields.length > 0) {
3804                         if (options.hasOwnProperty('fields')) {
3805                             filterFields(results);
3806                         }
3807                         callback(null,
3808                             results.filter(function (res) {
3809                                 if (typeof (res) === 'object') {
3810                                     return (Object.keys(res).length > 0);
3811                                 } else {
3812                                     return (true);
3813                                 }
3814                             })
3815                         );
3816                     } else {
3817                         for (r in results) {
3818                             short_results.push(results[r].uuid);
3819                         }
3820                         callback(null, short_results);
3821                     }
3822                 }
3823             });
3824         }
3825     });
3826 };
3827 
3828 // create a random new locally administered MAC address
3829 function generateMAC()
3830 {
3831     var data = [(Math.floor(Math.random() * 15) + 1).toString(16) + 2];
3832     for (var i = 0; i < 5; i++) {
3833         var oct = (Math.floor(Math.random() * 255) + 1).toString(16);
3834         if (oct.length == 1) {
3835             oct = '0' + oct;
3836         }
3837         data.push(oct);
3838     }
3839 
3840     return data.join(':');
3841 }
3842 
3843 // return the MAC address based on a VRRP Virtual Router ID
3844 function vrrpMAC(vrid) {
3845     return sprintf('00:00:5e:00:01:%02x', vrid);
3846 }
3847 
3848 // Ensure we've got all the datasets necessary to create this VM
3849 //
3850 // IMPORTANT:
3851 //
3852 // On SmartOS, we assume a provisioner or some other external entity has already
3853 // loaded the dataset into the system. This function just confirms that the
3854 // dataset actually exists.
3855 //
3856 function checkDatasets(payload, log, callback)
3857 {
3858     var checkme = [];
3859     var d;
3860     var disk;
3861 
3862     assert(log, 'no logger passed to checkDatasets()');
3863 
3864     log.debug('Checking for required datasets.');
3865 
3866     // build list of datasets we need to download (downloadme)
3867     for (disk in payload.add_disks) {
3868         if (payload.add_disks.hasOwnProperty(disk)) {
3869             d = payload.add_disks[disk];
3870             if (d.hasOwnProperty('image_uuid')) {
3871                 checkme.push(payload.zpool + '/'
3872                     + d.image_uuid);
3873             }
3874         }
3875     }
3876 
3877     function checker(dataset, cb) {
3878         zfs(['list', '-o', 'name', '-H', dataset], log, function (err, fds) {
3879             if (err) {
3880                 log.error({'err': err, 'stdout': fds.stdout,
3881                     'stderr': fds.stderr}, 'zfs list ' + dataset + ' '
3882                     + 'exited with' + ' code ' + err.code + ': ' + err.message);
3883                 cb(new Error('unable to find dataset: ' + dataset));
3884             } else {
3885                 cb();
3886             }
3887         });
3888     }
3889 
3890     // check that we have all the volumes
3891     async.forEachSeries(checkme, checker, function (err) {
3892         if (err) {
3893             log.error(err, 'checkDatasets() failed to find required '
3894                 + 'volumes');
3895             callback(err);
3896         } else {
3897             // progress(100, 'we have all necessary datasets');
3898             callback();
3899         }
3900     });
3901 }
3902 
3903 function lookupConflicts(macs, ips, vrids, log, callback) {
3904     var conflict = false;
3905     var load_fields;
3906 
3907     load_fields = ['brand', 'state', 'nics', 'uuid', 'zonename', 'zone_state'];
3908 
3909     assert(log, 'no logger passed to lookupConflicts()');
3910 
3911     log.debug('checking for conflicts with '
3912         + JSON.stringify(macs) + ', ' + JSON.stringify(ips) + ' and '
3913         + JSON.stringify(vrids));
3914 
3915     if (macs.length === 0 && ips.length === 0 && vrids.length === 0) {
3916         log.debug('returning from conflict check (nothing to check)');
3917         callback(null, conflict);
3918         return;
3919     }
3920 
3921     preloadZoneData(null, {fields: load_fields, log: log},
3922         function (err, data) {
3923 
3924         var records = data.records;
3925         var uuid;
3926         var uuids = [];
3927 
3928         if (err) {
3929             callback(err);
3930             return;
3931         }
3932 
3933         for (uuid in records) {
3934             uuids.push(uuid);
3935         }
3936 
3937         // this is parallel!
3938         async.forEach(uuids, function (z_uuid, cb) {
3939             loadVM(z_uuid, data, {fields: load_fields, log: log},
3940                 function (error, obj) {
3941 
3942                 var ip;
3943                 var mac;
3944                 var vrid;
3945 
3946                 if (error) {
3947                     if (error.code === 'ENOENT') {
3948                         // zone likely was deleted since lookup, ignore it
3949                         cb();
3950                     } else {
3951                         cb(error);
3952                     }
3953                     return;
3954                 }
3955 
3956                 if (obj.state === 'failed' && obj.zone_state !== 'running') {
3957                     // Ignore zones that are failed unless they're 'running'
3958                     // which they shouldn't be because they get stopped on
3959                     // failure.
3960                     cb();
3961                     return;
3962                 }
3963 
3964                 for (ip in ips) {
3965                     if (ips[ip] !== 'dhcp'
3966                         && matcher(obj, {'nics.*.ip': ips[ip]})) {
3967 
3968                         log.error('Found conflict: ' + obj.uuid
3969                             + ' already has IP ' + ips[ip]);
3970                         conflict = true;
3971                     }
3972                 }
3973                 for (mac in macs) {
3974                     if (matcher(obj, {'nics.*.mac': macs[mac]})) {
3975                         log.error('Found conflict: ' + obj.uuid
3976                             + ' already has MAC ' + macs[mac]);
3977                         conflict = true;
3978                     }
3979                 }
3980                 for (vrid in vrids) {
3981                     if (matcher(obj, {'nics.*.vrrp_vrid': vrids[vrid]})) {
3982                         log.error('Found conflict: ' + obj.uuid
3983                             + ' already has VRID ' + vrids[vrid]);
3984                         conflict = true;
3985                     }
3986                 }
3987                 cb();
3988             });
3989         }, function (e) {
3990             if (e) {
3991                 callback(e);
3992             } else {
3993                 log.debug('returning from conflict check');
3994                 callback(null, conflict);
3995             }
3996         });
3997     });
3998 }
3999 
4000 function lookupInvalidNicTags(nics, log, callback) {
4001     var etherstubs = [];
4002     var nic_tags = {};
4003 
4004     assert(log, 'no logger passed to lookupInvalidNicTags()');
4005 
4006     if (!nics || nics.length === 0) {
4007         callback();
4008         return;
4009     }
4010 
4011     async.parallel([
4012         function (cb) {
4013             dladm.showEtherstub(null, log, function (err, stubs) {
4014                 if (err) {
4015                     cb(err);
4016                 } else {
4017                     etherstubs = stubs;
4018                     cb();
4019                 }
4020             });
4021         }, function (cb) {
4022             VM.getSysinfo([], {log: log}, function (err, sysinfo) {
4023                 if (err) {
4024                     cb(err);
4025                 } else {
4026                     var nic;
4027                     var tag;
4028                     for (nic in sysinfo['Network Interfaces']) {
4029                         nic = sysinfo['Network Interfaces'][nic];
4030                         for (tag in nic['NIC Names']) {
4031                             nic_tags[nic['NIC Names'][tag]] = 1;
4032                         }
4033                     }
4034                     cb();
4035                 }
4036             });
4037         }
4038     ], function (err, results) {
4039         if (err) {
4040             callback(err);
4041             return;
4042         }
4043 
4044         var nic;
4045         for (nic in nics) {
4046             nic = nics[nic];
4047             if (!nic.hasOwnProperty('nic_tag')) {
4048                 continue;
4049             }
4050             if (!nic_tags.hasOwnProperty(nic.nic_tag)
4051                 && (etherstubs.indexOf(nic.nic_tag) === -1)) {
4052                 callback(new Error('Invalid nic tag "' + nic.nic_tag + '"'));
4053                 return;
4054             }
4055         }
4056 
4057         callback();
4058         return;
4059     });
4060 }
4061 
4062 // create a new zvol for a VM
4063 function createVolume(volume, log, callback)
4064 {
4065     var refreserv;
4066     var size;
4067     var snapshot;
4068 
4069     assert(log, 'no logger passed for createVolume()');
4070 
4071     log.debug('creating volume ' + JSON.stringify(volume));
4072 
4073     if (volume.hasOwnProperty('image_size')) {
4074         size = volume.image_size;
4075     } else if (volume.hasOwnProperty('size')) {
4076         size = volume.size;
4077     } else {
4078         callback(new Error('FATAL: createVolume(' + JSON.stringify(volume)
4079             + '): ' + 'has no size or image_size'));
4080         return;
4081     }
4082 
4083     if (volume.hasOwnProperty('refreservation')) {
4084         refreserv = volume.refreservation;
4085     } else {
4086         log.debug('defaulting to refreservation = ' + size);
4087         refreserv = size;
4088     }
4089 
4090     async.series([
4091         function (cb) {
4092             if (volume.hasOwnProperty('image_uuid')) {
4093                 snapshot = volume.zpool + '/' + volume.image_uuid + '@final';
4094                 zfs(['get', '-Ho', 'value', 'name', snapshot], log,
4095                     function (err, fds) {
4096 
4097                     if (err) {
4098                         if (fds.stderr.match('dataset does not exist')) {
4099                             // no @final, so we'll make a new snapshot @<uuid>
4100                             snapshot = volume.zpool + '/' + volume.image_uuid
4101                                 + '@' + volume.uuid;
4102 
4103                             zfs(['snapshot', snapshot], log, function (e) {
4104                                 cb(e);
4105                             });
4106                         } else {
4107                             cb(err);
4108                         }
4109                     } else {
4110                         // @final is here!
4111                         cb();
4112                     }
4113                 });
4114             } else {
4115                 cb();
4116             }
4117         }, function (cb) {
4118             var args;
4119             var target;
4120 
4121             target = volume.zpool + '/' + volume.uuid;
4122             if (volume.hasOwnProperty('image_uuid')) {
4123                 // This volume is from a template/dataset/image so we create
4124                 // it as a clone of a the @final snapshot on the original.
4125                 // we already set 'snapshot' to the correct location above.
4126                 args = ['clone', '-F'];
4127                 if (volume.hasOwnProperty('compression')) {
4128                     args.push('-o', 'compression='
4129                         + volume.compression);
4130                 }
4131                 if (volume.hasOwnProperty('block_size')) {
4132                     args.push('-o', 'volblocksize='
4133                         + volume.block_size);
4134                 }
4135                 args.push('-o', 'refreservation=' + refreserv + 'M');
4136                 args.push(snapshot, target);
4137                 zfs(args, log, function (e) {
4138                     if (e) {
4139                         cb(e);
4140                     } else {
4141                         volume.path = '/dev/zvol/rdsk/' + target;
4142                         cb();
4143                     }
4144                 });
4145             } else {
4146                 // This volume is not from a template/dataset/image so we create
4147                 // a blank new zvol for it.
4148                 args = ['create'];
4149                 if (volume.hasOwnProperty('compression')) {
4150                     args.push('-o', 'compression='
4151                         + volume.compression);
4152                 }
4153                 if (volume.hasOwnProperty('block_size')) {
4154                     args.push('-o', 'volblocksize='
4155                         + volume.block_size);
4156                 }
4157                 args.push('-o', 'refreservation=' + refreserv + 'M', '-V',
4158                     size + 'M', target);
4159                 zfs(args, log, function (err, fds) {
4160                     if (err) {
4161                         cb(err);
4162                     } else {
4163                         volume.path = '/dev/zvol/rdsk/' + target;
4164                         cb();
4165                     }
4166                 });
4167             }
4168         }
4169     ], function (err, results) {
4170         callback(err);
4171     });
4172 }
4173 
4174 // Create all the volumes for a given VM property set
4175 function createVolumes(payload, log, callback)
4176 {
4177     var createme = [];
4178     var d;
4179     var disk;
4180     var disk_idx = 0;
4181     var used_disk_indexes = [];
4182 
4183     assert(log, 'no logger passed to createVolumes()');
4184 
4185     log.debug('creating volumes: ' + JSON.stringify(payload.add_disks));
4186 
4187     if (payload.hasOwnProperty('used_disk_indexes')) {
4188         used_disk_indexes = payload.used_disk_indexes;
4189     }
4190 
4191     for (disk in payload.add_disks) {
4192         if (payload.add_disks.hasOwnProperty(disk)) {
4193             d = payload.add_disks[disk];
4194 
4195             // we don't create CDROM devices or disk devices which have the
4196             // nocreate: true property.
4197             if (d.media !== 'cdrom' && !d.nocreate) {
4198                 // skip to the next unused one.
4199                 while (used_disk_indexes.indexOf(disk_idx) !== -1) {
4200                     disk_idx++;
4201                 }
4202 
4203                 d.index = disk_idx;
4204                 d.uuid = payload.uuid + '-disk' + disk_idx;
4205                 used_disk_indexes.push(Number(disk_idx));
4206                 if (!d.hasOwnProperty('zpool')) {
4207                     d.zpool = payload.zpool;
4208                 }
4209                 createme.push(d);
4210             }
4211         }
4212     }
4213 
4214     function loggedCreateVolume(volume, cb) {
4215         return createVolume(volume, log, cb);
4216     }
4217 
4218     // create all the volumes we found that we need.
4219     async.forEachSeries(createme, loggedCreateVolume, function (err) {
4220         if (err) {
4221             callback(err);
4222         } else {
4223             callback();
4224         }
4225     });
4226 }
4227 
4228 // writes a Zone's metadata JSON to /zones/<uuid>/config/metadata.json
4229 // and /zones/<uuid>/config/tags.json.
4230 function updateMetadata(vmobj, payload, log, callback)
4231 {
4232     var cmdata = {};
4233     var imdata = {};
4234     var key;
4235     var mdata = {};
4236     var mdata_filename;
4237     var tags = {};
4238     var tags_filename;
4239     var zonepath;
4240 
4241     assert(log, 'no logger passed to updateMetadata()');
4242 
4243     if (vmobj.hasOwnProperty('zonepath')) {
4244         zonepath = vmobj.zonepath;
4245     } else if (vmobj.hasOwnProperty('zpool')
4246         && vmobj.hasOwnProperty('zonename')) {
4247 
4248         zonepath = '/' + vmobj.zpool + '/' + vmobj.zonename;
4249     } else {
4250         callback(new Error('unable to find zonepath for '
4251             + JSON.stringify(vmobj)));
4252         return;
4253     }
4254 
4255     // paths are under zonepath but not zoneroot
4256     mdata_filename = zonepath + '/config/metadata.json';
4257     tags_filename = zonepath + '/config/tags.json';
4258 
4259     // customer_metadata
4260     for (key in vmobj.customer_metadata) {
4261         if (vmobj.customer_metadata.hasOwnProperty(key)) {
4262             cmdata[key] = vmobj.customer_metadata[key];
4263             if (payload.hasOwnProperty('remove_customer_metadata')
4264                 && payload.remove_customer_metadata.indexOf(key) !== -1) {
4265 
4266                 // in the remove_* list, don't load it.
4267                 delete cmdata[key];
4268             }
4269         }
4270     }
4271 
4272     for (key in payload.set_customer_metadata) {
4273         if (payload.set_customer_metadata.hasOwnProperty(key)) {
4274             cmdata[key] = payload.set_customer_metadata[key];
4275         }
4276     }
4277 
4278     // internal_metadata
4279     for (key in vmobj.internal_metadata) {
4280         if (vmobj.internal_metadata.hasOwnProperty(key)) {
4281             imdata[key] = vmobj.internal_metadata[key];
4282             if (payload.hasOwnProperty('remove_internal_metadata')
4283                 && payload.remove_internal_metadata.indexOf(key) !== -1) {
4284 
4285                 // in the remove_* list, don't load it.
4286                 delete imdata[key];
4287             }
4288         }
4289     }
4290 
4291     for (key in payload.set_internal_metadata) {
4292         if (payload.set_internal_metadata.hasOwnProperty(key)) {
4293             imdata[key] = payload.set_internal_metadata[key];
4294         }
4295     }
4296 
4297     // same thing for tags
4298     for (key in vmobj.tags) {
4299         if (vmobj.tags.hasOwnProperty(key)) {
4300             tags[key] = vmobj.tags[key];
4301             if (payload.hasOwnProperty('remove_tags')
4302                 && payload.remove_tags.indexOf(key) !== -1) {
4303 
4304                 // in the remove_* list, don't load it.
4305                 delete tags[key];
4306             }
4307         }
4308     }
4309 
4310     for (key in payload.set_tags) {
4311         if (payload.set_tags.hasOwnProperty(key)) {
4312             tags[key] = payload.set_tags[key];
4313         }
4314     }
4315 
4316     mdata = {'customer_metadata': cmdata, 'internal_metadata': imdata};
4317     fs.writeFile(mdata_filename, JSON.stringify(mdata, null, 2),
4318         function (err) {
4319             if (err) {
4320                 callback(err);
4321             } else {
4322                 log.debug('wrote metadata to ' + mdata_filename);
4323                 fs.writeFile(tags_filename, JSON.stringify(tags, null, 2),
4324                     function (e) {
4325                         if (e) {
4326                             callback(e);
4327                         } else {
4328                             log.debug('wrote tags to' + tags_filename);
4329                             callback();
4330                         }
4331                     }
4332                 );
4333             }
4334         }
4335     );
4336 }
4337 
4338 function saveMetadata(payload, log, callback)
4339 {
4340     var protovm = {};
4341 
4342     assert(log, 'no logger passed to saveMetadata()');
4343 
4344     if (!payload.hasOwnProperty('zonepath')
4345         || !payload.hasOwnProperty('zpool')
4346         || !payload.hasOwnProperty('zonename')) {
4347 
4348         callback(new Error('saveMetadata payload is missing zone '
4349             + 'properties.'));
4350         return;
4351     }
4352 
4353     protovm.zonepath = payload.zonepath;
4354     protovm.zpool = payload.zpool;
4355     protovm.zonename = payload.zonename;
4356     protovm.customer_metadata = {};
4357     protovm.tags = {};
4358 
4359     if (payload.hasOwnProperty('tags')) {
4360         payload.set_tags = payload.tags;
4361         delete payload.tags;
4362     }
4363     if (payload.hasOwnProperty('customer_metadata')) {
4364         payload.set_customer_metadata = payload.customer_metadata;
4365         delete payload.customer_metadata;
4366     }
4367     if (payload.hasOwnProperty('internal_metadata')) {
4368         payload.set_internal_metadata = payload.internal_metadata;
4369         delete payload.internal_metadata;
4370     }
4371 
4372     updateMetadata(protovm, payload, log, callback);
4373 }
4374 
4375 // writes a zone's metadata JSON to /zones/<uuid>/config/routes.json
4376 function updateRoutes(vmobj, payload, log, callback)
4377 {
4378     var filename;
4379     var key;
4380     var routes = {};
4381     var zonepath;
4382 
4383     assert(log, 'no logger passed to updateRoutes()');
4384 
4385     if (vmobj.hasOwnProperty('zonepath')) {
4386         zonepath = vmobj.zonepath;
4387     } else if (vmobj.hasOwnProperty('zpool')
4388         && vmobj.hasOwnProperty('zonename')) {
4389 
4390         zonepath = '/' + vmobj.zpool + '/' + vmobj.zonename;
4391     } else {
4392         callback(new Error('unable to find zonepath for '
4393             + JSON.stringify(vmobj)));
4394         return;
4395     }
4396 
4397     // paths are under zonepath but not zoneroot
4398     filename = zonepath + '/config/routes.json';
4399 
4400     for (key in vmobj.routes) {
4401         if (vmobj.routes.hasOwnProperty(key)) {
4402             routes[key] = vmobj.routes[key];
4403             if (payload.hasOwnProperty('remove_routes')
4404                 && payload.remove_routes.indexOf(key) !== -1) {
4405 
4406                 // in the remove_* list, don't load it.
4407                 delete routes[key];
4408             }
4409         }
4410     }
4411 
4412     for (key in payload.set_routes) {
4413         if (payload.set_routes.hasOwnProperty(key)) {
4414             routes[key] = payload.set_routes[key];
4415         }
4416     }
4417 
4418     fs.writeFile(filename, JSON.stringify(routes, null, 2),
4419         function (err) {
4420             if (err) {
4421                 callback(err);
4422             } else {
4423                 log.debug('wrote routes to ' + filename);
4424                 callback();
4425             }
4426         });
4427 }
4428 
4429 function saveRoutes(payload, log, callback)
4430 {
4431     var protovm = {};
4432 
4433     assert(log, 'no logger passed to saveRoutes()');
4434 
4435     if (!payload.hasOwnProperty('zonepath')
4436         || !payload.hasOwnProperty('zpool')
4437         || !payload.hasOwnProperty('zonename')) {
4438 
4439         callback(new Error('saveRoutes payload is missing zone '
4440             + 'properties.'));
4441         return;
4442     }
4443 
4444     protovm.zonepath = payload.zonepath;
4445     protovm.zpool = payload.zpool;
4446     protovm.zonename = payload.zonename;
4447 
4448     if (payload.hasOwnProperty('routes')) {
4449         payload.set_routes = payload.routes;
4450         delete payload.routes;
4451     }
4452 
4453     updateRoutes(protovm, payload, log, callback);
4454 }
4455 
4456 function createVM(payload, log, callback)
4457 {
4458     assert(log, 'no logger passed to createVM()');
4459 
4460     async.series([
4461         function (cb) {
4462             if (!payload.create_only) {
4463                 // progress(2, 'checking required datasets');
4464                 checkDatasets(payload, log, cb);
4465             } else {
4466                 cb();
4467             }
4468         }, function (cb) {
4469             if (!payload.create_only) {
4470                 // progress(29, 'creating volumes');
4471                 createVolumes(payload, log, cb);
4472             } else {
4473                 cb();
4474             }
4475         }, function (cb) {
4476             // progress(51, 'creating zone container');
4477             createZone(payload, log, cb);
4478         }
4479     ], function (err, results) {
4480         if (err) {
4481             callback(err);
4482         } else {
4483             callback(null, results);
4484         }
4485     });
4486 }
4487 
4488 function fixZoneinitMetadataSock(zoneroot, log, callback)
4489 {
4490     var mdata_00;
4491 
4492     // ensure we're safe to touch these files, zone should not be running here
4493     // so this just guards against malicious datasets.
4494     ['/var/zoneinit/includes', '/root/zoneinit.d'].forEach(function (dir) {
4495         assertSafeZonePath(zoneroot, dir, {type: 'dir', enoent_ok: true});
4496     });
4497 
4498     function replaceData(filename, cb) {
4499         fs.readFile(filename, 'utf8', function (error, data) {
4500             if (error) {
4501                 log.error(error, 'failed to load 00-mdata.sh for replacement');
4502                 cb(error);
4503                 return;
4504             }
4505 
4506             data = data.replace(/\/var\/run\/smartdc\/metadata.sock/g,
4507                 '/.zonecontrol/metadata.sock');
4508 
4509             log.trace('writing [' + data + '] to ' + filename);
4510             fs.writeFile(filename, data, 'utf8', function (err) {
4511                 if (err) {
4512                     log.error(err, 'failed to write ' + filename);
4513                 }
4514                 cb(err);
4515             });
4516         });
4517     }
4518 
4519     // try /var/zoneinit/includes/00-mdata.sh first, since that's in new images
4520     mdata_00 = path.join(zoneroot, '/var/zoneinit/includes/00-mdata.sh');
4521     fs.exists(mdata_00, function (exists1) {
4522         if (exists1) {
4523             log.info('fixing socket in /var/zoneinit/includes/00-mdata.sh');
4524             replaceData(mdata_00, callback);
4525         } else {
4526             // didn't exist, so try location it exists in older images eg. 1.6.3
4527             mdata_00 = path.join(zoneroot, '/root/zoneinit.d/00-mdata.sh');
4528             fs.exists(mdata_00, function (exists2) {
4529                 if (exists2) {
4530                     log.info('fixing socket in /root/zoneinit.d/00-mdata.sh');
4531                     replaceData(mdata_00, callback);
4532                 } else {
4533                     log.info('no 00-mdata.sh to cleanup in zoneinit');
4534                     callback();
4535                 }
4536             });
4537         }
4538     });
4539 }
4540 
4541 function fixMdataFetchStart(zonepath, log, callback)
4542 {
4543     // svccfg validates zonepath
4544     var mdata_fetch_start = '/lib/svc/method/mdata-fetch';
4545 
4546     svccfg(zonepath, ['-s', 'svc:/smartdc/mdata:fetch', 'setprop', 'start/exec',
4547         '=', mdata_fetch_start], log, function (error, stdio) {
4548 
4549         if (error) {
4550             log.error(error, 'failed to set mdata:fetch start method');
4551         } else {
4552             log.info('successfully set mdata:fetch start method');
4553         }
4554 
4555         callback(error);
4556     });
4557 }
4558 
4559 function cleanupMessyDataset(zonepath, brand, log, callback)
4560 {
4561     var command;
4562     var zoneroot = path.join(zonepath, '/root');
4563 
4564     assert(log, 'no logger passed to cleanupMessyDataset()');
4565 
4566     try {
4567         ['/var/adm', '/var/svc/log', '/var/svc/manifest', '/root/zoneinit.d']
4568             .forEach(function (dir) {
4569 
4570             // This will ensure these are safe if they exist.
4571             assertSafeZonePath(zoneroot, dir, {type: 'dir', enoent_ok: true});
4572         });
4573     } catch (e) {
4574         log.error(e, 'Unable to cleanup dataset: ' + e.message);
4575         callback(e);
4576         return;
4577     }
4578 
4579     // We've verified the directories here exist, and have no symlinks in the
4580     // path (or don't exist) so rm -f <dir>/<file> should be safe regardless of
4581     // the type of <file>
4582 
4583     command = 'rm -f '
4584         + zoneroot + '/var/adm/utmpx '
4585         + zoneroot + '/var/adm/wtmpx '
4586         + zoneroot + '/var/svc/log/*.log '
4587         + zoneroot + '/var/svc/mdata '
4588         + zoneroot + '/var/svc/manifest/mdata.xml ';
4589 
4590     if (! BRAND_OPTIONS[brand].features.zoneinit) {
4591         // eg. joyent-minimal (don't need zoneinit)
4592         command = command + zoneroot + '/root/zoneinit.xml '
4593             + zoneroot + '/root/zoneinit '
4594             + '&& rm -rf ' + zoneroot + '/root/zoneinit.d ';
4595     }
4596 
4597     command = command + '&& touch ' + zoneroot + '/var/adm/wtmpx';
4598     log.debug(command);
4599     exec(command, function (error, stdout, stderr) {
4600         log.debug({err: error, stdout: stdout, stderr: stderr},
4601             'returned from cleaning up dataset');
4602         if (error || !BRAND_OPTIONS[brand].features.zoneinit) {
4603             // either we already failed or this zone doesn't use zoneinit so
4604             // we don't need to bother fixing zoneinit's scripts.
4605             callback(error);
4606         } else {
4607             fixZoneinitMetadataSock(zoneroot, log, function (err) {
4608                 // See OS-2314, currently we assume all zones w/ zoneinit also
4609                 // have broken mdata:fetch when images are created from them.
4610                 // Attempt to fix that too.
4611                 fixMdataFetchStart(zonepath, log, callback);
4612             });
4613         }
4614     });
4615 }
4616 
4617 // Helper for unlinking and replacing a file that you've already confirmed
4618 // has no symlinks. Throws error when fs.writeFileSync does, or when
4619 // fs.unlinkSync throws non ENOENT.
4620 function replaceFile(zoneroot, filename, data) {
4621     // first delete, in case file itself is a link
4622     try {
4623         fs.unlinkSync(path.join(zoneroot, filename));
4624     } catch (e) {
4625         if (e.code !== 'ENOENT') {
4626             throw e;
4627         }
4628     }
4629 
4630     fs.writeFileSync(path.join(zoneroot, filename), data);
4631 }
4632 
4633 // NOTE: we write these out initially before the zone is started, but after that
4634 // rely on mdata-fetch in the zone to do the updates since we can't safely write
4635 // these files in the zones.
4636 function writeZoneNetfiles(payload, log, callback)
4637 {
4638     var hostname;
4639     var n;
4640     var nic;
4641     var primary_found = false;
4642     var zoneroot;
4643 
4644     assert(log, 'no logger passed to writeZoneNetfiles()');
4645     assert(payload.hasOwnProperty('zonepath'), 'no .zonepath in payload');
4646 
4647     zoneroot = payload.zonepath + '/root';
4648 
4649     try {
4650         assertSafeZonePath(zoneroot, '/etc', {type: 'dir', enoent_ok: true});
4651     } catch (e) {
4652         log.error(e, 'Unable to write zone net files: ' + e.message);
4653         callback(e);
4654         return;
4655     }
4656 
4657     log.info('Writing network files to zone root');
4658 
4659     try {
4660         for (nic in payload.add_nics) {
4661             if (payload.add_nics.hasOwnProperty(nic)) {
4662                 n = payload.add_nics[nic];
4663 
4664                 if (n.ip != 'dhcp') {
4665                     replaceFile(zoneroot, '/etc/hostname.'
4666                         + n.interface, n.ip + ' netmask ' + n.netmask
4667                         + ' up' + '\n');
4668                 }
4669 
4670                 if (n.hasOwnProperty('primary') && !primary_found) {
4671                     // only allow one primary network
4672                     primary_found = true;
4673                     if (n.hasOwnProperty('gateway')) {
4674                         replaceFile(zoneroot, '/etc/defaultrouter',
4675                             n.gateway + '\n');
4676                     }
4677                     if (n.ip == 'dhcp') {
4678                         replaceFile(zoneroot, '/etc/dhcp.' + n.interface, '');
4679                     }
4680                 }
4681             }
4682         }
4683 
4684         // It's possible we don't have zonename or hostname set because of the
4685         // ordering of adding the UUID. In any case, we'll have at least a uuid
4686         // here.
4687         if (payload.hasOwnProperty('hostname')) {
4688             hostname = payload.hostname;
4689         } else if (payload.hasOwnProperty('zonename')) {
4690             hostname = payload.zonename;
4691         } else {
4692             hostname = payload.uuid;
4693         }
4694 
4695         replaceFile(zoneroot, '/etc/nodename', hostname + '\n');
4696     } catch (e) {
4697         log.error(e, 'Unable to write zone networking files: ' + e.message);
4698         callback(e);
4699         return;
4700     }
4701 
4702     callback();
4703 }
4704 
4705 /*
4706  * NOTE: once we no longer support old datasets that need the 'zoneconfig' file,
4707  * this function and calls to it can be removed.
4708  *
4709  * This writes out the zoneconfig file that is used by the zoneinit service in
4710  * joyent branded zones' datasets.
4711  *
4712  */
4713 function writeZoneconfig(payload, log, callback)
4714 {
4715     var data;
4716     var hostname;
4717     var n;
4718     var nic;
4719     var zoneroot;
4720 
4721     assert(log, 'no logger passed to writeZoneconfig()');
4722     assert(payload.hasOwnProperty('zonepath'), 'no .zonepath in payload');
4723 
4724     zoneroot = payload.zonepath + '/root';
4725 
4726     log.info('Writing config for zoneinit');
4727 
4728     if (payload.hasOwnProperty('hostname')) {
4729         hostname = payload.hostname;
4730     } else {
4731         hostname = payload.zonename;
4732     }
4733 
4734     data = 'TEMPLATE_VERSION=0.0.1\n'
4735         + 'ZONENAME=' + payload.zonename + '\n'
4736         + 'HOSTNAME=' + hostname + '.' + payload.dns_domain + '\n'
4737         + 'TMPFS=' + payload.tmpfs + 'm\n';
4738 
4739     if (payload.hasOwnProperty('add_nics') && payload.add_nics[0]) {
4740 
4741         if (payload.add_nics[0] && payload.add_nics[0].ip != 'dhcp') {
4742             data = data + 'PUBLIC_IP=' + payload.add_nics[0].ip + '\n';
4743         }
4744         if (payload.add_nics[1] && payload.add_nics[1].ip != 'dhcp') {
4745             data = data + 'PRIVATE_IP=' + payload.add_nics[1].ip + '\n';
4746         } else if (payload.add_nics[0] && payload.add_nics[0].ip != 'dhcp') {
4747             // zoneinit uses private_ip for /etc/hosts, we want to
4748             // make that same as public, if there's no actual private.
4749             data = data + 'PRIVATE_IP=' + payload.add_nics[0].ip + '\n';
4750         }
4751     }
4752 
4753     if (payload.hasOwnProperty('resolvers')) {
4754         // zoneinit appends to resolv.conf rather than overwriting, so just
4755         // add to the zoneconfig and let zoneinit handle it
4756         data = data + 'RESOLVERS="' + payload.resolvers.join(' ') + '"\n';
4757     }
4758 
4759     for (nic in payload.add_nics) {
4760         if (payload.add_nics.hasOwnProperty(nic)) {
4761             n = payload.add_nics[nic];
4762             data = data + n.interface.toUpperCase() + '_MAC=' + n.mac + '\n'
4763                 + n.interface.toUpperCase() + '_INTERFACE='
4764                 + n.interface.toUpperCase() + '\n';
4765 
4766             if (n.ip != 'dhcp') {
4767                 data = data + n.interface.toUpperCase() + '_IP=' + n.ip + '\n'
4768                     + n.interface.toUpperCase() + '_NETMASK='
4769                     + n.netmask + '\n';
4770             }
4771         }
4772     }
4773 
4774     try {
4775         assertSafeZonePath(zoneroot, '/var/svc/log/system-zoneinit:default.log',
4776             {type: 'file', enoent_ok: true});
4777         assertSafeZonePath(zoneroot, '/root/zoneconfig',
4778             {type: 'file', enoent_ok: true});
4779 
4780         replaceFile(zoneroot, '/var/svc/log/system-zoneinit:default.log', '');
4781 
4782         log.debug('writing zoneconfig ' + JSON.stringify(data) + ' to '
4783             + zoneroot);
4784 
4785         replaceFile(zoneroot, '/root/zoneconfig', data);
4786         callback();
4787     } catch (e) {
4788         log.error(e, 'Unable to write zoneconfig files: ' + e.message);
4789         callback(e);
4790         return;
4791     }
4792 }
4793 
4794 function zonecfg(args, log, callback)
4795 {
4796     var cmd = '/usr/sbin/zonecfg';
4797 
4798     assert(log, 'no logger passed to zonecfg()');
4799 
4800     log.debug(cmd + ' ' + args.join(' '));
4801     execFile(cmd, args, function (error, stdout, stderr) {
4802         if (error) {
4803             callback(error, {'stdout': stdout, 'stderr': stderr});
4804         } else {
4805             callback(null, {'stdout': stdout, 'stderr': stderr});
4806         }
4807     });
4808 }
4809 
4810 function zonecfgFile(data, args, log, callback)
4811 {
4812     var tmpfile = '/tmp/zonecfg.' + process.pid + '.tmp';
4813 
4814     assert(log, 'no logger passed to zonecfgFile()');
4815 
4816     fs.writeFile(tmpfile, data, function (err, result) {
4817         if (err) {
4818             // On failure we don't delete the tmpfile so we can debug it.
4819             callback(err);
4820         } else {
4821             args.push('-f');
4822             args.push(tmpfile);
4823 
4824             zonecfg(args, log, function (e, fds) {
4825                 if (e) {
4826                     // keep temp file around for investigation
4827                     callback(e, fds);
4828                 } else {
4829                     fs.unlink(tmpfile, function () {
4830                         callback(null, fds);
4831                     });
4832                 }
4833             });
4834         }
4835     });
4836 }
4837 
4838 function zoneadm(args, log, callback)
4839 {
4840     var cmd = '/usr/sbin/zoneadm';
4841 
4842     assert(log, 'no logger passed to zoneadm()');
4843 
4844     log.debug(cmd + ' ' + args.join(' '));
4845     execFile(cmd, args, function (error, stdout, stderr) {
4846         if (error) {
4847             callback(error, {'stdout': stdout, 'stderr': stderr});
4848         } else {
4849             callback(null, {'stdout': stdout, 'stderr': stderr});
4850         }
4851     });
4852 }
4853 
4854 function zfs(args, log, callback)
4855 {
4856     var cmd = '/usr/sbin/zfs';
4857 
4858     assert(log, 'no logger passed to zfs()');
4859 
4860     log.debug(cmd + ' ' + args.join(' '));
4861     execFile(cmd, args, function (error, stdout, stderr) {
4862         if (error) {
4863             callback(error, {'stdout': stdout, 'stderr': stderr});
4864         } else {
4865             callback(null, {'stdout': stdout, 'stderr': stderr});
4866         }
4867     });
4868 }
4869 
4870 exports.getSysinfo = function (args, options, callback)
4871 {
4872     var cmd = '/usr/bin/sysinfo';
4873     var log;
4874 
4875     // we used to allow just one argument (callback) and we also allow 2 args
4876     // (args, callback) so that options is optional.
4877     if (arguments.length === 1) {
4878         callback = arguments[0];
4879         args = [];
4880         options = {};
4881     }
4882     if (arguments.length === 2) {
4883         callback = arguments[1];
4884         options = {};
4885     }
4886 
4887     ensureLogging(false);
4888     if (options.hasOwnProperty('log')) {
4889         log = options.log;
4890     } else {
4891         log = VM.log.child({action: 'getSysinfo'});
4892     }
4893 
4894     log.debug(cmd + ' ' + args.join(' '));
4895     execFile(cmd, args, function (error, stdout, stderr) {
4896         var sysinfo;
4897 
4898         if (error) {
4899             callback(error, {'stdout': stdout, 'stderr': stderr});
4900         } else {
4901             try {
4902                 sysinfo = JSON.parse(stdout.toString());
4903             } catch (e) {
4904                 sysinfo = {};
4905             }
4906             callback(null, sysinfo);
4907         }
4908     });
4909 };
4910 
4911 /*
4912  * This watches zone transitions and calls callback when specified
4913  * state is reached.  Optionally you can set a timeout which will
4914  * call your callback when the timeout occurs whether the transition
4915  * has happened or not.
4916  *
4917  * payload needs to have at least .zonename and .uuid
4918  *
4919  */
4920 exports.waitForZoneState = function (payload, state, options, callback)
4921 {
4922     var log;
4923     var sysevent_state;
4924     var timeout;
4925     var timeout_secs = PROVISION_TIMEOUT;
4926     var watcher;
4927 
4928     // options is optional
4929     if (arguments.length === 3) {
4930         callback = arguments[2];
4931         options = {};
4932     }
4933 
4934     ensureLogging(false);
4935     if (options.hasOwnProperty('log')) {
4936         log = options.log;
4937     } else {
4938         log = VM.log.child({action: 'waitForZoneState', vm: payload.uuid});
4939     }
4940 
4941     if (options.hasOwnProperty('timeout')) {
4942         timeout_secs = options.timeout;
4943     }
4944 
4945     sysevent_state = state;
4946     if (state === 'installed') {
4947         // Apparently the zone status 'installed' equals sysevent status
4948         // 'uninitialized'
4949         sysevent_state = 'uninitialized';
4950     }
4951 
4952     function done() {
4953         if (timeout) {
4954             clearTimeout(timeout);
4955             timeout = null;
4956         }
4957     }
4958 
4959     function handler(err, obj) {
4960         if (err) {
4961             done();
4962             callback(err);
4963             return;
4964         }
4965         log.trace('handler got: ' + JSON.stringify(obj));
4966         if (obj.zonename !== payload.zonename) {
4967             return;
4968         }
4969 
4970         if (obj.newstate === sysevent_state) {
4971             // Load again to confirm
4972             VM.lookup({'zonename': obj.zonename},
4973                 {fields: ['zone_state'], log: log},
4974                 function (error, res) {
4975                     var handler_retry;
4976 
4977                     if (error) {
4978                         watcher.cleanup();
4979                         done();
4980                         callback(error);
4981                         return;
4982                     }
4983 
4984                     if (res.length !== 1) {
4985                         watcher.cleanup();
4986                         done();
4987                         callback(new Error('lookup could no find VM '
4988                             + obj.zonename));
4989                         return;
4990                     }
4991 
4992                     if (res[0].hasOwnProperty('zone_state')
4993                         && res[0].zone_state === state) {
4994 
4995                         // found the state we're looking for, success!
4996                         log.debug('saw zone go to ' + obj.newstate + ' ('
4997                             + state + ') calling callback()');
4998                         watcher.cleanup();
4999                         done();
5000                         callback();
5001                     } else if (timeout) {
5002                         // we saw a state change to a state we don't care about
5003                         // so if we've not timed out try reloading again in a
5004                         // second.
5005                         if (!handler_retry) {
5006                             handler_retry = setTimeout(function () {
5007                                 if (timeout) {
5008                                     // try again if wait timeout is still set
5009                                     handler(null, obj);
5010                                 }
5011                                 handler_retry = null;
5012                             }, 1000);
5013                             log.debug('zone state after lookup: '
5014                                 + res[0].zone_state + ', still waiting');
5015                         } else {
5016                             log.debug('zone in wrong state but we already'
5017                                 + ' have a handler running');
5018                         }
5019                     } else {
5020                         // no timeout set and we're not at the correct state
5021                         log.error('failed to reach state: ' + state);
5022                         callback(new Error('failed to reach state: ' + state));
5023                     }
5024                 }
5025             );
5026         }
5027     }
5028 
5029     watcher = watchZoneTransitions(handler, log);
5030 
5031     timeout = setTimeout(function () {
5032         var err;
5033 
5034         done();
5035         watcher.cleanup();
5036         err = new Error('timed out waiting for zone to transition to ' + state);
5037         err.code = 'ETIMEOUT';
5038         callback(err);
5039     }, timeout_secs * 1000);
5040 
5041     // after we've started the watcher (if we checked before there'd be a race)
5042     // we check whether we're already in the target state, if we are close it
5043     // down and return.
5044     VM.load(payload.uuid, {fields: ['zone_state'], log: log},
5045         function (err, obj) {
5046 
5047         if (err) {
5048             watcher.cleanup();
5049             done();
5050             callback(err);
5051         } else if (obj.hasOwnProperty('zone_state')
5052             && obj.zone_state === state) {
5053 
5054             watcher.cleanup();
5055             done();
5056             log.info('VM is in state ' + state);
5057             callback(); // at correct state!
5058         }
5059     });
5060 };
5061 
5062 // handler() will be called with an object describing the transition for any
5063 // transitions seen (after any filtering).  The only filtering here is to remove
5064 // duplicate events.  Other filtering should be done by the caller.
5065 function watchZoneTransitions(handler, log) {
5066     var buffer = '';
5067     var chunks;
5068     var cleanup;
5069     var watcher;
5070     var watcher_pid;
5071 
5072     assert(log, 'no logger passed to watchZoneTransitions()');
5073 
5074     if (!zoneevent) {
5075 
5076         zoneevent = new EventEmitter();
5077 
5078         log.debug('/usr/vm/sbin/zoneevent');
5079         watcher = spawn('/usr/vm/sbin/zoneevent', [],
5080             {'customFds': [-1, -1, -1]});
5081         log.debug('zoneevent running with pid ' + watcher.pid);
5082         watcher_pid = watcher.pid;
5083 
5084         watcher.stdout.on('data', function (data) {
5085             var chunk;
5086             var obj;
5087             var prev_msg;
5088 
5089             buffer += data.toString();
5090             chunks = buffer.split('\n');
5091             while (chunks.length > 1) {
5092                 chunk = chunks.shift();
5093                 obj = JSON.parse(chunk);
5094 
5095                 if (obj === prev_msg) {
5096                     // Note: sometimes sysevent emits multiple events for the
5097                     // same status, we only want the first one here because just
5098                     // because sysevent does it, doesn't make it right.
5099                     log.debug('duplicate zoneevent message! '
5100                         + JSON.stringify(obj));
5101                 } else if (zoneevent) {
5102                     zoneevent.emit('zoneevent', null, obj);
5103                 }
5104             }
5105             buffer = chunks.pop();
5106         });
5107 
5108         // doesn't take input.
5109         watcher.stdin.end();
5110 
5111         watcher.on('exit', function (code) {
5112             log.warn('zoneevent watcher ' + watcher_pid + ' exited: ',
5113                 JSON.stringify(code));
5114             // tell all the listeners of this zoneevent (if there are any) that
5115             // we exited.  Then null it out so next time we'll make a new one.
5116             zoneevent.emit('zoneevent', new Error('zoneevent watcher exited '
5117                 + 'prematurely with code: ' + code));
5118             zoneevent = null;
5119         });
5120     }
5121 
5122     cleanup = function () {
5123         var listeners;
5124 
5125         if (zoneevent) {
5126             listeners = zoneevent.listeners('zoneevent');
5127 
5128             log.debug('cleanup called w/ listeners: '
5129                 + util.inspect(listeners));
5130             zoneevent.removeListener('zoneevent', handler);
5131             if (zoneevent.listeners('zoneevent').length === 0) {
5132                 log.debug('zoneevent watcher ' + watcher_pid
5133                     + ' cleanup called');
5134                 zoneevent = null;
5135                 if (watcher) {
5136                     watcher.stdout.destroy(); // so we don't send more 'data'
5137                     watcher.stderr.destroy();
5138                     watcher.removeAllListeners('exit'); // so don't fail on kill
5139                     watcher.kill();
5140                     watcher = null;
5141                 }
5142             }
5143         } else if (watcher) {
5144             watcher.stdout.destroy(); // so we don't send more 'data'
5145             watcher.stderr.destroy();
5146             watcher.removeAllListeners('exit'); // so don't fail on our kill
5147             watcher.kill();
5148             watcher = null;
5149         }
5150     };
5151 
5152     zoneevent.on('zoneevent', handler);
5153 
5154     return ({'cleanup': cleanup});
5155 }
5156 
5157 function fixPayloadMemory(payload, vmobj, log)
5158 {
5159     var brand;
5160     var max_locked;
5161     var max_phys;
5162     var min_overhead;
5163     var ram;
5164 
5165     assert(log, 'no logger passed to fixPayloadMemory()');
5166 
5167     if (vmobj.hasOwnProperty('brand')) {
5168         brand = vmobj.brand;
5169     } else if (payload.hasOwnProperty('brand')) {
5170         brand = payload.brand;
5171     }
5172 
5173     if (BRAND_OPTIONS[brand].features.default_memory_overhead
5174         && payload.hasOwnProperty('ram')
5175         && !payload.hasOwnProperty('max_physical_memory')) {
5176 
5177         // For now we add overhead to the memory caps for KVM zones, this
5178         // is for the qemu process itself.  Since customers don't have direct
5179         // access to zone memory, this exists mostly to protect against bugs.
5180         payload.max_physical_memory = (payload.ram
5181             + BRAND_OPTIONS[brand].features.default_memory_overhead);
5182     } else if (payload.hasOwnProperty('ram')
5183         && !payload.hasOwnProperty('max_physical_memory')) {
5184 
5185         payload.max_physical_memory = payload.ram;
5186     }
5187 
5188     if (payload.hasOwnProperty('max_physical_memory')) {
5189         if (!payload.hasOwnProperty('max_locked_memory')) {
5190             if (vmobj.hasOwnProperty('max_locked_memory')
5191                 && vmobj.hasOwnProperty('max_physical_memory')) {
5192 
5193                 // we don't have a new value, so first try to keep the same
5194                 // delta that existed before btw. max_phys and max_locked
5195                 payload.max_locked_memory = payload.max_physical_memory
5196                     - (vmobj.max_physical_memory - vmobj.max_locked_memory);
5197             } else {
5198                 // existing obj doesn't have max_locked, add one now
5199                 payload.max_locked_memory = payload.max_physical_memory;
5200             }
5201         }
5202 
5203         if (!payload.hasOwnProperty('max_swap')) {
5204             if (vmobj.hasOwnProperty('max_swap')
5205                 && vmobj.hasOwnProperty('max_physical_memory')) {
5206 
5207                 // we don't have a new value, so first try to keep the same
5208                 // delta that existed before btw. max_phys and max_swap
5209                 if (vmobj.max_swap === MINIMUM_MAX_SWAP
5210                     && vmobj.max_swap <= MINIMUM_MAX_SWAP
5211                     && payload.max_physical_memory >= MINIMUM_MAX_SWAP) {
5212                     // in this case we artificially inflated before to meet
5213                     // minimum tie back to ram.
5214                     payload.max_swap = payload.max_physical_memory;
5215                 } else {
5216                     payload.max_swap = payload.max_physical_memory
5217                         + (vmobj.max_swap - vmobj.max_physical_memory);
5218                 }
5219             } else {
5220                 // existing obj doesn't have max_swap, add one now
5221                 payload.max_swap = payload.max_physical_memory;
5222             }
5223 
5224             // never add a max_swap less than MINIMUM_MAX_SWAP
5225             if (payload.max_swap < MINIMUM_MAX_SWAP) {
5226                 payload.max_swap = MINIMUM_MAX_SWAP;
5227             }
5228         }
5229     }
5230 
5231     // if we're updating tmpfs it must be lower than our new max_physical or
5232     // if we're not also changing max_physical, it must be lower than the
5233     // current one.
5234     if (payload.hasOwnProperty('tmpfs')) {
5235         if (payload.hasOwnProperty('max_physical_memory')
5236             && (Number(payload.tmpfs)
5237                 > Number(payload.max_physical_memory))) {
5238 
5239             payload.tmpfs = payload.max_physical_memory;
5240         } else if (Number(payload.tmpfs)
5241             > Number(vmobj.max_physical_memory)) {
5242 
5243             payload.tmpfs = vmobj.max_physical_memory;
5244         }
5245     }
5246 
5247     if (payload.hasOwnProperty('max_physical_memory')
5248         && BRAND_OPTIONS[brand].features.use_tmpfs
5249         && !payload.hasOwnProperty('tmpfs')) {
5250 
5251         if (vmobj.hasOwnProperty('max_physical_memory')
5252             && vmobj.hasOwnProperty('tmpfs')) {
5253 
5254             // change tmpfs to be the same ratio of ram as before
5255             payload.tmpfs = ((vmobj.tmpfs / vmobj.max_physical_memory)
5256                 * payload.max_physical_memory);
5257             payload.tmpfs = Number(payload.tmpfs).toFixed();
5258         } else {
5259             // tmpfs must be < max_physical_memory, if not: pretend it was
5260             payload.tmpfs = payload.max_physical_memory;
5261         }
5262     }
5263 
5264     // now that we've possibly adjusted target values, lower/raise values to
5265     // satisify max/min.
5266 
5267     min_overhead = BRAND_OPTIONS[brand].features.min_memory_overhead;
5268     if (min_overhead) {
5269         ram = payload.hasOwnProperty('ram') ? payload.ram : vmobj.ram;
5270         max_phys = payload.hasOwnProperty('max_physical_memory')
5271             ? payload.max_physical_memory : vmobj.max_physical_memory;
5272         max_locked = payload.hasOwnProperty('max_locked_memory')
5273             ? payload.max_locked_memory : vmobj.max_locked_memory;
5274 
5275         if ((ram + min_overhead) > max_phys) {
5276             payload.max_physical_memory = (ram + min_overhead);
5277         }
5278         if ((ram + min_overhead) > max_locked) {
5279             payload.max_locked_memory = (ram + min_overhead);
5280         }
5281     }
5282 
5283     if (payload.hasOwnProperty('max_locked_memory')) {
5284         if (payload.hasOwnProperty('max_physical_memory')) {
5285             if (payload.max_locked_memory > payload.max_physical_memory) {
5286                 log.warn('max_locked_memory (' + payload.max_locked_memory
5287                     + ') > max_physical_memory (' + payload.max_physical_memory
5288                     + ') clamping to ' + payload.max_physical_memory);
5289                 payload.max_locked_memory = payload.max_physical_memory;
5290             }
5291         } else if (vmobj.hasOwnProperty('max_physical_memory')) {
5292             // new payload doesn't have a max_physical, so clamp to vmobj's
5293             if (payload.max_locked_memory > vmobj.max_physical_memory) {
5294                 log.warn('max_locked_memory (' + payload.max_locked_memory
5295                     + ') > vm.max_physical_memory (' + vmobj.max_physical_memory
5296                     + ') clamping to ' + vmobj.max_physical_memory);
5297                 payload.max_locked_memory = vmobj.max_physical_memory;
5298             }
5299         }
5300     }
5301 
5302     if (payload.hasOwnProperty('max_swap')) {
5303         if (payload.hasOwnProperty('max_physical_memory')) {
5304             if (payload.max_swap < payload.max_physical_memory) {
5305                 log.warn('max_swap (' + payload.max_swap
5306                     + ') < max_physical_memory (' + payload.max_physical_memory
5307                     + ') raising to ' + payload.max_physical_memory);
5308                 payload.max_swap = payload.max_physical_memory;
5309             }
5310         } else if (vmobj.hasOwnProperty('max_physical_memory')) {
5311             // new payload doesn't have a max_physical, so raise to vmobj's
5312             if (payload.max_swap < vmobj.max_physical_memory) {
5313                 log.warn('max_swap (' + payload.max_swap
5314                     + ') < vm.max_physical_memory (' + vmobj.max_physical_memory
5315                     + ') raising to ' + vmobj.max_physical_memory);
5316                 payload.max_swap = vmobj.max_physical_memory;
5317             }
5318         }
5319     }
5320 }
5321 
5322 // generate a new UUID if payload doesn't have one (also ensures that this uuid
5323 // does not already belong to a zone).
5324 function createZoneUUID(payload, log, callback)
5325 {
5326     var uuid;
5327 
5328     assert(log, 'no logger passed to createZoneUUID()');
5329 
5330     if (payload.hasOwnProperty('uuid')) {
5331         // Ensure that the uuid is not already used.
5332         getZoneRecords(null, log, function (err, records) {
5333             if (err) {
5334                 callback(err);
5335             } else {
5336                 if (records.hasOwnProperty(payload.uuid)) {
5337                     callback(new Error('vm with UUID ' + payload.uuid
5338                         + ' already exists.'));
5339                 } else {
5340                     callback(null, payload.uuid);
5341                 }
5342             }
5343         });
5344     } else {
5345         log.debug('/usr/bin/uuid -v 4');
5346         execFile('/usr/bin/uuid', ['-v', '4'], function (err, stdout, stderr) {
5347             if (err) {
5348                 callback(err);
5349                 return;
5350             }
5351 
5352             // chomp trailing spaces and newlines
5353             uuid = stdout.toString().replace(/\s+$/g, '');
5354             payload.uuid = uuid;
5355             log.info('generated uuid ' + uuid + ' for new VM');
5356             getZoneRecords(null, log, function (e, records) {
5357                 if (e) {
5358                     callback(e);
5359                 } else {
5360                     if (records.hasOwnProperty(payload.uuid)) {
5361                         callback(new Error('vm with UUID ' + payload.uuid
5362                             + 'already exists.'));
5363                     } else {
5364                         callback(null, payload.uuid);
5365                     }
5366                 }
5367             });
5368         });
5369     }
5370 }
5371 
5372 function applyZoneDefaults(payload, log)
5373 {
5374     var allowed;
5375     var disk;
5376     var disks;
5377     var n;
5378     var nic;
5379     var nics;
5380     var zvol;
5381 
5382     assert(log, 'no logger passed to applyZoneDefaults()');
5383 
5384     log.debug('applying zone defaults');
5385 
5386     if (!payload.hasOwnProperty('owner_uuid')) {
5387         // We assume that this all-zero uuid can be treated as 'admin'
5388         payload.owner_uuid = '00000000-0000-0000-0000-000000000000';
5389     }
5390 
5391     if (!payload.hasOwnProperty('autoboot')) {
5392         payload.autoboot = true;
5393     }
5394 
5395     if (!payload.hasOwnProperty('brand')) {
5396         payload.brand = 'joyent';
5397     }
5398 
5399     if (!payload.hasOwnProperty('zpool')) {
5400         payload.zpool = 'zones';
5401     }
5402 
5403     if (!payload.hasOwnProperty('dns_domain')) {
5404         payload.dns_domain = 'local';
5405     }
5406 
5407     if (!payload.hasOwnProperty('cpu_shares')) {
5408         payload.cpu_shares = 100;
5409     } else {
5410         if (payload.cpu_shares > 65535) {
5411             log.info('capping cpu_shares at 64k (was: '
5412                 + payload.cpu_shares + ')');
5413             payload.cpu_shares = 65535; // max is 64K
5414         }
5415     }
5416 
5417     if (!payload.hasOwnProperty('zfs_io_priority')) {
5418         payload.zfs_io_priority = 100;
5419     }
5420 
5421     if (!payload.hasOwnProperty('max_lwps')) {
5422         payload.max_lwps = 2000;
5423     }
5424 
5425     // We need to set the RAM here because we use it as the default for
5426     // the max_physical_memory below. If we've set max_phys and we're not
5427     // KVM, we'll use that instead of ram anyway.
5428     if (!payload.hasOwnProperty('ram')) {
5429         payload.ram = 256;
5430     }
5431 
5432     fixPayloadMemory(payload, {}, log);
5433 
5434     allowed = BRAND_OPTIONS[payload.brand].allowed_properties;
5435     if (allowed.hasOwnProperty('vcpus') && !payload.hasOwnProperty('vcpus')) {
5436         payload.vcpus = 1;
5437     }
5438 
5439     if (BRAND_OPTIONS[payload.brand].features.use_tmpfs
5440         && (!payload.hasOwnProperty('tmpfs')
5441             || (Number(payload.tmpfs) > Number(payload.max_physical_memory)))) {
5442 
5443         payload.tmpfs = payload.max_physical_memory;
5444     }
5445 
5446     if (!payload.hasOwnProperty('limit_priv')) {
5447         // note: the limit privs are going to be added to the brand and
5448         // shouldn't need to be set here by default when that's done.
5449         if (BRAND_OPTIONS[payload.brand].features.limit_priv) {
5450             payload.limit_priv
5451                 = BRAND_OPTIONS[payload.brand].features.limit_priv.join(',');
5452         } else {
5453             payload.limit_priv = 'default';
5454         }
5455     }
5456 
5457     if (!payload.hasOwnProperty('quota')) {
5458         payload.quota = '10'; // in GiB
5459     }
5460 
5461     if (!payload.hasOwnProperty('billing_id')) {
5462         payload.billing_id = '00000000-0000-0000-0000-000000000000';
5463     }
5464 
5465     if (payload.hasOwnProperty('add_disks')) {
5466         // update
5467         disks = payload.add_disks;
5468     } else if (payload.hasOwnProperty('disks')) {
5469         disks = payload.disks;
5470     } else {
5471         // no disks at all
5472         disks = [];
5473     }
5474 
5475     for (disk in disks) {
5476         if (disks.hasOwnProperty(disk)) {
5477             zvol = disks[disk];
5478             if (!zvol.hasOwnProperty('model')
5479                 && payload.hasOwnProperty('disk_driver')) {
5480 
5481                 zvol.model = payload.disk_driver;
5482             }
5483             if (!zvol.hasOwnProperty('media')) {
5484                 zvol.media = 'disk';
5485             }
5486         }
5487     }
5488 
5489     if (payload.hasOwnProperty('add_nics')) {
5490         // update
5491         nics = payload.add_nics;
5492     } else if (payload.hasOwnProperty('nics')) {
5493         nics = payload.nics;
5494     } else {
5495         // no disks at all
5496         nics = [];
5497     }
5498 
5499     for (nic in nics) {
5500         if (nics.hasOwnProperty(nic)) {
5501             n = nics[nic];
5502             if (!n.hasOwnProperty('model')
5503                 && payload.hasOwnProperty('nic_driver')) {
5504 
5505                 n.model = payload.nic_driver;
5506             }
5507         }
5508     }
5509 }
5510 
5511 function validRecordSize(candidate)
5512 {
5513     if (candidate < 512) {
5514         // too low
5515         return (false);
5516     } else if (candidate > 131072) {
5517         // too high
5518         return (false);
5519     } else if ((candidate & (candidate - 1)) !== 0) {
5520         // not a power of 2
5521         return (false);
5522     }
5523 
5524     return (true);
5525 }
5526 
5527 // This function gets called for both create and update to check that payload
5528 // properties are reasonable. If vmobj is null, create is assumed, otherwise
5529 // update is assumed.
5530 function checkPayloadProperties(payload, vmobj, log, callback)
5531 {
5532     var array_fields = [
5533         'add_nics', 'update_nics', 'remove_nics',
5534         'add_disks', 'update_disks', 'remove_disks',
5535         'add_filesystems', 'update_filesystems', 'remove_filesystems'
5536     ];
5537     var changed_nics = [];
5538     var current_ips = [];
5539     var current_macs = [];
5540     var current_primary_ips = [];
5541     var current_vrids = [];
5542     var disk;
5543     var dst;
5544     var field;
5545     var filesys;
5546     var i;
5547     var ips = [];
5548     var is_nic = false;
5549     var live_ok;
5550     var mac;
5551     var macs = [];
5552     var m;
5553     var n;
5554     var nic;
5555     var nics_result = {};
5556     var nics_result_ordered = [];
5557     var nic_fields = ['add_nics', 'update_nics'];
5558     var only_vrrp_nics = true;
5559     var primary_nics;
5560     var prop;
5561     var props;
5562     var ram;
5563     var route;
5564     var routes_result = {};
5565     var brand;
5566     var vrids = [];
5567     var zvol;
5568 
5569     assert(log, 'no logger passed to checkPayloadProperties()');
5570 
5571     if (vmobj) {
5572         brand = vmobj.brand;
5573     } else if (payload.hasOwnProperty('brand')) {
5574         brand = payload.brand;
5575     } else {
5576         callback(new Error('unable to determine brand for VM'));
5577     }
5578 
5579     /* check types of fields that should be arrays */
5580     for (field in array_fields) {
5581         field = array_fields[field];
5582         if (payload.hasOwnProperty(field) && ! Array.isArray(payload[field])) {
5583             callback(new Error(field + ' must be an array.'));
5584             return;
5585         }
5586     }
5587 
5588     if (!vmobj) {
5589         // This is a CREATE
5590 
5591         // These should have already been enforced
5592         if (payload.max_locked_memory > payload.max_physical_memory) {
5593             callback(new Error('max_locked_memory must be <= '
5594                 + 'max_physical_memory'));
5595             return;
5596         }
5597         if (payload.max_swap < payload.max_physical_memory) {
5598             callback(new Error('max_swap must be >= max_physical_memory'));
5599             return;
5600         }
5601 
5602         // We used to use zone_path instead of zonepath, so accept that too.
5603         if (payload.hasOwnProperty('zone_path')
5604             && !payload.hasOwnProperty('zonepath')) {
5605 
5606             payload.zonepath = payload.zone_path;
5607             delete payload.zone_path;
5608         }
5609     } else {
5610         // This is an UPDATE
5611 
5612         // can't update disks of a running VM
5613         if (payload.hasOwnProperty('add_disks')
5614             || payload.hasOwnProperty('remove_disks')) {
5615 
5616             if ((vmobj.state !== 'stopped')
5617                 || (vmobj.state === 'provisioning'
5618                 && vmobj.zone_state !== 'installed')) {
5619 
5620                 callback(new Error('updates to disks are only allowed when '
5621                     + 'state is "stopped", currently: ' + vmobj.state + ' ('
5622                     + vmobj.zone_state + ')'));
5623                 return;
5624             }
5625         }
5626 
5627         // For update_disks we can update refreservation and compression values
5628         // while running. If there are other parameters to update though we'll
5629         // reject.
5630         if (payload.hasOwnProperty('update_disks')) {
5631             if ((vmobj.state !== 'stopped')
5632                 || (vmobj.state === 'provisioning'
5633                 && vmobj.zone_state !== 'installed')) {
5634 
5635                 live_ok = true;
5636 
5637                 payload.update_disks.forEach(function (d) {
5638                     var key;
5639                     var keys = Object.keys(d);
5640 
5641                     while ((keys.length > 0) && live_ok) {
5642                         key = keys.pop();
5643                         if ([
5644                             'compression',
5645                             'path',
5646                             'refreservation'
5647                             ].indexOf(key) === -1) {
5648 
5649                             // this key is not allowed!
5650                             live_ok = false;
5651                         }
5652                     }
5653                 });
5654 
5655                 if (!live_ok) {
5656                     callback(new Error('at least one specified update to disks '
5657                         + 'is only allowed when state is "stopped", currently: '
5658                         + vmobj.state + ' (' + vmobj.zonestate + ')'));
5659                     return;
5660                 }
5661             }
5662         }
5663 
5664         // if there's a min_overhead we ensure values are higher than ram.
5665         if (BRAND_OPTIONS[brand].features.min_memory_overhead) {
5666             if (payload.hasOwnProperty('ram')) {
5667                 ram = payload.ram;
5668             } else {
5669                 ram = vmobj.ram;
5670             }
5671 
5672             // ensure none of these is < ram
5673             if (payload.hasOwnProperty('max_physical_memory')
5674                 && payload.max_physical_memory < ram) {
5675 
5676                 callback(new Error('vm.max_physical_memory ('
5677                     + payload.max_physical_memory + ') cannot be lower than'
5678                     + ' vm.ram (' + ram + ')'));
5679                 return;
5680             }
5681             if (payload.hasOwnProperty('max_locked_memory')
5682                 && payload.max_locked_memory < ram) {
5683 
5684                 callback(new Error('vm.max_locked_memory ('
5685                     + payload.max_locked_memory + ') cannot be lower than'
5686                     + ' vm.ram (' + ram + ')'));
5687                 return;
5688             }
5689             // This should not be allowed anyway because max_swap will be raised
5690             // to match max_physical_memory if you set it lower.
5691             if (payload.hasOwnProperty('max_swap')) {
5692                 if (payload.max_swap < ram) {
5693                     callback(new Error('vm.max_swap ('
5694                         + payload.max_swap + ') cannot be lower than'
5695                         + ' vm.ram (' + ram + ')'));
5696                     return;
5697                 } else if (payload.max_swap < MINIMUM_MAX_SWAP) {
5698                     callback(new Error('vm.max_swap ('
5699                         + payload.max_swap + ') cannot be lower than '
5700                         + MINIMUM_MAX_SWAP + 'MiB'));
5701                     return;
5702                 }
5703             }
5704         }
5705 
5706         /*
5707          * keep track of current IPs/MACs so we can make sure they're not being
5708          * duplicated.
5709          *
5710          */
5711         for (nic in vmobj.nics) {
5712             nic = vmobj.nics[nic];
5713             if (nic.hasOwnProperty('ip') && nic.ip !== 'dhcp') {
5714                 current_ips.push(nic.ip);
5715             }
5716             if (nic.hasOwnProperty('mac')) {
5717                 current_macs.push(nic.mac);
5718             }
5719             if (nic.hasOwnProperty('vrrp_vrid')) {
5720                 current_vrids.push(nic.vrrp_vrid);
5721             }
5722             if (nic.hasOwnProperty('vrrp_primary_ip')) {
5723                 current_primary_ips.push(nic.vrrp_primary_ip);
5724             }
5725 
5726             if (nic.hasOwnProperty('mac') || nic.hasOwnProperty('vrrp_vrid')) {
5727                 mac = nic.hasOwnProperty('mac') ? nic.mac
5728                     : vrrpMAC(nic.vrrp_vrid);
5729                 if (!nics_result.hasOwnProperty(mac)) {
5730                     nics_result[mac] = nic;
5731                     nics_result_ordered.push(nic);
5732                 }
5733             }
5734         }
5735 
5736         // Keep track of route additions / deletions, to make sure that
5737         // we're not setting link-local routes against nics that don't exist
5738         for (route in vmobj.routes) {
5739             routes_result[route] = vmobj.routes[route];
5740         }
5741     }
5742 
5743     if (payload.hasOwnProperty('add_disks')) {
5744         for (disk in payload.add_disks) {
5745             if (payload.add_disks.hasOwnProperty(disk)) {
5746                 zvol = payload.add_disks[disk];
5747 
5748                 // path is only allowed in 2 cases when adding a disk:
5749                 //
5750                 // 1) for cdrom devices
5751                 // 2) when nocreate is specified
5752                 //
5753                 if (zvol.hasOwnProperty('path')) {
5754                     if (zvol.media !== 'cdrom' && !zvol.nocreate) {
5755                         callback(new Error('you cannot specify a path for a '
5756                             + 'disk unless you set nocreate=true'));
5757                         return;
5758                     }
5759                 }
5760 
5761                 // NOTE: We'll have verified the .zpool argument is a valid
5762                 // zpool using VM.validate() if it's set.
5763 
5764                 if (zvol.hasOwnProperty('block_size')
5765                     && !validRecordSize(zvol.block_size)) {
5766 
5767                     callback(new Error('invalid .block_size(' + zvol.block_size
5768                         + '), must be 512-131072 and a power of 2.'));
5769                     return;
5770                 }
5771 
5772                 if (zvol.hasOwnProperty('block_size')
5773                     && zvol.hasOwnProperty('image_uuid')) {
5774 
5775                     callback(new Error('setting both .block_size and '
5776                         + '.image_uuid on a volume is invalid'));
5777                 }
5778 
5779                 if (zvol.hasOwnProperty('compression')) {
5780                     if (VM.COMPRESSION_TYPES.indexOf(zvol.compression) === -1) {
5781                         callback(new Error('invalid compression setting for '
5782                             + 'disk, must be one of: '
5783                             + VM.COMPRESSION_TYPES.join(', ')));
5784                     }
5785                 }
5786 
5787                 if (!zvol.hasOwnProperty('model')
5788                     || zvol.model === 'undefined') {
5789 
5790                     if (vmobj && vmobj.hasOwnProperty('disk_driver')) {
5791                         zvol.model = vmobj.disk_driver;
5792                         log.debug('set model to ' + zvol.model
5793                             + ' from disk_driver');
5794                     } else if (vmobj && vmobj.hasOwnProperty('disks')
5795                         && vmobj.disks.length > 0 && vmobj.disks[0].model) {
5796 
5797                         zvol.model = vmobj.disks[0].model;
5798                         log.debug('set model to ' + zvol.model + ' from disk0');
5799                     } else {
5800                         callback(new Error('missing .model option for '
5801                             + 'disk: ' + JSON.stringify(zvol)));
5802                         return;
5803                     }
5804                 } else if (VM.DISK_MODELS.indexOf(zvol.model) === -1) {
5805                     callback(new Error('"' + zvol.model + '"'
5806                         + ' is not a valid disk model. Valid are: '
5807                         + VM.DISK_MODELS.join(',')));
5808                     return;
5809                 }
5810             }
5811         }
5812     }
5813 
5814     if (payload.hasOwnProperty('update_disks')) {
5815         for (disk in payload.update_disks) {
5816             if (payload.update_disks.hasOwnProperty(disk)) {
5817                 zvol = payload.update_disks[disk];
5818 
5819                 if (zvol.hasOwnProperty('compression')) {
5820                     if (VM.COMPRESSION_TYPES.indexOf(zvol.compression) === -1) {
5821                         callback(new Error('invalid compression type for '
5822                             + 'disk, must be one of: '
5823                             + VM.COMPRESSION_TYPES.join(', ')));
5824                     }
5825                 }
5826 
5827                 if (zvol.hasOwnProperty('block_size')) {
5828                     callback(new Error('cannot change .block_size for a disk '
5829                         + 'after creation'));
5830                     return;
5831                 }
5832             }
5833         }
5834     }
5835 
5836     // If we're receiving, we might not have the filesystem yet
5837     if (!payload.hasOwnProperty('transition')
5838         || payload.transition.transition !== 'receiving') {
5839 
5840         for (filesys in payload.filesystems) {
5841             filesys = payload.filesystems[filesys];
5842             if (!fs.existsSync(filesys.source)) {
5843                 callback(new Error('missing requested filesystem: '
5844                     + filesys.source));
5845                 return;
5846             }
5847         }
5848     }
5849 
5850     if (payload.hasOwnProperty('default_gateway')
5851         && payload.default_gateway !== '') {
5852 
5853         log.warn('DEPRECATED: default_gateway should no longer be used, '
5854             + 'instead set one NIC primary and use nic.gateway.');
5855     }
5856 
5857     primary_nics = 0;
5858     for (field in nic_fields) {
5859         field = nic_fields[field];
5860         if (payload.hasOwnProperty(field)) {
5861             for (nic in payload[field]) {
5862                 if (payload[field].hasOwnProperty(nic)) {
5863                     n = payload[field][nic];
5864 
5865                     // MAC will always conflict in update, since that's the key
5866                     if (field === 'add_nics' && n.hasOwnProperty('mac')) {
5867                         if ((macs.indexOf(n.mac) !== -1)
5868                             || current_macs.indexOf(n.mac) !== -1) {
5869 
5870                             callback(new Error('Cannot add multiple NICs with '
5871                                 + 'the same MAC: ' + n.mac));
5872                             return;
5873                         }
5874                         macs.push(n.mac);
5875                     }
5876 
5877                     if (field === 'add_nics' || field === 'update_nics') {
5878                         if (n.hasOwnProperty('primary')) {
5879                             if (n.primary !== true) {
5880                                 callback(new Error('invalid value for NIC\'s '
5881                                     + 'primary flag: ' + n.primary + ' (must be'
5882                                     + ' true)'));
5883                                 return;
5884                             }
5885                             primary_nics++;
5886                         }
5887                         changed_nics.push(n);
5888                     }
5889 
5890                     if (n.hasOwnProperty('ip') && n.ip != 'dhcp') {
5891                         if (ips.indexOf(n.ip) !== -1
5892                             || current_ips.indexOf(n.ip) !== -1) {
5893 
5894                             callback(new Error('Cannot add multiple NICs with '
5895                                 + 'the same IP: ' + n.ip));
5896                             return;
5897                         }
5898                         ips.push(n.ip);
5899                     }
5900 
5901                     if (n.hasOwnProperty('vrrp_vrid')) {
5902                         if (current_vrids.indexOf(n.vrrp_vrid) !== -1
5903                             || vrids.indexOf(n.vrrp_vrid) !== -1) {
5904                             callback(new Error('Cannot add multiple NICs with '
5905                                 + 'the same VRID: ' + n.vrrp_vrid));
5906                             return;
5907                         }
5908                         vrids.push(n.vrrp_vrid);
5909                     }
5910 
5911                     if (field === 'add_nics'
5912                         && n.hasOwnProperty('vrrp_vrid')
5913                         && n.hasOwnProperty('mac')) {
5914                         callback(
5915                             new Error('Cannot set both mac and vrrp_vrid'));
5916                         return;
5917                     }
5918 
5919                     if (n.hasOwnProperty('vrrp_primary_ip')) {
5920                         current_primary_ips.push(n.vrrp_primary_ip);
5921                     }
5922 
5923                     if (BRAND_OPTIONS[brand].features.model_required
5924                         && field === 'add_nics'
5925                         && (!n.hasOwnProperty('model') || !n.model
5926                         || n.model === 'undefined' || n.model.length === 0)) {
5927 
5928 
5929                         if (vmobj && vmobj.hasOwnProperty('nic_driver')) {
5930                             n.model = vmobj.nic_driver;
5931                             log.debug('set model to ' + n.model
5932                                 + ' from nic_driver');
5933                         } else if (vmobj && vmobj.hasOwnProperty('nics')
5934                             && vmobj.nics.length > 0 && vmobj.nics[0].model) {
5935 
5936                             n.model = vmobj.nics[0].model;
5937                             log.debug('set model to ' + n.model + ' from nic0');
5938                         } else {
5939                             callback(new Error('missing .model option for NIC: '
5940                                 + JSON.stringify(n)));
5941                             return;
5942                         }
5943                     }
5944 
5945                     if (field === 'add_nics' && n.ip !== 'dhcp'
5946                         && (!n.hasOwnProperty('netmask')
5947                         || !net.isIPv4(n.netmask))) {
5948 
5949                         callback(new Error('invalid or missing .netmask option '
5950                             + 'for NIC: ' + JSON.stringify(n)));
5951                         return;
5952                     }
5953 
5954                     if ((field === 'add_nics' || field === 'update_nics')
5955                         && n.hasOwnProperty('ip') && n.ip !== 'dhcp'
5956                         && !net.isIPv4(n.ip)) {
5957 
5958                         callback(new Error('invalid IP for NIC: '
5959                             + JSON.stringify(n)));
5960                         return;
5961                     }
5962 
5963                     if (field === 'add_nics' && (!n.hasOwnProperty('nic_tag')
5964                         || !n.nic_tag.match(/^[a-zA-Z0-9\_]+$/))) {
5965 
5966                         callback(new Error('invalid or missing .nic_tag option '
5967                             + 'for NIC: ' + JSON.stringify(n)));
5968                         return;
5969                     }
5970 
5971                     if (field === 'update_nics' && n.hasOwnProperty('model')
5972                         && (!n.model || n.model === 'undefined'
5973                         || n.model.length === 0)) {
5974 
5975                         callback(new Error('invalid .model option for NIC: '
5976                             + JSON.stringify(n)));
5977                         return;
5978                     }
5979 
5980                     if (field === 'update_nics' && n.hasOwnProperty('netmask')
5981                         && (!n.netmask || !net.isIPv4(n.netmask))) {
5982 
5983                         callback(new Error('invalid .netmask option for NIC: '
5984                             + JSON.stringify(n)));
5985                         return;
5986                     }
5987 
5988                     if (field === 'update_nics' && n.hasOwnProperty('nic_tag')
5989                         && !n.nic_tag.match(/^[a-zA-Z0-9\_]+$/)) {
5990 
5991                         callback(new Error('invalid .nic_tag option for NIC: '
5992                             + JSON.stringify(n)));
5993                         return;
5994                     }
5995 
5996                     if (n.hasOwnProperty('mac')
5997                         || n.hasOwnProperty('vrrp_vrid')) {
5998                         mac = n.hasOwnProperty('mac') ? n.mac
5999                             : vrrpMAC(n.vrrp_vrid);
6000                         if (nics_result.hasOwnProperty(mac)) {
6001                             var p;
6002                             for (p in n) {
6003                                 nics_result[mac][p] = n[p];
6004                             }
6005 
6006                             nics_result_ordered.forEach(function (on) {
6007                                 if (on.hasOwnProperty('mac') && on.mac == mac) {
6008                                     for (p in n) {
6009                                         on[p] = n[p];
6010                                     }
6011                                 }
6012                             });
6013                         } else {
6014                             nics_result[mac] = n;
6015                             nics_result_ordered.push(n);
6016                         }
6017                     }
6018 
6019                     if ((field === 'add_nics' || field === 'update_nics')
6020                         && n.hasOwnProperty('allowed_ips')) {
6021                         try {
6022                             validateIPlist(n.allowed_ips);
6023                         } catch (ipListErr) {
6024                             callback(ipListErr);
6025                             return;
6026                         }
6027                     }
6028                 }
6029             }
6030         }
6031     }
6032 
6033     if (payload.hasOwnProperty('remove_nics')) {
6034         for (m in payload.remove_nics) {
6035             m = payload.remove_nics[m];
6036             n = nics_result[m];
6037             if (!n) {
6038                 continue;
6039             }
6040             if (n.hasOwnProperty('ip') && n.ip != 'dhcp') {
6041                 i = ips.indexOf(n.ip);
6042                 if (i !== -1) {
6043                     ips.splice(i, 1);
6044                 }
6045                 i = current_ips.indexOf(n.ip);
6046                 if (i !== -1) {
6047                     current_ips.splice(i, 1);
6048                 }
6049             }
6050             delete nics_result[m];
6051 
6052             for (i in nics_result_ordered) {
6053                 n = nics_result_ordered[i];
6054                 if (n.hasOwnProperty('mac') && n.mac == m) {
6055                     nics_result_ordered.splice(i, 1);
6056                     break;
6057                 }
6058             }
6059         }
6060     }
6061 
6062     // nics_result now has the state of the nics after the update - now check
6063     // properties that depend on each other or on other nics
6064     for (n in nics_result) {
6065         n = nics_result[n];
6066         if (n.hasOwnProperty('vrrp_vrid')) {
6067             if (n.hasOwnProperty('ip')
6068                 && current_primary_ips.indexOf(n.ip) !== -1) {
6069                 callback(
6070                     new Error(
6071                         'Cannot set vrrp_primary_ip to the IP of a VRRP nic'));
6072                 return;
6073             }
6074 
6075             if (!n.hasOwnProperty('vrrp_primary_ip')) {
6076                 callback(new Error(
6077                     'vrrp_vrid set but not vrrp_primary_ip'));
6078                 return;
6079             }
6080         } else {
6081             only_vrrp_nics = false;
6082         }
6083     }
6084 
6085     if (only_vrrp_nics && Object.keys(nics_result).length !== 0) {
6086         callback(new Error('VM cannot contain only VRRP nics'));
6087         return;
6088     }
6089 
6090     for (i in current_primary_ips) {
6091         i = current_primary_ips[i];
6092         if ((current_ips.indexOf(i) === -1)
6093             && (ips.indexOf(i) === -1)) {
6094             callback(new Error(
6095                 'vrrp_primary_ip must belong to the same VM'));
6096             return;
6097         }
6098     }
6099 
6100     // Since we always need a primary nic, don't allow a value other than true
6101     // for primary flag. Also ensure we're not trying to set primary for more
6102     // than one nic.
6103     if (primary_nics > 1) {
6104         callback(new Error('payload specifies more than 1 primary NIC'));
6105         return;
6106     }
6107 
6108     if (payload.hasOwnProperty('vga')
6109         && VM.VGA_TYPES.indexOf(payload.vga) === -1) {
6110 
6111         callback(new Error('Invalid VGA type: "' + payload.vga
6112             + '", supported types are: ' + VM.VGA_TYPES.join(',')));
6113         return;
6114     }
6115 
6116     function validLocalRoute(r) {
6117         var nicIdx = r.match(/nics\[(\d+)\]/);
6118         if (!nicIdx) {
6119             is_nic = false;
6120             return false;
6121         }
6122         is_nic = true;
6123 
6124         if (nics_result_ordered.length === 0) {
6125             return false;
6126         }
6127 
6128         nicIdx = Number(nicIdx[1]);
6129         if (!nics_result_ordered[nicIdx]
6130             || !nics_result_ordered[nicIdx].hasOwnProperty('ip')
6131             || nics_result_ordered[nicIdx].ip === 'dhcp') {
6132             return false;
6133         }
6134 
6135         return true;
6136     }
6137 
6138     props = [ 'routes', 'set_routes' ];
6139     for (prop in props) {
6140         prop = props[prop];
6141         if (payload.hasOwnProperty(prop)) {
6142             for (dst in payload[prop]) {
6143                 var src = payload[prop][dst];
6144 
6145                 if (!net.isIPv4(dst) && !isCIDR(dst)) {
6146                     callback(new Error('Invalid route destination: "' + dst
6147                         + '" (must be IP address or CIDR)'));
6148                     return;
6149                 }
6150 
6151                 if (!net.isIPv4(src) && !validLocalRoute(src)) {
6152                     callback(new Error(
6153                         is_nic ? 'Route gateway: "' + src
6154                             + '" refers to non-existent or DHCP nic'
6155                         : 'Invalid route gateway: "' + src
6156                             + '" (must be IP address or nic)'));
6157                     return;
6158                 }
6159 
6160                 routes_result[dst] = src;
6161             }
6162         }
6163     }
6164 
6165     if (payload.hasOwnProperty('remove_routes')) {
6166         for (dst in payload.remove_routes) {
6167             dst = payload.remove_routes[dst];
6168             delete routes_result[dst];
6169         }
6170     }
6171 
6172     // Now that we've applied all updates to routes, make sure that all
6173     // link-local routes refer to a nic that still exists
6174     for (dst in routes_result) {
6175         if (!net.isIPv4(routes_result[dst])
6176             && !validLocalRoute(routes_result[dst])) {
6177             callback(new Error('Route gateway: "' + routes_result[dst]
6178                 + '" refers to non-existent or DHCP nic'));
6179             return;
6180         }
6181     }
6182 
6183     // Ensure password is not too long
6184     if (payload.hasOwnProperty('vnc_password')
6185         && payload.vnc_password.length > 8) {
6186 
6187         callback(new Error('VNC password is too long, maximum length is 8 '
6188             + 'characters.'));
6189         return;
6190     }
6191 
6192     props = ['zfs_root_recsize', 'zfs_data_recsize'];
6193     for (prop in props) {
6194         prop = props[prop];
6195         if (payload.hasOwnProperty(prop)) {
6196             if (payload[prop] === 0 || payload[prop] === '') {
6197                 // this is the default, so set it back to that.
6198                 payload[prop] = 131072;
6199             } else if (!validRecordSize(payload[prop])) {
6200                 callback(new Error('invalid ' + prop + ' (' + payload[prop]
6201                     + '), must be 512-131072 and a power of 2. '
6202                     + '(0 to disable)'));
6203                 return;
6204             }
6205         }
6206     }
6207     props = ['zfs_root_compression', 'zfs_data_compression'];
6208     for (prop in props) {
6209         prop = props[prop];
6210 
6211         if (payload.hasOwnProperty(prop)) {
6212             if (VM.COMPRESSION_TYPES.indexOf(payload[prop]) === -1) {
6213                 callback(new Error('invalid compression type for '
6214                     + payload[prop] + ', must be one of: '
6215                     + VM.COMPRESSION_TYPES.join(', ')));
6216             }
6217         }
6218     }
6219 
6220     // Ensure MACs and IPs are not already used on this vm
6221     // NOTE: can't check other nodes yet.
6222 
6223     async.series([
6224         function (cb) {
6225             lookupConflicts(macs, ips, vrids, log, function (error, conflict) {
6226                 if (error) {
6227                     cb(error);
6228                 } else {
6229                     if (conflict) {
6230                         cb(new Error('Conflict detected with another '
6231                             + 'vm, please check the MAC, IP, and VRID'));
6232                     } else {
6233                         log.debug('no conflicts');
6234                         cb();
6235                     }
6236                 }
6237             });
6238         }, function (cb) {
6239             lookupInvalidNicTags(changed_nics, log, function (e) {
6240                 if (e) {
6241                     cb(e);
6242                 } else {
6243                     cb();
6244                 }
6245             });
6246         }, function (cb) {
6247             // We only allow adding firewall rules on create
6248             if (vmobj) {
6249                 log.debug('update: not validating firewall data');
6250                 cb();
6251                 return;
6252             }
6253 
6254             if (!payload.hasOwnProperty('firewall')) {
6255                 log.debug('no firewall data in payload: not validating');
6256                 cb();
6257                 return;
6258             }
6259             validateFirewall(payload, log, cb);
6260         }
6261     ], function (err) {
6262         log.trace('leaving checkPayloadProperties()');
6263         callback(err);
6264     });
6265 }
6266 
6267 function createDelegatedDataset(payload, log, callback)
6268 {
6269     var args;
6270     var ds;
6271     var zcfg = '';
6272 
6273     assert(log, 'no logger passed to createDelegatedDataset()');
6274 
6275     if (payload.delegate_dataset) {
6276         log.info('creating delegated dataset.');
6277         if (!payload.hasOwnProperty('zfs_filesystem')) {
6278             callback(new Error('payload missing zfs_filesystem'));
6279             return;
6280         }
6281         ds = path.join(payload.zfs_filesystem, '/data');
6282 
6283         args = ['create'];
6284         if (payload.hasOwnProperty('zfs_data_compression')) {
6285             args.push('-o', 'compression=' + payload.zfs_data_compression);
6286         }
6287         if (payload.hasOwnProperty('zfs_data_recsize')) {
6288             args.push('-o', 'recsize=' + payload.zfs_data_recsize);
6289         }
6290         args.push(ds);
6291 
6292         zfs(args, log, function (err) {
6293             if (err) {
6294                 callback(err);
6295                 return;
6296             }
6297 
6298             zcfg = zcfg + 'add dataset; set name=' + ds + '; end\n';
6299             zonecfg(['-u', payload.uuid, zcfg], log, function (e, fds) {
6300                 if (e) {
6301                     log.error({'err': e, stdout: fds.stdout,
6302                         stderr: fds.stderr}, 'unable to add delegated dataset '
6303                         + ds + ' to ' + payload.uuid);
6304                     callback(e);
6305                 } else {
6306                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
6307                         'added delegated dataset ' + ds);
6308                     callback();
6309                 }
6310             });
6311         });
6312     } else {
6313         callback();
6314     }
6315 }
6316 
6317 function buildAddRemoveList(vmobj, payload, type, key, updatable)
6318 {
6319     var add = [];
6320     var add_key;
6321     var field;
6322     var newobj;
6323     var oldobj;
6324     var plural = type + 's';
6325     var remove = [];
6326     var remove_key;
6327     var update_key;
6328 
6329     // initialize some plurals
6330     add_key = 'add_' + plural;
6331     remove_key = 'remove_' + plural;
6332     update_key = 'update_' + plural;
6333 
6334     // There's no way to update properties on a disk or nic with zonecfg
6335     // currently.  Yes, really.  So any disks/nics that should be updated, we
6336     // remove then add with the new properties.
6337     if (payload.hasOwnProperty(update_key)) {
6338         for (newobj in payload[update_key]) {
6339             newobj = payload[update_key][newobj];
6340             for (oldobj in vmobj[plural]) {
6341                 oldobj = vmobj[plural][oldobj];
6342 
6343                 if (oldobj[key] === newobj[key]) {
6344                     // This is the one to update: remove and add.
6345                     remove.push(oldobj[key]);
6346 
6347                     // only some fields make sense to update.
6348                     for (field in updatable) {
6349                         field = updatable[field];
6350                         if (newobj.hasOwnProperty(field)) {
6351                             oldobj[field] = newobj[field];
6352                         }
6353                     }
6354 
6355                     add.push(oldobj);
6356                 }
6357             }
6358         }
6359     }
6360 
6361     if (payload.hasOwnProperty(remove_key)) {
6362         for (newobj in payload[remove_key]) {
6363             newobj = payload[remove_key][newobj];
6364             remove.push(newobj);
6365         }
6366     }
6367 
6368     if (payload.hasOwnProperty(add_key)) {
6369         for (newobj in payload[add_key]) {
6370             newobj = payload[add_key][newobj];
6371             add.push(newobj);
6372         }
6373     }
6374 
6375     return ({'add': add, 'remove': remove});
6376 }
6377 
6378 function buildDiskZonecfg(vmobj, payload)
6379 {
6380     var add = [];
6381     var disk;
6382     var lists;
6383     var remove = [];
6384     var zcfg = '';
6385 
6386     lists = buildAddRemoveList(vmobj, payload, 'disk', 'path',
6387         UPDATABLE_DISK_PROPS);
6388     remove = lists.remove;
6389     add = lists.add;
6390 
6391     // remove is a list of disk paths, add a remove for each now.
6392     for (disk in remove) {
6393         disk = remove[disk];
6394         zcfg = zcfg + 'remove -F device match=' + disk + '\n';
6395     }
6396 
6397     for (disk in add) {
6398         disk = add[disk];
6399 
6400         zcfg = zcfg + 'add device\n'
6401             + 'set match=' + disk.path + '\n'
6402             + 'add property (name=boot, value="'
6403             + (disk.boot ? 'true' : 'false') + '")\n'
6404             + 'add property (name=model, value="' + disk.model + '")\n';
6405 
6406         if (disk.hasOwnProperty('media')) {
6407             zcfg = zcfg
6408                 + 'add property (name=media, value="'
6409                 + disk.media + '")\n';
6410         }
6411 
6412         if (disk.hasOwnProperty('image_size')) {
6413             zcfg = zcfg
6414                 + 'add property (name=image-size, value="'
6415                 + disk.image_size + '")\n';
6416         } else if (disk.hasOwnProperty('size')) {
6417             zcfg = zcfg + 'add property (name=size, value="'
6418                 + disk.size + '")\n';
6419         }
6420 
6421         if (disk.hasOwnProperty('image_uuid')) {
6422             zcfg = zcfg
6423                 + 'add property (name=image-uuid, value="'
6424                 + disk.image_uuid + '")\n';
6425         }
6426 
6427         if (disk.hasOwnProperty('image_name')) {
6428             zcfg = zcfg + 'add property (name=image-name, value="'
6429                 + disk.image_name + '")\n';
6430         }
6431 
6432         zcfg = zcfg + 'end\n';
6433     }
6434 
6435     return zcfg;
6436 }
6437 
6438 function buildNicZonecfg(vmobj, payload)
6439 {
6440     var add;
6441     var lists;
6442     var matches;
6443     var n;
6444     var new_primary;
6445     var nic;
6446     var nic_idx = 0;
6447     var remove;
6448     var updated_primary;
6449     var used_nic_indexes = [];
6450     var zcfg = '';
6451 
6452     if (vmobj.hasOwnProperty('nics')) {
6453         // check whether we're adding or updating to set the primary flag. If we
6454         // are also find the existing NIC with the primary flag. If that's not
6455         // being removed, update it to remove the primary flag.
6456         if (payload.hasOwnProperty('add_nics')) {
6457             for (nic in payload.add_nics) {
6458                 nic = payload.add_nics[nic];
6459                 if (nic.hasOwnProperty('primary')) {
6460                     new_primary = nic.mac;
6461                 }
6462             }
6463         }
6464         if (payload.hasOwnProperty('update_nics')) {
6465             for (nic in payload.update_nics) {
6466                 nic = payload.update_nics[nic];
6467                 if (nic.hasOwnProperty('primary')) {
6468                     new_primary = nic.mac;
6469                 }
6470             }
6471         }
6472         if (new_primary) {
6473             // find old primary
6474             for (nic in vmobj.nics) {
6475                 nic = vmobj.nics[nic];
6476                 if (nic.hasOwnProperty('primary') && nic.mac !== new_primary) {
6477                     // we have a new primary, so un-primary the old.
6478                     if (payload.hasOwnProperty('remove_nics')
6479                         && payload.remove_nics.indexOf(nic.mac) !== -1) {
6480 
6481                         // we're removing the old primary so: done.
6482                         break;
6483                     } else if (payload.hasOwnProperty('update_nics')) {
6484                         updated_primary = false;
6485                         for (n in payload.update_nics) {
6486                             n = payload.update_nics[n];
6487                             if (n.mac === nic.mac) {
6488                                 n.primary = false;
6489                                 updated_primary = true;
6490                             }
6491                         }
6492                         if (!updated_primary) {
6493                             payload.update_nics.push({'mac': nic.mac,
6494                                 'primary': false});
6495                         }
6496                     } else {
6497                         // just add a new update to unset the
6498                         payload.update_nics =
6499                             [ {'mac': nic.mac, 'primary': false} ];
6500                     }
6501                 }
6502             }
6503         }
6504     }
6505 
6506     lists = buildAddRemoveList(vmobj, payload, 'nic', 'mac',
6507         UPDATABLE_NIC_PROPS);
6508     remove = lists.remove;
6509     add = lists.add;
6510 
6511     // create a list of used indexes so we can find the free ones
6512     if (vmobj.hasOwnProperty('nics')) {
6513         for (n in vmobj.nics) {
6514             if (vmobj.nics[n].hasOwnProperty('interface')) {
6515                 matches = vmobj.nics[n].interface.match(/^net(\d+)$/);
6516                 if (matches) {
6517                     used_nic_indexes.push(Number(matches[1]));
6518                 }
6519             }
6520         }
6521     }
6522 
6523     // assign next available interface for nics without one
6524     for (nic in add) {
6525         nic = add[nic];
6526         if (!nic.hasOwnProperty('interface')) {
6527             while (used_nic_indexes.indexOf(nic_idx) !== -1) {
6528                 nic_idx++;
6529             }
6530             nic.interface = 'net' + nic_idx;
6531             used_nic_indexes.push(Number(nic_idx));
6532         }
6533 
6534         // Changing the VRID changes the MAC address too, since the VRID is
6535         // encoded in the MAC. This can't be done until after
6536         // buildAddRemoveList above, since mac is used as the key to figure
6537         // out which nic is which
6538         if (nic.hasOwnProperty('vrrp_vrid')) {
6539             nic.mac = vrrpMAC(nic.vrrp_vrid);
6540         }
6541     }
6542 
6543     // remove is a list of nic macs, add a remove for each now.
6544     for (nic in remove) {
6545         nic = remove[nic];
6546         zcfg = zcfg + 'remove net mac-addr=' + ruinMac(nic) + '\n';
6547     }
6548 
6549     // properties that don't require any validation - add them if they're
6550     // present:
6551     var nicProperties = ['ip', 'netmask', 'network_uuid', 'model',
6552         'dhcp_server', 'allow_dhcp_spoofing', 'blocked_outgoing_ports',
6553         'allow_ip_spoofing', 'allow_mac_spoofing', 'allow_restricted_traffic',
6554         'allow_unfiltered_promisc', 'vrrp_vrid', 'vrrp_primary_ip'];
6555 
6556     for (nic in add) {
6557         nic = add[nic];
6558 
6559         zcfg = zcfg
6560             + 'add net\n'
6561             + 'set physical=' + nic.interface + '\n'
6562             + 'set mac-addr=' + ruinMac(nic.mac) + '\n';
6563 
6564         if (nic.hasOwnProperty('nic_tag')) {
6565             zcfg = zcfg + 'set global-nic=' + nic.nic_tag + '\n';
6566         }
6567 
6568         if (nic.hasOwnProperty('gateway') && nic.gateway.length > 0) {
6569             zcfg = zcfg + 'add property (name=gateway, value="'
6570                 + nic.gateway + '")\n';
6571         }
6572 
6573         if (nic.hasOwnProperty('primary') && nic.primary) {
6574             zcfg = zcfg + 'add property (name=primary, value="true")\n';
6575         }
6576 
6577         if (nic.hasOwnProperty('vlan_id') && (nic.vlan_id !== '0')) {
6578             zcfg = zcfg + 'set vlan-id=' + nic.vlan_id + '\n';
6579         }
6580 
6581         if (nic.hasOwnProperty('allowed_ips')) {
6582             zcfg = zcfg
6583                 + 'add property (name=allowed_ips, value="'
6584                 + nic.allowed_ips.join(',') + '")\n';
6585         }
6586 
6587         for (var prop in nicProperties) {
6588             prop = nicProperties[prop];
6589             if (nic.hasOwnProperty(prop)) {
6590                 zcfg = zcfg + 'add property (name=' + prop + ', value="'
6591                     + nic[prop] + '")\n';
6592             }
6593         }
6594 
6595         zcfg = zcfg + 'end\n';
6596     }
6597 
6598     return zcfg;
6599 }
6600 
6601 function buildFilesystemZonecfg(vmobj, payload)
6602 {
6603     var add = [];
6604     var filesystem;
6605     var lists;
6606     var opt;
6607     var remove = [];
6608     var zcfg = '';
6609 
6610     lists = buildAddRemoveList(vmobj, payload, 'filesystem', 'target', []);
6611     remove = lists.remove;
6612     add = lists.add;
6613 
6614     // remove is a list of disk paths, add a remove for each now.
6615     for (filesystem in remove) {
6616         filesystem = remove[filesystem];
6617         zcfg = zcfg + 'remove fs match=' + filesystem + '\n';
6618     }
6619 
6620     for (filesystem in add) {
6621         filesystem = add[filesystem];
6622 
6623         zcfg = zcfg + 'add fs\n' + 'set dir=' + filesystem.target + '\n'
6624             + 'set special=' + filesystem.source + '\n' + 'set type='
6625             + filesystem.type + '\n';
6626         if (filesystem.hasOwnProperty('raw')) {
6627             zcfg = zcfg + 'set raw=' + filesystem.raw + '\n';
6628         }
6629         if (filesystem.hasOwnProperty('options')) {
6630             for (opt in filesystem.options) {
6631                 opt = filesystem.options[opt];
6632                 zcfg = zcfg + 'add options "' + opt + '"\n';
6633             }
6634         }
6635         zcfg = zcfg + 'end\n';
6636     }
6637 
6638     return zcfg;
6639 }
6640 
6641 function buildZonecfgUpdate(vmobj, payload, log)
6642 {
6643     var brand;
6644     var tmp;
6645     var zcfg = '';
6646 
6647     assert(log, 'no logger passed to buildZonecfgUpdate()');
6648 
6649     log.debug({vmobj: vmobj, payload: payload},
6650         'parameters to buildZonecfgUpdate()');
6651 
6652     if (vmobj && vmobj.hasOwnProperty('brand')) {
6653         brand = vmobj.brand;
6654     } else {
6655         brand = payload.brand;
6656     }
6657 
6658     // Global properties can just be set, no need to clear anything first.
6659     if (payload.hasOwnProperty('cpu_shares')) {
6660         zcfg = zcfg + 'set cpu-shares=' + payload.cpu_shares.toString() + '\n';
6661     }
6662     if (payload.hasOwnProperty('zfs_io_priority')) {
6663         zcfg = zcfg + 'set zfs-io-priority='
6664             + payload.zfs_io_priority.toString() + '\n';
6665     }
6666     if (payload.hasOwnProperty('max_lwps')) {
6667         zcfg = zcfg + 'set max-lwps=' + payload.max_lwps.toString() + '\n';
6668     }
6669     if (payload.hasOwnProperty('limit_priv')) {
6670         zcfg = zcfg + 'set limitpriv="' + payload.limit_priv + '"\n';
6671     }
6672 
6673     if (!BRAND_OPTIONS[brand].features.use_vm_autoboot
6674         && payload.hasOwnProperty('autoboot')) {
6675 
6676         // kvm autoboot is managed by the vm-autoboot attr instead
6677         zcfg = zcfg + 'set autoboot=' + payload.autoboot.toString() + '\n';
6678     }
6679 
6680     // Capped Memory properties are special
6681     if (payload.hasOwnProperty('max_physical_memory')
6682         || payload.hasOwnProperty('max_locked_memory')
6683         || payload.hasOwnProperty('max_swap')) {
6684 
6685         // Capped memory parameters need either an add or select first.
6686         if (vmobj.hasOwnProperty('max_physical_memory')
6687             || vmobj.hasOwnProperty('max_locked_memory')
6688             || vmobj.hasOwnProperty('max_swap')) {
6689 
6690             // there's already a capped-memory section, use that.
6691             zcfg = zcfg + 'select capped-memory; ';
6692         } else {
6693             zcfg = zcfg + 'add capped-memory; ';
6694         }
6695 
6696         if (payload.hasOwnProperty('max_physical_memory')) {
6697             zcfg = zcfg + 'set physical='
6698                 + payload.max_physical_memory.toString() + 'm; ';
6699         }
6700         if (payload.hasOwnProperty('max_locked_memory')) {
6701             zcfg = zcfg + 'set locked='
6702                 + payload.max_locked_memory.toString() + 'm; ';
6703         }
6704         if (payload.hasOwnProperty('max_swap')) {
6705             zcfg = zcfg + 'set swap='
6706                 + payload.max_swap.toString() + 'm; ';
6707         }
6708 
6709         zcfg = zcfg + 'end\n';
6710     }
6711 
6712     // Capped CPU is special
6713     if (payload.hasOwnProperty('cpu_cap')) {
6714         if (vmobj.hasOwnProperty('cpu_cap')) {
6715             zcfg = zcfg + 'select capped-cpu; ';
6716         } else {
6717             zcfg = zcfg + 'add capped-cpu; ';
6718         }
6719 
6720         zcfg = zcfg + 'set ncpus='
6721             + (Number(payload.cpu_cap) * 0.01).toString() + '; end\n';
6722     }
6723 
6724     // set to empty string so property is removed when not true or when not
6725     // false if that's the default for the property.
6726     if (payload.hasOwnProperty('do_not_inventory')) {
6727         if (payload.do_not_inventory !== true) {
6728             // removing sets false as that's the default.
6729             payload.do_not_inventory = '';
6730         }
6731     }
6732 
6733     if (payload.hasOwnProperty('archive_on_delete')) {
6734         if (payload.archive_on_delete !== true) {
6735             // removing sets false as that's the default.
6736             payload.archive_on_delete = '';
6737         }
6738     }
6739 
6740     if (payload.hasOwnProperty('firewall_enabled')) {
6741         if (payload.firewall_enabled !== true) {
6742             // removing sets false as that's the default.
6743             payload.firewall_enabled = '';
6744         }
6745     }
6746 
6747     if (payload.hasOwnProperty('restart_init')) {
6748         if (payload.restart_init === true) {
6749             // removing sets true as that's the default.
6750             payload.restart_init = '';
6751         }
6752     }
6753 
6754     // Attributes
6755     function setAttr(attr, attr_name, value) {
6756         if (!value) {
6757             value = payload[attr_name];
6758         }
6759 
6760         if (payload.hasOwnProperty(attr_name)) {
6761             if ((typeof (value) !== 'boolean')
6762                 && (!value || trim(value.toString()) === '')) {
6763 
6764                 // empty values we either remove or ignore.
6765                 if (vmobj.hasOwnProperty(attr_name)) {
6766                     zcfg = zcfg + 'remove attr name=' + attr + ';';
6767                     // else do nothing, we don't add empty values.
6768                 }
6769             } else {
6770                 if (attr_name === 'resolvers'
6771                     && vmobj.hasOwnProperty('resolvers')
6772                     && vmobj.resolvers.length === 0) {
6773 
6774                     // special case for resolvers: we always have 'resolvers'
6775                     // in the object, but if it's empty we don't have it in the
6776                     // zonecfg. Add instead of the usual update.
6777                     zcfg = zcfg + 'add attr; set name="' + attr + '"; '
6778                         + 'set type=string; ';
6779                 } else if (vmobj.hasOwnProperty(attr_name)) {
6780                     zcfg = zcfg + 'select attr name=' + attr + '; ';
6781                 } else {
6782                     zcfg = zcfg + 'add attr; set name="' + attr + '"; '
6783                         + 'set type=string; ';
6784                 }
6785                 zcfg = zcfg + 'set value="' + value.toString() + '"; end\n';
6786             }
6787         }
6788     }
6789     setAttr('billing-id', 'billing_id');
6790     setAttr('owner-uuid', 'owner_uuid');
6791     setAttr('package-name', 'package_name');
6792     setAttr('package-version', 'package_version');
6793     setAttr('tmpfs', 'tmpfs');
6794     setAttr('hostname', 'hostname');
6795     setAttr('dns-domain', 'dns_domain');
6796     setAttr('default-gateway', 'default_gateway');
6797     setAttr('do-not-inventory', 'do_not_inventory');
6798     setAttr('archive-on-delete', 'archive_on_delete');
6799     setAttr('firewall-enabled', 'firewall_enabled');
6800     setAttr('restart-init', 'restart_init');
6801     setAttr('init-name', 'init_name');
6802     setAttr('disk-driver', 'disk_driver');
6803     setAttr('nic-driver', 'nic_driver');
6804 
6805     if (payload.hasOwnProperty('resolvers')) {
6806         setAttr('resolvers', 'resolvers', payload.resolvers.join(','));
6807     }
6808     if (payload.hasOwnProperty('alias')) {
6809         tmp = '';
6810         if (payload.alias) {
6811             tmp = new Buffer(payload.alias).toString('base64');
6812         }
6813         setAttr('alias', 'alias', tmp);
6814     }
6815 
6816     if (BRAND_OPTIONS[brand].features.use_vm_autoboot) {
6817         setAttr('vm-autoboot', 'autoboot');
6818     }
6819 
6820     // XXX Used on KVM but can be passed in for 'OS' too. We only setAttr on KVM
6821     if (BRAND_OPTIONS[brand].features.type === 'KVM') {
6822         setAttr('ram', 'ram');
6823     }
6824 
6825     // NOTE: Thanks to normalizePayload() we'll only have these when relevant
6826     setAttr('vcpus', 'vcpus');
6827     setAttr('boot', 'boot');
6828     setAttr('cpu-type', 'cpu_type');
6829     setAttr('vga', 'vga');
6830     setAttr('vnc-port', 'vnc_port');
6831     setAttr('spice-port', 'spice_port');
6832     setAttr('virtio-txtimer', 'virtio_txtimer');
6833     setAttr('virtio-txburst', 'virtio_txburst');
6834 
6835     // We use base64 here for these next five options:
6836     //
6837     //  vnc_password
6838     //  spice_password
6839     //  spice_opts
6840     //  qemu_opts
6841     //  qemu_extra_opts
6842     //
6843     // since these can contain characters zonecfg doesn't like.
6844     //
6845     if (payload.hasOwnProperty('vnc_password')) {
6846         if (payload.vnc_password === ''
6847             && (vmobj.hasOwnProperty('vnc_password')
6848             && vmobj.vnc_password !== '')) {
6849 
6850             log.warn('Warning: VNC password was removed for VM '
6851                 + vmobj.uuid + ' but VM needs to be restarted for change to'
6852                 + 'take effect.');
6853         }
6854         if (payload.vnc_password.length > 0
6855             && !vmobj.hasOwnProperty('vnc_password')) {
6856 
6857             log.warn('Warning: VNC password was added to VM '
6858                 + vmobj.uuid + ' but VM needs to be restarted for change to'
6859                 + 'take effect.');
6860         }
6861 
6862         setAttr('vnc-password', 'vnc_password',
6863             new Buffer(payload.vnc_password).toString('base64'));
6864     }
6865     if (payload.hasOwnProperty('spice_password')) {
6866         if (payload.spice_password === ''
6867             && (vmobj.hasOwnProperty('spice_password')
6868             && vmobj.spice_password !== '')) {
6869 
6870             log.warn('Warning: SPICE password was removed for VM '
6871                 + vmobj.uuid + ' but VM needs to be restarted for change to'
6872                 + 'take effect.');
6873         }
6874         if (payload.spice_password.length > 0
6875             && !vmobj.hasOwnProperty('spice_password')) {
6876 
6877             log.warn('Warning: SPICE password was added to VM '
6878                 + vmobj.uuid + ' but VM needs to be restarted for change to'
6879                 + 'take effect.');
6880         }
6881 
6882         setAttr('spice-password', 'spice_password',
6883             new Buffer(payload.spice_password).toString('base64'));
6884     }
6885     if (payload.hasOwnProperty('spice_opts')) {
6886         setAttr('spice-opts', 'spice_opts',
6887             new Buffer(payload.spice_opts).toString('base64'));
6888     }
6889     if (payload.hasOwnProperty('qemu_opts')) {
6890         setAttr('qemu-opts', 'qemu_opts',
6891             new Buffer(payload.qemu_opts).toString('base64'));
6892     }
6893     if (payload.hasOwnProperty('qemu_extra_opts')) {
6894         setAttr('qemu-extra-opts', 'qemu_extra_opts',
6895             new Buffer(payload.qemu_extra_opts).toString('base64'));
6896     }
6897 
6898     // Handle disks
6899     if (payload.hasOwnProperty('disks')
6900         || payload.hasOwnProperty('add_disks')
6901         || payload.hasOwnProperty('update_disks')
6902         || payload.hasOwnProperty('remove_disks')) {
6903 
6904         zcfg = zcfg + buildDiskZonecfg(vmobj, payload);
6905     }
6906 
6907     if (payload.hasOwnProperty('fs_allowed')) {
6908         if (payload.fs_allowed === '') {
6909             zcfg = zcfg + 'clear fs-allowed\n';
6910         } else {
6911             zcfg = zcfg + 'set fs-allowed="' + payload.fs_allowed + '"\n';
6912         }
6913     }
6914 
6915     if (payload.hasOwnProperty('filesystems')
6916         || payload.hasOwnProperty('add_filesystems')
6917         || payload.hasOwnProperty('update_filesystems')
6918         || payload.hasOwnProperty('add_filesystems')) {
6919 
6920         zcfg = zcfg + buildFilesystemZonecfg(vmobj, payload);
6921     }
6922 
6923     zcfg = zcfg + buildNicZonecfg(vmobj, payload);
6924 
6925     return zcfg;
6926 }
6927 
6928 // Checks that QMP is responding to query-status and if so passes the boolean
6929 // value of the hwsetup parameter to the callback.
6930 //
6931 // vmobj must have:
6932 //
6933 // zonepath
6934 //
6935 function checkHWSetup(vmobj, log, callback)
6936 {
6937     var q;
6938     var socket;
6939 
6940     assert(log, 'no logger passed to checkHWSetup()');
6941 
6942     q = new Qmp(log);
6943     socket = vmobj.zonepath + '/root/tmp/vm.qmp';
6944 
6945     q.connect(socket, function (error) {
6946         if (error) {
6947             log.error(error, 'q.connect(): Error: ' + error.message);
6948             callback(error);
6949             return;
6950         }
6951         q.command('query-status', null, function (e, result) {
6952             if (e) {
6953                 log.error(e, 'q.command(query-status): Error: ' + e.message);
6954                 callback(e);
6955                 return;
6956             }
6957             q.disconnect();
6958             callback(null, result.hwsetup ? true : false);
6959             return;
6960         });
6961     });
6962 }
6963 
6964 // cb (if set) will be called with an Error if we can't setup the interval loop
6965 // otherwise when the loop is shut down.
6966 //
6967 // vmobj must have:
6968 //
6969 //  brand
6970 //  state
6971 //  uuid
6972 //  zonepath
6973 //  zoneroot
6974 //
6975 function markProvisionedWhenHWSetup(vmobj, options, cb)
6976 {
6977     var ival_handle;
6978     var log;
6979     var loop_interval = 3; // seconds
6980     var zoneroot;
6981 
6982     log = options.log;
6983     assert(log, 'no logger passed to markProvisionedWenHWSetup()');
6984     assert(vmobj.hasOwnProperty('zonepath'), 'no zonepath in vmobj');
6985 
6986     zoneroot = path.join(vmobj.zoneroot, '/root');
6987 
6988     if (!BRAND_OPTIONS[vmobj.brand].features.wait_for_hwsetup) {
6989         // do nothing for zones where we don't wait for hwsetup
6990         cb(new Error('brand ' + vmobj.brand + ' does not support hwsetup'));
6991         return (null);
6992     }
6993 
6994     // Ensure the dataset doesn't have unsafe links as /var or /var/svc
6995     // Since we're checking the 'file' provision_success, this also guarantees
6996     // that if it already exists, it's not a symlink.
6997     try {
6998         assertSafeZonePath(zoneroot, '/var/svc/provision_success',
6999             {type: 'file', enoent_ok: true});
7000     } catch (e) {
7001         cb(e);
7002         return (null);
7003     }
7004 
7005     if (!options) {
7006         options = {};
7007     }
7008 
7009     // if caller wants they can change the interval
7010     if (options.hasOwnProperty('interval')) {
7011         loop_interval = options.interval;
7012     }
7013 
7014     log.debug('setting hwsetup interval ' + vmobj.uuid);
7015     ival_handle = setInterval(function () {
7016         VM.load(vmobj.uuid, {fields: ['transition_expire', 'uuid'], log: log},
7017             function (err, obj) {
7018 
7019             var timeout_remaining;
7020             var ival = ival_handle;
7021 
7022             function done() {
7023                 if (ival_handle) {
7024                     log.debug('clearing hwsetup interval ' + vmobj.uuid);
7025                     clearInterval(ival);
7026                     ival = null;
7027                 } else {
7028                     log.debug('done but no hwsetup interval ' + vmobj.uuid);
7029                 }
7030             }
7031 
7032             if (err) {
7033                 // If the VM was deleted between calls, nothing much we can do.
7034                 log.error(err, 'Unable to load ' + vmobj.uuid + ' '
7035                     + err.message);
7036                 done();
7037                 cb(err);
7038                 return;
7039             }
7040 
7041             // we only do anything if we're still waiting for provisioning
7042             if (vmobj.state !== 'provisioning') {
7043                 done();
7044                 cb();
7045                 return;
7046             }
7047 
7048             timeout_remaining =
7049                 (Number(obj.transition_expire) - Date.now(0)) / 1000;
7050 
7051             if (timeout_remaining <= 0) {
7052                 // IMPORTANT: this may run multiple times, must be idempotent
7053 
7054                 log.warn('Marking VM ' + vmobj.uuid + ' as "failed" because'
7055                     + ' timeout expired and we are still "provisioning"');
7056                 VM.markVMFailure(vmobj, {log: log}, function (mark_err) {
7057                     log.warn(mark_err, 'zoneinit failed, zone is '
7058                         + 'being stopped for manual investigation.');
7059                     done();
7060                     cb();
7061                 });
7062                 return;
7063             }
7064 
7065             checkHWSetup(vmobj, log, function (check_err, result) {
7066                 if (check_err) {
7067                     log.debug(check_err, 'checkHWSetup Error: '
7068                         + check_err.message);
7069                     return;
7070                 }
7071 
7072                 if (result) {
7073                     log.debug('QMP says VM ' + vmobj.uuid
7074                         + ' completed hwsetup');
7075                     VM.unsetTransition(vmobj, {log: log}, function (unset_err) {
7076                         var provisioning;
7077                         var provision_success;
7078 
7079                         provisioning = path.join(vmobj.zonepath,
7080                             '/root/var/svc/provisioning');
7081                         provision_success = path.join(vmobj.zonepath,
7082                             '/root/var/svc/provision_success');
7083 
7084                         if (unset_err) {
7085                             log.error(unset_err);
7086                         } else {
7087                             log.debug('cleared transition to provisioning on'
7088                                 + ' ' + vmobj.uuid);
7089                         }
7090 
7091                         fs.rename(provisioning, provision_success,
7092                             function (e) {
7093 
7094                             if (e) {
7095                                 if (e.code === 'ENOENT') {
7096                                     log.debug(e);
7097                                 } else {
7098                                     log.error(e);
7099                                 }
7100                             }
7101 
7102                             done();
7103                             cb();
7104                             return;
7105                         });
7106                     });
7107                 }
7108             });
7109         });
7110     }, loop_interval * 1000);
7111 
7112     return (ival_handle);
7113 }
7114 
7115 function archiveVM(uuid, options, callback)
7116 {
7117     var archive_dirname;
7118     var dirmode;
7119     var log;
7120     var patterns_to_archive = [];
7121     var vmobj;
7122 
7123     /*jsl:ignore*/
7124     dirmode = 0755;
7125     /*jsl:end*/
7126 
7127     if (options.hasOwnProperty('log')) {
7128         log = options.log;
7129     } else {
7130         log = VM.log;
7131     }
7132 
7133     log.debug('attempting to archive debug data for VM ' + uuid);
7134 
7135     async.series([
7136         function (cb) {
7137             // ensure directory exists
7138             archive_dirname = path.join('/zones/archive', uuid);
7139 
7140             fs.mkdir(archive_dirname, dirmode, function (e) {
7141                 log.debug(e, 'attempted to create ' + archive_dirname);
7142                 cb(e);
7143                 return;
7144             });
7145         }, function (cb) {
7146             VM.load(uuid, {log: log}, function (err, obj) {
7147                 if (err) {
7148                     cb(err);
7149                     return;
7150                 }
7151                 vmobj = obj;
7152                 cb();
7153             });
7154         }, function (cb) {
7155             // write vmobj to archive
7156             var filename;
7157 
7158             filename = path.join(archive_dirname, 'vm.json');
7159 
7160             fs.writeFile(filename, JSON.stringify(vmobj, null, 2) + '\n',
7161                 function (err, result) {
7162 
7163                 if (err) {
7164                     log.error(err, 'failed to create ' + filename + ': '
7165                         + err.message);
7166                 } else {
7167                     log.info('archived data to ' + filename);
7168                 }
7169 
7170                 cb(); // ignore error
7171             });
7172         }, function (cb) {
7173             var cmdline = '/usr/sbin/zfs list -t all -o name | grep '
7174                 + vmobj.zonename + ' | xargs zfs get -pH all >'
7175                 + path.join(archive_dirname, 'zfs.dump');
7176 
7177             log.debug(cmdline);
7178             exec(cmdline, function (e, stdout, stderr) {
7179                 if (e) {
7180                     e.stdout = stdout;
7181                     e.stderr = stderr;
7182                     log.error({err: e}, 'failed to create '
7183                         + path.join(archive_dirname, 'zfs.dump'));
7184                     cb(e);
7185                     return;
7186                 }
7187                 log.info('archived data to ' + path.join(archive_dirname,
7188                     'zfs.dump'));
7189                 cb();
7190             });
7191         }, function (cb) {
7192             patterns_to_archive.push({
7193                 src: path.join('/etc/zones/', vmobj.zonename + '.xml'),
7194                 dst: path.join(archive_dirname, 'zone.xml')
7195             });
7196             patterns_to_archive.push({
7197                 src: path.join(vmobj.zonepath, 'config'),
7198                 dst: archive_dirname,
7199                 targ: path.join(archive_dirname, 'config')
7200             });
7201             patterns_to_archive.push({
7202                 src: path.join(vmobj.zonepath, 'cores'),
7203                 dst: archive_dirname,
7204                 targ: path.join(archive_dirname, 'cores')
7205             });
7206 
7207             if (vmobj.brand === 'kvm') {
7208                 patterns_to_archive.push({
7209                     src: path.join(vmobj.zonepath, 'root/tmp/vm*.log*'),
7210                     dst: path.join(archive_dirname, 'vmlogs'),
7211                     create_dst_dir: true
7212                 });
7213                 patterns_to_archive.push({
7214                     src: path.join(vmobj.zonepath, 'root/startvm'),
7215                     dst: archive_dirname,
7216                     targ: path.join(archive_dirname, 'startvm')
7217                 });
7218             } else {
7219                 patterns_to_archive.push({
7220                     src: path.join(vmobj.zonepath, 'root/var/svc/log/*'),
7221                     dst: path.join(archive_dirname, 'svclogs'),
7222                     create_dst_dir: true
7223                 });
7224                 patterns_to_archive.push({
7225                     src: path.join(vmobj.zonepath, 'root/var/adm/messages*'),
7226                     dst: path.join(archive_dirname, 'admmsgs'),
7227                     create_dst_dir: true
7228                 });
7229             }
7230 
7231             async.forEachSeries(patterns_to_archive, function (pattern, c) {
7232 
7233                 function cpPattern(p, cp_cb) {
7234                     var cmdline = '/usr/bin/cp -RP ' + p.src + ' ' + p.dst;
7235                     var targ = p.targ || p.dst;
7236 
7237                     log.debug(cmdline);
7238                     exec(cmdline, function (e, stdout, stderr) {
7239                         if (e) {
7240                             e.stdout = stdout;
7241                             e.stderr = stderr;
7242                             log.error({err: e}, 'failed to archive data to '
7243                                 + targ);
7244                         } else {
7245                             log.info('archived data to ' + targ);
7246                         }
7247                         // we don't return errors here because on error copying
7248                         // one pattern we still want to grab the others.
7249                         cp_cb();
7250                     });
7251                 }
7252 
7253                 if (pattern.create_dst_dir) {
7254                     fs.mkdir(pattern.dst, dirmode, function (e) {
7255                         if (!e) {
7256                             log.info('created ' + pattern.dst);
7257                         } else {
7258                             log.error({err: e}, 'failed to create '
7259                                 + pattern.dst);
7260                         }
7261                         cpPattern(pattern, c);
7262                     });
7263                 } else {
7264                     cpPattern(pattern, c);
7265                 }
7266             }, function (e) {
7267                 log.info('finished archiving VM ' + vmobj.uuid);
7268                 cb(e);
7269             });
7270         }
7271     ], function () {
7272         // XXX we ignore errors as failures to archive will not block VM delete.
7273         callback();
7274     });
7275 }
7276 
7277 // vmobj argument should have:
7278 //
7279 // transition_to
7280 // uuid
7281 // zonename
7282 //
7283 exports.markVMFailure = function (vmobj, options, cb)
7284 {
7285     var log;
7286 
7287     // options is optional
7288     if (arguments.length === 2) {
7289         cb = arguments[1];
7290         options = {};
7291     }
7292 
7293     if (!vmobj || !vmobj.hasOwnProperty('uuid')
7294         || !vmobj.hasOwnProperty('zonename')) {
7295 
7296         cb(new Error('markVMFailure needs uuid + zonename'));
7297         return;
7298     }
7299 
7300     ensureLogging(true);
7301     if (options.hasOwnProperty('log')) {
7302         log = options.log;
7303     } else {
7304         log = VM.log.child({action: 'markVMFailure', vm: vmobj.uuid});
7305     }
7306 
7307     function dumpDebugInfo(zonename, callback) {
7308         var errors = {};
7309 
7310         async.series([
7311             function (ptree_cb) {
7312                 // note: if the zone is not running this returns empty but still
7313                 // exits 0
7314                 execFile('/usr/bin/ptree', ['-z', zonename],
7315                     function (ptree_err, ptree_stdout, ptree_stderr) {
7316 
7317                         if (ptree_err) {
7318                             log.error(ptree_err, 'unable to get ptree from '
7319                                 + zonename + ': ' + ptree_stderr);
7320                             errors.ptree_err = ptree_err;
7321                         } else {
7322                             log.warn('processes running in ' + zonename
7323                                 + ' at fail time:\n' + ptree_stdout);
7324                         }
7325 
7326                         ptree_cb(); // don't fail on error here.
7327                     }
7328                 );
7329             }, function (svcs_cb) {
7330                 execFile('/usr/bin/svcs', ['-xv', '-z', zonename],
7331                     function (svcs_err, svcs_stdout, svcs_stderr) {
7332 
7333                         if (svcs_err) {
7334                             log.error(svcs_err, 'unable to get svcs from '
7335                                 + zonename + ': ' + svcs_stderr);
7336                             errors.svcs_err = svcs_err;
7337                         } else {
7338                             log.warn('svcs -xv output for ' + zonename
7339                                 + ' at fail time:\n' + svcs_stdout);
7340                         }
7341 
7342                         svcs_cb(); // don't fail on error here.
7343                     }
7344                 );
7345             }, function (kstat_cb) {
7346                 execFile('/usr/bin/kstat', ['-n', zonename.substr(0, 30)],
7347                     function (kstat_err, kstat_stdout, kstat_stderr) {
7348 
7349                         if (kstat_err) {
7350                             log.error(kstat_err, 'unable to get kstats from '
7351                                 + zonename + ': ' + kstat_stderr);
7352                             errors.kstat_err = kstat_err;
7353                         } else {
7354                             log.warn('kstat output for ' + zonename
7355                                 + ' at fail time:\n' + kstat_stdout);
7356                         }
7357 
7358                         kstat_cb(); // don't fail on error here.
7359                     }
7360                 );
7361             }
7362         ], function () {
7363             callback(errors);
7364         });
7365     }
7366 
7367     dumpDebugInfo(vmobj.zonename, function (debug_err) {
7368         var zcfg;
7369 
7370         // note: we don't treat failure to dump debug info as a fatal error.
7371         log.warn(debug_err, 'zone setup failed, zone is being stopped '
7372             + 'for manual investigation.');
7373 
7374         // Mark the zone as 'failed'
7375         zcfg = 'remove -F attr name=failed; add attr; set name=failed; '
7376             + 'set value="provisioning"; set type=string; end';
7377 
7378         zonecfg(['-u', vmobj.uuid, zcfg], log, function (zonecfg_err, fds) {
7379 
7380             if (zonecfg_err) {
7381                 log.error({err: zonecfg_err, stdout: fds.stdout,
7382                     stderr: fds.stderr}, 'Unable to set failure flag on '
7383                     + vmobj.uuid + ': ' + zonecfg_err.message);
7384             } else {
7385                 log.debug({stdout: fds.stdout, stderr: fds.stderr},
7386                     'set failure flag on ' + vmobj.uuid);
7387             }
7388 
7389             // attempt to remove transition
7390             VM.unsetTransition(vmobj, {log: log}, function (unset_err) {
7391                 if (unset_err) {
7392                     log.error(unset_err);
7393                 }
7394 
7395                 VM.stop(vmobj.uuid, {force: true, log: log},
7396                     function (stop_err) {
7397 
7398                     // only log errors because there's nothing to do
7399 
7400                     if (stop_err) {
7401                         log.error(stop_err, 'failed to stop VM '
7402                             + vmobj.uuid + ': ' + stop_err.message);
7403                     }
7404 
7405                     cb();
7406                 });
7407             });
7408         });
7409     });
7410 };
7411 
7412 function svccfg(zonepath, args, log, callback)
7413 {
7414     var cmd = '/usr/sbin/svccfg';
7415     var exec_options = {};
7416     var zoneroot = path.join(zonepath, '/root');
7417 
7418     assert(log, 'no logger passed to svccfg()');
7419 
7420     try {
7421         assertSafeZonePath(zoneroot, '/etc/svc/repository.db',
7422             {type: 'file', enoent_ok: false});
7423     } catch (e) {
7424         log.error(e, 'Error validating /etc/svc/repository.db: ' + e.message);
7425         callback(e);
7426         return;
7427     }
7428 
7429     exec_options = {
7430         env: {
7431             'SVCCFG_CONFIGD_PATH': '/lib/svc/bin/svc.configd',
7432             'SVCCFG_REPOSITORY':
7433                 path.join(zonepath, 'root', '/etc/svc/repository.db')
7434         }
7435     };
7436 
7437     log.debug({'command': cmd + ' ' + args.join(' '),
7438         'exec_options': exec_options}, 'modifying svc repo in ' + zonepath);
7439     execFile(cmd, args, exec_options, function (error, stdout, stderr) {
7440         if (error) {
7441             callback(error, {'stdout': stdout, 'stderr': stderr});
7442         } else {
7443             callback(null, {'stdout': stdout, 'stderr': stderr});
7444         }
7445     });
7446 }
7447 
7448 // This calls cb() when /var/svc/provisioning is gone. When this calls cb()
7449 // with an Error object, the provision is considered failed so this should
7450 // only happen when something timed out that is unrelated to the user.
7451 //
7452 // This returns a function that can be called with no arguments to cancel
7453 // all timers and actions pending from this function.  It will also then not
7454 // call the cb().
7455 //
7456 // IMPORTANT: this is only exported to be used by vmadmd. Do not use elsewhere!
7457 //
7458 // vmobj fields:
7459 //
7460 //  state
7461 //  transition_expire
7462 //  uuid
7463 //  zonepath
7464 //
7465 exports.waitForProvisioning = function (vmobj, options, cb)
7466 {
7467     var dirname = path.join(vmobj.zonepath, 'root', '/var/svc');
7468     var filename = path.join(dirname, 'provisioning');
7469     var ival_h;
7470     var log;
7471     var timeout;
7472     var timeout_remaining = PROVISION_TIMEOUT; // default to whole thing
7473     var watcher;
7474 
7475     // options is optional
7476     if (arguments.length === 2) {
7477         cb = arguments[1];
7478         options = {};
7479     }
7480 
7481     ensureLogging(true);
7482     if (options.hasOwnProperty('log')) {
7483         log = options.log;
7484     } else {
7485         log = VM.log.child({action: 'waitForProvisioning', vm: vmobj.uuid});
7486     }
7487 
7488     function done() {
7489         if (timeout) {
7490             log.debug('clearing provision timeout for ' + vmobj.uuid);
7491             clearTimeout(timeout);
7492             timeout = null;
7493         }
7494         if (watcher) {
7495             log.debug('closing /var/svc/provisioning watcher for '
7496                 + vmobj.uuid);
7497             watcher.close();
7498             watcher = null;
7499         }
7500         if (ival_h) {
7501             log.debug('closing hwsetup check interval for ' + vmobj.uuid);
7502             clearInterval(ival_h);
7503             ival_h = null;
7504         }
7505     }
7506 
7507     if ((vmobj.state === 'provisioning')
7508         && (vmobj.hasOwnProperty('transition_expire'))) {
7509 
7510         timeout_remaining =
7511             (Number(vmobj.transition_expire) - Date.now(0)) / 1000;
7512 
7513         // Always give it at least 1 second's chance.
7514         if (timeout_remaining < 1) {
7515             timeout_remaining = 1;
7516         }
7517     } else {
7518         // don't know what to do here we're not provisioning.
7519         log.warn('waitForProvisioning called when ' + vmobj.uuid
7520             + ' was not provisioning');
7521         cb();
7522         return (null);
7523     }
7524 
7525     log.debug({
7526         'transition_expire': Number(vmobj.transition_expire),
7527         'now': Date.now(0)
7528     }, 'waiting ' + timeout_remaining + ' sec(s) for provisioning');
7529 
7530     log.debug('setting provision timeout for ' + vmobj.uuid);
7531     timeout = setTimeout(function () {
7532         log.warn('Marking VM ' + vmobj.uuid + ' as a "failure" because we '
7533             + 'hit waitForProvisioning() timeout.');
7534         VM.markVMFailure(vmobj, {log: log}, function (err) {
7535             var errstr = 'timed out waiting for /var/svc/provisioning to move'
7536                 + ' for ' + vmobj.uuid;
7537             if (err) {
7538                 log.warn(err, 'markVMFailure(): ' + err.message);
7539             }
7540             log.error(errstr);
7541             done();
7542             cb(new Error(errstr));
7543         });
7544     }, (timeout_remaining * 1000));
7545 
7546     // this starts a loop that will move provisioning -> provision_success when
7547     // the hardware of the VM has been initialized the first time.
7548     if (BRAND_OPTIONS[vmobj.brand].features.wait_for_hwsetup) {
7549         ival_h = markProvisionedWhenHWSetup(vmobj, {log: log}, function (err) {
7550             if (err) {
7551                 log.error(err, 'error in markProvisionedWhenHWSetup()');
7552             }
7553             done();
7554             cb(err);
7555         });
7556         return (done);
7557     }
7558 
7559     watcher = fs.watch(filename, function (evt, file) {
7560         // We only care about 'rename' which also fires when the file is
7561         // deleted.
7562         log.debug('watcher.event(' + vmobj.uuid + '): ' + evt);
7563         if (evt === 'rename') {
7564             fs.exists(filename, function (exists) {
7565                 if (exists) {
7566                     // somehow we still have /var/svc/provisioning!
7567                     log.warn('Marking VM ' + vmobj.uuid + ' as a "failure"'
7568                         + ' because we still have /var/svc/provisioning after '
7569                         + 'rename');
7570                     VM.markVMFailure(vmobj, {log: log}, function (err) {
7571                         if (err) {
7572                             log.warn(err, 'markVMFailure(): ' + err.message);
7573                         }
7574                         done();
7575                         cb(new Error('/var/svc/provisioning exists after '
7576                             + 'rename!'));
7577                     });
7578                     return;
7579                 }
7580 
7581                 // So long as /var/svc/provisioning is gone, we don't care what
7582                 // replaced it.  Success or failure of user script doesn't
7583                 // matter for the state, it's provisioned now. Caller should
7584                 // now clear the transition.
7585                 done();
7586                 cb();
7587                 return;
7588             });
7589         }
7590     });
7591 
7592     log.debug('created watcher for ' + vmobj.uuid);
7593     return (done);
7594 };
7595 
7596 // create and install a 'joyent' or 'kvm' brand zone.
7597 function installZone(payload, log, callback)
7598 {
7599     var load_fields;
7600     var receiving = false;
7601     var reprovisioning = false;
7602     var vmobj;
7603     var zoneinit = {};
7604 
7605     assert(log, 'no logger passed to installZone()');
7606 
7607     log.debug('installZone()');
7608 
7609     load_fields = [
7610         'brand',
7611         'firewall_enabled',
7612         'missing',
7613         'nics',
7614         'owner_uuid',
7615         'routes',
7616         'state',
7617         'tags',
7618         'transition_to',
7619         'transition_expire',
7620         'uuid',
7621         'zonename',
7622         'zonepath'
7623     ];
7624 
7625     if (payload.reprovisioning) {
7626         log.debug('installZone(): reprovisioning');
7627         reprovisioning = true;
7628     }
7629 
7630     async.series([
7631         function (cb) {
7632 
7633             VM.load(payload.uuid, {fields: load_fields, log: log},
7634                 function (err, obj) {
7635 
7636                 if (err) {
7637                     cb(err);
7638                     return;
7639                 }
7640                 vmobj = obj;
7641                 cb();
7642             });
7643         }, function (cb) {
7644             var thing;
7645             var missing = false;
7646             var msg;
7647             var things = ['datasets', 'filesystems', 'disks'];
7648 
7649             if (vmobj.state === 'receiving') {
7650                 receiving = true;
7651                 msg = 'zone is still missing:';
7652                 for (thing in things) {
7653                     thing = things[thing];
7654                     if (vmobj.missing[thing].length !== 0) {
7655                         msg = msg + ' ' + vmobj.missing[thing].length + ' '
7656                             + thing + ',';
7657                         missing = true;
7658                     }
7659                 }
7660                 msg = rtrim(msg, ',');
7661 
7662                 if (missing) {
7663                     cb(new Error('Unable to complete install for '
7664                         + vmobj.uuid + ' ' + msg));
7665                     return;
7666                 }
7667             }
7668             cb();
7669         }, function (cb) {
7670             // Install the zone.
7671             // This will create the dataset and mark the zone 'installed'.
7672             var args;
7673 
7674             if (reprovisioning) {
7675                 // reprovisioning we do *most* of install, but not this.
7676                 cb();
7677                 return;
7678             }
7679 
7680             args = ['-z', vmobj.zonename, 'install', '-q',
7681                 payload.quota.toString()];
7682 
7683             // For both OS and KVM VMs you can pass an image_uuid at the
7684             // top-level. This will be your zone's root dataset. On KVM the user
7685             // is never exposed to this. It's used there for something like
7686             // SPICE.
7687             if (payload.hasOwnProperty('image_uuid')) {
7688                 args.push('-t', payload.image_uuid, '-x', 'nodataset');
7689             }
7690 
7691             zoneadm(args, log, function (err, fds) {
7692                 if (err) {
7693                     log.error({err: err, stdout: fds.stdout,
7694                         stderr: fds.stderr}, 'zoneadm failed to install: '
7695                         + err.message);
7696                     cb(err);
7697                 } else {
7698                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
7699                         'zoneadm installed zone');
7700                     cb();
7701                 }
7702             });
7703         }, function (cb) {
7704             // Apply compression if set
7705             var args = [];
7706             if (payload.hasOwnProperty('zfs_root_compression')) {
7707                 args = ['set', 'compression='
7708                     + payload.zfs_root_compression, payload.zfs_filesystem];
7709                 zfs(args, log, function (err) {
7710                     cb(err);
7711                 });
7712             } else {
7713                 cb();
7714             }
7715         }, function (cb) {
7716             // Apply recsize if set
7717             var args = [];
7718             if (payload.hasOwnProperty('zfs_root_recsize')) {
7719                 args = ['set', 'recsize=' + payload.zfs_root_recsize,
7720                     payload.zfs_filesystem];
7721                 zfs(args, log, function (err) {
7722                     cb(err);
7723                 });
7724             } else {
7725                 cb();
7726             }
7727         }, function (cb) {
7728             // Some zones can have an additional 'data' dataset delegated to
7729             // them for use in the zone.  This will set that up.  If the option
7730             // is not set, the following does nothing.
7731             if (!receiving && !reprovisioning) {
7732                 createDelegatedDataset(payload, log, function (err) {
7733                     if (err) {
7734                         cb(err);
7735                     } else {
7736                         cb();
7737                     }
7738                 });
7739             } else {
7740                 cb();
7741             }
7742         }, function (cb) {
7743             // Write out the zone's metadata
7744             // Note: we don't do this when receiving because dataset will
7745             // already contain metadata and we don't want to wipe that out.
7746             if (!receiving && !reprovisioning) {
7747                 saveMetadata(payload, log, function (err) {
7748                     if (err) {
7749                         log.error(err, 'unable to save metadata: '
7750                             + err.message);
7751                         cb(err);
7752                     } else {
7753                         cb();
7754                     }
7755                 });
7756             } else {
7757                 cb();
7758             }
7759         }, function (cb) {
7760             // Write out the zone's routes
7761             // Note: we don't do this when receiving because dataset will
7762             // already contain routes and we don't want to wipe that out.
7763             if (!receiving && !reprovisioning) {
7764                 saveRoutes(payload, log, function (err) {
7765                     if (err) {
7766                         log.error(err, 'unable to save routes: '
7767                             + err.message);
7768                         cb(err);
7769                     } else {
7770                         cb();
7771                     }
7772                 });
7773             } else {
7774                 cb();
7775             }
7776         }, function (cb) {
7777             // if we were receiving, we're done receiving now
7778             if (receiving) {
7779                 VM.unsetTransition(vmobj, {log: log}, cb);
7780             } else {
7781                 cb();
7782             }
7783         }, function (cb) {
7784             // var zoneinit is in installZone() scope
7785 
7786             // when receiving zoneinit is never run.
7787             if (receiving) {
7788                 cb();
7789                 return;
7790             }
7791 
7792             getZoneinitJSON(vmobj.zonepath, log, function (zoneinit_err, data) {
7793 
7794                 if (zoneinit_err) {
7795                     // NOTE: not existing is not going to give us a zoneinit_err
7796                     log.warn(zoneinit_err, 'error in getZoneinitJSON');
7797                     cb(zoneinit_err);
7798                     return;
7799                 }
7800 
7801                 if (data) {
7802                     zoneinit = data;
7803                 } else {
7804                     zoneinit = {};
7805                 }
7806 
7807                 cb();
7808             });
7809         }, function (cb) {
7810             // var_svc_provisioning is at installZone() scope
7811 
7812             // If we're not receiving, we're provisioning a new VM and in that
7813             // case we write the /var/svc/provisioning file which should exist
7814             // until something in the zone decides provisioning is complete. At
7815             // that point it will be moved to either:
7816             //
7817             //    /var/svc/provision_success
7818             //    /var/svc/provision_failure
7819             //
7820             // to indicate that the provisioning setup has been completed.
7821 
7822             if (receiving) {
7823                 cb();
7824                 return;
7825             }
7826 
7827             fs.writeFile(path.join(vmobj.zonepath, 'root',
7828                 '/var/svc/provisioning'), '', function (err, result) {
7829 
7830                 if (err) {
7831                     log.error(err, 'failed to create '
7832                         + '/var/svc/provisioning: ' + err.message);
7833                 } else {
7834                     log.debug('created /var/svc/provisioning in '
7835                         + path.join(vmobj.zonepath, 'root'));
7836                 }
7837 
7838                 cb(err);
7839             });
7840         }, function (cb) {
7841             // For joyent and joyent-minimal at least, set the timeout for the
7842             // svc start method to the value specified in the payload, or a
7843             // default.
7844 
7845             var timeout;
7846 
7847             if (BRAND_OPTIONS[vmobj.brand].features.update_mdata_exec_timeout) {
7848 
7849                 if (payload.hasOwnProperty('mdata_exec_timeout')) {
7850                     timeout = payload.mdata_exec_timeout;
7851                 } else {
7852                     timeout = DEFAULT_MDATA_TIMEOUT;
7853                 }
7854 
7855                 svccfg(vmobj.zonepath, [
7856                     '-s', 'svc:/smartdc/mdata:execute',
7857                     'setprop', 'start/timeout_seconds', '=', 'count:', timeout
7858                     ], log, function (error, stdio) {
7859 
7860                     if (error) {
7861                         log.error(error, 'failed to set mdata:exec timeout');
7862                         cb(error);
7863                         return;
7864                     }
7865 
7866                     cb();
7867                 });
7868             } else {
7869                 cb();
7870             }
7871 
7872         }, function (cb) {
7873             // This writes out the 'zoneconfig' file used by zoneinit to root's
7874             // home directory in the zone.
7875             if (! receiving
7876                 && BRAND_OPTIONS[vmobj.brand].features.zoneinit
7877                 && (! zoneinit.hasOwnProperty('features')
7878                 || zoneinit.features.zoneconfig)) {
7879 
7880                 // No 'features' means old dataset.  If we have old dataset or
7881                 // one that really wants a zoneconfig, write it out.
7882 
7883                 writeZoneconfig(payload, log, function (err) {
7884                     cb(err);
7885                 });
7886             } else {
7887                 cb();
7888             }
7889         }, function (cb) {
7890             if (BRAND_OPTIONS[vmobj.brand].features.write_zone_netfiles
7891                 && !receiving) {
7892 
7893                 writeZoneNetfiles(payload, log, function (err) {
7894                     cb(err);
7895                 });
7896             } else {
7897                 cb();
7898             }
7899         }, function (cb) {
7900             if (vmobj.hasOwnProperty('zonepath')
7901                 && BRAND_OPTIONS[vmobj.brand].features.cleanup_dataset
7902                 && !receiving) {
7903 
7904                 cleanupMessyDataset(vmobj.zonepath, vmobj.brand, log,
7905                     function (err) {
7906 
7907                     cb(err);
7908                 });
7909             } else {
7910                 cb();
7911             }
7912         }, function (cb) {
7913             // Firewall data has not changed when reprovisioning, so we don't
7914             // re-run addFirewallData()
7915             if (reprovisioning) {
7916                 cb();
7917                 return;
7918             }
7919 
7920             // Add firewall data if it was included
7921             addFirewallData(payload, vmobj, log, cb);
7922         }, function (cb) {
7923 
7924             var cancel;
7925             var calledback = false;
7926             var prov_wait = true;
7927             // var_svc_provisioning is at installZone() scope
7928 
7929             // The vm is now ready to start, we'll start if autoboot is set. If
7930             // not, we also don't want to wait for 'provisioning'.
7931             if (!payload.autoboot) {
7932                 cb();
7933                 return;
7934             }
7935 
7936             // In these cases we never wait for provisioning -> running
7937             if (payload.nowait || receiving || vmobj.state !== 'provisioning') {
7938                 prov_wait = false;
7939             }
7940 
7941             // most VMs support the /var/svc/provision{ing,_success,_failure}
7942             // files. For those, if !nowait, we wait for the file to change
7943             // from provisioning -> either provision_success, or
7944             // provision_failure.
7945 
7946             if (prov_wait) {
7947                 // wait for /var/svc/provisioning -> provision_success/failure
7948                 cancel = VM.waitForProvisioning(vmobj, {log: log},
7949                     function (err) {
7950 
7951                     log.debug(err, 'waited for provisioning');
7952 
7953                     if (!err) {
7954                         log.info('provisioning complete: '
7955                             + '/var/svc/provisioning is gone');
7956                         // this will clear the provision transition
7957                         VM.unsetTransition(vmobj, {log: log},
7958                             function (unset_err) {
7959 
7960                             if (unset_err) {
7961                                 log.error(unset_err, 'error unsetting '
7962                                     + 'transition: ' + unset_err.message);
7963                             }
7964                             // this and the cb in the VM.start callback might
7965                             // both run if we don't check this.
7966                             if (!calledback) {
7967                                 calledback = true;
7968                                 cb(unset_err);
7969                             }
7970                         });
7971                     } else {
7972                         // failed but might not be able to cb if VM.start's
7973                         // callback already did.
7974                         log.error(err, 'error waiting for provisioning: '
7975                             + err.message);
7976                         // this and the cb in the VM.start callback might
7977                         // both run if we don't check this.
7978                         if (!calledback) {
7979                             calledback = true;
7980                             cb(err);
7981                         }
7982                     }
7983                 });
7984             }
7985 
7986             VM.start(payload.uuid, {}, {log: log}, function (err, res) {
7987                 if (err) {
7988                     // we failed to start so we'll never see provisioning, so
7989                     // cancel that and return the error.
7990                     if (cancel) {
7991                         log.info('cancelling VM.waitForProvisioning');
7992                         cancel();
7993                     }
7994                     // this and the cb in the VM.waitForProvisioning
7995                     // callback might both run if we don't check this.
7996                     if (!calledback) {
7997                         calledback = true;
7998                         cb(err);
7999                     }
8000                     return;
8001                 }
8002                 // if we're waiting for 'provisioning' VM.waitForProvisioning's
8003                 // callback will call cb().  If we're not going to wait, we call
8004                 // it here.
8005                 if (!prov_wait) {
8006                     // this and the cb in the VM.waitForProvisioning
8007                     // callback might both run if we don't check this.
8008                     if (!calledback) {
8009                         calledback = true;
8010                         cb();
8011                     }
8012                 }
8013             });
8014         }], function (error) {
8015             callback(error);
8016         }
8017     );
8018 }
8019 
8020 function getZoneinitJSON(rootpath, log, cb)
8021 {
8022     var filename;
8023     var zoneroot;
8024 
8025     assert(log, 'no logger passed to getZoneinitJSON()');
8026 
8027     zoneroot = path.join('/', rootpath, 'root');
8028     filename = path.join(zoneroot, '/var/zoneinit/zoneinit.json');
8029 
8030     try {
8031         assertSafeZonePath(zoneroot, '/var/zoneinit/zoneinit.json',
8032             {type: 'file', enoent_ok: true});
8033     } catch (e) {
8034         log.error(e, 'Error validating /var/zoneinit/zoneinit.json: '
8035             + e.message);
8036         cb(e);
8037         return;
8038     }
8039 
8040     fs.readFile(filename, function (error, data) {
8041         var zoneinit;
8042 
8043         if (error && (error.code === 'ENOENT')) {
8044             // doesn't exist, leave empty
8045             log.debug('zoneinit.json does not exist.');
8046             cb();
8047         } else if (error) {
8048             // error reading: fail.
8049             cb(error);
8050         } else {
8051             // success try to load json
8052             try {
8053                 zoneinit = JSON.parse(data.toString());
8054                 log.debug({'zoneinit_json': zoneinit},
8055                     'parsed zoneinit.json');
8056                 cb(null, zoneinit);
8057             } catch (e) {
8058                 cb(e);
8059             }
8060         }
8061     });
8062 }
8063 
8064 function getDatasetMountpoint(dataset, log, callback)
8065 {
8066     var args;
8067     var cmd = '/usr/sbin/zfs';
8068     var mountpoint;
8069 
8070     assert(log, 'no logger passed to getDatasetMountpoint()');
8071 
8072     args = ['get', '-H', '-o', 'value', 'mountpoint', dataset];
8073 
8074     log.debug(cmd + ' ' + args.join(' '));
8075     execFile(cmd, args, function (error, stdout, stderr) {
8076         if (error) {
8077             log.error(error, 'zfs get failed with: ' + stderr);
8078             callback(error);
8079         } else {
8080             mountpoint = stdout.replace(/\n/g, '');
8081             log.debug('mountpoint: "' + mountpoint + '"');
8082             callback(null, mountpoint);
8083         }
8084     });
8085 }
8086 
8087 // TODO: pull data out of the massive zfs list we pulled earlier
8088 function checkDatasetProvisionable(payload, log, callback)
8089 {
8090     var dataset;
8091 
8092     assert(log, 'no logger passed to checkDatasetProvisionable()');
8093 
8094     if (BRAND_OPTIONS[payload.brand].features.var_svc_provisioning) {
8095         // when the brand always supports /var/svc/provisioning we don't have to
8096         // worry about the dataset not supporting it.
8097         callback(true);
8098         return;
8099     }
8100 
8101     if (!payload.hasOwnProperty('zpool')
8102         || !payload.hasOwnProperty('image_uuid')) {
8103 
8104         log.error('missing properties required to find dataset: '
8105             + JSON.stringify(payload));
8106         callback(false);
8107         return;
8108     }
8109 
8110     dataset = payload.zpool + '/' + payload.image_uuid;
8111 
8112     getDatasetMountpoint(dataset, log, function (dataset_err, mountpoint) {
8113         if (dataset_err) {
8114             log.error('unable to find mount point for ' + dataset);
8115             callback(false);
8116             return;
8117         }
8118 
8119         getZoneinitJSON(dataset, log, function (zoneinit_err, zoneinit) {
8120             var filename_1_6_x;
8121             var filename_1_8_x;
8122 
8123             if (zoneinit_err) {
8124                 log.error(zoneinit_err, 'getZoneinitJSON() failed, assuming '
8125                     + 'not provisionable.');
8126                 callback(false);
8127                 return;
8128             } else if (!zoneinit) {
8129                 log.debug('no data from getZoneinitJSON(), using {}');
8130                 zoneinit = {};
8131             }
8132 
8133             if (zoneinit.hasOwnProperty('features')) {
8134                 if (zoneinit.features.var_svc_provisioning) {
8135                     log.info('zoneinit.features.var_svc_provisioning is '
8136                         + 'set.');
8137                     callback(true);
8138                     return;
8139                 }
8140                 // we have features but not var_svc_provisioning === true means
8141                 // we can't provision. Fall through and return false.
8142             } else {
8143                 // Didn't load zoneinit features, so check for datasets that
8144                 // have // 04-mdata.sh.  For 1.6.x and earlier datasets this was
8145                 // in /root but in 1.8.0 and 1.8.1 it is in /var/zoneinit.  For
8146                 // 1.8.2 and later we'll not get here as the zoneinit.json will
8147                 // exist and we'll use that.
8148                 filename_1_6_x = path.join(mountpoint, 'root',
8149                     '/root/zoneinit.d/04-mdata.sh');
8150                 filename_1_8_x = path.join(mountpoint, 'root',
8151                     '/var/zoneinit/includes/04-mdata.sh');
8152 
8153                 if (fs.existsSync(filename_1_6_x)) {
8154                     log.info(filename_1_6_x + ' exists');
8155                     callback(true);
8156                     return;
8157                 } else {
8158                     log.debug(filename_1_6_x + ' does not exist');
8159                     if (fs.existsSync(filename_1_8_x)) {
8160                         log.info(filename_1_8_x + ' exists');
8161                         callback(true);
8162                         return;
8163                     } else {
8164                         log.debug(filename_1_8_x + ' does not exist');
8165                         // this was our last chance.
8166                         // Fall through and return false.
8167                     }
8168                 }
8169             }
8170 
8171             callback(false);
8172             return;
8173         });
8174     });
8175 }
8176 
8177 // create and install a 'joyent' or 'kvm' brand zone.
8178 function createZone(payload, log, callback)
8179 {
8180     var create_time;
8181     var n;
8182     var now = new Date;
8183     var primary_found;
8184     var provision_timeout = PROVISION_TIMEOUT;
8185     var t;
8186     var vm_version;
8187     var zcfg;
8188 
8189     assert(log, 'no logger passed to createZone()');
8190 
8191     log.debug('createZone()');
8192 
8193     payload.zfs_filesystem = payload.zpool + '/' + payload.zonename;
8194     payload.zonepath = '/' + payload.zfs_filesystem;
8195 
8196     // we add create-timestamp in all cases except where we're receiving since
8197     // in that case we want to preserve the original create-timestamp.
8198     if (!payload.hasOwnProperty('transition')
8199         || (payload.transition.transition !== 'receiving')
8200         || !payload.hasOwnProperty('create_timestamp')) {
8201 
8202         create_time = now.toISOString();
8203     } else {
8204         create_time = payload.create_timestamp;
8205     }
8206 
8207     // we add vm-version (property v) in all cases except where we're receiving
8208     // since in that case we want to preserve the original version.
8209     if (!payload.hasOwnProperty('transition')
8210         || (payload.transition.transition !== 'receiving')
8211         || !payload.hasOwnProperty('v')) {
8212 
8213         vm_version = 1;
8214     } else {
8215         vm_version = payload.v;
8216     }
8217 
8218     // set the properties that can't be updated later here.
8219     zcfg = 'create -b\n'
8220         + 'set zonepath=' + payload.zonepath + '\n'
8221         + 'set brand=' + payload.brand + '\n'
8222         + 'set uuid=' + payload.uuid + '\n'
8223         + 'set ip-type=exclusive\n'
8224         + 'add attr; set name="vm-version"; set type=string; set value="'
8225         + vm_version + '"; end\n'
8226         + 'add attr; set name="create-timestamp"; set type=string; set value="'
8227         + create_time + '"; end\n';
8228 
8229     if (payload.hasOwnProperty('transition')) {
8230         // IMPORTANT: this is for internal use only and should not be documented
8231         // as an option for create's payload.  Used for receive.
8232         t = payload.transition;
8233         zcfg = zcfg
8234             + buildTransitionZonecfg(t.transition, t.target, t.timeout) + '\n';
8235     } else {
8236         // Assume this is really a new VM, add transition called 'provisioning'
8237         // only if the machine is going to be booting.
8238         if (!payload.hasOwnProperty('autoboot') || payload.autoboot) {
8239             zcfg = zcfg + buildTransitionZonecfg('provisioning', 'running',
8240                 provision_timeout * 1000) + '\n';
8241         }
8242     }
8243 
8244     // We call the property 'dataset-uuid' even though the property name is
8245     // image_uuid because existing VMs in the wild will be using dataset-uuid
8246     // already, and we are the point where the image becomes a dataset anyway.
8247     if (payload.hasOwnProperty('image_uuid')) {
8248         zcfg = zcfg + 'add attr; set name="dataset-uuid"; set type=string; '
8249             + 'set value="' + payload.image_uuid + '"; end\n';
8250     }
8251 
8252     if (BRAND_OPTIONS[payload.brand].features.use_vm_autoboot) {
8253         // we always set autoboot=false for VM zones, since we want vmadmd to
8254         // boot them and not the zones tools.  Use vm-autoboot to control VMs
8255         zcfg = zcfg + 'set autoboot=false\n';
8256     }
8257 
8258     // ensure that we have a primary nic, even if one wasn't specified
8259     if (payload.hasOwnProperty('add_nics') && payload.add_nics.length != 0) {
8260         primary_found = false;
8261 
8262         for (n in payload.add_nics) {
8263             n = payload.add_nics[n];
8264             if (n.hasOwnProperty('primary') && n.primary) {
8265                 primary_found = true;
8266                 break;
8267             }
8268         }
8269         if (!primary_found) {
8270             payload.add_nics[0].primary = true;
8271         }
8272     }
8273 
8274     // Passing an empty first parameter here, tells buildZonecfgUpdate that
8275     // we're talking about a new machine.
8276     zcfg = zcfg + buildZonecfgUpdate({}, payload, log);
8277 
8278     // include the zonecfg in the debug output to help track down problems.
8279     log.debug(zcfg);
8280 
8281     // send the zonecfg data we just generated as a file to zonecfg,
8282     // this will create the zone.
8283     zonecfgFile(zcfg, ['-z', payload.zonename], log, function (err, fds) {
8284         if (err || payload.create_only) {
8285             log.error({err: err, zcfg: zcfg, stdout: fds.stdout,
8286                 stderr: fds.stderr}, 'failed to modify zonecfg');
8287             callback(err);
8288         } else {
8289             log.debug({stdout: fds.stdout, stderr: fds.stderr},
8290                 'modified zonecfg');
8291             installZone(payload, log, callback);
8292         }
8293     });
8294 }
8295 
8296 function normalizeNics(payload, vmobj)
8297 {
8298     var n;
8299     var nic;
8300 
8301     // ensure all NICs being created/added have a MAC, remove the 'index' if it
8302     // is passed (that's deprecated), rename 'interface' to 'physical'.
8303     if (payload.hasOwnProperty('add_nics')) {
8304         for (n in payload.add_nics) {
8305             if (payload.add_nics.hasOwnProperty(n)) {
8306                 nic = payload.add_nics[n];
8307 
8308                 if (!nic.hasOwnProperty('mac')
8309                     && !nic.hasOwnProperty('vrrp_vrid')) {
8310                     nic.mac = generateMAC();
8311                 }
8312                 delete nic.index;
8313                 if (nic.hasOwnProperty('interface')) {
8314                     nic.physical = nic.interface;
8315                     delete nic.interface;
8316                 }
8317 
8318                 // nics.*.primary only supports true value, unset false. We also
8319                 // handle the case here why they used the deprecated '1' value.
8320                 // We will have already warned them, but still support for now.
8321                 if (nic.hasOwnProperty('primary')) {
8322                     if (nic.primary || nic.primary === '1'
8323                         || nic.primary === 1) {
8324 
8325                         nic.primary = true;
8326                     } else {
8327                         delete nic.primary;
8328                     }
8329                 }
8330             }
8331         }
8332     }
8333 }
8334 
8335 /*
8336  * This is called for both create and update, everything here should be safe for
8337  * both.  The vmobj will be set if it's an update.
8338  *
8339  */
8340 function normalizePayload(payload, vmobj, log, callback)
8341 {
8342     var action;
8343     var allowed;
8344     var brand;
8345     var property;
8346 
8347     assert(log, 'no logger passed to normalizePayload()');
8348 
8349     // fix type of arguments that should be numbers, do this here so that fixing
8350     // memory works correctly later using math.
8351     for (property in payload) {
8352         if (payload.hasOwnProperty(property)) {
8353             if (PAYLOAD_PROPERTIES.hasOwnProperty(property)
8354                 && PAYLOAD_PROPERTIES[property].type === 'integer'
8355                 && payload[property] !== undefined) {
8356                 // undefined is a special case since we use that to unset props
8357 
8358                 payload[property] = Number(payload[property]);
8359                 if (isNaN(payload[property])) {
8360                     callback(new Error('Invalid value for ' + property + ': '
8361                         + JSON.stringify(payload[property]) + ':'
8362                         + typeof (payload[property])));
8363                     return;
8364                 }
8365             }
8366         }
8367     }
8368 
8369     if (payload.hasOwnProperty('quota') && payload.quota === undefined) {
8370         // when unsetting quota we set to 0
8371         payload.quota = 0;
8372     }
8373 
8374     if (vmobj) {
8375         /* update */
8376         fixPayloadMemory(payload, vmobj, log);
8377         action = 'update';
8378     } else {
8379         /* this also calls fixPayloadMemory() */
8380         applyZoneDefaults(payload, log);
8381 
8382         if (payload.hasOwnProperty('create_only')
8383             && payload.transition.transition === 'receiving') {
8384 
8385             action = 'receive';
8386         } else {
8387             action = 'create';
8388         }
8389     }
8390 
8391     // Should always have a brand after we applied defaults.
8392     if (vmobj && vmobj.hasOwnProperty('brand')) {
8393         brand = vmobj.brand;
8394     } else if (payload.hasOwnProperty('brand')) {
8395         brand = payload.brand;
8396     } else {
8397         callback(new Error('Unable to determine brand for payload'));
8398         return;
8399     }
8400 
8401     // Historically we supported dataset_uuid for joyent+joyent-minimal and
8402     // zone_dataset_uuid for kvm. Now we just support image_uuid so give a
8403     // deprecation warning and translate if old version specified. This needs
8404     // to happen before VM.validate because image_uuid is required for most
8405     // VMs.
8406     allowed = BRAND_OPTIONS[brand].allowed_properties;
8407     if ((allowed.hasOwnProperty('dataset_uuid')
8408             && payload.hasOwnProperty('dataset_uuid'))
8409         || (allowed.hasOwnProperty('zone_dataset_uuid')
8410             && payload.hasOwnProperty('zone_dataset_uuid'))) {
8411 
8412         property = (payload.hasOwnProperty('dataset_uuid') ? 'dataset_uuid'
8413             : 'zone_dataset_uuid');
8414 
8415         if (payload.hasOwnProperty('image_uuid')) {
8416             log.warn('DEPRECATED option ' + property + ' found, '
8417                 + 'ignoring. In the future use image_uuid only.');
8418         } else {
8419             log.warn('DEPRECATED option ' + property + ' found, '
8420                 + 'ignoring. In the future use image_uuid instead.');
8421             payload.image_uuid = payload[property];
8422             delete payload.dataset_uuid;
8423         }
8424     }
8425 
8426     // after ZoneDefaults have been applied, we should always have zone. Now
8427     // we validate the payload properties and remove any that are invalid. If
8428     // there are bad values we'll just fail.
8429     VM.validate(brand, action, payload, {log: log}, function (errors) {
8430         var bad_prop;
8431         var compound_props = ['disks', 'nics', 'filesystems'];
8432         var matches;
8433         var obj;
8434         var prop;
8435 
8436         if (errors) {
8437             if (errors.hasOwnProperty('bad_brand')) {
8438                 callback(new Error('Invalid brand while validating payload: '
8439                     + JSON.stringify(brand)));
8440                 return;
8441             }
8442             if (errors.bad_values.length > 0) {
8443                 callback(new Error('Invalid value(s) for: '
8444                     + errors.bad_values.join(',')));
8445                 return;
8446             }
8447             if (errors.missing_properties.length > 0) {
8448                 callback(new Error('Missing required properties: '
8449                     + errors.missing_properties.join(',')));
8450                 return;
8451             }
8452             for (bad_prop in errors.bad_properties) {
8453                 bad_prop = errors.bad_properties[bad_prop];
8454                 log.warn('Warning, invalid ' + action + ' property: ['
8455                     + bad_prop + '] removing from payload.');
8456 
8457                 // for bad properties like nics.*.allow_unfiltered_promisc we
8458                 // need to remove it from add_nics, update_nics, etc.
8459                 for (prop in compound_props) {
8460                     prop = compound_props[prop];
8461 
8462                     matches = new RegExp('^' + prop
8463                         + '\\.\\*\\.(.*)$').exec(bad_prop);
8464                     if (matches) {
8465                         if (payload.hasOwnProperty(prop)) {
8466                             for (obj in payload[prop]) {
8467                                 delete payload[prop][obj][matches[1]];
8468                             }
8469                         }
8470                         if (payload.hasOwnProperty('add_' + prop)) {
8471                             for (obj in payload['add_' + prop]) {
8472                                 delete payload['add_' + prop][obj][matches[1]];
8473                             }
8474                         }
8475                         if (payload.hasOwnProperty('update_' + prop)) {
8476                             for (obj in payload['update_' + prop]) {
8477                                 delete payload['update_'
8478                                     + prop][obj][matches[1]];
8479                             }
8480                         }
8481                     }
8482                 }
8483 
8484                 delete payload[bad_prop];
8485             }
8486         }
8487 
8488         // By the time we got here all the properties in the payload are allowed
8489 
8490         // Now we make sure we've got a zonename (use uuid if not already set)
8491         if (!payload.hasOwnProperty('zonename')
8492             || payload.zonename === undefined) {
8493 
8494             payload.zonename = payload.uuid;
8495         }
8496 
8497         // You use 'disks' and 'nics' when creating, but the underlying
8498         // functions expect add_disks and add_nics, so we rename them now that
8499         // we've confirmed we've got the correct thing for this action.
8500         if (payload.hasOwnProperty('disks')) {
8501             if (payload.hasOwnProperty('add_disks')) {
8502                 callback(new Error('Cannot specify both "disks" and '
8503                     + '"add_disks"'));
8504                 return;
8505             }
8506             payload.add_disks = payload.disks;
8507             delete payload.disks;
8508         }
8509         if (payload.hasOwnProperty('nics')) {
8510             if (payload.hasOwnProperty('add_nics')) {
8511                 callback(new Error('Cannot specify both "nics" and '
8512                     + '"add_nics"'));
8513                 return;
8514             }
8515             payload.add_nics = payload.nics;
8516             delete payload.nics;
8517         }
8518         if (payload.hasOwnProperty('filesystems')) {
8519             if (payload.hasOwnProperty('add_filesystems')) {
8520                 callback(new Error('Cannot specify both "filesystems" and '
8521                     + '"add_filesystems"'));
8522                 return;
8523             }
8524             payload.add_filesystems = payload.filesystems;
8525             delete payload.filesystems;
8526         }
8527 
8528         // if there's a zfs_root_* and no zfs_data_*, normally the properties
8529         // would fall through, we don't want that.
8530         if (payload.hasOwnProperty('zfs_root_compression')
8531             && !payload.hasOwnProperty('zfs_data_compression')) {
8532 
8533             if (vmobj && vmobj.hasOwnProperty('zfs_data_compression')) {
8534                 // keep existing value.
8535                 payload.zfs_data_compression = vmobj.zfs_data_compression;
8536             } else {
8537                 // keep default value.
8538                 payload.zfs_data_compression = 'off';
8539             }
8540         }
8541         if (payload.hasOwnProperty('zfs_root_recsize')
8542             && !payload.hasOwnProperty('zfs_data_recsize')) {
8543 
8544             if (vmobj && vmobj.hasOwnProperty('zfs_data_recsize')) {
8545                 // keep existing value.
8546                 payload.zfs_data_recsize = vmobj.zfs_data_recsize;
8547             } else {
8548                 // keep default value.
8549                 payload.zfs_data_recsize = 131072;
8550             }
8551         }
8552 
8553         // this will ensure we've got a MAC, etc.
8554         normalizeNics(payload, vmobj);
8555 
8556         // Fix types for boolean fields in case someone put in 'false'/'true'
8557         // instead of false/true
8558         for (property in payload) {
8559             if (payload.hasOwnProperty(property)) {
8560                 if (PAYLOAD_PROPERTIES.hasOwnProperty(property)
8561                     && PAYLOAD_PROPERTIES[property].type === 'boolean') {
8562 
8563                     payload[property] = fixBooleanLoose(payload[property]);
8564                 }
8565             }
8566         }
8567 
8568         // We used to support zfs_storage_pool_name, but zpool is better.
8569         if (payload.hasOwnProperty('zfs_storage_pool_name')) {
8570             if (payload.hasOwnProperty('zpool')) {
8571                 log.warn('DEPRECATED option zfs_storage_pool_name found, '
8572                     + 'ignoring!');
8573             } else {
8574                 log.warn('DEPRECATED option zfs_storage_pool_name found, '
8575                     + 'replacing with zpool!');
8576                 payload.zpool = payload.zfs_storage_pool_name;
8577                 delete payload.zfs_storage_pool_name;
8578             }
8579         }
8580 
8581         // When creating a VM with SPICE you need the image_uuid, if you don't
8582         // pass that, we'll remove any SPICE options.
8583         if (action === 'create'
8584             && !payload.hasOwnProperty('image_uuid')) {
8585 
8586             if (payload.hasOwnProperty('spice_opts')
8587                 || payload.hasOwnProperty('spice_password')
8588                 || payload.hasOwnProperty('spice_port')) {
8589 
8590                 log.warn('Creating with SPICE options requires '
8591                     + 'image_uuid, REMOVING spice_*');
8592                 delete payload.spice_opts;
8593                 delete payload.spice_password;
8594                 delete payload.spice_port;
8595             }
8596         }
8597 
8598         checkPayloadProperties(payload, vmobj, log, function (e) {
8599             if (e) {
8600                 callback(e);
8601             } else {
8602                 callback();
8603             }
8604         });
8605     });
8606 }
8607 
8608 function buildTransitionZonecfg(transition, target, timeout)
8609 {
8610     var cmdline;
8611 
8612     cmdline = 'add attr; set name=transition; set value="'
8613         + transition + ':' + target + ':' + (Date.now(0) + timeout).toString()
8614         + '"; set type=string; end';
8615 
8616     return cmdline;
8617 }
8618 
8619 // vmobj should have:
8620 //
8621 //  uuid
8622 //  transition_to (if set)
8623 //
8624 exports.unsetTransition = function (vmobj, options, callback)
8625 {
8626     var log;
8627 
8628     // options is optional
8629     if (arguments.length === 2) {
8630         callback = arguments[1];
8631         options = {};
8632     }
8633 
8634     ensureLogging(true);
8635     if (options.hasOwnProperty('log')) {
8636         log = options.log;
8637     } else {
8638         log = VM.log.child({action: 'unsetTransition', vm: vmobj.uuid});
8639     }
8640 
8641     zonecfg(['-u', vmobj.uuid, 'remove -F attr name=transition'], log,
8642         function (err, fds) {
8643 
8644         if (err) {
8645             // log at info because this might be because already removed
8646             log.info({err: err, stdout: fds.stdout, stderr: fds.stderr},
8647                 'unable to remove transition for zone ' + vmobj.uuid);
8648         } else {
8649             log.debug({stdout: fds.stdout, stderr: fds.stderr},
8650                 'removed transition for zone ' + vmobj.uuid);
8651         }
8652 
8653         zonecfg(['-u', vmobj.uuid, 'info attr name=transition'], log,
8654             function (info_err, info_fds) {
8655 
8656             if (info_err) {
8657                 log.error({err: info_err, stdout: info_fds.stdout,
8658                     stderr: info_fds.stderr},
8659                     'failed to confirm transition removal');
8660                 callback(info_err);
8661                 return;
8662             }
8663 
8664             if (info_fds.stdout !== 'No such attr resource.\n') {
8665                 log.error({stdout: info_fds.stdout, stderr: info_fds.stderr},
8666                     'unknown error checking transition after removal');
8667                 callback(new Error('transition does not appear to have been '
8668                     + 'removed zonecfg said: ' + JSON.stringify(info_fds)));
8669                 return;
8670             }
8671 
8672             // removed the transition, now attempt to start if we're rebooting.
8673             if (vmobj.transition_to && vmobj.transition_to === 'start') {
8674                 log.debug('VM ' + vmobj.uuid + ' was stopping for reboot, '
8675                     + 'transitioning to start.');
8676                 VM.start(vmobj.uuid, {}, {log: log}, function (e) {
8677                     if (e) {
8678                         log.error(e, 'failed to start when clearing '
8679                             + 'transition');
8680                     }
8681                     callback();
8682                 });
8683             } else {
8684                 callback();
8685             }
8686         });
8687     });
8688 };
8689 
8690 //
8691 // vmobj fields used:
8692 //
8693 // transition
8694 // uuid
8695 //
8696 function setTransition(vmobj, transition, target, timeout, log, callback)
8697 {
8698     assert(log, 'no logger passed to setTransition()');
8699 
8700     if (!timeout) {
8701         callback(new Error('setTransition() requires timeout argument.'));
8702         return;
8703     }
8704 
8705     async.series([
8706         function (cb) {
8707             // unset an existing transition
8708             if (vmobj.hasOwnProperty('transition')) {
8709                 VM.unsetTransition(vmobj, {log: log}, cb);
8710             } else {
8711                 cb();
8712             }
8713         }, function (cb) {
8714             var zcfg;
8715 
8716             zcfg = buildTransitionZonecfg(transition, target, timeout);
8717             zonecfg(['-u', vmobj.uuid, zcfg], log, function (err, fds) {
8718                 if (err) {
8719                     log.error({err: err, stdout: fds.stdout,
8720                         stderr: fds.stderr}, 'failed to set transition='
8721                         + transition + ' for VM ' + vmobj.uuid);
8722                 } else {
8723                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
8724                         'set transition=' + transition + ' for vm '
8725                         + vmobj.uuid);
8726                 }
8727 
8728                 cb(err);
8729             });
8730         }
8731     ], function (error) {
8732         callback(error);
8733     });
8734 }
8735 
8736 function receiveVM(json, log, callback)
8737 {
8738     var payload = {};
8739 
8740     assert(log, 'no logger passed to receiveVM()');
8741 
8742     try {
8743         payload = JSON.parse(json);
8744     } catch (e) {
8745         callback(e);
8746         return;
8747     }
8748 
8749     payload.create_only = true;
8750 
8751     // adding transition here is considered to be *internal only* not for
8752     // consumer use and not to be documented as a property you can use with
8753     // create.
8754     payload.transition =
8755         {'transition': 'receiving', 'target': 'stopped', 'timeout': 86400};
8756 
8757     // We delete tags and metadata here becasue this exists in the root
8758     // dataset which we will be copying, so it would be duplicated here.
8759     delete payload.customer_metadata;
8760     delete payload.internal_metadata;
8761     delete payload.tags;
8762 
8763     // On receive we need to make sure that we don't create new disks so we
8764     // mark them all as nocreate. We also can't set the block_size of imported
8765     // volumes, so we remove that.
8766     if (payload.hasOwnProperty('disks')) {
8767         var disk_idx;
8768 
8769         for (disk_idx in payload.disks) {
8770             payload.disks[disk_idx].nocreate = true;
8771 
8772             if (payload.disks[disk_idx].image_uuid) {
8773                 delete payload.disks[disk_idx].block_size;
8774             }
8775         }
8776     }
8777 
8778     VM.create(payload, {log: log}, function (err, result) {
8779         if (err) {
8780             callback(err);
8781         }
8782 
8783         // don't include the special transition in the payload we write out.
8784         delete payload.transition;
8785 
8786         fs.writeFile('/etc/zones/' + payload.uuid + '-receiving.json',
8787             JSON.stringify(payload, null, 2), function (e) {
8788 
8789             if (e) {
8790                 callback(e);
8791                 return;
8792             }
8793 
8794             // ready for datasets
8795             callback(null, result);
8796         });
8797     });
8798 }
8799 
8800 function receiveStdinChunk(type, log, callback)
8801 {
8802     var child;
8803     var chunk_name = '';
8804     var chunk_size = 0;
8805     var json = '';
8806     var remaining = '';
8807 
8808     assert(log, 'no logger passed to receiveStdinChunk()');
8809 
8810     /*
8811      * XXX
8812      *
8813      * node 0.6.x removed support for arbitrary file descriptors which
8814      * means we can only handle stdin for now since we need to pass this
8815      * descriptor directly to the child.  0.8.x is supposed to reintroduce
8816      * this functionality.  When we do, this should be changed to open
8817      * the file and set fd to the descriptor, and we should be able to
8818      * get rid of vmunbundle.
8819      *
8820      */
8821 
8822     if (type === 'JSON') {
8823         log.info('/usr/vm/sbin/vmunbundle json');
8824         child = spawn('/usr/vm/sbin/vmunbundle', ['json'],
8825             {customFds: [0, -1, -1]});
8826     } else if (type === 'DATASET') {
8827         log.info('/usr/vm/sbin/vmunbundle dataset');
8828         child = spawn('/usr/vm/sbin/vmunbundle', ['dataset'],
8829             {customFds: [0, -1, -1]});
8830     } else {
8831         callback(new Error('Unsupported chunk type ' + type));
8832     }
8833 
8834     child.stderr.on('data', function (data) {
8835         var idx;
8836         var line;
8837         var matches;
8838 
8839         remaining += data.toString();
8840 
8841         idx = remaining.indexOf('\n');
8842         while (idx > -1) {
8843             line = trim(remaining.substring(0, idx));
8844             remaining = remaining.substring(idx + 1);
8845 
8846             log.debug('VMUNBUNDLE: ' + line);
8847             matches = line.match(/Size: ([\d]+)/);
8848             if (matches) {
8849                 chunk_size = Number(matches[1]);
8850             }
8851             matches = line.match(/Name: \[(.*)\]/);
8852             if (matches) {
8853                 chunk_name = matches[1];
8854             }
8855 
8856             idx = remaining.indexOf('\n');
8857         }
8858     });
8859 
8860     child.stdout.on('data', function (data) {
8861         json += data.toString();
8862         log.debug('json size is ' + json.length);
8863     });
8864 
8865     child.on('close', function (code) {
8866         log.debug('vmunbundle process exited with code ' + code);
8867         if (code === 3) {
8868             log.debug('vmbundle: end of bundle.');
8869             callback(null, 'EOF');
8870             return;
8871         } else if (code !== 0) {
8872             callback(new Error('vmunbundle exited with code ' + code));
8873             return;
8874         }
8875 
8876         // if it was a dataset, we've now imported it.
8877         // if it was json, we've now got it in the json var.
8878 
8879         if (type === 'DATASET') {
8880             log.info('Imported dataset ' + chunk_name);
8881             // delete 'sending' snapshot
8882             zfs(['destroy', '-F', chunk_name + '@sending'], log,
8883                 function (err, fds) {
8884                     if (err) {
8885                         log.warn(err, 'Failed to destroy ' + chunk_name
8886                             + '@sending: ' + err.message);
8887                     }
8888                     callback();
8889                 }
8890             );
8891         } else if (type === 'JSON' && chunk_name === 'JSON'
8892             && json.length <= chunk_size && json.length > 0) {
8893 
8894             receiveVM(json, log, function (e, result) {
8895                 if (e) {
8896                     callback(e);
8897                     return;
8898                 }
8899                 log.info('Receive returning: ' + JSON.stringify(result));
8900                 callback(null, result);
8901             });
8902         } else {
8903             log.debug('type: [' + type + ']');
8904             log.debug('chunk_name: [' + chunk_name + ']');
8905             log.debug('chunk_size: [' + chunk_size + ']');
8906             log.debug('json.length: [' + json.length + ']');
8907             log.warn('Failed to get ' + type + '!');
8908             callback(new Error('Failed to get ' + type + '!'));
8909         }
8910     });
8911 }
8912 
8913 exports.receive = function (target, options, callback)
8914 {
8915     var log;
8916 
8917     // options is optional
8918     if (arguments.length === 2) {
8919         callback = arguments[1];
8920         options = {};
8921     }
8922 
8923     ensureLogging(true);
8924 
8925     // We don't know anything about this VM yet, so we don't create a
8926     // VM.log.child.
8927     if (options.hasOwnProperty('log')) {
8928         log = options.log;
8929     } else {
8930         log = VM.log;
8931     }
8932 
8933     log.info('Receiving VM from: ' + JSON.stringify(target));
8934 
8935     if (target.hasOwnProperty('host') && target.hasOwnProperty('port')) {
8936         // network receive not yet supported either.
8937         callback(new Error('cannot receive from ' + JSON.stringify(target)));
8938         return;
8939     } else if (typeof (target) !== 'string' || target !== '-') {
8940         callback(new Error('cannot receive from ' + JSON.stringify(target)));
8941         return;
8942     }
8943 
8944     receiveStdinChunk('JSON', log, function (error, result) {
8945         var eof = false;
8946 
8947         if (error) {
8948             callback(error);
8949             return;
8950         }
8951         if (result && result === 'EOF') {
8952             callback(new Error('unable to find JSON in stdin.'));
8953         } else if (result && result.hasOwnProperty('uuid')) {
8954             // VM started receive, now need datasets
8955 
8956             // We have JSON, so we can log better now if we need one
8957             if (!options.hasOwnProperty('log')) {
8958                 log = VM.log.child({action: 'receive', vm: result.uuid});
8959             }
8960 
8961             log.info('Receiving VM ' + result.uuid);
8962             log.debug('now looking for datasets');
8963 
8964             async.whilst(
8965                 function () { return !eof; },
8966                 function (cb) {
8967                     receiveStdinChunk('DATASET', log, function (err, res) {
8968                         if (err) {
8969                             cb(err);
8970                             return;
8971                         }
8972                         if (res === 'EOF') {
8973                             eof = true;
8974                         }
8975                         cb();
8976                     });
8977                 }, function (err) {
8978                     if (err) {
8979                         callback(err);
8980                         return;
8981                     }
8982                     // no error so we read all the datasets, try an install.
8983                     log.info('receive calling VM.install: ' + eof);
8984                     VM.install(result.uuid, {log: log}, function (e) {
8985                         if (e) {
8986                             log.warn(e, 'couldn\'t install VM: '
8987                                 + e.message);
8988                         }
8989                         callback(e, result);
8990                     });
8991                 }
8992             );
8993         } else {
8994             callback(new Error('unable to receive JSON'));
8995         }
8996     });
8997 };
8998 
8999 exports.reprovision = function (uuid, payload, options, callback)
9000 {
9001     var log;
9002     var provision_timeout = PROVISION_TIMEOUT;
9003     var set_transition = false;
9004     var snapshot;
9005     var vmobj;
9006 
9007     // options is optional
9008     if (arguments.length === 3) {
9009         callback = arguments[2];
9010         options = {};
9011     }
9012 
9013     ensureLogging(true);
9014     if (options.hasOwnProperty('log')) {
9015         log = options.log;
9016     } else {
9017         log = VM.log.child({action: 'reprovision', vm: uuid});
9018     }
9019 
9020     log.info('Reprovisioning VM ' + uuid + ', original payload:\n'
9021             + JSON.stringify(payload, null, 2));
9022 
9023     async.waterfall([
9024         function (cb) {
9025             VM.load(uuid, {
9026                 fields: [
9027                     'brand',
9028                     'datasets',
9029                     'hostname',
9030                     'nics',
9031                     'quota',
9032                     'state',
9033                     'uuid',
9034                     'zfs_filesystem',
9035                     'zone_state',
9036                     'zonename',
9037                     'zonepath',
9038                     'zpool'
9039                 ],
9040                 log: log
9041             }, function (err, obj) {
9042                 if (err) {
9043                     cb(err);
9044                     return;
9045                 }
9046                 vmobj = obj;
9047                 log.debug('Loaded VM is: ' + JSON.stringify(vmobj, null, 2));
9048                 cb();
9049             });
9050         }, function (cb) {
9051             if (BRAND_OPTIONS[vmobj.brand].hasOwnProperty('features')
9052                 && BRAND_OPTIONS[vmobj.brand].features.reprovision
9053                 && BRAND_OPTIONS[vmobj.brand].features.brand_install_script) {
9054 
9055                 cb();
9056             } else {
9057                 cb(new Error('brand "' + vmobj.brand + '" does not yet support'
9058                     + ' reprovision'));
9059             }
9060         }, function (cb) {
9061             // only support image_uuid at top level (for non-KVM currently)
9062             if (!payload.hasOwnProperty('image_uuid')) {
9063                 cb(new Error('payload is missing image_uuid'));
9064             } else {
9065                 cb();
9066             }
9067         }, function (cb) {
9068             if (vmobj.hasOwnProperty('datasets') && vmobj.datasets.length > 1) {
9069                 cb(new Error('cannot support reprovision with multiple '
9070                     + 'delegated datasets'));
9071                 return;
9072             } else if (vmobj.hasOwnProperty('datasets')
9073                 && vmobj.datasets.length === 1
9074                 && vmobj.datasets[0] !== vmobj.zfs_filesystem + '/data') {
9075 
9076                 cb(new Error('cannot support reprovision with non-standard "'
9077                     + vmobj.datasets[0] + '" dataset'));
9078                 return;
9079             }
9080             cb();
9081         }, function (cb) {
9082             // TODO: change here when we support zvols/KVM, add size
9083             // & change type
9084 
9085             validateImage({
9086                 type: 'zone-dataset',
9087                 uuid: payload.image_uuid,
9088                 zpool: vmobj.zpool
9089             }, log, function (e) {
9090                 cb(e);
9091             });
9092         }, function (cb) {
9093             // ensure we're stopped before reprovision starts
9094             if (vmobj.zone_state !== 'installed') {
9095                 VM.stop(uuid, {log: log}, function (e) {
9096                     if (e) {
9097                         log.error(e, 'unable to stop VM ' + uuid + ': '
9098                             + e.message);
9099                     }
9100                     cb(e);
9101                 });
9102             } else {
9103                 cb();
9104             }
9105         }, function (cb) {
9106             // Set transition to provisioning now, we're going for it.
9107             setTransition(vmobj, 'provisioning', 'running',
9108                 (provision_timeout * 1000), log, function (err) {
9109                     if (err) {
9110                         cb(err);
9111                     } else {
9112                         set_transition = true;
9113                         cb();
9114                     }
9115                 });
9116         }, function (cb) {
9117             // we validated any delegated dataset above, so we just need to
9118             // remove the 'zoned' flag if we've got one.
9119             if (!vmobj.hasOwnProperty('datasets')
9120                 || vmobj.datasets.length === 0) {
9121 
9122                 cb();
9123                 return;
9124             }
9125             zfs(['set', 'zoned=off', vmobj.datasets[0]], log,
9126                 function (err, fds) {
9127 
9128                 if (err) {
9129                     log.error({err: err, stdout: fds.stdout,
9130                         stderr: fds.stderr}, 'Unable to turn off "zoned" for '
9131                         + vmobj.datasets[0]);
9132                 }
9133                 cb(err);
9134             });
9135         }, function (cb) {
9136             // if we have a delegated dataset, rename zones/<uuid>/data
9137             //     -> zones/<uuid>-reprovisioning-data
9138             if (!vmobj.hasOwnProperty('datasets')
9139                 || vmobj.datasets.length === 0) {
9140 
9141                 cb();
9142                 return;
9143             }
9144             zfs(['rename', '-f', vmobj.datasets[0], vmobj.zfs_filesystem
9145                 + '-reprovisioning-data'], log, function (err, fds) {
9146 
9147                 if (err) {
9148                     log.error({err: err, stdout: fds.stdout,
9149                         stderr: fds.stderr}, 'Unable to (temporarily) rename '
9150                         + vmobj.datasets[0]);
9151                 }
9152                 cb(err);
9153             });
9154         }, function (cb) {
9155             // unmount <zonepath>/cores so dataset is not busy
9156             zfs(['umount', vmobj.zonepath + '/cores'], log,
9157                 function (err, fds) {
9158 
9159                 if (err) {
9160                     if (trim(fds.stderr).match(/not a mountpoint$/)) {
9161                         log.info('ignoring failure to umount cores which '
9162                             + 'wasn\'t mounted');
9163                         cb();
9164                         return;
9165                     } else {
9166                         log.error({err: err, stdout: fds.stdout,
9167                             stderr: fds.stderr}, 'Unable to umount '
9168                             + vmobj.zonepath + '/cores');
9169                     }
9170                 }
9171                 cb(err);
9172             });
9173         }, function (cb) {
9174             // rename <zfs_filesystem> dataset out of the way
9175             zfs(['rename', '-f', vmobj.zfs_filesystem, vmobj.zfs_filesystem
9176                 + '-reprovisioning-root'], log, function (err, fds) {
9177 
9178                 if (err) {
9179                     log.error({err: err, stdout: fds.stdout,
9180                         stderr: fds.stderr}, 'Unable to (temporarily) rename '
9181                         + vmobj.zfs_filesystem);
9182                 }
9183                 cb(err);
9184             });
9185         }, function (cb) {
9186             var snapname = vmobj.zpool + '/' + payload.image_uuid + '@final';
9187 
9188             // ensure we've got our snapshot
9189             zfs(['get', '-Ho', 'value', 'type', snapname], log,
9190                 function (err, fds) {
9191 
9192                 if (!err) {
9193                     // snapshot already exists, use it
9194                     log.debug('snapshot "' + snapname + '" exists');
9195                     snapshot = snapname;
9196                     cb();
9197                     return;
9198                 }
9199 
9200                 if (fds.stderr.match(/dataset does not exist/)) {
9201                     // we'll use a different one. (falls throught to next func)
9202                     cb();
9203                 } else {
9204                     cb(err);
9205                 }
9206             });
9207         }, function (cb) {
9208             var snapname;
9209 
9210             if (snapshot) {
9211                 // already know which one to use, don't create one
9212                 cb();
9213                 return;
9214             }
9215 
9216             snapname = vmobj.zpool + '/' + payload.image_uuid
9217                 + '@' + vmobj.uuid;
9218 
9219             // ensure we've got a snapshot
9220             zfs(['get', '-Ho', 'value', 'type', snapname], log,
9221                 function (err, fds) {
9222 
9223                 if (!err) {
9224                     // snapshot already exists, use it
9225                     log.debug('snapshot "' + snapname + '" exists');
9226                     snapshot = snapname;
9227                     cb();
9228                     return;
9229                 }
9230 
9231                 if (fds.stderr.match(/dataset does not exist/)) {
9232                     zfs(['snapshot', snapname], log, function (e, snap_fds) {
9233                         if (e) {
9234                             e.stdout = snap_fds.stdout;
9235                             e.stderr = snap_fds.stderr;
9236                             log.error(e, 'Failed to create snapshot: '
9237                                 + e.message);
9238                         } else {
9239                             log.debug('created snapshot "' + snapname + '"');
9240                             snapshot = snapname;
9241                         }
9242                         cb(e);
9243                     });
9244                 } else {
9245                     cb(err);
9246                     return;
9247                 }
9248             });
9249         }, function (cb) {
9250             var args;
9251 
9252             // clone the new image creating a new dataset for zoneroot
9253             assert(snapshot);
9254 
9255             args = ['clone'];
9256             if (vmobj.hasOwnProperty('quota') && vmobj.quota > 0) {
9257                 args.push('-o');
9258                 args.push('quota=' + vmobj.quota + 'G');
9259             }
9260             args.push(snapshot);
9261             args.push(vmobj.zfs_filesystem);
9262 
9263             zfs(args, log, function (err, fds) {
9264                 if (err) {
9265                     log.error({err: err, stdout: fds.stdout,
9266                         stderr: fds.stderr}, 'Unable to create new clone of '
9267                         + payload.image_uuid);
9268                 }
9269                 cb(err);
9270             });
9271         }, function (cb) {
9272             var cmd;
9273 
9274             // copy zones/<uuid>-reprovisioning-root/config to
9275             // zones/<uuid>/config so we keep metadata and ipf rules.
9276             try {
9277                 fs.mkdirSync(vmobj.zonepath + '/config');
9278             } catch (e) {
9279                 if (e.code !== 'EEXIST') {
9280                     e.message = 'Unable to recreate ' + vmobj.zonepath
9281                         + '/config: ' + e.message;
9282                     cb(e);
9283                     return;
9284                 }
9285             }
9286 
9287             cmd = 'cp -pPR '
9288                 + vmobj.zonepath + '-reprovisioning-root/config/* '
9289                 + vmobj.zonepath + '/config/';
9290 
9291             log.debug(cmd);
9292             exec(cmd, function (error, stdout, stderr) {
9293                 log.debug({'stdout': stdout, 'stderr': stderr}, 'cp results');
9294                 if (error) {
9295                     error.stdout = stdout;
9296                     error.stderr = stderr;
9297                     cb(error);
9298                     return;
9299                 } else {
9300                     cb();
9301                 }
9302             });
9303         }, function (cb) {
9304             // destroy <zonepath>-reprovisioning-root, since it's no longer used
9305             zfs(['destroy', '-r', vmobj.zfs_filesystem
9306                 + '-reprovisioning-root'], log, function (err, fds) {
9307 
9308                 if (err) {
9309                     log.error({err: err, stdout: fds.stdout,
9310                         stderr: fds.stderr}, 'Unable to destroy '
9311                         + vmobj.zfs_filesystem + '-reprovisioning-root: '
9312                         + err.message);
9313                 }
9314                 cb(err);
9315             });
9316         }, function (cb) {
9317             // remount /zones/<uuid>/cores
9318             zfs(['mount', vmobj.zpool + '/cores/' + uuid], log,
9319                 function (err, fds) {
9320 
9321                 if (err) {
9322                     log.error({err: err, stdout: fds.stdout,
9323                         stderr: fds.stderr}, 'Unable to mount ' + vmobj.zonepath
9324                         + '/cores: ' + err.message);
9325                 }
9326                 cb(err);
9327             });
9328         }, function (cb) {
9329             var args = ['-r', '-R', vmobj.zonepath, '-z', vmobj.zonename];
9330             var cmd = BRAND_OPTIONS[vmobj.brand].features.brand_install_script;
9331 
9332             // We run the brand's install script here with the -r flag which
9333             // tells it to do everything that's relevant to reprovision.
9334 
9335             log.debug(cmd + ' ' + args.join(' '));
9336             execFile(cmd, args, function (error, stdout, stderr) {
9337                 var new_err;
9338 
9339                 if (error) {
9340                     new_err = new Error('Error running brand install script '
9341                         + cmd);
9342                     // error's message includes stderr.
9343                     log.error({err: error, stdout: stdout},
9344                         'brand install script exited with code ' + error.code);
9345                     cb(new_err);
9346                 } else {
9347                     log.debug(cmd + ' stderr:\n' + stderr);
9348                     cb();
9349                 }
9350             });
9351         }, function (cb) {
9352             // rename zones/<uuid>-reprovision-data -> zones/<uuid>/data
9353             if (!vmobj.hasOwnProperty('datasets')
9354                 || vmobj.datasets.length === 0) {
9355 
9356                 cb();
9357                 return;
9358             }
9359             zfs(['rename', '-f', vmobj.zfs_filesystem + '-reprovisioning-data',
9360                 vmobj.datasets[0]], log, function (err, fds) {
9361 
9362                 if (err) {
9363                     log.error({err: err, stdout: fds.stdout,
9364                         stderr: fds.stderr}, 'Unable to (temporarily) rename '
9365                         + vmobj.zfs_filesystem);
9366                 }
9367                 cb(err);
9368             });
9369         }, function (cb) {
9370             // set zoned=on for zones/<uuid>/data
9371             if (!vmobj.hasOwnProperty('datasets')
9372                 || vmobj.datasets.length === 0) {
9373 
9374                 cb();
9375                 return;
9376             }
9377             zfs(['set', 'zoned=on', vmobj.datasets[0]], log,
9378                 function (err, fds) {
9379 
9380                 if (err) {
9381                     log.error({err: err, stdout: fds.stdout,
9382                         stderr: fds.stderr}, 'Unable to set "zoned" for: '
9383                         + vmobj.datasets[0]);
9384                 }
9385                 cb(err);
9386             });
9387         }, function (cb) {
9388             // update zone's image_uuid field
9389             var zcfg = 'select attr name=dataset-uuid; set value="'
9390                 + payload.image_uuid + '"; end';
9391             zonecfg(['-u', uuid, zcfg], log, function (err, fds) {
9392                 if (err) {
9393                     log.error({err: err, stdout: fds.stdout,
9394                         stderr: fds.stderr}, 'unable to set image_uuid on VM '
9395                         + uuid);
9396                 }
9397                 cb(err);
9398             });
9399         }, function (cb) {
9400             var p = {
9401                 autoboot: true,
9402                 reprovisioning: true,
9403                 uuid: uuid,
9404                 zonename: vmobj.zonename,
9405                 zonepath: vmobj.zonepath
9406             };
9407 
9408             // NOTE: someday we could allow mdata_exec_timeout in the original
9409             // payload to reprovision and then pass it along here.
9410 
9411             // other fields used by installZone()
9412             [
9413                 'dns_domain',
9414                 'hostname',
9415                 'quota',
9416                 'resolvers',
9417                 'tmpfs',
9418                 'zfs_filesystem',
9419                 'zfs_root_compression',
9420                 'zfs_root_recsize'
9421             ].forEach(function (k) {
9422                 if (vmobj.hasOwnProperty(k)) {
9423                     p[k] = vmobj[k];
9424                 }
9425             });
9426 
9427             // nics needs to be called add_nics here
9428             if (vmobj.hasOwnProperty('nics')) {
9429                 p.add_nics = vmobj.nics;
9430             }
9431 
9432             installZone(p, log, function (err) {
9433                 log.debug(err, 'ran installZone() for reprovision');
9434                 cb(err);
9435             });
9436         }
9437     ], function (err) {
9438         if (err && set_transition) {
9439             // remove transition now, if we failed.
9440             VM.unsetTransition(vmobj, {log: log}, function () {
9441                 // err here is original err, we ignore failure to unset because
9442                 // nothing we can do about that..
9443                 callback(err);
9444             });
9445         } else {
9446             callback(err);
9447         }
9448     });
9449 };
9450 
9451 exports.install = function (uuid, options, callback)
9452 {
9453     var log;
9454 
9455     // options is optional
9456     if (arguments.length === 2) {
9457         callback = arguments[1];
9458         options = {};
9459     }
9460 
9461     ensureLogging(true);
9462     if (options.hasOwnProperty('log')) {
9463         log = options.log;
9464     } else {
9465         log = VM.log.child({action: 'install', vm: uuid});
9466     }
9467 
9468     log.info('Installing VM ' + uuid);
9469 
9470     fs.readFile('/etc/zones/' + uuid + '-receiving.json',
9471         function (err, data) {
9472             var payload;
9473 
9474             if (err) {
9475                 callback(err);
9476                 return;
9477             }
9478 
9479             try {
9480                 payload = JSON.parse(data.toString());
9481             } catch (e) {
9482                 callback(e);
9483                 return;
9484             }
9485 
9486             // installZone takes a payload
9487             installZone(payload, log, callback);
9488         }
9489     );
9490 
9491 };
9492 
9493 function getAllDatasets(vmobj)
9494 {
9495     var datasets = [];
9496     var disk;
9497 
9498     if (vmobj.hasOwnProperty('zfs_filesystem')) {
9499         datasets.push(vmobj.zfs_filesystem);
9500     }
9501 
9502     for (disk in vmobj.disks) {
9503         disk = vmobj.disks[disk];
9504         if (disk.hasOwnProperty('zfs_filesystem')) {
9505             datasets.push(disk.zfs_filesystem);
9506         }
9507     }
9508 
9509     return datasets;
9510 }
9511 
9512 //
9513 // Headers are 512 bytes and look like:
9514 //
9515 // MAGIC-VMBUNDLE\0
9516 // <VERSION>\0 -- ASCII #s
9517 // <CHECKSUM>\0 -- ASCII (not yet used)
9518 // <OBJ-NAME>\0 -- max length: 256
9519 // <OBJ-SIZE>\0 -- ASCII # of bytes
9520 // <PADDED-SIZE>\0 -- ASCII # of bytes, must be multiple of 512
9521 // ...\0
9522 //
9523 function chunkHeader(name, size, padding)
9524 {
9525     var header = new Buffer(512);
9526     var pos = 0;
9527 
9528     header.fill(0);
9529     pos += addString(header, 'MAGIC-VMBUNDLE', pos);
9530     pos += addString(header, sprintf('%d', 1), pos);
9531     pos += addString(header, 'CHECKSUM', pos);
9532     pos += addString(header, name, pos);
9533     pos += addString(header, sprintf('%d', size), pos);
9534     pos += addString(header, sprintf('%d', size + padding), pos);
9535 
9536     return (header);
9537 }
9538 
9539 // add the string to buffer at pos, returning pos of new end of the buffer.
9540 function addString(buf, str, pos)
9541 {
9542     var len = str.length;
9543     buf.write(str, pos);
9544     return (len + 1);
9545 }
9546 
9547 function sendJSON(target, json, log, cb)
9548 {
9549     var header;
9550     var pad;
9551     var padding = 0;
9552 
9553     assert(log, 'no logger passed for sendJSON()');
9554 
9555     if (target === 'stdout') {
9556         if ((json.length % 512) != 0) {
9557             padding = 512 - (json.length % 512);
9558         }
9559         header = chunkHeader('JSON', json.length, padding);
9560         process.stdout.write(header);
9561         process.stdout.write(json, 'ascii');
9562         if (padding > 0) {
9563             pad = new Buffer(padding);
9564             pad.fill(0);
9565             process.stdout.write(pad);
9566         }
9567         cb();
9568     } else {
9569         log.error('Don\'t know how to send JSON to '
9570             + JSON.stringify(target));
9571         cb(new Error('Don\'t know how to send JSON to '
9572             + JSON.stringify(target)));
9573     }
9574 }
9575 
9576 function sendDataset(target, dataset, log, callback)
9577 {
9578     var header;
9579 
9580     assert(log, 'no logger passed for sendDataset()');
9581 
9582     if (target === 'stdout') {
9583 
9584         async.series([
9585             function (cb) {
9586                 // delete any existing 'sending' snapshot
9587                 zfs(['destroy', '-F', dataset + '@sending'], log,
9588                     function (err, fds) {
9589                         // We don't expect this to succeed, since that means
9590                         // something left an @sending around. Warn if succeeds.
9591                         if (!err) {
9592                             log.warn('Destroyed pre-existing ' + dataset
9593                                 + '@sending');
9594                         }
9595                         cb();
9596                     }
9597                 );
9598             }, function (cb) {
9599                 zfs(['snapshot', dataset + '@sending'], log,
9600                     function (err, fds) {
9601 
9602                     cb(err);
9603                 });
9604             }, function (cb) {
9605                 header = chunkHeader(dataset, 0, 0);
9606                 process.stdout.write(header);
9607                 cb();
9608             }, function (cb) {
9609                 var child;
9610 
9611                 child = spawn('/usr/sbin/zfs',
9612                     ['send', '-p', dataset + '@sending'],
9613                     {customFds: [-1, 1, -1]});
9614                 child.stderr.on('data', function (data) {
9615                     var idx;
9616                     var lines = trim(data.toString()).split('\n');
9617 
9618                     for (idx in lines) {
9619                         log.debug('zfs send: ' + trim(lines[idx]));
9620                     }
9621                 });
9622                 child.on('close', function (code) {
9623                     log.debug('zfs send process exited with code '
9624                         + code);
9625                     cb();
9626                 });
9627             }, function (cb) {
9628                 zfs(['destroy', '-F', dataset + '@sending'], log,
9629                     function (err, fds) {
9630                         if (err) {
9631                             log.warn(err, 'Unable to destroy ' + dataset
9632                                 + '@sending: ' + err.message);
9633                         }
9634                         cb(err);
9635                     }
9636                 );
9637             }
9638         ], function (err) {
9639             if (err) {
9640                 log.error(err, 'Failed to send dataset: ' + err.message);
9641             } else {
9642                 log.info('Successfully sent dataset');
9643             }
9644             callback(err);
9645         });
9646     } else {
9647         log.error('Don\'t know how to send datasets to '
9648             + JSON.stringify(target));
9649         callback(new Error('Don\'t know how to send datasets to '
9650             + JSON.stringify(target)));
9651     }
9652 }
9653 
9654 exports.send = function (uuid, target, options, callback)
9655 {
9656     var datasets;
9657     var log;
9658     var vmobj;
9659 
9660     // options is optional
9661     if (arguments.length === 3) {
9662         callback = arguments[2];
9663         options = {};
9664     }
9665 
9666     ensureLogging(true);
9667     if (options.hasOwnProperty('log')) {
9668         log = options.log;
9669     } else {
9670         log = VM.log.child({action: 'send', vm: uuid});
9671     }
9672 
9673     target = 'stdout';
9674 
9675     log.info('Sending VM ' + uuid + ' to: ' + JSON.stringify(target));
9676     async.series([
9677         function (cb) {
9678             // make sure we *can* send first, to avoid wasting cycles
9679             if (target === 'stdout' && tty.isatty(1)) {
9680                 log.error('Cannot send VM to a TTY.');
9681                 cb(new Error('Cannot send VM to a TTY.'));
9682             } else {
9683                 cb();
9684             }
9685         }, function (cb) {
9686             // NOTE: for this load we always load all fields, because we need
9687             // to send them all to the target machine.
9688             VM.load(uuid, {log: log}, function (err, obj) {
9689                 if (err) {
9690                     cb(err);
9691                 } else {
9692                     vmobj = obj;
9693                     cb();
9694                 }
9695             });
9696         }, function (cb) {
9697             datasets = getAllDatasets(vmobj);
9698             if (datasets.length < 1) {
9699                 log.error('Cannot send VM with no datasets.');
9700                 cb(new Error('VM has no datasets.'));
9701             } else {
9702                 cb();
9703             }
9704         }, function (cb) {
9705             if (vmobj.state !== 'stopped') {
9706                 // In this case we need to stop it and make sure it stopped.
9707                 VM.stop(uuid, {log: log}, function (e) {
9708                     if (e) {
9709                         log.error(e, 'unable to stop VM ' + uuid + ': '
9710                             + e.message);
9711                         cb(e);
9712                         return;
9713                     }
9714                     VM.load(uuid, {fields: ['zone_state', 'uuid'], log: log},
9715                         function (error, obj) {
9716 
9717                         if (error) {
9718                             log.error(error, 'unable to reload VM ' + uuid
9719                                 + ': ' + error.message);
9720                             return;
9721                         }
9722                         if (obj.zone_state !== 'installed') {
9723                             log.error('after stop attempt, state is '
9724                                 + obj.zone_state + ' != installed');
9725                             cb(new Error('state after stopping is '
9726                                 + obj.zone_state + ' != installed'));
9727                             return;
9728                         }
9729                         cb();
9730                     });
9731                 });
9732             } else {
9733                 // already stopped, good to go!
9734                 cb();
9735             }
9736         }, function (cb) {
9737             // Clean up trash left from broken datasets (see OS-388)
9738             try {
9739                 fs.unlinkSync(vmobj.zonepath + '/SUNWattached.xml');
9740             } catch (err) {
9741                 // DO NOTHING, this file shouldn't have existed anyway.
9742             }
9743             try {
9744                 fs.unlinkSync(vmobj.zonepath + '/SUNWdetached.xml');
9745             } catch (err) {
9746                 // DO NOTHING, this file shouldn't have existed anyway.
9747             }
9748             cb();
9749         }, function (cb) {
9750             // send JSON
9751             var json = JSON.stringify(vmobj, null, 2) + '\n';
9752             sendJSON(target, json, log, cb);
9753         }, function (cb) {
9754             // send datasets
9755             async.forEachSeries(datasets, function (ds, c) {
9756                 sendDataset(target, ds, log, c);
9757             }, function (e) {
9758                 if (e) {
9759                     log.error('Failed to send datasets');
9760                 }
9761                 cb(e);
9762             });
9763         }
9764     ], function (err) {
9765         callback(err);
9766     });
9767 };
9768 
9769 exports.create = function (payload, options, callback)
9770 {
9771     var log;
9772 
9773     // options is optional
9774     if (arguments.length === 2) {
9775         callback = arguments[1];
9776         options = {};
9777     }
9778 
9779     ensureLogging(true);
9780 
9781     if (options.hasOwnProperty('log')) {
9782         log = options.log;
9783     } else {
9784         // default to VM.log until we have a uuid, then we'll switch.
9785         log = VM.log;
9786     }
9787 
9788     log.info('Creating VM, original payload:\n'
9789         + JSON.stringify(payload, null, 2));
9790 
9791     async.waterfall([
9792         function (cb) {
9793             // We get a UUID first so that we can attach as many log messages
9794             // as possible to this uuid.  Since we don't have a UUID here, we
9795             // send VM.log as the logger.  We'll switch to a log.child as soon
9796             // as we have uuid.
9797             createZoneUUID(payload, log, function (e, uuid) {
9798                 // either payload will have .uuid or we'll return error here.
9799                 cb(e);
9800             });
9801         }, function (cb) {
9802             // If we got here, payload now has .uuid and we can start logging
9803             // messages with that uuid if we didn't already have a logger.
9804             if (!options.hasOwnProperty('log')) {
9805                 log = VM.log.child({action: 'create', vm: payload.uuid});
9806             }
9807             cb();
9808         }, function (cb) {
9809             normalizePayload(payload, null, log, function (err) {
9810                 if (err) {
9811                     log.error(err, 'Failed to validate payload: '
9812                         + err.message);
9813                 } else {
9814                     log.debug('normalized payload:\n'
9815                         + JSON.stringify(payload, null, 2));
9816                 }
9817                 cb(err);
9818             });
9819         }, function (cb) {
9820             checkDatasetProvisionable(payload, log, function (provisionable) {
9821                 if (!provisionable) {
9822                     log.error('checkDatasetProvisionable() says dataset is '
9823                         + 'unprovisionable');
9824                     cb(new Error('provisioning dataset ' + payload.image_uuid
9825                         + ' with brand ' + payload.brand
9826                         + ' is not supported'));
9827                     return;
9828                 }
9829                 cb();
9830             });
9831         }, function (cb) {
9832             if (BRAND_OPTIONS[payload.brand].features.type === 'KVM') {
9833                 createVM(payload, log, function (error, result) {
9834                     if (error) {
9835                         cb(error);
9836                     } else {
9837                         cb(null, {'uuid': payload.uuid,
9838                             'zonename': payload.zonename});
9839                     }
9840                 });
9841             } else {
9842                 createZone(payload, log, function (error, result) {
9843                     if (error) {
9844                         cb(error);
9845                     } else {
9846                         cb(null, {'uuid': payload.uuid,
9847                             'zonename': payload.zonename});
9848                     }
9849                 });
9850             }
9851         }
9852     ], function (err, obj) {
9853         callback(err, obj);
9854     });
9855 };
9856 
9857 // delete a zvol
9858 function deleteVolume(volume, log, callback)
9859 {
9860     var args;
9861     var origin;
9862 
9863     assert(log, 'no logger passed to deleteVolume()');
9864 
9865     if (volume.missing) {
9866         // this volume doesn't actually exist, so skip trying to delete.
9867         log.info('volume ' + volume.path + ' doesn\'t exist, skipping '
9868             + 'deletion');
9869         callback();
9870         return;
9871     }
9872 
9873     async.series([
9874         function (cb) {
9875             args = ['get', '-Ho', 'value', 'origin', volume.zfs_filesystem];
9876             zfs(args, log, function (err, fds) {
9877                 if (err && fds.stderr.match('dataset does not exist')) {
9878                     log.info('volume ' + volume.path + ' doesn\'t exist, '
9879                         + 'skipping deletion');
9880                     cb();
9881                 } else {
9882                     origin = trim(fds.stdout);
9883                     log.info('found origin "' + origin + '"');
9884                     cb(err);
9885                 }
9886             });
9887         }, function (cb) {
9888             // use recursive delete to handle possible snapshots on volume
9889             args = ['destroy', '-rF', volume.zfs_filesystem];
9890             zfs(args, log, function (err, fds) {
9891                 // err will be non-null if something broke
9892                 cb(err);
9893             });
9894         }, function (cb) {
9895             // we never delete an @final snapshot, that's the one from recv
9896             // that imgadm left around for us on purpose.
9897             if (!origin || origin.length < 1 || origin == '-'
9898                 || origin.match('@final')) {
9899 
9900                 cb();
9901                 return;
9902             }
9903             args = ['destroy', '-rF', origin];
9904             zfs(args, log, function (err, fds) {
9905                 // err will be non-null if something broke
9906                 cb(err);
9907             });
9908         }
9909     ], function (err) {
9910         callback(err);
9911     });
9912 }
9913 
9914 function deleteZone(uuid, log, callback)
9915 {
9916     var load_fields;
9917     var vmobj;
9918 
9919     assert(log, 'no logger passed to deleteZone()');
9920 
9921     load_fields = [
9922         'archive_on_delete',
9923         'disks',
9924         'uuid',
9925         'zonename'
9926     ];
9927 
9928     async.series([
9929         function (cb) {
9930             VM.load(uuid, {fields: load_fields, log: log}, function (err, obj) {
9931                 if (err) {
9932                     cb(err);
9933                     return;
9934                 }
9935                 vmobj = obj;
9936                 cb();
9937             });
9938         }, function (cb) {
9939             log.debug('archive_on_delete is set to '
9940                 + !!vmobj.archive_on_delete);
9941             if (!vmobj.archive_on_delete) {
9942                 cb();
9943                 return;
9944             }
9945             archiveVM(vmobj.uuid, log, function () {
9946                 cb();
9947             });
9948         // TODO: replace these next two with VM.stop(..{force: true} ?
9949         }, function (cb) {
9950             log.debug('setting autoboot=false');
9951             zonecfg(['-u', uuid, 'set autoboot=false'], log, function (e, fds) {
9952                 if (e) {
9953                     log.warn({err: e, stdout: fds.stdout, stderr: fds.stderr},
9954                         'Error setting autoboot=false');
9955                 } else {
9956                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
9957                         'set autoboot=false');
9958                 }
9959                 cb();
9960             });
9961         }, function (cb) {
9962             log.debug('halting zone');
9963             zoneadm(['-u', uuid, 'halt', '-X'], log, function (e, fds) {
9964                 if (e) {
9965                     log.warn({err: e, stdout: fds.stdout, stderr: fds.stderr},
9966                         'Error halting zone');
9967                 } else {
9968                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
9969                         'halted zone');
9970                 }
9971                 cb();
9972             });
9973         }, function (cb) {
9974             log.debug('uninstalling zone');
9975             zoneadm(['-u', uuid, 'uninstall', '-F'], log, function (e, fds) {
9976                 if (e) {
9977                     log.warn({err: e, stdout: fds.stdout, stderr: fds.stderr},
9978                         'Error uninstalling zone: ' + e.message);
9979                 } else {
9980                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
9981                         'uninstalled zone');
9982                 }
9983                 cb();
9984             });
9985         }, function (cb) {
9986             function loggedDeleteVolume(volume, callbk) {
9987                 return deleteVolume(volume, log, callbk);
9988             }
9989 
9990             if (vmobj && vmobj.hasOwnProperty('disks')) {
9991                 async.forEachSeries(vmobj.disks, loggedDeleteVolume,
9992                     function (err) {
9993                         if (err) {
9994                             log.error(err, 'Unknown error deleting volumes: '
9995                                 + err.message);
9996                             cb(err);
9997                         } else {
9998                             log.info('successfully deleted volumes');
9999                             cb();
10000                         }
10001                     }
10002                 );
10003             } else {
10004                 log.debug('skipping volume destruction for diskless '
10005                     + vmobj.uuid);
10006                 cb();
10007             }
10008         }, function (cb) {
10009             if (vmobj.zonename) {
10010                 log.debug('deleting zone');
10011                 // XXX for some reason -u <uuid> doesn't work with delete
10012                 zonecfg(['-z', vmobj.zonename, 'delete', '-F'], log,
10013                     function (e, fds) {
10014 
10015                     if (e) {
10016                         log.warn({err: e, stdout: fds.stdout,
10017                             stderr: fds.stderr}, 'Error deleting VM');
10018                     } else {
10019                         log.debug({stdout: fds.stdout, stderr: fds.stderr},
10020                             'deleted VM ' + uuid);
10021                     }
10022                     cb();
10023                 });
10024             } else {
10025                 cb();
10026             }
10027         }, function (cb) {
10028             VM.load(uuid, {fields: ['uuid'], log: log, missing_ok: true},
10029                 function (err, obj) {
10030 
10031                 var gone = /^zoneadm:.*: No such zone configured/;
10032                 if (err && err.message.match(gone)) {
10033                     // the zone is gone, that's good.
10034                     log.debug('confirmed VM is gone.');
10035                     cb();
10036                 } else if (err) {
10037                     // there was a non-expected error.
10038                     cb(err);
10039                 } else {
10040                     // the VM still exists!
10041                     err = new Error('VM still exists after delete.');
10042                     err.code = 'EEXIST';
10043                     cb(err);
10044                 }
10045             });
10046         }, function (cb) {
10047             // delete the incoming payload if it exists
10048             fs.unlink('/etc/zones/' + vmobj.uuid + '-receiving.json',
10049                 function (e) {
10050                     // we can't do anyhing if this fails other than log
10051                     if (e && e.code !== 'ENOENT') {
10052                         log.warn(e, 'Failed to delete ' + vmobj.uuid
10053                             + '-receiving.json (' + e.code + '): ' + e.message);
10054                     }
10055                     cb();
10056                 }
10057             );
10058         }
10059     ], function (error) {
10060         callback(error);
10061     });
10062 }
10063 
10064 exports.delete = function (uuid, options, callback)
10065 {
10066     var attemptDelete;
10067     var last_try = 16;
10068     var log;
10069     var next_try = 1;
10070     var tries = 0;
10071 
10072     // options is optional
10073     if (arguments.length === 2) {
10074         callback = arguments[1];
10075         options = {};
10076     }
10077 
10078     ensureLogging(true);
10079 
10080     if (options.hasOwnProperty('log')) {
10081         log = options.log;
10082     } else {
10083         log = VM.log.child({action: 'delete', vm: uuid});
10084     }
10085 
10086     log.info('Deleting VM ' + uuid);
10087 
10088     attemptDelete = function (cb) {
10089         next_try = (next_try * 2);
10090         deleteZone(uuid, log, function (err) {
10091             tries++;
10092             if (err && err.code === 'EEXIST') {
10093                 // zone still existed, try again if we've not tried too much.
10094                 if (next_try <= last_try) {
10095                     log.info('VM.delete(' + tries + '): still there, '
10096                         + 'will try again in: ' + next_try + ' secs');
10097                     setTimeout(function () {
10098                         // try again
10099                         attemptDelete(cb);
10100                     }, next_try * 1000);
10101                 } else {
10102                     log.warn('VM.delete(' + tries + '): still there after'
10103                         + ' ' + next_try + ' seconds, giving up.');
10104                     cb(new Error('delete failed after ' + tries + ' attempts. '
10105                         + '(check the log for details)'));
10106                     return;
10107                 }
10108             } else if (err) {
10109                 // error but not one we can retry from.
10110                 log.error(err, 'VM.delete: FATAL: ' + err.message);
10111                 cb(err);
10112             } else {
10113                 // success!
10114                 log.debug('VM.delete: SUCCESS');
10115                 cb();
10116             }
10117         });
10118     };
10119 
10120     attemptDelete(function (err) {
10121         if (err) {
10122             log.error(err);
10123         }
10124         callback(err);
10125     });
10126 };
10127 
10128 // This function needs vmobj to have:
10129 //
10130 // brand
10131 // never_booted
10132 // uuid
10133 // zonename
10134 //
10135 function startZone(vmobj, log, callback)
10136 {
10137     var set_autoboot = 'set autoboot=true';
10138     var uuid = vmobj.uuid;
10139 
10140     assert(log, 'no logger passed to startZone()');
10141 
10142     log.debug('startZone starting ' + uuid);
10143 
10144     //
10145     // We set autoboot (or vm-autoboot) here because we've just intentionally
10146     // started this vm, so we want it to come up if the host is rebooted.
10147     //
10148     if (BRAND_OPTIONS[vmobj.brand].features.use_vm_autoboot) {
10149         set_autoboot = 'select attr name=vm-autoboot; set value=true; end';
10150     }
10151 
10152     async.series([
10153         function (cb) {
10154             // do the booting
10155             zoneadm(['-u', uuid, 'boot', '-X'], log, function (err, boot_fds) {
10156                 if (err) {
10157                     log.error({err: err, stdout: boot_fds.stdout,
10158                         stderr: boot_fds.stderr}, 'zoneadm failed to boot '
10159                         + 'VM');
10160                 } else {
10161                     log.debug({stdout: boot_fds.stdout,
10162                         stderr: boot_fds.stderr}, 'zoneadm booted VM');
10163                 }
10164                 cb(err);
10165             });
10166         }, function (cb) {
10167             // ensure it booted
10168             VM.waitForZoneState(vmobj, 'running', {timeout: 30, log: log},
10169                 function (err, result) {
10170 
10171                 if (err) {
10172                     if (err.code === 'ETIMEOUT') {
10173                         log.info(err, 'timeout waiting for zone to go to '
10174                             + '"running"');
10175                     } else {
10176                         log.error(err, 'unknown error waiting for zone to go'
10177                             + ' "running"');
10178                     }
10179                 } else {
10180                     // zone got to running
10181                     log.info('VM seems to have switched to "running"');
10182                 }
10183                 cb(err);
10184             });
10185         }, function (cb) {
10186             zonecfg(['-u', uuid, set_autoboot], log,
10187                 function (err, autoboot_fds) {
10188 
10189                 if (err) {
10190                     // The vm is running at this point, erroring out here would
10191                     // do no good, so we just log it.
10192                     log.error({err: err, stdout: autoboot_fds.stdout,
10193                         stderr: autoboot_fds.stderr}, 'startZone(): Failed to '
10194                         + set_autoboot + ' for ' + uuid);
10195                 } else {
10196                     log.debug({stdout: autoboot_fds.stdout,
10197                         stderr: autoboot_fds.stderr}, 'set autoboot');
10198                 }
10199                 cb(err);
10200             });
10201         }, function (cb) {
10202             if (!vmobj.never_booted) {
10203                 cb();
10204                 return;
10205             }
10206             zonecfg(['-u', uuid, 'remove attr name=never-booted' ], log,
10207                 function (err, neverbooted_fds) {
10208                     // Ignore errors here, because we're started.
10209                     if (err) {
10210                         log.warn({err: err, stdout: neverbooted_fds.stdout,
10211                             stderr: neverbooted_fds.stderr}, 'failed to remove '
10212                             + 'never-booted flag');
10213                     } else {
10214                         log.debug({stdout: neverbooted_fds.stdout,
10215                             stderr: neverbooted_fds.stderr}, 'removed '
10216                             + 'never-booted flag');
10217                     }
10218                     cb();
10219                 }
10220             );
10221         }
10222     ], function (err) {
10223         if (!err) {
10224             log.info('Started ' + uuid);
10225         }
10226         callback(err);
10227     });
10228 }
10229 
10230 // build the qemu cmdline and start up a VM
10231 //
10232 // vmobj needs any of the following that are defined:
10233 //
10234 // boot
10235 // brand
10236 // cpu_type
10237 // default_gateway
10238 // disks
10239 // hostname
10240 // internal_metadata
10241 // never_booted
10242 // nics
10243 // qemu_extra_opts
10244 // qemu_opts
10245 // ram
10246 // resolvers
10247 // spice_opts
10248 // spice_password
10249 // spice_port
10250 // state
10251 // uuid
10252 // vcpus
10253 // vga
10254 // virtio_txtimer
10255 // virtio_txburst
10256 // vnc_password
10257 // zone_state
10258 // zonename
10259 // zonepath
10260 //
10261 function startVM(vmobj, extra, log, callback)
10262 {
10263     var check_path;
10264     var cmdargs = [];
10265     var d;
10266     var defaultgw = '';
10267     var disk;
10268     var diskargs = '';
10269     var disk_idx = 0;
10270     var found;
10271     var hostname = vmobj.uuid;
10272     var mdata;
10273     var nic;
10274     var nic_idx = 0;
10275     var primary_found = false;
10276     var qemu_opts = '';
10277     var r;
10278     var script;
10279     var spiceargs;
10280     var uuid = vmobj.uuid;
10281     var virtio_txburst;
10282     var virtio_txtimer;
10283     var vnic_opts;
10284     var zoneroot;
10285 
10286     assert(log, 'no logger passed to startVM');
10287     assert(vmobj.hasOwnProperty('zonepath'), 'missing zonepath');
10288 
10289     log.debug('startVM(' + uuid + ')');
10290 
10291     if (!vmobj.hasOwnProperty('state')) {
10292         callback(new Error('Cannot start VM ' + uuid + ' which has no state'));
10293         return;
10294     }
10295 
10296     if ((vmobj.state !== 'stopped' && vmobj.state !== 'provisioning')
10297         || (vmobj.state === 'provisioning'
10298         && vmobj.zone_state !== 'installed')) {
10299 
10300         callback(new Error('Cannot start VM from state: ' + vmobj.state
10301             + ', must be "stopped"'));
10302         return;
10303     }
10304 
10305     zoneroot = path.join(vmobj.zonepath, '/root');
10306 
10307     // We're going to write to /startvm and /tmp/vm.metadata, we don't care if
10308     // they already exist, but we don't want them to be symlinks.
10309     try {
10310         assertSafeZonePath(zoneroot, '/startvm',
10311             {type: 'file', enoent_ok: true});
10312         assertSafeZonePath(zoneroot, '/tmp/vm.metadata',
10313             {type: 'file', enoent_ok: true});
10314     } catch (e) {
10315         log.error(e, 'Error validating files for startVM(): '
10316             + e.message);
10317         callback(e);
10318         return;
10319     }
10320 
10321     // XXX TODO: validate vmobj data is ok to start
10322 
10323     cmdargs.push('-m', vmobj.ram);
10324     cmdargs.push('-name', vmobj.uuid);
10325     cmdargs.push('-uuid', vmobj.uuid);
10326 
10327     if (vmobj.hasOwnProperty('cpu_type')) {
10328         cmdargs.push('-cpu', vmobj.cpu_type);
10329     } else {
10330         cmdargs.push('-cpu', 'qemu64');
10331     }
10332 
10333     if (vmobj.vcpus > 1) {
10334         cmdargs.push('-smp', vmobj.vcpus);
10335     }
10336 
10337     for (disk in vmobj.disks) {
10338         if (vmobj.disks.hasOwnProperty(disk)) {
10339             disk = vmobj.disks[disk];
10340             if (!disk.media) {
10341                 disk.media = 'disk';
10342             }
10343             diskargs = 'file=' + disk.path + ',if=' + disk.model
10344                 + ',index=' + disk_idx + ',media=' + disk.media;
10345             if (disk.boot) {
10346                 diskargs = diskargs + ',boot=on';
10347             }
10348             cmdargs.push('-drive', diskargs);
10349             disk_idx++;
10350         }
10351     }
10352 
10353     // extra payload can include additional disks that we want to include only
10354     // on this one boot.  It can also contain a boot parameter to control boot
10355     // device.  See qemu http://qemu.weilnetz.de/qemu-doc.html for info on
10356     // -boot options.
10357     if (extra.hasOwnProperty('disks')) {
10358         for (disk in extra.disks) {
10359             if (extra.disks.hasOwnProperty(disk)) {
10360                 disk = extra.disks[disk];
10361 
10362                 // ensure this is either a disk that gets mounted in or a
10363                 // file that's been dropped in to the zonepath
10364                 found = false;
10365                 for (d in vmobj.disks) {
10366                     if (!found && vmobj.disks.hasOwnProperty(d)) {
10367                         d = vmobj.disks[d];
10368                         if (d.path === disk.path) {
10369                             found = true;
10370                         }
10371                     }
10372                 }
10373                 check_path = path.join(vmobj.zonepath, 'root', disk.path);
10374                 if (!found && fs.existsSync(check_path)) {
10375                     found = true;
10376                 }
10377                 if (!found) {
10378                     callback(new Error('Cannot find disk: ' + disk.path));
10379                     return;
10380                 }
10381 
10382                 if (!disk.media) {
10383                     disk.media = 'disk';
10384                 }
10385                 diskargs = 'file=' + disk.path + ',if=' + disk.model
10386                     + ',index=' + disk_idx + ',media=' + disk.media;
10387                 if (disk.boot) {
10388                     diskargs = diskargs + ',boot=on';
10389                 }
10390                 cmdargs.push('-drive', diskargs);
10391                 disk_idx++;
10392             }
10393         }
10394     }
10395 
10396     // helpful values:
10397     // order=nc (network boot, then fallback to disk)
10398     // once=d (boot on disk once and the fallback to default)
10399     // order=c,once=d (boot on CDROM this time, but not subsequent boots)
10400     if (extra.hasOwnProperty('boot')) {
10401         cmdargs.push('-boot', extra.boot);
10402     } else if (vmobj.hasOwnProperty('boot')) {
10403         cmdargs.push('-boot', vmobj.boot);
10404     } else {
10405         // order=cd means try harddisk first (c) and cdrom if that fails (d)
10406         cmdargs.push('-boot', 'order=cd');
10407     }
10408 
10409     if (vmobj.hasOwnProperty('hostname')) {
10410         hostname = vmobj.hostname;
10411     }
10412 
10413     if (vmobj.hasOwnProperty('default_gateway')) {
10414         defaultgw = vmobj['default_gateway'];
10415     }
10416 
10417     /*
10418      * These tunables are set for all virtio vnics on this VM.
10419      */
10420     virtio_txtimer = VIRTIO_TXTIMER_DEFAULT;
10421     virtio_txburst = VIRTIO_TXBURST_DEFAULT;
10422     if (vmobj.hasOwnProperty('virtio_txtimer')) {
10423         virtio_txtimer = vmobj.virtio_txtimer;
10424     }
10425     if (vmobj.hasOwnProperty('virtio_txburst')) {
10426         virtio_txburst = vmobj.virtio_txburst;
10427     }
10428 
10429     for (nic in vmobj.nics) {
10430         if (vmobj.nics.hasOwnProperty(nic)) {
10431             nic = vmobj.nics[nic];
10432 
10433             // for virtio devices, we want to be able to set the txtimer and
10434             // txburst so we use a '-device' instead of a '-net' line.
10435             if (nic.model === 'virtio') {
10436                 cmdargs.push('-device',
10437                     'virtio-net-pci,mac=' + nic.mac
10438                     + ',tx=timer,x-txtimer=' + virtio_txtimer
10439                     + ',x-txburst=' + virtio_txburst
10440                     + ',vlan=' + nic_idx);
10441             } else {
10442                 cmdargs.push('-net',
10443                     'nic,macaddr=' + nic.mac
10444                     + ',vlan=' + nic_idx
10445                     + ',name=' + nic.interface
10446                     + ',model=' + nic.model);
10447             }
10448             vnic_opts = 'vnic,name=' + nic.interface
10449                 + ',vlan=' + nic_idx
10450                 + ',ifname=' + nic.interface;
10451 
10452             if (nic.ip != 'dhcp') {
10453                 vnic_opts = vnic_opts
10454                     + ',ip=' + nic.ip
10455                     + ',netmask=' + nic.netmask;
10456             }
10457 
10458             // The primary network provides the resolvers, default gateway
10459             // and hostname to prevent vm from trying to use settings
10460             // from more than one nic
10461             if (!primary_found) {
10462                 if (nic.hasOwnProperty('primary') && nic.primary) {
10463                     if (nic.hasOwnProperty('gateway') && nic.ip != 'dhcp') {
10464                         vnic_opts += ',gateway_ip=' + nic.gateway;
10465                     }
10466                     primary_found = true;
10467                 } else if (defaultgw && nic.hasOwnProperty('gateway')
10468                     && nic.gateway == defaultgw) {
10469 
10470                     /*
10471                      * XXX this exists here for backward compatibilty.  New VMs
10472                      *     and old VMs that are upgraded should not use
10473                      *     default_gateway.  When we've implemented autoupgrade
10474                      *     this block (and all reference to default_gateway)
10475                      *     can be removed.
10476                      */
10477 
10478                     if (nic.ip != 'dhcp') {
10479                         vnic_opts += ',gateway_ip=' + nic.gateway;
10480                     }
10481                     primary_found = true;
10482                 }
10483 
10484                 if (primary_found && nic.ip != 'dhcp') {
10485                     if (hostname) {
10486                         vnic_opts += ',hostname=' + hostname;
10487                     }
10488                     if (vmobj.hasOwnProperty('resolvers')) {
10489                         for (r in vmobj.resolvers) {
10490                             vnic_opts += ',dns_ip' + r + '='
10491                                 + vmobj.resolvers[r];
10492                         }
10493                     }
10494                 }
10495             }
10496 
10497             cmdargs.push('-net', vnic_opts);
10498             nic_idx++;
10499         }
10500     }
10501 
10502     cmdargs.push('-smbios', 'type=1,manufacturer=Joyent,'
10503         + 'product=SmartDC HVM,version=6.2012Q1,'
10504         + 'serial=' + vmobj.uuid + ',uuid=' + vmobj.uuid + ','
10505         + 'sku=001,family=Virtual Machine');
10506 
10507     cmdargs.push('-pidfile', '/tmp/vm.pid');
10508 
10509     if (vmobj.hasOwnProperty('vga')) {
10510         cmdargs.push('-vga', vmobj.vga);
10511     } else {
10512         cmdargs.push('-vga', 'std');
10513     }
10514 
10515     cmdargs.push('-chardev',
10516         'socket,id=qmp,path=/tmp/vm.qmp,server,nowait');
10517     cmdargs.push('-qmp', 'chardev:qmp');
10518 
10519     // serial0 is for serial console
10520     cmdargs.push('-chardev',
10521         'socket,id=serial0,path=/tmp/vm.console,server,nowait');
10522     cmdargs.push('-serial', 'chardev:serial0');
10523 
10524     // serial1 is used for metadata API
10525     cmdargs.push('-chardev',
10526         'socket,id=serial1,path=/tmp/vm.ttyb,server,nowait');
10527     cmdargs.push('-serial', 'chardev:serial1');
10528 
10529     if (!vmobj.qemu_opts) {
10530         if (vmobj.hasOwnProperty('vnc_password')
10531             && vmobj.vnc_password.length > 0) {
10532 
10533             cmdargs.push('-vnc', 'unix:/tmp/vm.vnc,password');
10534         } else {
10535             cmdargs.push('-vnc', 'unix:/tmp/vm.vnc');
10536         }
10537         if (vmobj.hasOwnProperty('spice_port')
10538             && vmobj.spice_port !== -1) {
10539 
10540             spiceargs = 'sock=/tmp/vm.spice';
10541             if (!vmobj.hasOwnProperty('spice_password')
10542                 || vmobj.spice_password.length <= 0) {
10543 
10544                 spiceargs = spiceargs + ',disable-ticketing';
10545 
10546                 // Otherwise, spice password is set via qmp, so we don't
10547                 // need to do anything here
10548             }
10549             if (vmobj.hasOwnProperty('spice_opts')
10550                 && vmobj.spice_opts.length > 0) {
10551 
10552                 spiceargs = spiceargs + ',' + vmobj.spice_opts;
10553             }
10554             cmdargs.push('-spice', spiceargs);
10555         }
10556         cmdargs.push('-parallel', 'none');
10557         cmdargs.push('-usb');
10558         cmdargs.push('-usbdevice', 'tablet');
10559         cmdargs.push('-k', 'en-us');
10560     } else {
10561         qemu_opts = vmobj.qemu_opts.toString();
10562     }
10563 
10564     if (vmobj.qemu_extra_opts) {
10565         qemu_opts = qemu_opts + ' ' + vmobj.qemu_extra_opts;
10566     }
10567 
10568     // This actually creates the qemu process
10569     script = '#!/usr/bin/bash\n\n'
10570         + 'exec >/tmp/vm.startvm.log 2>&1\n\n'
10571         + 'set -o xtrace\n\n'
10572         + 'if [[ -x /startvm.zone ]]; then\n'
10573         + '    exec /smartdc/bin/qemu-exec /startvm.zone "'
10574         + cmdargs.join('" "')
10575         + '" ' + qemu_opts + '\n'
10576         + 'else\n'
10577         + '    exec /smartdc/bin/qemu-exec /smartdc/bin/qemu-system-x86_64 "'
10578         + cmdargs.join('" "')
10579         + '" ' + qemu_opts + '\n'
10580         + 'fi\n\n'
10581         + 'exit 1\n';
10582 
10583     try {
10584         fs.writeFileSync(vmobj.zonepath + '/root/startvm', script);
10585         fs.chmodSync(vmobj.zonepath + '/root/startvm', '0755');
10586     } catch (e) {
10587         log.warn(e, 'Unable to create /startvm script in ' + vmobj.uuid);
10588         callback(new Error('cannot create /startvm'));
10589         return;
10590     }
10591 
10592     mdata = {
10593         'internal_metadata':
10594             vmobj.internal_metadata ? vmobj.internal_metadata : {}
10595     };
10596     fs.writeFile(path.join(vmobj.zonepath, '/root/tmp/vm.metadata'),
10597         JSON.stringify(mdata, null, 2) + '\n',
10598         function (err) {
10599             if (err) {
10600                 log.debug(err, 'FAILED TO write metadata to '
10601                     + '/tmp/vm.metadata: ' + err.message);
10602                 callback(err);
10603             } else {
10604                 log.debug('wrote metadata to /tmp/vm.metadata');
10605                 startZone(vmobj, log, callback);
10606             }
10607         }
10608     );
10609 }
10610 
10611 // according to usr/src/common/zfs/zfs_namecheck.c allowed characters are:
10612 //
10613 // alphanumeric characters plus the following: [-_.:%]
10614 //
10615 function validSnapshotName(snapname, log)
10616 {
10617     assert(log, 'no logger passed to validSnapshotName()');
10618 
10619     if (snapname.length < 1 || snapname.length > MAX_SNAPNAME_LENGTH) {
10620         log.error('Invalid snapname length: ' + snapname.length
10621             + ' valid range: [1-' + MAX_SNAPNAME_LENGTH + ']');
10622         return (false);
10623     }
10624 
10625     if (snapname.match(/[^a-zA-Z0-9\-\_\.\:\%]/)) {
10626         log.error('Invalid snapshot name: contains invalid characters.');
10627         return (false);
10628     }
10629 
10630     return (true);
10631 }
10632 
10633 function performSnapshotRollback(snapshots, log, callback)
10634 {
10635     assert(log, 'no logger passed to performSnapshotRollback()');
10636 
10637     // NOTE: we assume machine is stopped and snapshots are already validated
10638 
10639     function rollback(snapname, cb) {
10640         var args;
10641 
10642         args = ['rollback', '-r', snapname];
10643         zfs(args, log, function (zfs_err, fds) {
10644             if (zfs_err) {
10645                 log.error({'err': zfs_err, 'stdout': fds.stdout,
10646                     'stderr': fds.stdout}, 'zfs rollback of ' + snapname
10647                     + ' failed.');
10648                 cb(zfs_err);
10649                 return;
10650             }
10651             log.info('rolled back snapshot ' + snapname);
10652             log.debug('zfs destroy stdout: ' + fds.stdout);
10653             log.debug('zfs destroy stderr: ' + fds.stderr);
10654             cb();
10655         });
10656     }
10657 
10658     async.forEachSeries(snapshots, rollback, function (err) {
10659         if (err) {
10660             log.error(err, 'Unable to rollback some datasets.');
10661         }
10662         callback(err);
10663     });
10664 }
10665 
10666 function updateZonecfgTimestamp(vmobj, callback)
10667 {
10668     var file;
10669     var now;
10670 
10671     assert(vmobj.zonename, 'updateZonecfgTimestamp() vmobj must have '
10672         + '.zonename');
10673 
10674     file = path.join('/etc/zones/', vmobj.zonename + '.xml');
10675     now = new Date();
10676 
10677     fs.utimes(file, now, now, callback);
10678 }
10679 
10680 exports.rollback_snapshot = function (uuid, snapname, options, callback)
10681 {
10682     var load_fields;
10683     var log;
10684 
10685     // options is optional
10686     if (arguments.length === 3) {
10687         callback = arguments[2];
10688         options = {};
10689     }
10690 
10691     ensureLogging(true);
10692     if (options.hasOwnProperty('log')) {
10693         log = options.log;
10694     } else {
10695         log = VM.log.child({action: 'rollback_snapshot', vm: uuid});
10696     }
10697 
10698     if (!validSnapshotName(snapname, log)) {
10699         callback(new Error('Invalid snapshot name'));
10700         return;
10701     }
10702 
10703     load_fields = [
10704         'brand',
10705         'snapshots',
10706         'zfs_filesystem',
10707         'state',
10708         'uuid'
10709     ];
10710 
10711     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
10712         var found;
10713         var snap;
10714         var snapshot_list = [];
10715 
10716         if (err) {
10717             callback(err);
10718             return;
10719         }
10720 
10721         if (vmobj.brand === 'kvm') {
10722             callback(new Error('snapshots for KVM VMs currently unsupported'));
10723             return;
10724         }
10725 
10726         found = false;
10727         if (vmobj.hasOwnProperty('snapshots')) {
10728             for (snap in vmobj.snapshots) {
10729                 if (vmobj.snapshots[snap].name === snapname) {
10730                     found = true;
10731                     break;
10732                 }
10733             }
10734         }
10735         if (!found) {
10736             callback(new Error('No snapshot named "' + snapname + '" for '
10737                 + uuid));
10738             return;
10739         }
10740 
10741         snapshot_list = [vmobj.zfs_filesystem + '@vmsnap-' + snapname];
10742 
10743         if (vmobj.state !== 'stopped') {
10744             VM.stop(vmobj.uuid, {'force': true, log: log}, function (stop_err) {
10745                 if (stop_err) {
10746                     log.error(stop_err, 'failed to stop VM ' + vmobj.uuid
10747                         + ': ' + stop_err.message);
10748                     callback(stop_err);
10749                     return;
10750                 }
10751                 performSnapshotRollback(snapshot_list, log,
10752                     function (rollback_err) {
10753 
10754                     if (rollback_err) {
10755                         log.error(rollback_err, 'failed to '
10756                             + 'performSnapshotRollback');
10757                         callback(rollback_err);
10758                         return;
10759                     }
10760                     if (options.do_not_start) {
10761                         callback();
10762                     } else {
10763                         VM.start(vmobj.uuid, {}, {log: log}, callback);
10764                     }
10765                     return;
10766                 });
10767             });
10768         } else {
10769             performSnapshotRollback(snapshot_list, log,
10770                 function (rollback_err) {
10771 
10772                 if (rollback_err) {
10773                     log.error(rollback_err, 'failed to '
10774                         + 'performSnapshotRollback');
10775                     callback(rollback_err);
10776                     return;
10777                 }
10778                 if (options.do_not_start) {
10779                     callback();
10780                 } else {
10781                     VM.start(vmobj.uuid, {}, {log: log}, callback);
10782                 }
10783                 return;
10784             });
10785         }
10786     });
10787 };
10788 
10789 exports.delete_snapshot = function (uuid, snapname, options, callback)
10790 {
10791     var load_fields;
10792     var log;
10793 
10794     // options is optional
10795     if (arguments.length === 3) {
10796         callback = arguments[2];
10797         options = {};
10798     }
10799 
10800     ensureLogging(true);
10801     if (options.hasOwnProperty('log')) {
10802         log = options.log;
10803     } else {
10804         log = VM.log.child({action: 'delete_snapshot', vm: uuid});
10805     }
10806 
10807     if (!validSnapshotName(snapname, log)) {
10808         callback(new Error('Invalid snapshot name'));
10809         return;
10810     }
10811 
10812     load_fields = [
10813         'brand',
10814         'snapshots',
10815         'zfs_filesystem',
10816         'zonepath',
10817         'zonename'
10818     ];
10819 
10820     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
10821         var found;
10822         var mountpath;
10823         var mountpoint;
10824         var snap;
10825         var zoneroot;
10826 
10827         if (err) {
10828             callback(err);
10829             return;
10830         }
10831 
10832         if (vmobj.brand === 'kvm') {
10833             callback(new Error('snapshots for KVM VMs currently unsupported'));
10834             return;
10835         }
10836 
10837         found = false;
10838         if (vmobj.hasOwnProperty('snapshots')) {
10839             for (snap in vmobj.snapshots) {
10840                 if (vmobj.snapshots[snap].name === snapname) {
10841                     found = true;
10842                     break;
10843                 }
10844             }
10845         }
10846         if (!found) {
10847             callback(new Error('No snapshot named "' + snapname + '" for '
10848                 + uuid));
10849             return;
10850         }
10851 
10852         zoneroot = vmobj.zonepath + '/root';
10853         mountpath = '/checkpoints/' + snapname;
10854         mountpoint = zoneroot + '/' + mountpath;
10855 
10856         async.waterfall([
10857             function (cb) {
10858                 // Ensure it's safe for us to be doing something in this dir
10859                 try {
10860                     assertSafeZonePath(zoneroot, mountpath,
10861                         {type: 'dir', enoent_ok: true});
10862                 } catch (e) {
10863                     log.error(e, 'Unsafe mountpoint for checkpoints: '
10864                         + e.message);
10865                     cb(e);
10866                     return;
10867                 }
10868                 cb();
10869             }, function (cb) {
10870                 // umount snapshot
10871                 var argv;
10872                 var cmd = '/usr/sbin/umount';
10873 
10874                 argv = [mountpoint];
10875 
10876                 execFile(cmd, argv, function (e, stdout, stderr) {
10877                     if (e) {
10878                         log.error({err: e}, 'There was an error while '
10879                             + 'unmounting the snapshot: ' + e.message);
10880                         // we treat an error here as fatal only if the error
10881                         // was something other than 'not mounted'
10882                         if (!stderr.match(/ not mounted/)) {
10883                             cb(e);
10884                             return;
10885                         }
10886                     } else {
10887                         log.trace('umounted ' + mountpoint);
10888                     }
10889                     cb();
10890                 });
10891             }, function (cb) {
10892                 // remove the mountpoint directory
10893                 fs.rmdir(mountpoint, function (e) {
10894                     if (e) {
10895                         log.error(e);
10896                     } else {
10897                         log.trace('removed directory ' + mountpoint);
10898                     }
10899                     cb(); // XXX not fatal because might also not exist
10900                 });
10901             }, function (cb) {
10902                 var args;
10903 
10904                 args = ['destroy', vmobj.zfs_filesystem + '@vmsnap-'
10905                     + snapname];
10906 
10907                 zfs(args, log, function (e, fds) {
10908                     if (e) {
10909                         log.error({'err': e, 'stdout': fds.stdout,
10910                             'stderr': fds.stdout}, 'zfs destroy failed.');
10911                         cb(e);
10912                         return;
10913                     }
10914                     log.debug({err: e, stdout: fds.stdout, stderr: fds.stderr},
10915                         'zfs destroy ' + vmobj.zfs_filesystem + '@vmsnap-'
10916                         + snapname);
10917                     cb();
10918                 });
10919             }, function (cb) {
10920                 updateZonecfgTimestamp(vmobj, function (e) {
10921                     if (e) {
10922                         log.warn(e, 'failed to update timestamp after deleting '
10923                             + 'snapshot');
10924                     }
10925                     // don't pass err because there's no recovery possible
10926                     // (the snapshot's gone)
10927                     cb();
10928                 });
10929             }
10930         ], function (error) {
10931             callback(error);
10932         });
10933     });
10934 };
10935 
10936 exports.create_snapshot = function (uuid, snapname, options, callback)
10937 {
10938     var load_fields;
10939     var log;
10940 
10941     // options is optional
10942     if (arguments.length === 3) {
10943         callback = arguments[2];
10944         options = {};
10945     }
10946 
10947     ensureLogging(true);
10948 
10949     if (options.hasOwnProperty('log')) {
10950         log = options.log;
10951     } else {
10952         log = VM.log.child({action: 'create_snapshot', vm: uuid});
10953     }
10954 
10955     if (!validSnapshotName(snapname, log)) {
10956         callback(new Error('Invalid snapshot name'));
10957         return;
10958     }
10959 
10960     load_fields = [
10961         'brand',
10962         'datasets',
10963         'zone_state',
10964         'snapshots',
10965         'zfs_filesystem',
10966         'zonepath',
10967         'zonename'
10968     ];
10969 
10970     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
10971         var full_snapname;
10972         var mountpath;
10973         var mountpoint;
10974         var mount_snapshot = true;
10975         var snap;
10976         var snapshot_list = [];
10977         var zoneroot;
10978 
10979         if (err) {
10980             callback(err);
10981             return;
10982         }
10983 
10984         if (vmobj.brand === 'kvm') {
10985             callback(new Error('snapshots for KVM VMs currently unsupported'));
10986             return;
10987         }
10988 
10989         if (vmobj.hasOwnProperty('datasets') && vmobj.datasets.length > 0) {
10990             callback(new Error('Cannot currently snapshot zones that have '
10991                 + 'datasets'));
10992             return;
10993         }
10994 
10995         if (!vmobj.hasOwnProperty('zfs_filesystem')) {
10996             callback(new Error('vmobj missing zfs_filesystem, cannot create '
10997                 + 'snapshot'));
10998             return;
10999         }
11000 
11001         full_snapname = vmobj.zfs_filesystem + '@vmsnap-' + snapname;
11002 
11003         // Check that name not already used
11004         if (vmobj.hasOwnProperty('snapshots')) {
11005             for (snap in vmobj.snapshots) {
11006                 snap = vmobj.snapshots[snap];
11007 
11008                 if (snap.name === full_snapname) {
11009                     callback(new Error('snapshot with name "' + snapname
11010                         + '" already exists.'));
11011                     return;
11012                 } else {
11013                     log.debug('SKIPPING ' + snap.name);
11014                 }
11015             }
11016         }
11017 
11018         snapshot_list.push(full_snapname);
11019 
11020         // assert snapshot_list.length > 0
11021 
11022         log.info('Taking snapshot "' + snapname + '" of ' + uuid);
11023 
11024         zoneroot = vmobj.zonepath + '/root';
11025         mountpath = '/checkpoints/' + snapname;
11026         mountpoint = zoneroot + '/' + mountpath;
11027 
11028         async.waterfall([
11029             function (cb) {
11030                 // take the snapshot
11031                 var args;
11032                 args = ['snapshot'].concat(snapshot_list);
11033 
11034                 zfs(args, log, function (zfs_err, fds) {
11035                     if (zfs_err) {
11036                         log.error({err: zfs_err, stdout: fds.stdout,
11037                             stderr: fds.stdout}, 'zfs snapshot failed.');
11038                     } else {
11039                         log.debug({err: zfs_err, stdout: fds.stdout,
11040                             stderr: fds.stderr}, 'zfs ' + args.join(' '));
11041                     }
11042                     cb(zfs_err);
11043                 });
11044             }, function (cb) {
11045 
11046                 if (vmobj.zone_state !== 'running') {
11047                     log.info('Not mounting snapshot as zone is in state '
11048                         + vmobj.zone_state + ', must be: running');
11049                     mount_snapshot = false;
11050                     cb();
11051                     return;
11052                 }
11053 
11054                 // Ensure it's safe for us to be doing something in this dir
11055                 try {
11056                     assertSafeZonePath(zoneroot, mountpath,
11057                         {type: 'dir', enoent_ok: true});
11058                 } catch (e) {
11059                     log.error(e, 'Unsafe mountpoint for checkpoints: '
11060                         + e.message);
11061                     cb(e);
11062                     return;
11063                 }
11064                 cb();
11065             }, function (cb) {
11066                 // Make the mountpoint directory and parent
11067                 var newmode;
11068 
11069                 if (mount_snapshot === false) {
11070                     cb();
11071                     return;
11072                 }
11073 
11074                 /*jsl:ignore*/
11075                 newmode = 0755;
11076                 /*jsl:end*/
11077 
11078                 function doMkdir(dir, callbk) {
11079                     fs.mkdir(dir, newmode, function (e) {
11080                         if (e && e.code !== 'EEXIST') {
11081                             log.error({err: e}, 'unable to create mountpoint '
11082                                 + 'for checkpoints: ' + e.message);
11083                             callbk(e);
11084                             return;
11085                         }
11086                         callbk();
11087                     });
11088                 }
11089 
11090                 doMkdir(path.dirname(mountpoint), function (parent_e) {
11091                     if (parent_e) {
11092                         cb(parent_e);
11093                         return;
11094                     }
11095                     doMkdir(mountpoint, function (dir_e) {
11096                         if (dir_e) {
11097                             cb(dir_e);
11098                             return;
11099                         }
11100 
11101                         log.debug('created ' + mountpoint);
11102                         cb();
11103                     });
11104                 });
11105             }, function (cb) {
11106                 var argv;
11107                 var cmd = '/usr/sbin/mount';
11108                 var snapdir;
11109 
11110                 if (mount_snapshot === false) {
11111                     cb();
11112                     return;
11113                 }
11114 
11115                 snapdir = vmobj.zonepath + '/.zfs/snapshot/vmsnap-' + snapname
11116                     + '/root';
11117                 argv = [ '-F', 'lofs', '-o', 'ro,setuid,nodevices', snapdir,
11118                     mountpoint];
11119 
11120                 execFile(cmd, argv, function (e, stdout, stderr) {
11121                     if (e) {
11122                         log.error({err: e}, 'unable to mount snapshot: '
11123                             + e.message);
11124                     }
11125                     // not fatal becase snapshot was already created.
11126                     cb();
11127                 });
11128             }, function (cb) {
11129                 // update timestamp so last_modified gets bumped
11130                 updateZonecfgTimestamp(vmobj, function (e) {
11131                     if (e) {
11132                         log.warn(e,
11133                             'failed to update timestamp after snapshot');
11134                     }
11135                     // ignore error since there's no recovery
11136                     // (snapshot was created)
11137                     cb();
11138                 });
11139             }
11140         ], function (error) {
11141             callback(error);
11142         });
11143     });
11144 };
11145 
11146 exports.start = function (uuid, extra, options, callback)
11147 {
11148     var load_fields;
11149     var log;
11150 
11151     load_fields = [
11152         'brand',
11153         'nics',
11154         'state',
11155         'uuid',
11156         'zone_state',
11157         'zonename',
11158         'zonepath'
11159     ];
11160 
11161     // options is optional
11162     if (arguments.length === 3) {
11163         callback = arguments[2];
11164         options = {};
11165     }
11166 
11167     assert(callback, 'undefined callback!');
11168 
11169     ensureLogging(true);
11170     if (options.hasOwnProperty('log')) {
11171         log = options.log;
11172     } else {
11173         log = VM.log.child({action: 'start', vm: uuid});
11174     }
11175 
11176     log.info('Starting VM ' + uuid);
11177 
11178     VM.load(uuid, {log: log, fields: load_fields}, function (err, vmobj) {
11179         if (err) {
11180             callback(err);
11181         } else {
11182 
11183             if (vmobj.state === 'running') {
11184                 err = new Error('VM ' + vmobj.uuid + ' is already '
11185                     + '\'running\'');
11186                 err.code = 'EALREADYRUNNING';
11187                 callback(err);
11188                 return;
11189             }
11190 
11191             if ((vmobj.state !== 'stopped' && vmobj.state !== 'provisioning')
11192                 || (vmobj.state === 'provisioning'
11193                 && vmobj.zone_state !== 'installed')) {
11194 
11195                 err = new Error('Cannot to start vm from state "' + vmobj.state
11196                     + '", must be "stopped".');
11197                 log.error(err);
11198                 callback(err);
11199                 return;
11200             }
11201 
11202             lookupInvalidNicTags(vmobj.nics, log, function (e) {
11203                 var kvm_load_fields = [
11204                     'boot',
11205                     'brand',
11206                     'cpu_type',
11207                     'default_gateway',
11208                     'disks',
11209                     'hostname',
11210                     'internal_metadata',
11211                     'never_booted',
11212                     'nics',
11213                     'qemu_extra_opts',
11214                     'qemu_opts',
11215                     'ram',
11216                     'resolvers',
11217                     'spice_opts',
11218                     'spice_password',
11219                     'spice_port',
11220                     'state',
11221                     'uuid',
11222                     'vcpus',
11223                     'vga',
11224                     'virtio_txtimer',
11225                     'virtio_txburst',
11226                     'vnc_password',
11227                     'zone_state',
11228                     'zonename',
11229                     'zonepath'
11230                 ];
11231 
11232                 if (e) {
11233                     callback(e);
11234                     return;
11235                 }
11236 
11237                 if (BRAND_OPTIONS[vmobj.brand].features.type === 'KVM') {
11238                     // when we boot KVM we need a lot more fields, so load again
11239                     // in that case to get the fields we need.
11240                     VM.load(uuid, {log: log, fields: kvm_load_fields},
11241                         function (error, obj) {
11242 
11243                         if (error) {
11244                             callback(error);
11245                             return;
11246                         }
11247                         startVM(obj, extra, log, callback);
11248                     });
11249                 } else if (BRAND_OPTIONS[vmobj.brand].features.type === 'OS') {
11250                     startZone(vmobj, log, callback);
11251                 } else {
11252                     err = new Error('no idea how to start a vm with brand: '
11253                         + vmobj.brand);
11254                     log.error(err);
11255                     callback(err);
11256                 }
11257             });
11258         }
11259     });
11260 };
11261 
11262 function setRctl(zonename, rctl, value, log, callback)
11263 {
11264     var args;
11265 
11266     assert(log, 'no logger passed to setRctl()');
11267 
11268     args = ['-n', rctl, '-v', value.toString(), '-r', '-i', 'zone', zonename];
11269     log.debug('/usr/bin/prctl ' + args.join(' '));
11270     execFile('/usr/bin/prctl', args, function (error, stdout, stderr) {
11271         if (error) {
11272             log.error(error, 'setRctl() failed with: ' + stderr);
11273             callback(error);
11274         } else {
11275             callback();
11276         }
11277     });
11278 }
11279 
11280 function resizeTmp(zonename, newsize, log, callback)
11281 {
11282     var args;
11283 
11284     // NOTE: this used to update /etc/vfstab in the zone as well, but was
11285     // changed with OS-920.  Now vfstab is updated by mdata-fetch in the
11286     // zone instead, so that will happen next boot.  We still do the mount
11287     // so the property update happens on the running zone.
11288 
11289     assert(log, 'no logger passed to resizeTmp()');
11290 
11291     args = [zonename, '/usr/sbin/mount', '-F', 'tmpfs', '-o', 'remount,size='
11292         + newsize + 'm', '/tmp'];
11293     log.debug('/usr/sbin/zlogin ' + args.join(' '));
11294     execFile('/usr/sbin/zlogin', args, function (err, mnt_stdout, mnt_stderr) {
11295         if (err) {
11296             log.error({'err': err, 'stdout': mnt_stdout,
11297                 'stderr': mnt_stderr}, 'zlogin for ' + zonename
11298                 + ' exited with code ' + err.code + ' -- ' + err.message);
11299             // error here is not fatal as this should be fixed on reboot
11300         }
11301 
11302         callback();
11303     });
11304 }
11305 
11306 function resizeDisks(disks, updates, log, callback)
11307 {
11308     var d;
11309     var disk;
11310     var resized = 0;
11311     var vols = [];
11312 
11313     assert(log, 'no logger passed to resizeDisks()');
11314 
11315     for (disk in updates) {
11316         disk = updates[disk];
11317         for (d in disks) {
11318             d = disks[d];
11319             if (d.path === disk.path && disk.hasOwnProperty('size')) {
11320                 vols.push({'disk': d, 'new_size': disk.size});
11321             }
11322         }
11323     }
11324 
11325     function resize(vol, cb) {
11326         var args;
11327         var dsk = vol.disk;
11328         var size = vol.new_size;
11329 
11330         if (dsk.hasOwnProperty('zfs_filesystem')) {
11331             if (dsk.size > size) {
11332                 cb(new Error('cannot resize ' + dsk.zfs_filesystem
11333                     + ' new size must be greater than current size. ('
11334                     + dsk.size + ' > ' + dsk.size + ')'));
11335             } else if (dsk.size === size) {
11336                 // no point resizing if the old+new are the same
11337                 cb();
11338             } else {
11339                 args = ['set', 'volsize=' + size + 'M', dsk.zfs_filesystem];
11340                 zfs(args, log, function (err, fds) {
11341                     resized++;
11342                     cb(err);
11343                 });
11344             }
11345         } else {
11346             cb(new Error('could not find zfs_filesystem in '
11347                 + JSON.stringify(dsk)));
11348         }
11349     }
11350 
11351     async.forEachSeries(vols, resize, function (err) {
11352         if (err) {
11353             log.error(err, 'Unable to resize disks');
11354             callback(err);
11355         } else {
11356             callback(null, resized);
11357         }
11358     });
11359 }
11360 
11361 function updateVnicAllowedIPs(uuid, nic, log, callback)
11362 {
11363     var ips = [];
11364 
11365     assert(log, 'no logger passed to updateVnicAllowedIPs()');
11366 
11367     if (!uuid || !nic.interface) {
11368         callback();
11369         return;
11370     }
11371 
11372     if (nic.hasOwnProperty('allow_ip_spoofing') && nic.allow_ip_spoofing) {
11373         dladm.resetLinkProp(uuid, nic.interface, 'allowed-ips', log, callback);
11374         return;
11375     }
11376 
11377     if (nic.hasOwnProperty('ip')) {
11378         ips.push(nic.ip);
11379     }
11380 
11381     if (nic.hasOwnProperty('vrrp_primary_ip')) {
11382         ips.push(nic.vrrp_primary_ip);
11383     }
11384 
11385     if (nic.hasOwnProperty('allowed_ips')) {
11386         ips = ips.concat(nic.allowed_ips);
11387     }
11388 
11389     if (!ips.length === 0) {
11390         dladm.resetLinkProp(uuid, nic.interface, 'allowed-ips', log, callback);
11391     } else {
11392         dladm.setLinkProp(uuid, nic.interface, 'allowed-ips', ips, log,
11393             callback);
11394     }
11395 }
11396 
11397 function updateVnicProperties(uuid, vmobj, payload, log, callback)
11398 {
11399     assert(log, 'no logger passed to updateVnicProperties()');
11400 
11401     if (vmobj.state != 'running') {
11402         log.debug('VM not running: not updating vnic properties');
11403         callback(null);
11404         return;
11405     }
11406 
11407     if (!payload.hasOwnProperty('update_nics')) {
11408         log.debug(
11409             'No update_nics property: not updating vnic properties');
11410         callback(null);
11411         return;
11412     }
11413 
11414     async.forEach(payload.update_nics, function (nic, cb) {
11415         var opt;
11416         var needsUpdate = false;
11417         var needsIPupdate = false;
11418         var spoof_opts = {
11419             'allow_ip_spoofing': 'ip-nospoof',
11420             'allow_mac_spoofing': 'mac-nospoof',
11421             'allow_dhcp_spoofing': 'dhcp-nospoof',
11422             'allow_restricted_traffic': 'restricted'
11423         };
11424         var vm_nic;
11425 
11426         // First, determine if we've changed any of the spoofing opts in this
11427         // update:
11428         for (opt in spoof_opts) {
11429             if (nic.hasOwnProperty(opt)) {
11430                 needsUpdate = true;
11431                 break;
11432             }
11433         }
11434 
11435         if (nic.hasOwnProperty('vrrp_primary_ip')
11436             || nic.hasOwnProperty('allowed_ips')
11437             || nic.hasOwnProperty('allow_ip_spoofing')) {
11438             needsIPupdate = true;
11439         }
11440 
11441         for (vm_nic in vmobj.nics) {
11442             vm_nic = vmobj.nics[vm_nic];
11443             if (vm_nic.mac == nic.mac) {
11444                 break;
11445             }
11446         }
11447 
11448         if (!vm_nic) {
11449             cb(new Error('Unknown NIC: ' + nic.mac));
11450             return;
11451         }
11452 
11453         if (!needsUpdate) {
11454             log.debug('No spoofing / allowed IP opts updated for nic "'
11455                 + nic.mac + '": not updating');
11456             if (needsIPupdate) {
11457                 updateVnicAllowedIPs(uuid, vm_nic, log, cb);
11458             } else {
11459                 cb(null);
11460             }
11461             return;
11462         }
11463 
11464         // Using the updated nic object, figure out what spoofing opts to set
11465         for (opt in spoof_opts) {
11466             if (vm_nic.hasOwnProperty(opt) && fixBoolean(vm_nic[opt])) {
11467                 delete spoof_opts[opt];
11468             }
11469         }
11470 
11471         if (vm_nic.hasOwnProperty('dhcp_server')
11472                 && fixBoolean(vm_nic.dhcp_server)) {
11473             delete spoof_opts.allow_dhcp_spoofing;
11474             delete spoof_opts.allow_ip_spoofing;
11475         }
11476 
11477         if (Object.keys(spoof_opts).length === 0) {
11478             dladm.resetLinkProp(uuid, vm_nic.interface, 'protection', log,
11479                 function (err) {
11480                     if (err) {
11481                         cb(err);
11482                         return;
11483                     }
11484                     if (needsIPupdate) {
11485                         updateVnicAllowedIPs(uuid, vm_nic, log, cb);
11486                         return;
11487                     }
11488                     cb();
11489                     return;
11490                 });
11491         } else {
11492             dladm.setLinkProp(uuid, vm_nic.interface, 'protection',
11493                     Object.keys(spoof_opts).map(function (k) {
11494                         return spoof_opts[k];
11495                     }), log,
11496                 function (err) {
11497                     if (err) {
11498                         cb(err);
11499                         return;
11500                     }
11501                     if (needsIPupdate) {
11502                         updateVnicAllowedIPs(uuid, vm_nic, log, cb);
11503                         return;
11504                     }
11505                     cb();
11506                     return;
11507                 });
11508         }
11509     }, function (err) {
11510         if (err) {
11511             callback(err);
11512         } else {
11513             callback(null);
11514         }
11515     });
11516 }
11517 
11518 // Run a fw.js function that requires all VM records
11519 function firewallVMrun(opts, fn, log, callback)
11520 {
11521     assert(log, 'no logger passed to firewallVMrun()');
11522     VM.lookup({}, {fields: fw.VM_FIELDS, log: log}, function (err, records) {
11523         if (err) {
11524             callback(err);
11525             return;
11526         }
11527 
11528         opts.vms = records;
11529         if (fn.name == 'validatePayload') {
11530             opts.logName = 'VM-create';
11531         } else {
11532             opts.logName = 'VM-' + (fn.name || '');
11533         }
11534 
11535         if (opts.provisioning) {
11536             opts.vms.push(opts.provisioning);
11537             delete opts.provisioning;
11538         }
11539 
11540         fn(opts, callback);
11541         return;
11542     });
11543 }
11544 
11545 function validateFirewall(payload, log, callback)
11546 {
11547     assert(log, 'no logger passed to validateFirewall()');
11548 
11549     log.debug(toValidate, 'Validating firewall payload');
11550     var toValidate = payload.firewall;
11551     toValidate.provisioning = {
11552         'state': 'provisioning'
11553     };
11554 
11555     fw.VM_FIELDS.forEach(function (field) {
11556         if (payload.hasOwnProperty(field)) {
11557             toValidate.provisioning[field] = payload[field];
11558         }
11559     });
11560 
11561     if (payload.hasOwnProperty('add_nics')) {
11562         toValidate.provisioning.nics = payload.add_nics;
11563     }
11564 
11565     // We're not actually writing data to zonepath when validating, and we
11566     // don't actually have a zonepath created yet, so add a key so that the
11567     // payload passes validation
11568     if (!payload.hasOwnProperty('zonepath')) {
11569         toValidate.provisioning.zonepath = true;
11570     }
11571 
11572     log.debug({
11573         firewall: toValidate.firewall,
11574         provisioning: toValidate.provisioning,
11575         payload: payload
11576     }, 'Validating firewall payload');
11577 
11578     firewallVMrun(toValidate, fw.validatePayload, log,
11579         function (err, res) {
11580         if (err) {
11581             log.error(err, 'Error validating firewall payload');
11582             err.message = 'Invalid firewall payload: ' + err.message;
11583         }
11584 
11585         callback(err, res);
11586         return;
11587     });
11588 }
11589 
11590 function addFirewallData(payload, vmobj, log, callback)
11591 {
11592     var firewallOpts = payload.firewall;
11593 
11594     assert(log, 'no logger passed to addFirewallData()');
11595 
11596     if (!payload.hasOwnProperty('firewall')) {
11597         firewallOpts = {};
11598     }
11599     firewallOpts.localVMs = [vmobj];
11600 
11601     log.debug(firewallOpts, 'Adding firewall data');
11602     firewallVMrun(firewallOpts, fw.add, log, function (err, res) {
11603         if (err) {
11604             log.error(err, 'Error adding firewall data');
11605         }
11606 
11607         callback(err, res);
11608         return;
11609     });
11610 }
11611 
11612 function updateFirewallData(payload, vmobj, log, callback)
11613 {
11614     var enablePrefix = 'En';
11615     var enableFn = fw.enable;
11616     var firewallOpts = payload.firewall;
11617 
11618     assert(log, 'no logger passed to updateFirewallData()');
11619 
11620     if (!payload.hasOwnProperty('firewall')) {
11621         firewallOpts = {};
11622     }
11623     firewallOpts.localVMs = [vmobj];
11624 
11625     log.debug(firewallOpts, 'Updating firewall data');
11626     firewallVMrun(firewallOpts, fw.update, log, function (err, res) {
11627         if (err) {
11628             log.error(err, 'Error updating firewall data');
11629         }
11630 
11631         if (!payload.hasOwnProperty('firewall_enabled')) {
11632             callback(err, res);
11633             return;
11634         }
11635 
11636         if (!payload.firewall_enabled) {
11637             enableFn = fw.disable;
11638             enablePrefix = 'Dis';
11639         }
11640 
11641         log.debug('%sabling firewall for VM %s', enablePrefix, vmobj.uuid);
11642         firewallVMrun({ vm: vmobj }, enableFn, log, function (err2, res2) {
11643             if (err2) {
11644                 log.error(err, 'Error %sabling firewall',
11645                     enablePrefix.toLowerCase());
11646             }
11647 
11648             callback(err2, res2);
11649             return;
11650         });
11651     });
11652 }
11653 
11654 function restartMetadataService(vmobj, payload, log, callback) {
11655     var args;
11656 
11657     assert(log, 'no logger passed to restartMetadataService()');
11658 
11659     if (!BRAND_OPTIONS[vmobj.brand].hasOwnProperty('features')
11660         || !BRAND_OPTIONS[vmobj.brand].hasOwnProperty('features')
11661         || !BRAND_OPTIONS[vmobj.brand].features.mdata_restart) {
11662         log.debug('restarting mdata:fetch service not supported for brand '
11663             + vmobj.brand);
11664         callback();
11665         return;
11666     }
11667 
11668     if (vmobj.state !== 'running' || !payload.hasOwnProperty('resolvers')
11669         && !payload.hasOwnProperty('routes')
11670         && !payload.hasOwnProperty('set_routes')
11671         && !payload.hasOwnProperty('remove_routes')) {
11672         callback();
11673         return;
11674     }
11675 
11676     log.debug('restarting metadata service for: ' + vmobj.uuid);
11677 
11678     args = [vmobj.zonename, '/usr/sbin/svcadm', 'restart',
11679         'svc:/smartdc/mdata:fetch'];
11680     log.debug('/usr/sbin/zlogin ' + args.join(' '));
11681     execFile('/usr/sbin/zlogin', args, function (err, svc_stdout, svc_stderr) {
11682         if (err) {
11683             log.error({'err': err, 'stdout': svc_stdout,
11684                 'stderr': svc_stderr}, 'zlogin for ' + vmobj.zonename
11685                 + ' exited with code' + err.code + err.message);
11686             // error here is not fatal as this should be fixed on reboot
11687         }
11688 
11689         callback();
11690     });
11691 }
11692 
11693 function applyUpdates(oldobj, newobj, payload, log, callback)
11694 {
11695     var changed_datasets = false;
11696 
11697     assert(log, 'no logger passed to applyUpdates()');
11698 
11699     // Note: oldobj is the VM *before* the update, newobj *after*
11700     log.debug('applying updates to ' + oldobj.uuid);
11701 
11702     async.series([
11703         function (cb) {
11704             if (payload.hasOwnProperty('update_disks')
11705                 && oldobj.hasOwnProperty('disks')) {
11706 
11707                 resizeDisks(oldobj.disks, payload.update_disks, log,
11708                     function (err, resized) {
11709                         // If any were resized, mark that we changed something
11710                         if (!err && resized > 0) {
11711                             changed_datasets = true;
11712                         }
11713                         cb(err);
11714                     }
11715                 );
11716             } else {
11717                 cb();
11718             }
11719         }, function (cb) {
11720             if (payload.hasOwnProperty('quota')
11721                 && (Number(payload.quota) !== Number(oldobj.quota))) {
11722 
11723                 setQuota(newobj.zfs_filesystem, payload.quota, log,
11724                     function (err) {
11725 
11726                     if (!err) {
11727                         changed_datasets = true;
11728                     }
11729                     cb(err);
11730                 });
11731             } else {
11732                 cb();
11733             }
11734         }, function (cb) {
11735             // NOTE: we've already validated the value
11736             if (payload.hasOwnProperty('zfs_root_recsize')
11737                 && (payload.zfs_root_recsize !== oldobj.zfs_root_recsize)) {
11738 
11739                 zfs(['set', 'recsize=' + payload.zfs_root_recsize,
11740                     newobj.zfs_filesystem], log, function (err, fds) {
11741 
11742                     if (err) {
11743                         log.error(err, 'failed to apply zfs_root_recsize: '
11744                             + fds.stderr);
11745                         cb(new Error(rtrim(fds.stderr)));
11746                     } else {
11747                         cb();
11748                     }
11749                 });
11750             } else {
11751                 cb();
11752             }
11753         }, function (cb) {
11754             // NOTE: we've already validated the value.
11755             if (payload.hasOwnProperty('zfs_data_recsize')
11756                 && oldobj.hasOwnProperty('zfs_data_recsize')
11757                 && newobj.hasOwnProperty('datasets')
11758                 && (newobj.datasets.indexOf(newobj.zfs_filesystem
11759                     + '/data') !== -1)) {
11760 
11761                 zfs(['set', 'recsize=' + payload.zfs_data_recsize,
11762                     newobj.zfs_filesystem + '/data'], log, function (err, fds) {
11763 
11764                     if (err) {
11765                         log.error(err, 'failed to apply zfs_data_recsize: '
11766                             + fds.stderr);
11767                         cb(new Error(rtrim(fds.stderr)));
11768                     } else {
11769                         cb();
11770                     }
11771                 });
11772             } else {
11773                 cb();
11774             }
11775         }, function (cb) {
11776             // NOTE: we've already validated the value
11777             if (payload.hasOwnProperty('zfs_root_compression')
11778                 && (payload.zfs_root_compression !==
11779                     oldobj.zfs_root_compression)) {
11780 
11781                 zfs(['set', 'compression=' + payload.zfs_root_compression,
11782                     newobj.zfs_filesystem], log, function (err, fds) {
11783 
11784                     if (err) {
11785                         log.error(err, 'failed to apply '
11786                             + 'zfs_root_compression: ' + 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_data_compression')
11798                 && newobj.hasOwnProperty('datasets')
11799                 && (newobj.datasets.indexOf(newobj.zfs_filesystem
11800                     + '/data') !== -1)) {
11801 
11802                 zfs(['set', 'compression=' + payload.zfs_data_compression,
11803                     newobj.zfs_filesystem + '/data'], log, function (err, fds) {
11804 
11805                     if (err) {
11806                         log.error(err, 'failed to apply '
11807                             + 'zfs_data_compression: ' + fds.stderr);
11808                         cb(new Error(rtrim(fds.stderr)));
11809                     } else {
11810                         cb();
11811                     }
11812                 });
11813             } else {
11814                 cb();
11815             }
11816         }, function (cb) {
11817             var d;
11818             var disk;
11819             var zfs_updates = [];
11820 
11821             if (payload.hasOwnProperty('update_disks')) {
11822                 // loop through the disks we updated and perform any updates.
11823                 for (disk in payload.update_disks) {
11824                     disk = payload.update_disks[disk];
11825 
11826                     if (!disk) {
11827                         continue;
11828                     }
11829 
11830                     for (d in oldobj.disks) {
11831                         d = oldobj.disks[d];
11832                         if (d.path === disk.path
11833                             && d.hasOwnProperty('zfs_filesystem')) {
11834 
11835                             if (disk.hasOwnProperty('compression')) {
11836                                 zfs_updates.push({
11837                                     zfs_filesystem: d.zfs_filesystem,
11838                                     property: 'compression',
11839                                     value: disk.compression
11840                                 });
11841                             }
11842 
11843                             if (disk.hasOwnProperty('refreservation')) {
11844                                 zfs_updates.push({
11845                                     zfs_filesystem: d.zfs_filesystem,
11846                                     property: 'refreservation',
11847                                     value: disk.refreservation + 'M'
11848                                 });
11849                             }
11850                         }
11851                     }
11852                 }
11853                 if (zfs_updates.length > 0) {
11854                     log.debug('applying ' + zfs_updates.length
11855                         + ' zfs updates');
11856                     async.each(zfs_updates, function (props, f_cb) {
11857                         zfs(['set', props.property + '=' + props.value,
11858                             props.zfs_filesystem], log, function (err, fds) {
11859 
11860                             if (err) {
11861                                 log.error(err, 'failed to set ' + props.property
11862                                     + '=' + props.value + ' for '
11863                                     + props.zfs_filesystem);
11864                             }
11865                             f_cb(err);
11866                         });
11867                     }, function (err) {
11868                         log.debug({err: err}, 'end of zfs updates');
11869                         cb(err);
11870                     });
11871                 } else {
11872                     log.debug('no zfs updates to apply');
11873                     cb();
11874                 }
11875             } else {
11876                 cb();
11877             }
11878         }, function (cb) {
11879             var factor;
11880             var keys = [];
11881             var rctl;
11882             var rctls = {
11883                 'cpu_shares': ['zone.cpu-shares'],
11884                 'zfs_io_priority': ['zone.zfs-io-priority'],
11885                 'max_lwps': ['zone.max-lwps'],
11886                 'max_physical_memory': ['zone.max-physical-memory',
11887                     (1024 * 1024)],
11888                 'max_locked_memory': ['zone.max-locked-memory', (1024 * 1024)],
11889                 'max_swap': ['zone.max-swap', (1024 * 1024)],
11890                 'cpu_cap': ['zone.cpu-cap']
11891             };
11892 
11893             if (!BRAND_OPTIONS[oldobj.brand].features.update_rctls) {
11894                 cb();
11895                 return;
11896             }
11897 
11898             for (rctl in rctls) {
11899                 keys.push(rctl);
11900             }
11901 
11902             async.forEachSeries(keys, function (prop, c) {
11903                 rctl = rctls[prop][0];
11904                 if (rctls[prop][1]) {
11905                     factor = rctls[prop][1];
11906                 } else {
11907                     factor = 1;
11908                 }
11909 
11910                 if (payload.hasOwnProperty(prop)) {
11911                     setRctl(newobj.zonename, rctl,
11912                         Number(payload[prop]) * factor, log,
11913                         function (err) {
11914                             if (err) {
11915                                 log.warn(err, 'failed to set rctl: ' + prop);
11916                             }
11917                             c();
11918                         }
11919                     );
11920                 } else {
11921                     c();
11922                 }
11923             }, function (err) {
11924                 cb(err);
11925             });
11926         }, function (cb) {
11927             if ((payload.hasOwnProperty('vnc_password')
11928                 && (oldobj.vnc_password !== newobj.vnc_password))
11929                 || (payload.hasOwnProperty('vnc_port')
11930                     && (oldobj.vnc_port !== newobj.vnc_port))) {
11931 
11932                 // tell vmadmd to refresh_password and port (will restart
11933                 // listener)
11934                 postVmadmd(newobj.uuid, 'reload_display', {}, log,
11935                     function (e) {
11936 
11937                     if (e) {
11938                         cb(new Error('Unable to tell vmadmd to reload VNC: '
11939                             + e.message));
11940                     } else {
11941                         cb();
11942                     }
11943                 });
11944             } else if ((payload.hasOwnProperty('spice_password')
11945                 && (oldobj.spice_password !== newobj.spice_password))
11946                 || (payload.hasOwnProperty('spice_port')
11947                     && (oldobj.spice_port !== newobj.spice_port))) {
11948 
11949                 // tell vmadmd to refresh_password and port (will restart
11950                 // listener)
11951                 postVmadmd(newobj.uuid, 'reload_display', {}, log,
11952                     function (e) {
11953 
11954                     if (e) {
11955                         cb(new Error('Unable to tell vmadmd to reload SPICE: '
11956                             + e.message));
11957                     } else {
11958                         cb();
11959                     }
11960                 });
11961             } else {
11962                 cb();
11963             }
11964         }, function (cb) {
11965             // we do this last, since we need the memory in the zone updated
11966             // first if we're growing this.
11967             if (payload.hasOwnProperty('tmpfs')) {
11968                 resizeTmp(newobj.zonename, payload.tmpfs, log, cb);
11969             } else {
11970                 cb();
11971             }
11972         }, function (cb) {
11973             var now = new Date();
11974 
11975             // If we changed any dataset properties, we touch the zone's xml
11976             // file so that last_modified is correct.
11977             if (changed_datasets && newobj.hasOwnProperty('zonename')) {
11978                 fs.utimes('/etc/zones/' + newobj.zonename + '.xml', now, now,
11979                     function (err) {
11980                         if (err) {
11981                             log.warn(err, 'Unable to "touch" xml file for "'
11982                                 + newobj.zonename + '": ' + err.message);
11983                         } else {
11984                             log.debug('Touched ' + newobj.zonename
11985                                 + '.xml after datasets were modified.');
11986                         }
11987                         // We don't error out if we just couldn't touch because
11988                         // the actual updates above already did happen.
11989                         cb();
11990                     }
11991                 );
11992             } else {
11993                 cb();
11994             }
11995         }
11996 
11997     ], function (err, res) {
11998         log.debug('done applying updates to ' + oldobj.uuid);
11999         callback(err);
12000     });
12001 }
12002 
12003 exports.update = function (uuid, payload, options, callback)
12004 {
12005     var log;
12006     var new_vmobj;
12007     var vmobj;
12008 
12009     // options parameter is optional
12010     if (arguments.length === 3) {
12011         callback = arguments[2];
12012         options = {};
12013     }
12014 
12015     ensureLogging(true);
12016     if (options.hasOwnProperty('log')) {
12017         log = options.log;
12018     } else {
12019         log = VM.log.child({action: 'update', vm: uuid});
12020     }
12021 
12022     log.info('Updating VM ' + uuid + ' with initial payload:\n'
12023         + JSON.stringify(payload, null, 2));
12024 
12025     async.series([
12026         function (cb) {
12027             // for update we currently always load the whole vmobj since the
12028             // update functions may need to look at bits from the existing VM.
12029             VM.load(uuid, {log: log}, function (err, obj) {
12030                 if (err) {
12031                     cb(err);
12032                     return;
12033                 }
12034                 vmobj = obj;
12035                 cb();
12036             });
12037         }, function (cb) {
12038             normalizePayload(payload, vmobj, log, function (e) {
12039                 log.debug('Used payload:\n'
12040                     + JSON.stringify(payload, null, 2));
12041                 cb(e);
12042             });
12043         }, function (cb) {
12044             var deletables = [];
12045             var to_remove = [];
12046             var n;
12047 
12048             // destroy remove_disks before we add in case we're recreating with
12049             // an existing name.
12050 
12051             if (payload.hasOwnProperty('remove_disks')) {
12052                 to_remove = payload.remove_disks;
12053                 for (n in vmobj.disks) {
12054                     if (to_remove.indexOf(vmobj.disks[n].path) !== -1) {
12055                         deletables.push(vmobj.disks[n]);
12056                     }
12057                 }
12058             } else {
12059                 // no disks to remove so all done.
12060                 cb();
12061                 return;
12062             }
12063 
12064             function loggedDeleteVolume(volume, callbk) {
12065                 return deleteVolume(volume, log, callbk);
12066             }
12067 
12068             async.forEachSeries(deletables, loggedDeleteVolume,
12069                 function (err) {
12070                     if (err) {
12071                         log.error(err, 'Unknown error deleting volumes: '
12072                             + err.message);
12073                         cb(err);
12074                     } else {
12075                         log.info('successfully deleted volumes');
12076                         cb();
12077                     }
12078                 }
12079             );
12080         }, function (cb) {
12081             var disks = [];
12082             var matches;
12083             var n;
12084             var p;
12085             var used_disk_indexes = [];
12086 
12087             // create any new volumes we need.
12088             if (payload.hasOwnProperty('add_disks')) {
12089                 disks = payload.add_disks;
12090             }
12091 
12092             // create a list of used indexes so we can find the free ones to
12093             // use in createVolume()
12094             if (vmobj.hasOwnProperty('disks')) {
12095                 for (n in vmobj.disks) {
12096                     matches = vmobj.disks[n].path.match(/^.*-disk(\d+)$/);
12097                     if (matches) {
12098                         used_disk_indexes.push(Number(matches[1]));
12099                     }
12100                 }
12101             }
12102 
12103             // add the bits of payload createVolumes() needs.
12104             p = {'add_disks': disks};
12105             p.uuid = uuid;
12106             if (vmobj.hasOwnProperty('zpool')) {
12107                 p.zpool = vmobj.zpool;
12108             }
12109             p.used_disk_indexes = used_disk_indexes;
12110             createVolumes(p, log, function (e) {
12111                 cb(e);
12112             });
12113         }, function (cb) {
12114             updateMetadata(vmobj, payload, log, function (e) {
12115                 cb(e);
12116             });
12117         }, function (cb) {
12118             updateRoutes(vmobj, payload, log, function (e) {
12119                 cb(e);
12120             });
12121         }, function (cb) {
12122             var zcfg;
12123             // generate a payload and send as a file to zonecfg to update
12124             // the zone.
12125             zcfg = buildZonecfgUpdate(vmobj, payload, log);
12126             zonecfgFile(zcfg, ['-u', uuid], log, function (e, fds) {
12127                 if (e) {
12128                     log.error({err: e, stdout: fds.stdout, stderr: fds.stderr},
12129                         'unable to update zonecfg');
12130                 } else {
12131                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
12132                         'updated zonecfg');
12133                 }
12134                 cb(e);
12135             });
12136         }, function (cb) {
12137             restartMetadataService(vmobj, payload, log, function (e) {
12138                 cb(e);
12139             });
12140         }, function (cb) {
12141             updateVnicProperties(uuid, vmobj, payload, log, function (e) {
12142                 cb(e);
12143             });
12144         }, function (cb) {
12145             // Update the firewall data
12146             updateFirewallData(payload, vmobj, log, cb);
12147         }, function (cb) {
12148             // Do another full reload (all fields) so we can compare in
12149             // applyUpdates() and decide what's changed that we need to apply.
12150             VM.load(uuid, {log: log}, function (e, newobj) {
12151                 if (e) {
12152                     cb(e);
12153                 } else {
12154                     new_vmobj = newobj;
12155                     cb();
12156                 }
12157             });
12158         }, function (cb) {
12159             applyUpdates(vmobj, new_vmobj, payload, log, function () {
12160                 cb();
12161             });
12162         }
12163     ], function (e) {
12164         callback(e);
12165     });
12166 };
12167 
12168 function kill(uuid, log, callback)
12169 {
12170     var load_fields;
12171     var unset_autoboot = 'set autoboot=false';
12172 
12173     assert(log, 'no logger passed to kill()');
12174 
12175     log.info('Killing VM ' + uuid);
12176 
12177     load_fields = [
12178         'brand',
12179         'state',
12180         'transition_to',
12181         'uuid'
12182     ];
12183 
12184     /* We load here to ensure this vm exists. */
12185     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
12186         if (err) {
12187             callback(err);
12188             return;
12189         }
12190 
12191         if (BRAND_OPTIONS[vmobj.brand].features.use_vm_autoboot) {
12192             unset_autoboot =
12193                 'select attr name=vm-autoboot; set value=false; end';
12194         }
12195 
12196         zoneadm(['-u', uuid, 'halt', '-X'], log, function (e, fds) {
12197             var msg = trim(fds.stderr);
12198 
12199             if (msg.match(/zone is already halted$/)) {
12200                 // remove transition marker, since vm is not running now.
12201                 VM.unsetTransition(vmobj, {log: log}, function () {
12202                     var new_err;
12203 
12204                     new_err = new Error('VM ' + vmobj.uuid + ' is already '
12205                         + 'not \'running\' (currently: ' + vmobj.state + ')');
12206                     new_err.code = 'ENOTRUNNING';
12207                     callback(new_err);
12208                 });
12209             } else if (e) {
12210                 log.error({err: e, stdout: fds.stdout, stderr: fds.stderr},
12211                     'failed to halt VM ' + uuid);
12212                 callback(err, msg);
12213             } else {
12214                 log.debug({stdout: fds.stdout, stderr: fds.stderr},
12215                     'zoneadm halted VM ' + uuid);
12216                 zonecfg(['-u', uuid, unset_autoboot], log,
12217                     function (error, unset_fds) {
12218 
12219                     if (error) {
12220                         // The vm is dead at this point, erroring out here would
12221                         // do no good, so we just log it.
12222                         log.error({err: error, stdout: unset_fds.stdout,
12223                             stderr: unset_fds.stderr}, 'killVM(): Failed to '
12224                             + unset_autoboot);
12225                     } else {
12226                         log.debug({stdout: unset_fds.stdout,
12227                             stderr: unset_fds.stderr}, 'unset autoboot flag');
12228                     }
12229                     if (vmobj.state === 'stopping') {
12230                         // remove transition marker
12231                         VM.unsetTransition(vmobj, {log: log}, function () {
12232                             callback(null, msg);
12233                         });
12234                     } else {
12235                         callback(null, msg);
12236                     }
12237                 });
12238             }
12239         });
12240     });
12241 }
12242 
12243 function postVmadmd(uuid, action, args, log, callback)
12244 {
12245     var arg;
12246     var url_path = '/vm/' + uuid + '?action=' + action;
12247     var req;
12248 
12249     assert(log, 'no logger passed to postVmadmd()');
12250 
12251     if (args) {
12252         for (arg in args) {
12253             if (args.hasOwnProperty(arg)) {
12254                 url_path = url_path + '&' + arg + '=' + args[arg];
12255             }
12256         }
12257     }
12258 
12259     log.debug('HTTP POST ' + url_path);
12260     req = http.request(
12261         { method: 'POST', host: '127.0.0.1', port: '8080', path: url_path },
12262         function (res) {
12263 
12264             log.debug('HTTP STATUS: ' + res.statusCode);
12265             log.debug('HTTP HEADERS: ' + JSON.stringify(res.headers));
12266             res.setEncoding('utf8');
12267             res.on('data', function (chunk) {
12268                 log.debug('HTTP BODY: ' + chunk);
12269             });
12270             res.on('end', function () {
12271                 log.debug('HTTP conversation has completed.');
12272                 callback();
12273             });
12274         }
12275     );
12276     req.on('error', function (e) {
12277         log.error(e, 'HTTP error: ' + e.message);
12278         callback(e);
12279     });
12280     req.end();
12281 }
12282 
12283 // options parameter is *REQUIRED* for VM.stop()
12284 exports.stop = function (uuid, options, callback)
12285 {
12286     var load_fields;
12287     var log;
12288     var unset_autoboot = 'set autoboot=false';
12289     var vmobj;
12290 
12291     load_fields = [
12292         'brand',
12293         'state',
12294         'uuid',
12295         'zonename'
12296     ];
12297 
12298     if (!options) {
12299         options = {};
12300     }
12301 
12302     if (options.hasOwnProperty('force') && options.force) {
12303         ensureLogging(true);
12304         if (options.hasOwnProperty('log')) {
12305             log = options.log;
12306         } else {
12307             log = VM.log.child({action: 'stop-F', vm: uuid});
12308         }
12309         kill(uuid, log, callback);
12310         return;
12311     } else {
12312         ensureLogging(true);
12313         if (options.hasOwnProperty('log')) {
12314             log = options.log;
12315         } else {
12316             log = VM.log.child({action: 'stop', vm: uuid});
12317         }
12318     }
12319 
12320     log.info('Stopping VM ' + uuid);
12321 
12322     if (!options.timeout) {
12323         options.timeout = 180;
12324     }
12325     if (!options.transition_to) {
12326         options.transition_to = 'stopped';
12327     }
12328 
12329     async.series([
12330         function (cb) {
12331             /* We load here to ensure this vm exists. */
12332             VM.load(uuid, {log: log, fields: load_fields}, function (err, obj) {
12333                 var new_err;
12334 
12335                 if (err) {
12336                     log.error(err);
12337                     cb(err);
12338                     return;
12339                 } else {
12340                     vmobj = obj;
12341                     if (vmobj.state !== 'running') {
12342                         new_err = new Error('VM ' + vmobj.uuid + ' is already '
12343                             + 'not \'running\' (currently: ' + vmobj.state
12344                             + ')');
12345                         new_err.code = 'ENOTRUNNING';
12346                         cb(new_err);
12347                     } else {
12348                         cb();
12349                     }
12350                 }
12351             });
12352         }, function (cb) {
12353             // When stopping a VM that uses vm_autoboot, we assume we also do
12354             // the stop through vmadmd.
12355             if (BRAND_OPTIONS[vmobj.brand].features.use_vm_autoboot) {
12356                 async.series([
12357                     function (callbk) {
12358                         setTransition(vmobj, 'stopping', options.transition_to,
12359                             (options.timeout * 1000), log, function (err) {
12360 
12361                             callbk(err);
12362                         });
12363                     }, function (callbk) {
12364                         postVmadmd(vmobj.uuid, 'stop',
12365                             {'timeout': options.timeout}, log, function (err) {
12366 
12367                             if (err) {
12368                                 log.error(err);
12369                                 err.message = 'Unable to post "stop" to vmadmd:'
12370                                     + ' ' + err.message;
12371                             }
12372                             callbk(err);
12373                         });
12374                     }, function (callbk) {
12375 
12376                         // different version for VMs
12377                         unset_autoboot = 'select attr name=vm-autoboot; '
12378                             + 'set value=false; end';
12379 
12380                         zonecfg(['-u', uuid, unset_autoboot], log,
12381                             function (err, fds) {
12382                                 if (err) {
12383                                     // The vm is dead at this point, failing
12384                                     // here would do no good, so we just log it.
12385                                     log.error({err: err, stdout: fds.stdout,
12386                                         stderr: fds.stderr}, 'stop(): Failed to'
12387                                         + ' ' + unset_autoboot + ' for ' + uuid
12388                                         + ': ' + err.message);
12389                                 } else {
12390                                     log.info({stdout: fds.stdout,
12391                                         stderr: fds.stderr}, 'Stopped ' + uuid);
12392                                 }
12393                                 callbk();
12394                             }
12395                         );
12396                     }
12397                 ], function (err) {
12398                     cb(err);
12399                 });
12400             } else { // no vm_autoboot / vmadmd stop
12401                 cb();
12402             }
12403         }, function (cb) {
12404             var args;
12405 
12406             // joyent brand specific stuff
12407             args = [vmobj.zonename, '/usr/sbin/shutdown', '-y', '-g', '0',
12408                 '-i', '5'];
12409 
12410             // not using vm_autoboot means using the 'normal' boot process
12411             if (!BRAND_OPTIONS[vmobj.brand].features.use_vm_autoboot) {
12412                 async.series([
12413                     function (callbk) {
12414                         log.debug('/usr/sbin/zlogin ' + args.join(' '));
12415                         execFile('/usr/sbin/zlogin', args,
12416                             function (err, stdout, stderr) {
12417 
12418                             if (err) {
12419                                 log.error({'err': err, 'stdout': stdout,
12420                                     'stderr': stderr}, 'zlogin for '
12421                                     + vmobj.zonename + ' exited with code'
12422                                     + err.code + ': ' + err.message);
12423                                 callbk(err);
12424                             } else {
12425                                 log.debug('zlogin claims to have worked, '
12426                                     + 'stdout:\n' + stdout + '\nstderr:\n'
12427                                     + stderr);
12428                                 callbk();
12429                             }
12430                         });
12431                     }, function (callbk) {
12432                         zonecfg(['-u', uuid, unset_autoboot], log,
12433                             function (err, fds) {
12434                                 if (err) {
12435                                     // The vm is dead at this point, failing
12436                                     // do no good, so we just log it.
12437                                     log.warn({err: err, stdout: fds.stdout,
12438                                         stderr: fds.stderr}, 'Failed to '
12439                                         + unset_autoboot + ' for ' + uuid);
12440                                 } else {
12441                                     log.info({stdout: fds.stdout,
12442                                         stderr: fds.stderr}, 'Stopped ' + uuid);
12443                                 }
12444                                 callbk();
12445                             }
12446                         );
12447                     }
12448                 ], function (err) {
12449                     cb(err);
12450                 });
12451             } else { // using vmautoboot so won't shutdown from in the zone
12452                 cb();
12453             }
12454         }, function (cb) {
12455             // Verify it's shut down
12456             VM.waitForZoneState(vmobj, 'installed', {log: log},
12457                 function (err, result) {
12458 
12459                 if (err) {
12460                     if (err.code === 'ETIMEOUT') {
12461                         log.info(err, 'timeout waiting for zone to go to '
12462                             + '"installed"');
12463                     } else {
12464                         log.error(err, 'unknown error waiting for zone to go'
12465                             + ' "installed"');
12466                     }
12467                     cb(err);
12468                 } else {
12469                     // zone got to stopped
12470                     log.info('VM seems to have switched to "installed"');
12471                     cb();
12472                 }
12473             });
12474         }
12475     ], function (err) {
12476         callback(err);
12477     });
12478 };
12479 
12480 // sends several query-* commands to QMP to get details for a VM
12481 exports.info = function (uuid, types, options, callback)
12482 {
12483     var load_fields;
12484     var log;
12485 
12486     // options is optional
12487     if (arguments.length === 3) {
12488         callback = arguments[2];
12489         options = {};
12490     }
12491 
12492     ensureLogging(false);
12493     if (options.hasOwnProperty('log')) {
12494         log = options.log;
12495     } else {
12496         log = VM.log.child({action: 'info', vm: uuid});
12497     }
12498 
12499     load_fields = [
12500         'brand',
12501         'state',
12502         'uuid'
12503     ];
12504 
12505     // load to ensure we're a VM
12506     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
12507         var type;
12508 
12509         if (err) {
12510             callback(err);
12511             return;
12512         }
12513 
12514         if (!BRAND_OPTIONS[vmobj.brand].features.runtime_info) {
12515             //  XXX if support is added to other brands, update this message.
12516             callback(new Error('the info command is only supported for KVM '
12517                 + 'VMs'));
12518             return;
12519         }
12520 
12521         if (vmobj.state !== 'running' && vmobj.state !== 'stopping') {
12522             callback(new Error('Unable to get info for vm from state "'
12523                 + vmobj.state + '", must be "running" or "stopping".'));
12524             return;
12525         }
12526 
12527         if (!types) {
12528             types = ['all'];
12529         }
12530 
12531         for (type in types) {
12532             type = types[type];
12533             if (VM.INFO_TYPES.indexOf(type) === -1) {
12534                 callback(new Error('unknown info type: ' + type));
12535                 return;
12536             }
12537         }
12538 
12539         http.get({ host: '127.0.0.1', port: 8080, path: '/vm/' + uuid + '/info'
12540             + '?types=' + types.join(',') }, function (res) {
12541 
12542                 var data = '';
12543 
12544                 if (res.statusCode !== 200) {
12545                     callback(new Error('Unable to get info from vmadmd, query '
12546                         + 'returned ' + res.statusCode + '.'));
12547                 } else {
12548                     res.on('data', function (d) {
12549                         data = data + d.toString();
12550                     });
12551                     res.on('end', function (d) {
12552                         callback(null, JSON.parse(data));
12553                     });
12554                 }
12555             }
12556         ).on('error', function (e) {
12557             log.error(e);
12558             callback(e);
12559         });
12560     });
12561 };
12562 
12563 function reset(uuid, log, callback)
12564 {
12565     var load_fields;
12566 
12567     assert(log, 'no logger passed to reset()');
12568 
12569     log.info('Resetting VM ' + uuid);
12570 
12571     load_fields = [
12572         'brand',
12573         'state',
12574         'uuid'
12575     ];
12576 
12577     /* We load here to ensure this vm exists. */
12578     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
12579         if (err) {
12580             callback(err);
12581             return;
12582         }
12583 
12584         if (vmobj.state !== 'running') {
12585             callback(new Error('Cannot reset vm from state "'
12586                 + vmobj.state + '", must be "running".'));
12587             return;
12588         }
12589 
12590         if (BRAND_OPTIONS[vmobj.brand].features.use_vmadmd) {
12591             postVmadmd(vmobj.uuid, 'reset', {}, log, function (e) {
12592                 if (e) {
12593                     callback(new Error('Unable to post "reset" to '
12594                         + 'vmadmd: ' + e.message));
12595                 } else {
12596                     callback();
12597                 }
12598             });
12599         } else {
12600             zoneadm(['-u', vmobj.uuid, 'reboot', '-X'], log, function (e, fds) {
12601                 if (e) {
12602                     log.warn({err: e, stdout: fds.stdout, stderr: fds.stderr},
12603                         'zoneadm failed to reboot VM ' + vmobj.uuid);
12604                     callback(new Error(rtrim(fds.stderr)));
12605                 } else {
12606                     log.debug({stdout: fds.stdout, stderr: fds.stderr},
12607                         'zoneadm rebooted VM ' + vmobj.uuid);
12608                     callback();
12609                 }
12610             });
12611         }
12612     });
12613 }
12614 
12615 // options is *REQUIRED* for VM.reboot()
12616 exports.reboot = function (uuid, options, callback)
12617 {
12618     var cleanup;
12619     var log;
12620     var reboot_async = false;
12621     var reboot_complete = false;
12622     var vmobj;
12623 
12624     if (options.hasOwnProperty('log')) {
12625         log = options.log;
12626     }
12627 
12628     if (options.hasOwnProperty('force') && options.force) {
12629         ensureLogging(true);
12630         if (!log) {
12631             log = VM.log.child({action: 'reboot-F', vm: uuid});
12632         }
12633         reset(uuid, log, callback);
12634         return;
12635     } else {
12636         ensureLogging(true);
12637         log = VM.log.child({action: 'reboot', vm: uuid});
12638     }
12639 
12640     log.info('Rebooting VM ' + uuid);
12641 
12642     if (!options) {
12643         options = {};
12644     }
12645 
12646     async.series([
12647         function (cb) {
12648             var load_fields = [
12649                 'brand',
12650                 'nics',
12651                 'state',
12652                 'zonename'
12653             ];
12654 
12655             VM.load(uuid, {fields: load_fields, log: log},
12656                 function (err, obj) {
12657 
12658                 if (err) {
12659                     cb(err);
12660                     return;
12661                 }
12662 
12663                 if (obj.state !== 'running') {
12664                     cb(new Error('Cannot reboot vm from state "' + obj.state
12665                         + '", must be "running"'));
12666                     return;
12667                 }
12668 
12669                 vmobj = obj;
12670                 cb();
12671             });
12672         }, function (cb) {
12673             // If nic tags have disappeared out from under us, don't allow a
12674             // reboot that will put us into a bad state
12675             lookupInvalidNicTags(vmobj.nics, log, function (e) {
12676                 if (e) {
12677                     cb(new Error('Cannot reboot vm: ' + e.message));
12678                     return;
12679                 }
12680 
12681                 cb();
12682             });
12683 
12684         }, function (cb) {
12685             var watcherobj;
12686 
12687             if (!reboot_async) {
12688                 watcherobj = watchZoneTransitions(function (err, ze) {
12689                     if (!err && ze.zonename !== vmobj.zonename) {
12690                         // not something we need to handle
12691                         return;
12692                     }
12693 
12694                     if (err) {
12695                         // XXX what should we do here?
12696                         log.error(err);
12697                         return;
12698                     }
12699 
12700                     log.debug(ze); // TODO move to trace
12701 
12702                     if (ze.newstate === 'running'
12703                         && ze.oldstate !== 'running') {
12704 
12705                         if (watcherobj) {
12706                             // cleanup our watcher since we found what we're
12707                             // looking for.
12708                             cleanup();
12709                         }
12710 
12711                         reboot_complete = true;
12712                     }
12713                 }, log);
12714                 cleanup = watcherobj.cleanup;
12715             }
12716 
12717             cb();
12718         }, function (cb) {
12719             var args;
12720 
12721             if (BRAND_OPTIONS[vmobj.brand].features.use_vmadmd) {
12722                 // here we stop the machine and set a transition so vmadmd will
12723                 // start the machine once the stop finished.
12724                 options.transition_to = 'start';
12725                 options.log = log;
12726                 VM.stop(uuid, options, function (err) {
12727                     if (err) {
12728                         cb(err);
12729                     } else {
12730                         cb();
12731                     }
12732                 });
12733             } else {
12734                 // joyent branded zones
12735                 args = [vmobj.zonename, '/usr/sbin/shutdown', '-y', '-g', '0',
12736                     '-i', '6'];
12737                 log.debug('/usr/sbin/zlogin ' + args.join(' '));
12738                 execFile('/usr/sbin/zlogin', args,
12739                     function (err, stdout, stderr) {
12740                     if (err) {
12741                         log.error({'err': err, 'stdout': stdout,
12742                             'stderr': stderr}, 'zlogin for ' + vmobj.zonename
12743                             + ' exited with code' + err.code + ': '
12744                             + err.message);
12745                         cb(err);
12746                     } else {
12747                         cb();
12748                     }
12749                 });
12750             }
12751         }, function (cb) {
12752             var ival;
12753             var ticks = 0;
12754 
12755             if (reboot_async) {
12756                 cb();
12757                 return;
12758             } else {
12759                 ticks = 180 * 10; // (180 * 10) 100ms ticks = 3m
12760                 ival = setInterval(function () {
12761                     if (reboot_complete) {
12762                         log.debug('reboot marked complete, cleaning up');
12763                         clearInterval(ival);
12764                         cleanup();
12765                         cb();
12766                         return;
12767                     }
12768                     ticks--;
12769                     if (ticks <= 0) {
12770                         // timed out
12771                         log.debug('reboot timed out, cleaning up');
12772                         clearInterval(ival);
12773                         cleanup();
12774                         cb(new Error('timed out waiting for zone to reboot'));
12775                         return;
12776                     }
12777                 }, 100);
12778             }
12779         }
12780     ], function (err) {
12781         callback(err);
12782     });
12783 };
12784 
12785 // options is *REQUIRED* for VM.sysrq
12786 exports.sysrq = function (uuid, req, options, callback)
12787 {
12788     var load_fields;
12789     var log;
12790 
12791     ensureLogging(true);
12792 
12793     if (options.hasOwnProperty('log')) {
12794         log = options.log;
12795     } else {
12796         log = VM.log.child({action: 'sysrq-' + req, vm: uuid});
12797     }
12798 
12799     log.info('Sending sysrq "' + req + '" to ' + uuid);
12800 
12801     load_fields = [
12802         'brand',
12803         'state',
12804         'uuid'
12805     ];
12806 
12807     /* We load here to ensure this vm exists. */
12808     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
12809         if (err) {
12810             callback(err);
12811             return;
12812         }
12813 
12814         if (vmobj.state !== 'running' && vmobj.state !== 'stopping') {
12815             callback(new Error('Unable to send request to vm from state "'
12816                 + vmobj.state + '", must be "running" or "stopping".'));
12817             return;
12818         }
12819 
12820         if (BRAND_OPTIONS[vmobj.brand].features.type !== 'KVM') {
12821             callback(new Error('The sysrq command is only supported for KVM.'));
12822             return;
12823         }
12824 
12825         if (VM.SYSRQ_TYPES.indexOf(req) === -1) {
12826             callback(new Error('Invalid sysrq "' + req + '" valid values: '
12827                 + '"' + VM.SYSRQ_TYPES.join('","') + '".'));
12828             return;
12829         }
12830 
12831         postVmadmd(vmobj.uuid, 'sysrq', {'request': req}, log, function (e) {
12832             if (e) {
12833                 callback(new Error('Unable to post "sysrq" to vmadmd: '
12834                     + e.message));
12835             } else {
12836                 callback();
12837             }
12838         });
12839     });
12840 };
12841 
12842 exports.console = function (uuid, options, callback)
12843 {
12844     var load_fields;
12845     var log;
12846 
12847     // options is optional
12848     if (arguments.length === 2) {
12849         callback = arguments[1];
12850         options = {};
12851     }
12852 
12853     ensureLogging(false);
12854     if (options.hasOwnProperty('log')) {
12855         log = options.log;
12856     } else {
12857         log = VM.log.child({action: 'console', vm: uuid});
12858     }
12859 
12860     load_fields = [
12861         'brand',
12862         'state',
12863         'zonename',
12864         'zonepath'
12865     ];
12866 
12867     VM.load(uuid, {fields: load_fields, log: log}, function (err, vmobj) {
12868         var args;
12869         var child;
12870         var cmd;
12871         var stty;
12872 
12873         if (err) {
12874             callback(err);
12875             return;
12876         }
12877         if (vmobj.state !== 'running') {
12878             callback(new Error('cannot connect to console when state is '
12879                 + '"' + vmobj.state + '" must be "running".'));
12880             return;
12881         }
12882 
12883         if (BRAND_OPTIONS[vmobj.brand].features.zlogin_console) {
12884             cmd = '/usr/sbin/zlogin';
12885             args = ['-C', '-e', '\\035', vmobj.zonename];
12886 
12887             log.debug(cmd + ' ' + args.join(' '));
12888             child = spawn(cmd, args, {customFds: [0, 1, 2]});
12889             child.on('close', function (code) {
12890                 log.debug('zlogin process exited with code ' + code);
12891                 callback();
12892             });
12893         } else if (BRAND_OPTIONS[vmobj.brand].features.serial_console) {
12894             async.series([
12895                 function (cb) {
12896                     cmd = '/usr/bin/stty';
12897                     args = ['-g'];
12898                     stty = '';
12899 
12900                     log.debug(cmd + ' ' + args.join(' '));
12901                     child = spawn(cmd, args, {customFds: [0, -1, -1]});
12902                     child.stdout.on('data', function (data) {
12903                         // log.debug('data: ' + data.toString());
12904                         stty = data.toString();
12905                     });
12906                     child.on('close', function (code) {
12907                         log.debug('stty process exited with code ' + code);
12908                         cb();
12909                     });
12910                 }, function (cb) {
12911                     cmd = '/usr/bin/socat';
12912                     args = ['unix-client:' + vmobj.zonepath
12913                         + '/root/tmp/vm.console', '-,raw,echo=0,escape=0x1d'];
12914 
12915                     log.debug(cmd + ' ' + args.join(' '));
12916                     child = spawn(cmd, args, {customFds: [0, 1, 2]});
12917                     child.on('close', function (code) {
12918                         log.debug('zlogin process exited with code ' + code);
12919                         cb();
12920                     });
12921                 }, function (cb) {
12922                     cmd = '/usr/bin/stty';
12923                     args = [stty];
12924 
12925                     log.debug(cmd + ' ' + args.join(' '));
12926                     child = spawn(cmd, args, {customFds: [0, -1, -1]});
12927                     child.on('close', function (code) {
12928                         log.debug('stty process exited with code ' + code);
12929                         cb();
12930                     });
12931                 }
12932             ], function (e, results) {
12933                 callback(e);
12934             });
12935         } else {
12936             callback(new Error('Cannot get console for brand: ' + vmobj.brand));
12937         }
12938     });
12939 };