diff --git a/js/dos/freelist.js b/js/dos/freelist.js new file mode 100644 index 0000000..e02e58f --- /dev/null +++ b/js/dos/freelist.js @@ -0,0 +1,816 @@ +/** + ** ================================== + ** OOOO OOOO OOOO O O OOOO + ** O O O O O O O O O + ** O O O O O O O O O + ** OOOO OOOO OOOO O OOO OOOO + ** O O O O O O O O O + ** O O O O O O O O O + ** OOOO OOOO OOOO OOOO O O OOOO + ** ================================== + ** BSSLAB, Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR. + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2016 BSSLAB + ** $CREATED: 4/28/15 by sbosse. + ** $VERSION: 1.1.3 + ** + ** $INFO: + ** + ** DOS: Free cluster list management. This is a core feature + ** of the filesystem! + ** + ** Free and used cluster units: + ** + ** addr: blocks + ** size: blocks (!) + ** + ** 0. Init: + ** free_create + ** + ** 1. File Creation: + ** free_new or free_match [known final file size] + ** + ** 2. File size grow: + ** free_append [reserve enough space for further modifications + ** size > currently needed size] + ** + ** 3. File was committed: + ** free_merge [return not needed disk space] + ** + ** 4. File deletion: + ** free_merge + ** + ** + ** $ENDOFINFO + */ +"use strict"; + +var util = Require('util'); +var Io = Require('com/io'); +var Comp = Require('com/compat'); +var String = Comp.string; +var Array = Comp.array; +var Perv = Comp.pervasives; +var div = Comp.div; + +var Cluster_flag = { + Cluster_FREE: 1, // The cluster is indeed free *) + Cluster_USED: 2, // The cluster is currently used *) + + /* + ** The left side of the free cluster follows a currently used cluster + ** with an uncommitted file. In the case, the used cluster must be + ** expanded, the beginning of this free cluster is needed. After the + ** file was committed, the RESERVED flag must be cleared to allow + ** the efficient bottom divide mode again. + */ + + Cluster_RESERVED: 3, + + + print: function (flag) { + switch (flag) { + case Cluster_flag.Cluster_FREE: return 'Cluster_FREE'; + case Cluster_flag.Cluster_USED: return 'Cluster_USED'; + case Cluster_flag.Cluster_RESERVED: return 'Cluster_RESERVED'; + default: return 'cluster_flag?'; + } + } +}; + +var Free_divide_mode = { + Free_Bottom: 1, + Free_Half: 2, + Free_Top: 3 +}; + + + +/* +** Free list structure. +** +** The free cluster list is diveded in sublists with a limited +** size range. New entries are always put on the head of the +** list, therefore a FIFO order is achieved. +** +*/ + +var free_block = function (fb_addr,fb_size,fb_flag) { + this.fb_addr= fb_addr; // int; + this.fb_size= fb_size; // int; + this.fb_flag= fb_flag; // cluster_flag; +}; + +function Free_block (fb_addr,fb_size,fb_flag) { + var obj = new free_block(fb_addr,fb_size,fb_flag); + Object.preventExtensions(obj); + return obj; +} + +function fb_equal(fb1,fb2) { + if (fb1==undefined || fb2==undefined) return false; + else return (fb1.fb_addr==fb2.fb_addr && + fb1.fb_size==fb2.fb_size && + fb1.fb_flag==fb2.fb_flag) +} + +var nilfb = undefined; + +/* + ** Free block list management. Normally, the body list is emtpy, + ** and the tail list contains the free_block list. + */ + +var free_block_list = function (fl_body,fl_tail,fl_biggest) { + this.fl_body=fl_body; // free_block list; (* temporarily list *) + this.fl_tail=fl_tail; // free_block list; (* the real list *) + this.fl_biggest=fl_biggest; // free_block; +}; + +function Free_block_list (fl_body,fl_tail,fl_biggest) { + var obj = new free_block_list(fl_body,fl_tail,fl_biggest); + Object.preventExtensions(obj); + return obj; +} + +free_block_list.prototype.find_biggest = function () { + var big=undefined; + Array.iter(this.fl_tail,function (fb) { + if (big==undefined || fb.fb_size > big.fb_size) big=fb; + }); + this.fl_biggest=big; +}; + +/* + ** Divide a free cluster (addr,size') in at least a cluster + ** with newsize<=size' and if there are remains in one ore two + ** free clusters right or left and right from the middle of the + ** original cluster depending of the flags bottom. + ** + ** Mode Free_Bottom + ** + ** Before (addr,size) + ** ---------------------------------------------------- + ** + ** After: + ** ++++++++XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + ** + ** +: Extracted cluster + ** X: new free cluster + ** + ** Mode Free_Half + ** + ** Before (addr,size) + ** ---------------------------------------------------- + ** + ** After: + ** XXXXXXXXXXXXXXXXXXXXXXXXXX++++++++YYYYYYYYYYYYYYYYYY + ** + ** +: Extracted cluster + ** X,Y: new free clusters + ** + ** + ** This scheme is used because on file creation the file server + ** not know the final size of a file. Following modify requests + ** will increase the file size, and there must be a great probability + ** for a free cluster following the current file end. + ** + ** + ** Mode Free_Top + ** + ** Before (addr,size) + ** ---------------------------------------------------- + ** + ** After: + ** XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX++++++++ + ** + ** +: Extracted cluster + ** X: new free cluster + ** + ** Only useable if the final size of file is known (for example + ** with a afs_CREATE request and the afs_COMMIT/SAFETY flag set). + ** + ** + ** + ** Args: + ** old: original free cluster (addr,size') :free_block + ** size: desired size from the free cluster [blocks] + ** mode: + ** + ** + ** Return: + ** new: (addr,size,flag=USED) : free_block + ** left: (addr'',size'',flag=FREE) : free_block + ** right: (addr''',size''',flag=RESERVED) : free_block + ** + */ +function free_divide(old,size,mode) { + if (size < old.fb_size) { + switch (mode) { + case Free_divide_mode.Free_Bottom: + return [Free_block(old.fb_addr,size,Cluster_flag.Cluster_USED), + nilfb, + Free_block(old.fb_addr+size,old.fb_size-size,Cluster_flag.Cluster_RESERVED)]; + break; + case Free_divide_mode.Free_Half: + var os2 = div(old.fb_size,2); + if (size < os2) + return [Free_block(old.fb_addr+os2,size,Cluster_flag.Cluster_USED), + Free_block(old.fb_addr,os2,old.fb_flag), + Free_block(old.fb_addr+os2+size,old.fb_size-os2-size,Cluster_flag.Cluster_RESERVED)]; + else + return [Free_block(old.fb_addr,os2,Cluster_flag.Cluster_USED), + nilfb, + Free_block(old.fb_addr+os2,old.fb_size-os2,Cluster_flag.Cluster_RESERVED)]; + break; + case Free_divide_mode.Free_Top: + return [Free_block(old.fb_addr+old.fb_size-size,size,Cluster_flag.Cluster_USED), + Free_block(old.fb_addr,old.fb_size-size,old.fb_flag), + nilfb]; + break; + } + } else if (size == old.fb_size) { + return [old,nilfb,nilfb] + } else { + return [nilfb,nilfb,nilfb] + } +} + +/** + * + * @param {free_block_list []} fbs_array + * @param {number [] []} fbs_range (start*size) array + * @param {number} fbs_num + */ +var free_blocks = function (fbs_array,fbs_range,fbs_num) { + this.fbs_array=fbs_array; // free_block_list array; + this.fbs_range=fbs_range; // (int * int) array; + this.fbs_num=fbs_num; // number of lists + this.fbs_fragments=0; // current number of free clusters + this.fbs_compacthres=50; // initial #fragments threshold for compaction run +}; + +free_blocks.prototype.print = function () { + var str=''; + var flar = this.fbs_array; + //var srar = this.fbs_range; + //var nmax = this.fbs_num - 1; + str='Free cluster list:\n'; + Array.iter(flar,function (fcl,i) { + str=str+' #'+i+' tail\n'; + Array.iter (fcl.fl_tail,function (fb) { + str=str+' [addr='+fb.fb_addr+' size='+fb.fb_size+' flag='+Cluster_flag.print(fb.fb_flag)+']\n'; + }); + str=str+' #'+i+' body\n'; + Array.iter (fcl.fl_body,function (fb) { + str=str+' [addr='+fb.fb_addr+' size='+fb.fb_size+' flag='+Cluster_flag.print(fb.fb_flag)+']\n'; + }); + }); + return str; +}; + +/* + ** Find a free cluster (X) with start address 'addr' and remove it + ** from the free list. It's assumed that this cluster + ** is somewhere in the front of a free list. Therefore, all + ** free cluster lists are searched in a round-robinson style. + ** + ** L : AAAAAA XXXX BBBB -> + ** L': AAAAAA BBBB -> + ** + ** XXXX + ** + */ +free_blocks.prototype.free_find = function (addr) { + var flar = this.fbs_array; + var nmax = this.fbs_num; + var found = nilfb; + var empty = 0; + var mask = ((1 << nmax) - 1); + + function iter (n) { + Array.match(flar[n].fl_tail, + function (hd,tl) { + if (hd.fb_addr != addr) { + flar[n].fl_body.push(hd); + flar[n].fl_tail = tl; + var next = (n + 1) == nmax ? 0 : (n + 1); + + iter(next); + } + else { + /* + ** Success. + */ + found = hd; + flar[n].fl_tail=Array.merge(flar[n].fl_body, tl); + flar[n].fl_body = []; + + if (fb_equal(found, flar[n].fl_biggest)) + flar[n].find_biggest(); + } + }, + function () { + empty=empty | (1 << n); + var next = (n+1)==nmax?0 : (n+1); + if ((empty & mask) != mask) iter (next); + }) + } + iter(0); + this.fbs_fragments=0; + for (var i = 0; i + ** + ** L': XXXXXX AAAAAA BBBBBB + ** + * + * + * + * @param {free_block} fb + */ +free_blocks.prototype.free_insert = function (fb) { + var flar = this.fbs_array; + var srar = this.fbs_range; + var nmax = this.fbs_num-1; + var size = fb.fb_size; + + + function iter(n) { + var low,high; + low=srar[n][0]; + high=srar[n][1]; + if (size >= low && size < high) { + flar[n].fl_tail = Array.merge([fb], flar[n].fl_tail); + if ((flar[n].fl_biggest && (fb.fb_size > flar[n].fl_biggest.fb_size)) || flar[n].fl_biggest==nilfb) + flar[n].fl_biggest = fb; + } else { + if (n > 0) + iter(n - 1); + else + Io.err('[FREEL] free_insert: inconsistent free list/size=' + size); + } + } + if (fb!=nilfb) iter(nmax); +}; + +/** + ** Try to merge a free cluster (X) (previously allocated + ** with free_new or free_append) after a file creation + ** with an already existing one from the free list. + ** The new cluster is flagged with Cluster_FREE! The merged cluster + ** (if any) or the cluster X is inserted in the free list again. + ** + ** L: AAAA XXXXX BBBBB -> + ** + ** XXXXXX + + ** YYYYYYYYYY = + ** ZZZZZZZZZZZZZZZZ -> + ** + ** L': AAAA ZZZZZZZZZZZZZZZZ BBBBB + * + * @param {free_block} fb + */ +free_blocks.prototype.free_merge = function (fb) { + if (fb != nilfb) { + fb.fb_flag = Cluster_flag.Cluster_FREE; + var addr = fb.fb_addr + fb.fb_size; + var size = fb.fb_size; + + var fb2 = this.free_find(addr); + if (fb2!=nilfb) + this.free_insert(Free_block(fb.fb_addr,size + fb2.fb_size,fb.fb_flag)); + else + this.free_insert(fb); + } +}; + +/** + ** Search the free cluster list for a cluster with + ** start address addr. Take the front of the cluster + ** with the desired size, and insert the rest in the list again. + ** Return the extracted cluster OR undefined. + ** + ** Units: + ** addr,size : blocks + ** + ** L: AAAA XXXX BBBBB -> + ** + ** L': AAAA XX BBBBB -> + ** + ** XX + ** + ** + * + * + * @param {number} addr + * @param {number} size + * @returns {free_block|undefined} + */ +free_blocks.prototype.free_append = function (addr,size) { + var fb = this.free_find(addr); + if (fb.fb_size >= size) { + var newc, right; + var blocks = free_divide(fb, size, Free_divide_mode.Free_Bottom); + newc = blocks[0]; + //left = blocks[1]; + right = blocks[2]; + this.free_insert(right); + return newc; + } + else { + this.free_insert(fb); + return undefined; + } + +}; + +/** + ** Get a free cluster with specified size somewhere in the file system. + ** A file was created. The biggest cluster in a freelist is + ** taken and split to the desired size and the rest. Searched is performed + ** from the largest free clusters down to the smallest possible. + ** + ** Units: + ** size: blocks + ** + * + * + * @param {number} size + * @returns {free_block|undefined} + */ +free_blocks.prototype.free_new = function (size) { + var self=this; + + if (this.fbs_fragments>this.fbs_compacthres) self.free_compact(); + + var flar = this.fbs_array; + var srar = this.fbs_range; + var nmax = this.fbs_num-1; + + + function iter (n) { + var low; + var found=undefined; + function find () { + if (flar[n].fl_biggest.fb_size >= size) { + Array.match(flar[n].fl_tail, + function (hd,tl){ + if (fb_equal(hd,flar[n].fl_biggest)) + { + flar[n].fl_tail = Array.merge(flar[n].fl_body, tl); + flar[n].fl_body = []; + flar[n].find_biggest(); + found = hd; + } + else { + Array.append(flar[n].fl_body,hd); + flar[n].fl_tail = tl; + find(); + } + }) + } + } + + low=srar[n][0]; + //high=srar[n][1]; + if (!Array.empty(flar[n].fl_tail)) { + /* + ** Find the biggest cluster in this list! + */ + find(); + if (found!=undefined) { + return found; + } + else { + if (n > 0 && size < low) + return iter(n-1); + else + return undefined; + } + } else if (size < low) { + if (n > 0) + return iter(n-1); + else + return undefined; + + } else return undefined; + } + var cl = iter(nmax); + /* + ** Move the body lists to the front of the tail lists. + */ + for (var i = 0; i this.fbs_compacthres) self.free_compact(); + + var flar = this.fbs_array; + var srar = this.fbs_range; + var nmax = this.fbs_num-1; + + function iter (n) { + var high; + //low=srar[n][0]; + high=srar[n][1]; + if (size <= high && !Array.empty(flar[n].fl_tail)) { + /* + ** Find the best matching cluster! + */ + + var found = undefined; + Array.iter (flar[n].fl_tail, function(fb) { + if (fb.fb_size >= size && + ((found && found.fb_size > fb.fb_size) || + found==undefined)) + found = fb; + }); + if (found==undefined) { + if (n < nmax) + return iter(n + 1); + else + return undefined; + } else { + + /* + ** Remove the found free cluster from the free list. + */ + flar[n].fl_body = []; + Array.iter(flar[n].fl_tail, function (fb) { + if (!fb_equal(fb, found)) + Array.append(flar[n].fl_body,fb); + }); + flar[n].fl_tail = flar[n].fl_body; + flar[n].fl_body = []; + if (fb_equal(found, flar[n].fl_biggest)) + flar[n].find_biggest(); + return found; + } + + } else { + if (n < nmax) + return iter (n+1); + else + return undefined; + } + + } + /* + ** Start searching in the free cluster list with the smallest + ** cluster sizes! + */ + var cl = iter(0); + if (cl!=undefined) { + var newc,left; + var blocks = free_divide(cl,size,Free_divide_mode.Free_Top); + newc = blocks[0]; + left = blocks[1]; + if (left!=nilfb) this.free_insert(left); + return newc; + } else return undefined; +}; + +/** + ** Release a reserved cluster with the start address 'addr'. + ** It' assumed that this cluster is somewhere in the front of a free list. + ** Therefore, all free cluster lists are searched in a round-robinson style. + ** After file creation is finished, normally the free_merge + ** function do this job in a more efficient way. + ** + ** L: AAAA XXXXXX(RES) BBB -> + ** L': AAAA XXXXXX(FREE) BBB + ** + * + * @param {number} addr + */ +free_blocks.prototype.free_release = function (addr) { + var flar = this.fbs_array; + var nmax = this.fbs_num; + var empty = 0; + var mask = ((1 << nmax) - 1); + function iter (n) { + Array.match(flar[n].fl_tail, + function (hd,tl){ + if (hd.fb_addr != addr) { + + Array.append(flar[n].fl_body, hd); + flar[n].fl_tail = tl; + var next = ((n + 1) == nmax) ? 0 : (n + 1); + + iter(next); + } else { + /* + ** Success. + */ + hd.fb_flag = Free_block.Cluster_FREE; + } + }, + function () { + empty = empty | (1 << n); + var next = ((n+1) == nmax)?0:(n+1); + if ((empty & mask) != mask) + iter(next); + }) + } + iter(0); + /* + ** Move the body list to the front of the tail list. + */ + this.fbs_fragments=0; + for (var i = 0; ithis.fbs_compacthres) self.free_compact(); +}; + +/** + ** Create a free cluster list object handler. The size range is divided + ** linear in n equidistant ranges (decades of tenth). + * + * @param {number} n + * @param {number} size + */ +free_blocks.prototype.free_create = function (n,size) { + var i; + var srar = []; + var flar = []; + this.fbs_fragments=0; + for (i = 0; i d*10) + srar.push([d,size]); + else + srar.push([d,d*10]); + this.fbs_array=flar; + this.fbs_range=srar; + this.fbs_num=n; + +}; + +/** + ** Compact the freelist (merge contiguous clusters). Simply said, + ** but hard to perform. + * + * + */ +free_blocks.prototype.free_compact = function () { + var self=this; + var i; + /* + ** First merge all sub lists to one huge list. + */ + + var fl = []; + for (i = 0; i f2.fb_addr)?1:-1}); + + /* + ** Try to merge contiguous clusters. + */ + var merged=[]; + var lastfb = Free_block(0,0,Cluster_flag.Cluster_FREE); + Array.iter(fl2,function (fs) { + if ((lastfb.fb_addr + lastfb.fb_size) != fs.fb_addr) { + + if ((lastfb.fb_addr + lastfb.fb_size) != 0) { + + merged.push(lastfb); + } + lastfb = fs; + } else { + + lastfb = Free_block(lastfb.fb_addr, fs.fb_size + lastfb.fb_size, lastfb.fb_flag); + } + }); + merged.push(lastfb); + /* + ** Now rebuild the freelist. + */ + var flar = []; + for(i=0;ithis.fbs_compacthres) this.fbs_compacthres=this.fbs_fragments+10; + else if (this.fbs_fragments<(this.fbs_compacthres/2)) this.fbs_compacthres=Perv.max(5,this.fbs_fragments*2); + +}; + +/** + ** Return the number of free clusters, the total free space and + ** an array containing (low,high,num) free list statistics. + * + * @returns {[number,number,array]} totn,tots,tota + */ +free_blocks.prototype.free_info = function () { + var flar = this.fbs_array; + var srar = this.fbs_range; + var nmax = this.fbs_num; + var totn = 0; + var tots = 0; + var tota = []; + for (var i = 0; i < nmax; i++) { + totn = totn + Array.length(flar[i].fl_tail); + var count = 0; + Array.iter(flar[i].fl_tail, function (fb) { + tots = tots + fb.fb_size; + count++; + }); + var low, high; + low = srar[i][0]; + high = srar[i][1]; + tota.push([low, high, count]); + } + return [totn,tots,tota] +}; + +module.exports = { + Cluster_flag:Cluster_flag, + Free_block:Free_block, + Free_blocks: function (fbs_array,fbs_range,fbs_num) { + var obj = new free_blocks(fbs_array,fbs_range,fbs_num); + Object.preventExtensions(obj); + return obj; + } +}; + +