mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-09-10 22:14:48 -05:00
288 lines
12 KiB
JavaScript
288 lines
12 KiB
JavaScript
var S = require("./structs.js"),
|
|
_ = require("./helpers.js");
|
|
|
|
function _baseChain(vol) {
|
|
var chain = {};
|
|
|
|
chain.sectorSize = vol._sectorSize;
|
|
|
|
function posFromOffset(off) {
|
|
var secSize = chain.sectorSize,
|
|
offset = off % secSize,
|
|
sector = (off - offset) / secSize;
|
|
return {sector:sector, offset:offset};
|
|
}
|
|
|
|
var sectorCache = vol._makeCache();
|
|
Object.defineProperty(chain, 'cacheAdvice', {
|
|
enumerable: true,
|
|
get: function () { return sectorCache.advice(); },
|
|
set: function (v) { sectorCache.advice(v); }
|
|
});
|
|
chain._vol_readSectors = vol._readSectors.bind(vol, sectorCache);
|
|
chain._vol_writeSectors = vol._writeSectors.bind(vol, sectorCache);
|
|
|
|
// cb(error, bytesRead, buffer)
|
|
chain.readFromPosition = function (targetPos, buffer, cb) {
|
|
if (typeof targetPos === 'number') targetPos = posFromOffset(targetPos);
|
|
if (typeof buffer === 'number') buffer = _.allocBuffer(buffer);
|
|
/* NOTE: to keep our contract with the volume driver, we need to read on _full_ sector boundaries!
|
|
So we divide the read into [up to] three parts: {preface, main, trailer}
|
|
This is kind of unfortunate, but in practice should often still be reasonably efficient. */
|
|
if (targetPos.offset) chain.readSectors(targetPos.sector, _.allocBuffer(chain.sectorSize), function (e,d) {
|
|
if (e || !d) cb(e, 0, buffer);
|
|
else { // copy preface into `buffer`
|
|
var dBeg = targetPos.offset,
|
|
dEnd = dBeg + buffer.length;
|
|
d.copy(buffer, 0, dBeg, dEnd);
|
|
if (dEnd > d.length) readMain();
|
|
else cb(null, buffer.length, buffer);
|
|
}
|
|
}); else readMain();
|
|
function readMain() {
|
|
var prefaceLen = targetPos.offset && (chain.sectorSize - targetPos.offset),
|
|
trailerLen = (buffer.length - prefaceLen) % chain.sectorSize,
|
|
mainSector = (prefaceLen) ? targetPos.sector + 1 : targetPos.sector,
|
|
mainBuffer = (trailerLen) ? buffer.slice(prefaceLen, -trailerLen) : buffer.slice(prefaceLen);
|
|
if (mainBuffer.length) chain.readSectors(mainSector, mainBuffer, function (e,d) {
|
|
if (e || !d) cb(e, prefaceLen, buffer);
|
|
else if (!trailerLen) cb(null, buffer.length, buffer);
|
|
else readTrailer();
|
|
}); else readTrailer();
|
|
function readTrailer() {
|
|
var trailerSector = mainSector + (mainBuffer.length / chain.sectorSize);
|
|
chain.readSectors(trailerSector, _.allocBuffer(chain.sectorSize), function (e,d) {
|
|
if (e || !d) cb(e, buffer.length-trailerLen, buffer);
|
|
else {
|
|
d.copy(buffer, buffer.length-trailerLen, 0, trailerLen);
|
|
cb(null, buffer.length, buffer);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
// cb(error)
|
|
chain.writeToPosition = function (targetPos, data, cb) {
|
|
_.log(_.log.DBG, "WRITING", data.length, "bytes at", targetPos, "in", this.toJSON(), data);
|
|
if (typeof targetPos === 'number') targetPos = posFromOffset(targetPos);
|
|
|
|
var prefaceBuffer = (targetPos.offset) ? data.slice(0, chain.sectorSize-targetPos.offset) : null;
|
|
if (prefaceBuffer) _modifySector(targetPos.sector, targetPos.offset, prefaceBuffer, function (e) {
|
|
if (e) cb(e);
|
|
else if (prefaceBuffer.length < data.length) writeMain();
|
|
else cb();
|
|
}); else writeMain();
|
|
function writeMain() {
|
|
var prefaceLen = (prefaceBuffer) ? prefaceBuffer.length : 0,
|
|
trailerLen = (data.length - prefaceLen) % chain.sectorSize,
|
|
mainSector = (prefaceLen) ? targetPos.sector + 1 : targetPos.sector,
|
|
mainBuffer = (trailerLen) ? data.slice(prefaceLen, -trailerLen) : data.slice(prefaceLen);
|
|
if (mainBuffer.length) chain.writeSectors(mainSector, mainBuffer, function (e) {
|
|
if (e) cb(e);
|
|
else if (!trailerLen) cb();
|
|
else writeTrailer();
|
|
}); else writeTrailer();
|
|
function writeTrailer() {
|
|
var trailerSector = mainSector + (mainBuffer.length / chain.sectorSize),
|
|
trailerBuffer = data.slice(data.length-trailerLen); // WORKAROUND: https://github.com/tessel/runtime/issues/721
|
|
_modifySector(trailerSector, 0, trailerBuffer, cb);
|
|
}
|
|
}
|
|
function _modifySector(sec, off, data, cb) {
|
|
chain.readSectors(sec, _.allocBuffer(chain.sectorSize), function (e, orig) {
|
|
if (e) return cb(e);
|
|
orig || (orig = _.allocBuffer(chain.sectorSize, 0));
|
|
data.copy(orig, off);
|
|
chain.writeSectors(sec, orig, cb);
|
|
});
|
|
}
|
|
};
|
|
|
|
return chain;
|
|
};
|
|
|
|
|
|
|
|
exports.clusterChain = function (vol, firstCluster, _parent) {
|
|
var chain = _baseChain(vol),
|
|
cache = [firstCluster];
|
|
|
|
chain.firstCluster = firstCluster;
|
|
|
|
function _cacheIsComplete() {
|
|
return cache[cache.length-1] === 'eof';
|
|
}
|
|
|
|
function extendCacheToInclude(i, cb) { // NOTE: may `cb()` before returning!
|
|
if (i < cache.length) cb(null, cache[i]);
|
|
else if (_cacheIsComplete()) cb(null, 'eof');
|
|
else vol.fetchFromFAT(cache[cache.length-1], function (e,d) {
|
|
if (e) cb(e);
|
|
else if (typeof d === 'string' && d !== 'eof') cb(S.err.IO());
|
|
else {
|
|
cache.push(d);
|
|
extendCacheToInclude(i, cb);
|
|
}
|
|
});
|
|
}
|
|
|
|
function expandChainToLength(clusterCount, cb) {
|
|
if (!_cacheIsComplete()) throw Error("Must be called only when cache is complete!");
|
|
else cache.pop(); // remove 'eof' entry until finished
|
|
|
|
function addCluster(clustersNeeded, lastCluster) {
|
|
if (!clustersNeeded) cache.push('eof'), cb();
|
|
else vol.allocateInFAT(lastCluster, function (e, newCluster) {
|
|
if (e) cb(e);
|
|
else vol.storeToFAT(lastCluster, newCluster, function (e) {
|
|
if (e) return cb(e);
|
|
|
|
cache.push(newCluster);
|
|
addCluster(clustersNeeded-1, newCluster);
|
|
});
|
|
});
|
|
}
|
|
addCluster(clusterCount - cache.length, cache[cache.length - 1]);
|
|
}
|
|
|
|
function shrinkChainToLength(clusterCount, cb) {
|
|
if (!_cacheIsComplete()) throw Error("Must be called only when cache is complete!");
|
|
else cache.pop(); // remove 'eof' entry until finished
|
|
|
|
function removeClusters(count, cb) {
|
|
if (!count) cache.push('eof'), cb();
|
|
else vol.storeToFAT(cache.pop(), 'free', function (e) {
|
|
if (e) cb(e);
|
|
else removeClusters(count - 1, cb);
|
|
});
|
|
}
|
|
// NOTE: for now, we don't remove the firstCluster ourselves; we should though!
|
|
if (clusterCount) removeClusters(cache.length - clusterCount, cb);
|
|
else removeClusters(cache.length - 1, cb);
|
|
}
|
|
|
|
// [{firstSector,numSectors},{firstSector,numSectors},…]
|
|
function determineSectorGroups(sectorIdx, numSectors, alloc, cb) {
|
|
var sectorOffset = sectorIdx % vol._sectorsPerCluster,
|
|
clusterIdx = (sectorIdx - sectorOffset) / vol._sectorsPerCluster,
|
|
numClusters = Math.ceil((numSectors + sectorOffset) / vol._sectorsPerCluster),
|
|
chainLength = clusterIdx + numClusters;
|
|
extendCacheToInclude(chainLength-1, function (e,c) {
|
|
if (e) cb(e);
|
|
else if (c === 'eof' && alloc) expandChainToLength(chainLength, function (e) {
|
|
if (e) cb(e);
|
|
else _determineSectorGroups();
|
|
});
|
|
else _determineSectorGroups();
|
|
});
|
|
function _determineSectorGroups() {
|
|
// …now we have a complete cache
|
|
var groups = [],
|
|
_group = null;
|
|
for (var i = clusterIdx; i < chainLength; ++i) {
|
|
var c = (i < cache.length) ? cache[i] : 'eof';
|
|
if (c === 'eof') break;
|
|
else if (_group && c !== _group._nextCluster) {
|
|
groups.push(_group);
|
|
_group = null;
|
|
}
|
|
if (!_group) _group = {
|
|
_nextCluster: c+1,
|
|
firstSector: vol._firstSectorOfCluster(c) + sectorOffset,
|
|
numSectors: vol._sectorsPerCluster - sectorOffset
|
|
}; else {
|
|
_group._nextCluster += 1;
|
|
_group.numSectors += vol._sectorsPerCluster;
|
|
}
|
|
sectorOffset = 0; // only first group is offset
|
|
}
|
|
if (_group) groups.push(_group);
|
|
cb(null, groups, i === chainLength);
|
|
}
|
|
}
|
|
|
|
chain.readSectors = function (i, dest, cb) {
|
|
var groupOffset = 0, groupsPending;
|
|
determineSectorGroups(i, dest.length / chain.sectorSize, false, function (e, groups, complete) {
|
|
if (e) cb(e);
|
|
else if (!complete) groupsPending = -1, _pastEOF(cb);
|
|
else if ((groupsPending = groups.length)) groups.forEach(function (group) {
|
|
var groupLength = group.numSectors * chain.sectorSize,
|
|
groupBuffer = dest.slice(groupOffset, groupOffset += groupLength);
|
|
chain._vol_readSectors(group.firstSector, groupBuffer, function (e,d) {
|
|
if (e && groupsPending !== -1) groupsPending = -1, cb(e);
|
|
else if (--groupsPending === 0) cb(null, dest);
|
|
});
|
|
});
|
|
else cb(null, dest); // 0-length destination case
|
|
});
|
|
};
|
|
|
|
// TODO: does this handle NOSPC condition?
|
|
chain.writeSectors = function (i, data, cb) {
|
|
var groupOffset = 0, groupsPending;
|
|
determineSectorGroups(i, data.length / chain.sectorSize, true, function (e, groups) {
|
|
if (e) cb(e);
|
|
else if ((groupsPending = groups.length)) groups.forEach(function (group) {
|
|
var groupLength = group.numSectors * chain.sectorSize,
|
|
groupBuffer = data.slice(groupOffset, groupOffset += groupLength);
|
|
chain._vol_writeSectors(group.firstSector, groupBuffer, function (e) {
|
|
if (e && groupsPending !== -1) groupsPending = -1, cb(e);
|
|
else if (--groupsPending === 0) cb();
|
|
});
|
|
});
|
|
else cb(); // 0-length data case
|
|
});
|
|
};
|
|
|
|
chain.truncate = function (numSectors, cb) {
|
|
extendCacheToInclude(Infinity, function (e,c) {
|
|
if (e) return cb(e);
|
|
|
|
var currentLength = cache.length-1,
|
|
clustersNeeded = Math.ceil(numSectors / vol._sectorsPerCluster);
|
|
if (clustersNeeded < currentLength) shrinkChainToLength(clustersNeeded, cb);
|
|
else if (clustersNeeded > currentLength) expandChainToLength(clustersNeeded, cb);
|
|
else cb();
|
|
});
|
|
};
|
|
|
|
|
|
chain.toJSON = function () {
|
|
return {firstCluster:firstCluster};
|
|
};
|
|
|
|
return chain;
|
|
};
|
|
|
|
exports.sectorChain = function (vol, firstSector, numSectors) {
|
|
var chain = _baseChain(vol);
|
|
|
|
chain.firstSector = firstSector;
|
|
chain.numSectors = numSectors;
|
|
|
|
chain.readSectors = function (i, dest, cb) {
|
|
if (i < numSectors) chain._vol_readSectors(firstSector+i, dest, cb);
|
|
else _pastEOF(cb);
|
|
};
|
|
|
|
chain.writeSectors = function (i, data, cb) {
|
|
if (i < numSectors) chain._vol_writeSectors(firstSector+i, data, cb);
|
|
else _.delayedCall(cb, S.err.NOSPC());
|
|
};
|
|
|
|
chain.truncate = function (i, cb) {
|
|
_.delayedCall(cb, S.err.INVAL());
|
|
};
|
|
|
|
chain.toJSON = function () {
|
|
return {firstSector:firstSector, numSectors:numSectors};
|
|
};
|
|
|
|
return chain;
|
|
};
|
|
|
|
// NOTE: used with mixed feelings, broken out to mark uses
|
|
function _pastEOF(cb) { _.delayedCall(cb, null, null); }
|