/** ** ================================== ** 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; } };