-
Notifications
You must be signed in to change notification settings - Fork 13
/
vol.js
165 lines (145 loc) · 6.95 KB
/
vol.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
var S = require("./structs.js"),
c = require("./chains.js"),
$ = require("./cache.js"),
_ = require("./helpers.js");
exports.init = function (volume, opts, bootSector) {
if (bootSector[510] !== 0x55 || bootSector[511] !== 0xAA) throw Error("Invalid volume signature!");
var isFAT16 = bootSector.readUInt16LE(S.boot16.fields['FATSz16'].offset),
bootStruct = (isFAT16) ? S.boot16 : S.boot32,
BS = bootStruct.valueFromBytes(bootSector);
_.log(_.log.DBG, "Boot sector info:", BS);
bootSector = null; // allow GC
if (!BS.BytsPerSec) throw Error("This looks like an ExFAT volume! (unsupported)");
else if (BS.BytsPerSec !== volume.sectorSize) throw Error("Sector size mismatch with FAT table.");
var FATSz = (isFAT16) ? BS.FATSz16 : BS.FATSz32,
rootDirSectors = Math.ceil((BS.RootEntCnt * 32) / BS.BytsPerSec),
firstDataSector = BS.ResvdSecCnt + (BS.NumFATs * FATSz) + rootDirSectors,
totSec = (BS.TotSec16) ? BS.TotSec16 : BS.TotSec32,
dataSec = totSec - firstDataSector,
countofClusters = Math.floor(dataSec / BS.SecPerClus);
// avoid corrupting sectors from other partitions or whatnot
if (totSec > volume.numSectors) throw Error("Volume size mismatch!");
var fatType;
if (countofClusters < 4085) {
fatType = 'fat12';
} else if (countofClusters < 65525) {
fatType = 'fat16';
} else {
fatType = 'fat32';
}
_.log(_.log.DBG, "rootDirSectors", rootDirSectors, "firstDataSector", firstDataSector, "countofClusters", countofClusters, "=>", fatType);
var vol = {};
vol.opts = opts;
vol._sectorSize = BS.BytsPerSec;
vol._sectorsPerCluster = BS.SecPerClus;
vol._firstSectorOfCluster = function (n) {
return firstDataSector + (n-2)*vol._sectorsPerCluster;
};
vol._makeCache = function () {
return $.wrapDriver(volume);
};
vol._readSectors = function (cache, secNum, dest, cb) {
if (typeof dest === 'function') {
cb = dest;
dest = _.allocBuffer(vol._sectorSize);
}
_.log(_.log.DBG, "vol._readSectors", secNum, dest.length);
if (secNum < volume.numSectors) cache.readSectors(secNum, dest, function (e) { cb(e, dest); });
else throw Error("Invalid sector number!");
};
vol._writeSectors = function (cache, secNum, data, cb) {
_.log(_.log.DBG, "vol._writeSectors", secNum, data.length);
// NOTE: these are internal assertions, public API will get proper `S.err`s
if (data.length % volume.sectorSize) throw Error("Buffer length not a multiple of sector size");
else if (opts.ro) throw Error("Read-only filesystem");
else if (secNum < volume.numSectors) cache.writeSectors(secNum, data, cb);
else throw Error("Invalid sector number!");
};
function fatInfoForCluster(n) {
var entryStruct = S.fatField[fatType],
FATOffset = (fatType === 'fat12') ? Math.floor(n/2) * entryStruct.size : n * entryStruct.size,
SecNum = BS.ResvdSecCnt + Math.floor(FATOffset / BS.BytsPerSec);
EntOffset = FATOffset % BS.BytsPerSec;
return {sector:SecNum-BS.ResvdSecCnt, offset:EntOffset, struct:entryStruct};
}
// TODO: all this FAT manipulation is crazy inefficient! needs read caching *and* write caching
// …the best place for cache might be in `volume` handler, though. add a `flush` method to that spec?
// TODO: how should we handle redundant FATs? mirror every write? just ignore completely? copy-on-eject?
var fatChain = c.sectorChain(vol, BS.ResvdSecCnt, FATSz);
fatChain.cacheAdvice = 'RANDOM';
vol.fetchFromFAT = function (clusterNum, cb) {
var info = fatInfoForCluster(clusterNum);
fatChain.readFromPosition(info, info.struct.size, function (e,n,d) {
if (e) return cb(e);
var status = info.struct.valueFromBytes(d), prefix;
if (fatType === 'fat12') {
if (clusterNum % 2) {
status = (status.field1ab << 4) + status.field1c;
} else {
status = (status.field0a << 8) + status.field0bc;
}
}
else if (fatType === 'fat32') {
status &= 0x0FFFFFFF;
}
var prefix = S.fatPrefix[fatType];
if (status === S.fatStat.free) cb(null, 'free');
else if (status === S.fatStat._undef) cb(null, '-invalid-');
else if (status > prefix+S.fatStat.eofMin) cb(null, 'eof');
else if (status === prefix+S.fatStat.bad) cb(null, 'bad');
else if (status > prefix+S.fatStat.rsvMin) cb(null, 'reserved');
else cb(null, status);
});
};
vol.storeToFAT = function (clusterNum, status, cb) {
if (typeof status === 'string') {
status = S.fatStat[status];
status += S.fatPrefix[fatType];
}
var info = fatInfoForCluster(clusterNum);
// TODO: technically fat32 needs to *preserve* the high 4 bits
if (fatType === 'fat12') fatChain.readFromPosition(info, info.struct.size, function (e,n,d) {
var value = info.struct.valueFromBytes(d);
if (clusterNum % 2) {
value.field1ab = status >>> 4;
value.field1c = status & 0x0F;
} else {
value.field0a = status >>> 8;
value.field0bc = status & 0xFF;
}
var entry = info.struct.bytesFromValue(value);
fatChain.writeToPosition(info, entry, cb);
}); else {
var entry = info.struct.bytesFromValue(status);
fatChain.writeToPosition(info, entry, cb);
}
};
vol.allocateInFAT = function (hint, cb) {
if (typeof hint === 'function') {
cb = hint;
hint = 2; // TODO: cache a better starting point?
}
function searchForFreeCluster(num, cb) {
if (num < countofClusters) vol.fetchFromFAT(num, function (e, status) {
if (e) cb(e);
else if (status === 'free') cb(null, num);
else searchForFreeCluster(num+1, cb);
}); else cb(S.err.NOSPC()); // TODO: try searching backwards from hint…
}
searchForFreeCluster(hint, function (e, clusterNum) {
if (e) cb(e);
else vol.storeToFAT(clusterNum, 'eof', cb.bind(null,null,clusterNum));
});
};
vol.rootDirectoryChain = (isFAT16) ?
c.sectorChain(vol, firstDataSector - rootDirSectors, rootDirSectors) :
c.clusterChain(vol, BS.RootClus);
vol.rootDirectoryChain.cacheAdvice = 'WILLNEED';
vol.chainForCluster = c.clusterChain.bind(c, vol);
vol.chainFromJSON = function (d) {
return ('numSectors' in d) ?
c.sectorChain(vol, d.firstSector, d.numSectors) :
c.clusterChain(vol, d.firstCluster);
};
return vol;
}