Print this page
OS-2547 VM.js should protect writes to configuration files with a lockfile

Split Close
Expand all
Collapse all
          --- old/src/vm/node_modules/VM.js
          +++ new/src/vm/node_modules/VM.js
↓ open down ↓ 59 lines elided ↑ open up ↑
  60   60   */
  61   61  
  62   62  // Ensure we're using the platform's node
  63   63  require('/usr/node/node_modules/platform_node_version').assert();
  64   64  
  65   65  var assert = require('assert');
  66   66  var async = require('/usr/node/node_modules/async');
  67   67  var bunyan = require('/usr/node/node_modules/bunyan');
  68   68  var cp = require('child_process');
  69   69  var dladm = require('/usr/vm/node_modules/dladm');
       70 +var lock = require('/usr/vm/node_modules/locker').lock;
  70   71  var EventEmitter = require('events').EventEmitter;
  71   72  var exec = cp.exec;
  72   73  var execFile = cp.execFile;
  73   74  var expat = require('/usr/node/node_modules/node-expat');
  74   75  var fs = require('fs');
  75   76  var fw = require('/usr/fw/lib/fw');
  76   77  var http = require('http');
  77   78  var net = require('net');
  78   79  var path = require('path');
  79   80  var Qmp = require('/usr/vm/node_modules/qmp').Qmp;
↓ open down ↓ 4138 lines elided ↑ open up ↑
4218 4219      // create all the volumes we found that we need.
4219 4220      async.forEachSeries(createme, loggedCreateVolume, function (err) {
4220 4221          if (err) {
4221 4222              callback(err);
4222 4223          } else {
4223 4224              callback();
4224 4225          }
4225 4226      });
4226 4227  }
4227 4228  
     4229 +function writeAndRename(log, name, destfile, file_data, callback)
     4230 +{
     4231 +    var tempfile = destfile + '.new';
     4232 +
     4233 +    log.debug('writing ' + name + ' to ' + tempfile);
     4234 +
     4235 +    fs.writeFile(tempfile, file_data, function (err) {
     4236 +        if (err) {
     4237 +            callback(err);
     4238 +            return;
     4239 +        }
     4240 +
     4241 +        log.debug('wrote ' + name + ' to ' + tempfile);
     4242 +        log.debug('renaming from ' + tempfile + ' to ' + destfile);
     4243 +
     4244 +        fs.rename(tempfile, destfile, function (_err) {
     4245 +            if (_err) {
     4246 +                callback(_err);
     4247 +                return;
     4248 +            }
     4249 +
     4250 +            log.debug('renamed from ' + tempfile + ' to ' + destfile);
     4251 +            callback();
     4252 +        });
     4253 +    });
     4254 +}
     4255 +
4228 4256  // writes a Zone's metadata JSON to /zones/<uuid>/config/metadata.json
4229 4257  // and /zones/<uuid>/config/tags.json.
4230 4258  function updateMetadata(vmobj, payload, log, callback)
4231 4259  {
4232 4260      var cmdata = {};
4233 4261      var imdata = {};
4234 4262      var key;
4235 4263      var mdata = {};
4236 4264      var mdata_filename;
4237 4265      var tags = {};
↓ open down ↓ 69 lines elided ↑ open up ↑
4307 4335          }
4308 4336      }
4309 4337  
4310 4338      for (key in payload.set_tags) {
4311 4339          if (payload.set_tags.hasOwnProperty(key)) {
4312 4340              tags[key] = payload.set_tags[key];
4313 4341          }
4314 4342      }
4315 4343  
4316 4344      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      -            }
     4345 +
     4346 +    async.series([
     4347 +        function (next) {
     4348 +            writeAndRename(log, 'metadata', mdata_filename,
     4349 +                JSON.stringify(mdata, null, 2), next);
     4350 +        },
     4351 +        function (next) {
     4352 +            writeAndRename(log, 'tags', tags_filename,
     4353 +                JSON.stringify(tags, null, 2), next);
4334 4354          }
4335      -    );
     4355 +    ], callback);
