Mon 21 Jul 22:43:21 CEST 2025

This commit is contained in:
sbosse 2025-07-21 22:46:23 +02:00
parent 4f806d1f5b
commit c715cd3509

816
js/dos/freelist.js Normal file
View File

@ -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<nmax;i++) {
flar[i].fl_tail=Array.merge(flar[i].fl_body,flar[i].fl_tail);
flar[i].fl_body=[];
this.fbs_fragments=this.fbs_fragments+flar[i].fl_tail.length;
}
return found;
};
/**
** Insert a free cluster (X) in the appropriate free cluster list.
** This cluster is inserted on the head of the appropriate list
** (FIFO order).
**
** L: AAAAAA BBBBBB
**
** XXXXXX ->
**
** 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 <nmax;i++) {
flar[i].fl_tail=Array.merge(flar[i].fl_body,flar[i].fl_tail);
flar[i].fl_body=[];
}
var mode = (cl.fb_flag == Cluster_flag.Cluster_RESERVED)? Free_divide_mode.Free_Half:Free_divide_mode.Free_Bottom;
var newc,left,right;
var blocks = free_divide(cl,size,mode);
newc = blocks[0];
left = blocks[1];
right = blocks[2];
this.free_insert(right);
if (mode==Free_divide_mode.Free_Half)
self.free_insert(left);
return newc;
};
/**
** Get a free cluster with specified size somewhere in the file system.
** A file was created. The best matching cluster in a freelist is
** taken and split to the desired size and the rest. The free cluster
** is taken from the best matching free cluster list.
** Only useful in the case the final size of a file is already
** known (e.g. small files transferred with a single request).
**
** Units:
** size: blocks
**
* @param {number} size
* @returns {free_block|undefined}
*/
free_blocks.prototype.free_match = 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 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; i<nmax;i++) {
flar[i].fl_tail=Array.merge(flar[i].fl_body,flar[i].fl_tail);
flar[i].fl_body=[];
this.fbs_fragments=this.fbs_fragments+flar[i].fl_tail.length;
}
if (this.fbs_fragments>this.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 <n; i ++) {
flar.push(Free_block_list([], [], nilfb));
}
var d = 1;
for (i = 0; i <n-1; i ++) {
srar.push([d, d * 10]);
d = d * 10;
}
if (size > 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<self.fbs_num; i++) {
fl=Array.merge(fl, this.fbs_array[i].fl_tail);
}
/*
** Now resort the freelist with increasing disk address
** order.
*/
var fl2 = Array.sort(fl,function (f1,f2) {return (f1.fb_addr > 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;i<this.fbs_num;i++) {
flar.push(Free_block_list([],[],nilfb));
}
this.fbs_array=flar;
Array.iter (merged,function (fb) {
self.free_insert(fb);
});
/*
** Huhhh, all work is done.
*/
this.fbs_fragments=0;
for (i = 0; i<this.fbs_num;i++) {
this.fbs_fragments=this.fbs_fragments+flar[i].fl_tail.length;
}
if (this.fbs_fragments>this.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;
}
};