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

*** 65,74 **** --- 65,75 ---- var assert = require('assert'); var async = require('/usr/node/node_modules/async'); var bunyan = require('/usr/node/node_modules/bunyan'); var cp = require('child_process'); var dladm = require('/usr/vm/node_modules/dladm'); + var lock = require('/usr/vm/node_modules/locker').lock; var EventEmitter = require('events').EventEmitter; var exec = cp.exec; var execFile = cp.execFile; var expat = require('/usr/node/node_modules/node-expat'); var fs = require('fs');
*** 4223,4232 **** --- 4224,4260 ---- callback(); } }); } + function writeAndRename(log, name, destfile, file_data, callback) + { + var tempfile = destfile + '.new'; + + log.debug('writing ' + name + ' to ' + tempfile); + + fs.writeFile(tempfile, file_data, function (err) { + if (err) { + callback(err); + return; + } + + log.debug('wrote ' + name + ' to ' + tempfile); + log.debug('renaming from ' + tempfile + ' to ' + destfile); + + fs.rename(tempfile, destfile, function (_err) { + if (_err) { + callback(_err); + return; + } + + log.debug('renamed from ' + tempfile + ' to ' + destfile); + callback(); + }); + }); + } + // writes a Zone's metadata JSON to /zones/<uuid>/config/metadata.json // and /zones/<uuid>/config/tags.json. function updateMetadata(vmobj, payload, log, callback) { var cmdata = {};
*** 4312,4340 **** tags[key] = payload.set_tags[key]; } } mdata = {'customer_metadata': cmdata, 'internal_metadata': imdata}; ! fs.writeFile(mdata_filename, JSON.stringify(mdata, null, 2), ! function (err) { ! if (err) { ! callback(err); ! } else { ! log.debug('wrote metadata to ' + mdata_filename); ! fs.writeFile(tags_filename, JSON.stringify(tags, null, 2), ! function (e) { ! if (e) { ! callback(e); ! } else { ! log.debug('wrote tags to' + tags_filename); ! callback(); ! } ! } ! ); ! } } ! ); } function saveMetadata(payload, log, callback) { var protovm = {}; --- 4340,4360 ---- tags[key] = payload.set_tags[key]; } } mdata = {'customer_metadata': cmdata, 'internal_metadata': imdata}; ! ! async.series([ ! function (next) { ! writeAndRename(log, 'metadata', mdata_filename, ! JSON.stringify(mdata, null, 2), next); ! }, ! function (next) { ! writeAndRename(log, 'tags', tags_filename, ! JSON.stringify(tags, null, 2), next); } ! ], callback); } function saveMetadata(payload, log, callback) { var protovm = {};
*** 12003,12012 **** --- 12023,12034 ---- exports.update = function (uuid, payload, options, callback) { var log; var new_vmobj; var vmobj; + var unlock; + var lockpath; // options parameter is optional if (arguments.length === 3) { callback = arguments[2]; options = {};
*** 12022,12031 **** --- 12044,12066 ---- log.info('Updating VM ' + uuid + ' with initial payload:\n' + JSON.stringify(payload, null, 2)); async.series([ function (cb) { + lockpath = '/var/run/vm.' + uuid + '.config.lockfile'; + log.debug('acquiring lock on ' + lockpath); + lock(lockpath, function (err, _unlock) { + log.debug('acquired lock on ' + lockpath); + if (err) { + cb(err); + return; + } + unlock = _unlock; + cb(); + }); + }, + function (cb) { // for update we currently always load the whole vmobj since the // update functions may need to look at bits from the existing VM. VM.load(uuid, {log: log}, function (err, obj) { if (err) { cb(err);
*** 12159,12169 **** --- 12194,12219 ---- applyUpdates(vmobj, new_vmobj, payload, log, function () { cb(); }); } ], function (e) { + // If we were able to hold the lockfile, and thus have an unlock + // callback, we must call it before returning, whether or not + // there was an error. + if (unlock) { + log.debug('releasing lock on ' + lockpath); + unlock(function (unlock_err) { + if (unlock_err) { + log.error(err, 'unlock error! (path ' + lockpath + ')'); + } else { + log.debug('released lock on ' + lockpath); + } + callback(e); + }); + } else { callback(e); + } }); }; function kill(uuid, log, callback) {