4336 4356  }
4337 4357  
4338 4358  function saveMetadata(payload, log, callback)
4339 4359  {
4340 4360      var protovm = {};
4341 4361  
4342 4362      assert(log, 'no logger passed to saveMetadata()');
4343 4363  
4344 4364      if (!payload.hasOwnProperty('zonepath')
4345 4365          || !payload.hasOwnProperty('zpool')
↓ open down ↓ 7652 lines elided ↑ open up ↑
11998 12018          log.debug('done applying updates to ' + oldobj.uuid);
11999 12019          callback(err);
12000 12020      });
12001 12021  }
12002 12022  
12003 12023  exports.update = function (uuid, payload, options, callback)
12004 12024  {
12005 12025      var log;
12006 12026      var new_vmobj;
12007 12027      var vmobj;
     12028 +    var unlock;
     12029 +    var lockpath;
12008 12030  
12009 12031      // options parameter is optional
12010 12032      if (arguments.length === 3) {
12011 12033          callback = arguments[2];
12012 12034          options = {};
12013 12035      }
12014 12036  
12015 12037      ensureLogging(true);
12016 12038      if (options.hasOwnProperty('log')) {
12017 12039          log = options.log;
12018 12040      } else {
12019 12041          log = VM.log.child({action: 'update', vm: uuid});
12020 12042      }
12021 12043  
12022 12044      log.info('Updating VM ' + uuid + ' with initial payload:\n'
12023 12045          + JSON.stringify(payload, null, 2));
12024 12046  
12025 12047      async.series([
12026 12048          function (cb) {
     12049 +            lockpath = '/var/run/vm.' + uuid + '.config.lockfile';
     12050 +            log.debug('acquiring lock on ' + lockpath);
     12051 +            lock(lockpath, function (err, _unlock) {
     12052 +                log.debug('acquired lock on ' + lockpath);
     12053 +                if (err) {
     12054 +                    cb(err);
     12055 +                    return;
     12056 +                }
     12057 +                unlock = _unlock;
     12058 +                cb();
     12059 +            });
     12060 +        },
     12061 +        function (cb) {
12027 12062              // for update we currently always load the whole vmobj since the
12028 12063              // update functions may need to look at bits from the existing VM.
12029 12064              VM.load(uuid, {log: log}, function (err, obj) {
12030 12065                  if (err) {
12031 12066                      cb(err);
12032 12067                      return;
12033 12068                  }
12034 12069                  vmobj = obj;
12035 12070                  cb();
12036 12071              });
↓ open down ↓ 117 lines elided ↑ open up ↑
12154 12189                      new_vmobj = newobj;
12155 12190                      cb();
12156 12191                  }
12157 12192              });
12158 12193          }, function (cb) {
12159 12194              applyUpdates(vmobj, new_vmobj, payload, log, function () {
12160 12195                  cb();
12161 12196              });
12162 12197          }
12163 12198      ], function (e) {
12164      -        callback(e);
     12199 +        // If we were able to hold the lockfile, and thus have an unlock
     12200 +        // callback, we must call it before returning, whether or not
     12201 +        // there was an error.
     12202 +        if (unlock) {
     12203 +            log.debug('releasing lock on ' + lockpath);
     12204 +            unlock(function (unlock_err) {
     12205 +                if (unlock_err) {
     12206 +                    log.error(err, 'unlock error! (path ' + lockpath + ')');
     12207 +                } else {
     12208 +                    log.debug('released lock on ' + lockpath);
     12209 +                }
     12210 +                callback(e);
     12211 +            });
     12212 +        } else {
     12213 +            callback(e);
     12214 +        }
12165 12215      });
12166 12216  };
12167 12217  
12168 12218  function kill(uuid, log, callback)
12169 12219  {
12170 12220      var load_fields;
12171 12221      var unset_autoboot = 'set autoboot=false';
12172 12222  
12173 12223      assert(log, 'no logger passed to kill()');
12174 12224  
↓ open down ↓ 765 lines elided ↑ open up ↑
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX