- added correct diskfree reporting error ENOSPC
[flfs:flfs.git] / luafs.lua
1 #!/usr/bin/env lua
2
3 local fuse = require 'fuse'
4 local lfs  = require "lfs"
5
6 list = require 'list'        -- must be global for loadstring()!
7
8 local S_WID     = 1 --world
9 local S_GID     = 2^3 --group
10 local S_UID     = 2^6 --owner
11 local S_SID     = 2^9 --sticky bits etc.
12 local S_IFIFO   = 1*2^12
13 local S_IFCHR   = 2*2^12
14 local S_IFDIR   = 4*2^12
15 local S_IFBLK   = 6*2^12
16 local S_IFREG   = 2^15
17 local S_IFLNK   = S_IFREG + S_IFCHR
18
19 -- for access(), taken from unistd.h
20 local R_OK      = 1 -- Test for read permissions
21 local W_OK      = 2 -- Test for write permissions
22 local X_OK      = 3 -- Test for execute permissions
23 local F_OK      = 4 -- Test for existence
24
25 local EPERM        = -1
26 local ENOENT       = -2
27 local EEXIST       = -17
28 local EINVAL       = -22
29 local EFBIG        = -27
30 local ENOSPC       = -28
31 local ENAMETOOLONG = -36
32 local ENOSYS       = -38
33 local ENOATTR      = -516
34 local ENOTSUPP     = -524
35
36 local BLOCKSIZE    = 4096
37 local STRIDE       = 1
38 local MAXINT       = 2^32 -1
39
40 --
41 -- shortcuts, lua speedups in fact
42 --
43 local substr    = string.sub
44 local floor     = math.floor
45 local time      = os.time
46 local join      = table.concat
47 local push      = table.insert
48 push            = table.insert  -- must be global for loadstring()!
49 local pop       = table.remove
50 local sort      = table.sort
51 local format    = string.format
52 local split     = string.gmatch
53 local match     = string.match
54 local find      = string.find
55
56 local function shift(t)
57     return pop(t,1)
58 end
59
60 local function pairsByKeys(t, f)
61     local a = {}
62     for n in pairs(t) do 
63         push(a, n) 
64     end
65     sort(a, f)
66     local i = 0      -- iterator variable
67     local iter = function ()   -- iterator function
68         i = i + 1
69         if a[i] == nil then return nil
70         else return a[i], t[a[i]]
71         end
72     end
73     return iter
74 end
75
76
77 local function _bxor(x, y)
78    local z = 0
79    for i = 0, 31 do
80       if (x % 2 == 0) then                      -- x had a '0' in bit i
81          if ( y % 2 == 1) then                  -- y had a '1' in bit i
82             y = y - 1 
83             z = z + 2 ^ i                       -- set bit i of z to '1' 
84          end
85       else                                      -- x had a '1' in bit i
86          x = x - 1
87          if (y % 2 == 0) then                   -- y had a '0' in bit i
88             z = z + 2 ^ i                       -- set bit i of z to '1' 
89          else
90             y = y - 1 
91          end
92       end
93       y = y / 2
94       x = x / 2
95    end
96    return z
97 end
98
99
100 local function _bnot(a)   return MAXINT - a end
101 local function _band(a,b) return ((a+b) - _bxor(a,b))/2 end
102 local function _bor(a,b)  return MAXINT - _band(MAXINT - a, MAXINT - b) end
103 local function set_bits(mode, bits)
104     return _bor(mode, bits)
105 end
106
107 local function splitpath(string) 
108     local dir,file = match(string, "(.-)([^/\\]*)$") 
109     dir = match(dir, "(.-)[/\\]?$")
110     if dir == '' then
111         dir = "/"
112     end
113     return dir,file
114 end
115
116 local function mk_mode(owner, group, world, sticky)
117     return owner * S_UID + group * S_GID + world + (sticky or 0) * S_SID
118 end
119
120 local function new_meta(mymode, uid, gid, now)
121     inode_start = inode_start + 1
122     return {
123         mode  = mymode,
124         ino   = inode_start,
125         uid   = uid,
126         gid   = gid,
127         size  = 0,
128         atime = now,
129         mtime = now,
130         ctime = now
131     }
132 end
133
134 -- needed to get the correct / permissions (from FUSE mount user)
135 local uid,gid,pid,puid,pgid = fuse.context()
136
137 -- empty block precalculated: block of \x00 for size BLOCKSIZE
138 local t = {}
139 for i=1,BLOCKSIZE do
140     push(t, "\000")
141 end
142 local empty_block = join(t)
143
144
145 --
146 -- fs_meta, inode_start and block_nr are the global variables that are needed
147 -- globally to go over the journal easy
148 --
149 --
150 -- FIXME: implement correct journal size'ing
151 block_nr     = 1 * 1024 * 1024 * 1024 / BLOCKSIZE
152 inode_start  = 1
153 max_block_nr = 0
154 fs_meta      = {}
155 fs_meta["/"]               = new_meta(mk_mode(7,5,5) + S_IFDIR, uid, gid, time())
156 fs_meta["/"].directorylist = {}
157 fs_meta["/"].nlink         = 3
158 fs_meta["/.journal"]          = new_meta(set_bits(mk_mode(7,5,5), S_IFREG), uid, gid, time())
159 fs_meta["/.journal"].blockmap = list:new{}
160 fs_meta["/.journal"].freelist = {[0]=block_nr - 1}
161
162 freelist       = {}
163 freelist_index = {}
164 blocks_in_freelist = 0
165
166 local journal_fh
167
168 --
169 -- FUSE methods (object)
170 --
171 luafs = {
172 init = function(self, proto_major, proto_minor, async_read, max_write, max_readahead)
173
174     -- open the blockdevice
175     journal_fh = assert(io.open(self.metadev, "r+"))
176     journal_fh:setvbuf("no")
177
178     -- find the size of it
179     local blockdev_size = journal_fh:seek("end",0)
180     max_block_nr = floor(blockdev_size / BLOCKSIZE) - STRIDE
181     say("blockdev "..self.metadev.." size:"..blockdev_size..",start block:"..block_nr..",max block:"..max_block_nr)
182     
183
184     --
185     -- read in the state the filesystem was at umount, this *must* be done
186     -- *before* the update change methods are made a little bit further
187     --
188     -- FIXME: 'self' cannot be used here, find out how, see also
189     --        serializemeta() for more information. Now a record in the
190     --        journal is of the form:
191     --
192     --          luafs.<method>(self, ...)
193     --
194     --        because luafs is a global that can be accessed (while 'self'
195     --        cannot as it is local?!):
196     --
197     --          loadstring(<journalentry>)()
198     --
199     --
200     say("start reading metadata from "..self.metadev) 
201     journal_fh:seek("set",0)
202
203     local journal_size = 0
204     local journal_str  = ''
205     local start_done   = false
206
207     local journal_f,err=load(function()
208         if not start_done then
209             start_done = true
210             return "local a\n"
211         end
212         local nstr = journal_fh:read(BLOCKSIZE)
213         -- first char of the next block is null, thus it is the end of
214         -- the journal/state, so we end the loop
215         while nstr and substr(nstr,1,1) ~= '\000' do
216             journal_str = journal_str..nstr
217             local last_i
218             local i = find(journal_str, "\n", 1, true)
219             while i do
220                 last_i = i
221                 i = find(journal_str, "\n", i+1, true)
222             end 
223             if last_i then
224                 print("last_i:"..last_i)
225                 local l = substr(journal_str, 0, last_i)
226                 journal_size = journal_size + #l
227                 print(l)
228                 journal_str = substr(journal_str, #l + 1)
229                 print("return, len:"..#journal_str)
230                 return "a=function() "..l.." end\na()\n"
231             end
232             nstr = journal_fh:read(BLOCKSIZE)
233             print("len:"..#journal_str)
234         end
235         return nil
236     end)
237     if not journal_f then
238         error(err)
239     end
240     local status, err = pcall(journal_f)
241     if not status then
242         print("was error:"..err)
243         error(err)
244     end
245     local journal_meta = fs_meta["/.journal"]
246     journal_meta.size = journal_size 
247     say("done reading metadata from "..self.metadev) 
248
249     --
250     -- loop over all the functions and add a wrapper to write meta data`
251     --
252     local change_methods = {
253         rmdir       = true,
254         mkdir       = true,
255         create      = true,
256         mknod       = true,
257         setxattr    = true,
258         removexattr = true,
259         truncate    = true,
260         link        = true,
261         unlink      = true,
262         symlink     = true,
263         chmod       = true,
264         chown       = true,
265         utime       = true,
266         utimens     = true,
267         rename      = true,
268         _setblock   = true
269     }
270     for k, _ in pairs(change_methods) do
271         local fusemethod  = self[k]
272         local prefix      = "luafs."..k.."(self,"
273         self[k] = function(self,...) 
274
275             -- always add the time at the end, methods that change the metastate
276             -- usually need this to adjust the ctime
277             arg[#arg+1] = time()
278
279             -- persistency: make the lua function call
280             local o = {}
281             for i,w in ipairs(arg) do
282                 if type(arg[i]) == "number" then
283                     o[i] = arg[i]
284                 elseif type(arg[i]) == "string" then
285                     o[i] = format("%q", arg[i])
286                 end
287             end
288
289             -- ....and save it to the metafile
290             local je = prefix..join(o,",")..")\n"
291             luafs.journal_write(self, journal_meta, je)
292
293             -- really call the function
294             return fusemethod(self, unpack(arg))
295         end
296     end
297
298     -- add the context wrapper
299     local context_needing_methods = {
300         mkdir       = true,
301         create      = true,
302         mknod       = true,
303         symlink     = true,
304         chown       = true
305     }
306     for k, _ in pairs(context_needing_methods) do
307         local fusemethod  = self[k]
308         local fusecontext = fuse.context
309         self[k] = function(self,...) 
310             
311             arg[#arg+1], arg[#arg+2] = fusecontext()
312
313             -- really call the function
314             return fusemethod(self, unpack(arg))
315         end
316     end
317
318     return 0
319 end,
320
321 journal_write = function(self, journal_meta, journal_entry)
322     local current_js = journal_meta.size
323     local next_bi    = floor((current_js+#journal_entry)/BLOCKSIZE)
324     if js == 0 or next_bi ~= floor(current_js/BLOCKSIZE) then
325         journal_fh:seek('set', BLOCKSIZE * next_bi)
326         journal_fh:write(empty_block, empty_block)
327         journal_fh:flush()
328     end
329     journal_fh:seek('set', current_js)
330     journal_fh:write(journal_entry)
331     journal_fh:flush()
332     journal_meta.size = current_js + #journal_entry 
333     return 
334 end,
335
336 rmdir = function(self, path, ctime)
337     if next(fs_meta[path].directorylist) then
338         return EEXIST 
339     end
340
341     local parent,dir = splitpath(path)
342     fs_meta[parent].nlink = fs_meta[parent].nlink - 1
343     fs_meta[parent].directorylist[dir] = nil
344     fs_meta[parent].ctime = ctime
345     fs_meta[parent].mtime = ctime
346     fs_meta[path] = nil
347     return 0
348 end,
349
350 mkdir = function(self, path, mode, cuid, cgid, ctime)
351     if #path > 1024 then
352         return ENAMETOOLONG
353     end
354     local parent,subdir = splitpath(path)
355     print("parentdir:"..parent)
356     fs_meta[path] = new_meta(mode + S_IFDIR, cuid, cgid, ctime)
357     fs_meta[path].directorylist = {}
358     fs_meta[path].nlink = 2
359     fs_meta[parent].nlink = fs_meta[parent].nlink + 1
360     fs_meta[parent].directorylist[subdir] = fs_meta[path]
361     fs_meta[parent].ctime = ctime
362     fs_meta[parent].mtime = ctime
363
364     print("made dir, mode:"..fs_meta[path].mode)
365     return 0
366 end,
367
368 opendir = function(self, path)
369     return 0, { t=fs_meta[path].directorylist, k=nil }
370 end,
371
372 readdir = function(self, path, offset, dir_fh)
373     local dir_ent, dir_ent_meta = next(dir_fh.t, dir_fh.k)
374     if dir_ent == nil then
375         return 0, {}
376     end
377     print("readdir(),v:"..dir_ent)
378     dir_fh.k = dir_ent
379     local n = path
380     if path ~= "/" then n = n .. "/" end
381     print("readdir():meta from:"..n..dir_ent)
382     n = fs_meta[n..dir_ent]
383     return 0, {{d_name=dir_ent, offset=offset + 1, d_type=n.mode, ino=n.ino}}
384 end,
385
386 releasedir = function(self, path, dirent)
387     dirent.k = nil
388     dirent.t = nil
389     -- eventually the last reference to it will disappear
390     return 0
391 end,
392
393 open = function(self, path, mode)
394     local entity = fs_meta[path]
395     if entity then
396         return 0, { f=entity }
397     else
398         return ENOENT, nil
399     end
400 end,
401
402 create = function(self, path, mode, flags, cuid, cgid, ctime)
403     local parent,file = splitpath(path)
404     print("parent:"..parent..",file:"..file)
405     if mode == 32768 then
406         mode = mk_mode(6,4,4)
407     end
408     fs_meta[path] = new_meta(set_bits(mode, S_IFREG), cuid, cgid, ctime)
409     fs_meta[path].blockmap = list:new()
410     fs_meta[parent].directorylist[file] = fs_meta[path]
411     fs_meta[parent].ctime = ctime
412     fs_meta[parent].mtime = ctime
413     return 0, { f=fs_meta[path] }
414 end,
415
416 read = function(self, path, size, offset, obj)
417     local map   = fs_meta[path].blockmap
418     local findx = floor(offset/BLOCKSIZE)
419     local lindx = floor((offset + size)/BLOCKSIZE) - 1
420     if findx == lindx then
421         local b = self:_getblock(map[findx]) 
422         return 0, substr(b,offset % BLOCKSIZE,offset%BLOCKSIZE+size)
423     end
424     local str = {}
425     for i=findx,lindx-1 do
426         push(str, self:_getblock(map[i]))
427     end
428     push(str, substr(self:_getblock(map[lindx]),0,offset%BLOCKSIZE+size))
429     return 0, join(str)
430 end,
431
432 write = function(self, path, buf, offset, obj)
433
434     -- This call is *NOT* journaled, instead the resulting _setblock() calls
435     -- are.. we don't want to rewrite on journal traversal, we just want to set
436     -- the blocks again
437
438     local entity = fs_meta[path]
439     local data   = {}
440     local map    = entity.blockmap
441     local findx  = floor(offset/BLOCKSIZE)
442
443     -- BLOCKSIZE matches ours + offset falls on the start: just assign
444     if offset % BLOCKSIZE == 0 and #buf == BLOCKSIZE then
445         print("blocksize matches and offset falls on boundary:"..findx)
446                 
447                 -- no need to read in block, it will be written entirely anyway
448         data[findx]  = buf
449
450     else
451         local lindx = floor((offset + #buf - 1)/BLOCKSIZE)
452
453                 -- used for both next if/else sections
454         local block = self:_getblock(map[findx])
455
456         -- fast and nice: same index, but substr() is needed
457         if findx == lindx then
458             local a = offset % BLOCKSIZE
459             local b = a + #buf + 1
460
461             data[findx]  = substr(block,0,a) .. buf .. substr(block,b)
462         else
463             -- simple checks don't match: multiple blocks need to be adjusted.
464             -- I'll do that in 3 steps:
465
466             -- start: will exist, as findx!=lindx
467             local boffset = offset - findx*BLOCKSIZE
468             local a,b = 0,BLOCKSIZE - boffset
469             data[findx]  = substr(block, 0, boffset) .. substr(buf, a, b)
470
471             -- middle: doesn't necessarily have to exist
472             for i=findx+1,lindx-1 do
473                                 -- no need to read in block, it will be written entirely anyway
474                 a, b = b + 1, b + 1 + BLOCKSIZE
475                 data[i] = substr(buf, a, b) 
476             end
477
478             -- end: maybe exist, as findx!=lindx, and not ending on blockboundary
479                 block = self:_getblock(map[lindx])
480             a, b = b + 1, b + 1 + BLOCKSIZE
481             data[lindx]  = substr(buf, a, b) .. substr(block, b)
482
483         end
484     end
485
486     -- rewrite all blocks to disk
487     for i, _ in pairs(data) do
488
489         -- find a new block that's free
490         local ok, new_block_nr = pcall(luafs._getnextfreeblocknr, self, entity, STRIDE)
491         if not ok then
492             obj.errorcode = ENOSPC
493             return ENOSPC
494         end
495         
496         -- really writ the data to the new block
497         self:_writeblock(path, new_block_nr, data[i])
498
499         -- save the blocknr for the journal
500         data[i] = new_block_nr
501     end
502     
503     -- adjust the metadata in the journal, we piggyback the new size in this
504     -- call. This way, when traversing the journal, we can set the size correct
505     local size = entity.size > (offset + #buf) and entity.size or (offset + #buf)
506     for i, _ in pairs(data) do
507         self:_setblock(path, i, data[i], size)
508     end
509
510     return #buf
511 end,
512
513 _getnextfreeblocknr = function (self, meta, stride_wanted)
514     meta.freelist = meta.freelist or {}
515     local bfree = meta.freelist
516     local nextfreeblock = next(bfree)
517     if not nextfreeblock then 
518         if #freelist_index > 0 then
519             next_free_stride = freelist_index[1]
520             print('_getnextfreeblocknr:'..next_free_stride..
521                   ',size:'..freelist[next_free_stride])
522             if freelist[next_free_stride] - next_free_stride >= stride_wanted then
523                 freelist[next_free_stride + stride_wanted] = freelist[next_free_stride]
524                 freelist_index[1] = next_free_stride + stride_wanted
525             else 
526                 print('_getnextfreeblocknr:setting to nil:'..next_free_stride)
527                 shift(freelist_index)
528             end
529             freelist[next_free_stride] = nil
530             blocks_in_freelist = blocks_in_freelist - stride_wanted
531         else
532             -- watermark shift
533             next_free_stride = block_nr
534             block_nr = block_nr + stride_wanted
535             if block_nr >= max_block_nr then
536                 block_nr = block_nr - stride_wanted
537                 error({code=1, message="Disk Full"})
538             end
539         end
540         nextfreeblock = next_free_stride
541         if stride_wanted > 1 then
542             bfree[nextfreeblock + 1] = nextfreeblock + stride_wanted - 1
543         end
544     else
545         print("from file freelist:"..nextfreeblock..',bfree[nextfreeblock]:'..bfree[nextfreeblock])
546         if     bfree[nextfreeblock] ~= nextfreeblock 
547            and not bfree[nextfreeblock+1]
548            and nextfreeblock < stride_wanted*floor(nextfreeblock/stride_wanted) + stride_wanted then
549             bfree[nextfreeblock+1] = bfree[nextfreeblock]
550         end
551         bfree[nextfreeblock]   = nil
552     end
553     return nextfreeblock
554 end,
555
556 _addtofreelist = function (self, blocklist)
557     if not blocklist then 
558         return  
559     end
560     for i,b in pairs(blocklist) do
561         print("_addtofreelist:i:"..i..",b:"..b)
562         freelist[i] = b
563         blocks_in_freelist = blocks_in_freelist + b - i + 1
564         push(freelist_index, i)
565     end
566     -- FIXME: bad idea, implement a better one
567     --luafs._canonicalize_freelist(self)
568     return
569 end,
570
571 _freesingleblock = function (self, b, meta)
572     meta.freelist[b] = b
573     return
574 end,
575
576 _writeblock = function(self, path, blocknr, blockdata)
577
578     -- this is an actual write of the data to disk. This does not change the
579     -- meta journal, that is done seperately, and, it is done at the end.
580     --
581     
582     assert(journal_fh:seek('set', BLOCKSIZE*blocknr))
583     assert(journal_fh:write(blockdata))
584     assert(journal_fh:flush())
585 end,
586
587 _setblock = function(self, path, i, bnr, size, ctime)
588
589     -- a call to this function will also write a meta journal entry, write() is
590     -- *NOT* journaled, these calls are instead, so that the writing of data is
591     -- always strict
592
593     local e = fs_meta[path]
594
595     -- FIXME: hack ahead: when traversing the journal, self is nil. So we
596     -- pretent to requested a block here, just like it is done in write()
597     -- itself. The returned block should be the same as bnr here I think.
598     if not self then
599         local dummy = luafs._getnextfreeblocknr(self, e, STRIDE)
600         if dummy ~= bnr then
601             error("Internal error: bnr~=dummy: bnr:"..
602                     bnr..",_getnextfreeblocknr():"..dummy)
603         end
604         if bnr > block_nr then
605             block_nr = bnr
606         end
607     end
608
609     -- free the previous block
610     if e.blockmap[i] then
611         luafs._freesingleblock(self, e.blockmap[i], e)
612     end
613     
614     -- reset that block with the new one
615     e.blockmap[i] = bnr
616
617     -- adjust meta data
618     e.size        = size
619     e.ctime       = ctime
620     e.mtime       = ctime
621
622     return 0
623 end,
624
625 _getblock = function(self, blocknr)
626     
627     if blocknr ~= nil then
628         assert(journal_fh:seek('set', BLOCKSIZE*blocknr))
629         local a = assert(journal_fh:read(BLOCKSIZE))
630         print("_getblock|return:"..#a)
631         if a and #a then
632             return a
633         end
634     end
635     return empty_block
636 end,
637
638 release = function(self, path, obj)
639     obj.f = nil
640     return 0
641 end,
642
643 flush = function(self, path, obj)
644     if obj and obj.errorcode then
645         return obj.errorcode
646     end
647     return 0
648 end,
649
650 ftruncate = function(self, path, size, obj)
651     return self:truncate(path, size)
652 end,
653
654 truncate = function(self, path, size, ctime)
655
656     if size < 0 then
657         return EINVAL
658     end
659
660     -- FIXME:
661     -- restriction of lua? or fuse.c? or.. maybe find out someday why there's a
662     -- weird max and fix it. I want my files to be big! :-)
663     if size >= 2147483647 then
664         return EFBIG
665     end
666
667     local m = fs_meta[path]
668
669     if size > 0 then
670     
671         -- if the truncate call would make it bigger: just adjust the size, no
672         -- point in allready allocating a block
673         if size >= m.size then
674             m.size = size
675         end
676
677         -- new size would be smaller: update at least 1 block + give all the
678         -- remainder truncated blocks back to the freelist
679
680         -- update blockmap
681         local lindx = floor(size/BLOCKSIZE)
682
683         -- free the blocks to the filesystem's freelist!
684         local remainder = list.truncate(m.blockmap, lindx + 1)
685         luafs._addtofreelist(self, m.freelist)
686         luafs._addtofreelist(self, remainder)
687         
688
689         -- FIXME: dirty hack: self == nil is init fase, during run-fase (pre
690         --        this init mount()), the block was written allready
691         if self then
692             local str = self:_getblock(m.blockmap[lindx])
693
694             -- always write as a new block
695             local ok, new_block_nr = pcall(luafs._getnextfreeblocknr, self, m, STRIDE)
696             if not ok then
697                 return ENOSPC
698             end
699
700             self:_writeblock(path, new_block_nr, substr(str,0,size%BLOCKSIZE))
701
702             -- this puts an entry in the journal for the block set, with
703             -- correct size and all.
704             --
705             -- Thus, truncate has 2 calls in the journal:
706             --   1. truncate()
707             --   2. _setblock()
708             --
709             -- During mount, the journal needs those 2 together, which is in
710             -- fact not safe!
711             --
712             -- This is because I don't want to have no truncate(): then I would
713             -- need _setblock() for all null-ed blocks.
714             --
715             self:_setblock(path, lindx, new_block_nr, size)
716         end
717     else 
718
719         -- free the blocks: just add to the filesystem's freelist
720         luafs._addtofreelist(self, m.freelist)
721         luafs._addtofreelist(self, (rawget(m.blockmap, '_original')).list)
722
723         m.freelist = nil
724         m.blockmap = list:new()
725         m.ctime    = ctime
726         m.mtime    = ctime
727         m.size     = 0
728     end
729
730     return 0
731 end,
732
733 rename = function(self, from, to, ctime)
734
735     -- FUSE handles paths, e.g. a file being moved to a directory: the 'to'
736     -- becomes that target directory + "/" + basename(from).
737     --
738
739     -- if the target still exists, e.g. when you move a file to another file,
740     -- first free the blocks: just add to the filesystem's freelist. For this
741     -- we can simply unlink it, just make sure we use the real unlink, not the
742     -- journalled one. Of course, we only unlink when it's a file, not in other
743     -- cases. unlink here also maintains the nlink parameter.
744     if fs_meta[to] and fs_meta[to].blockmap then
745         luafs._unlink(self, to, ctime)
746     end
747
748     -- rename main node
749     fs_meta[to]   = fs_meta[from]
750     fs_meta[from] = nil
751
752     -- rename both parent's references to the renamed entity
753     local p,e
754
755     -- 'to'
756     p, e = splitpath(to)
757     fs_meta[p].directorylist[e] = fs_meta[to]
758     fs_meta[p].nlink = fs_meta[p].nlink + 1
759     fs_meta[p].ctime = ctime
760     fs_meta[p].mtime = ctime
761
762     -- 'from'
763     p,e = splitpath(from)
764     fs_meta[p].directorylist[e] = nil
765     fs_meta[p].nlink = fs_meta[p].nlink - 1
766     fs_meta[p].ctime = ctime
767     fs_meta[p].mtime = ctime
768
769     -- rename all decendants, maybe not such a good idea to use this
770     -- mechanism, but don't forget, how many times does one rename e.g.
771     -- /usr and such.. ;-). for a plain file (or empty subdir), this is for
772     -- isn't even executed (looped actually)
773     --
774     if fs_meta[to].directorylist then
775         local ts = to   .. "/"
776         local fs = from .. "/"
777         for sub in pairs(fs_meta[to].directorylist) do
778             ts = ts .. sub
779             fs = fs .. sub
780             print("r:"..sub..",to:"..ts..",from:"..fs)
781             fs_meta[ts] = fs_meta[fs]
782             fs_meta[fs] = nil
783         end 
784     end
785
786     return 0
787 end,
788
789 symlink = function(self, from, to, cuid, cgid, ctime)
790     -- 'from' isn't used,.. that can be even from a seperate filesystem, e.g.
791     -- when someone makes a symlink on this filesystem...
792     local parent,file = splitpath(to)
793     fs_meta[to] = new_meta(mk_mode(7,7,7) + S_IFLNK, cuid, cgid, ctime)
794     fs_meta[to].target = from
795     fs_meta[parent].directorylist[file] = fs_meta[to]
796     fs_meta[parent].ctime = ctime
797     fs_meta[parent].mtime = ctime
798     return 0
799 end,
800
801 readlink = function(self, path)
802     local entity = fs_meta[path]
803     if entity then
804         return 0, fs_meta[path].target
805     else
806         return ENOENT, nil
807     end
808 end,
809
810 link = function(self, from, to, ctime)
811     local entity = fs_meta[from]
812     if entity then
813         -- update meta
814         entity.ctime = ctime
815         entity.nlink = (entity.nlink or 1) + 1
816
817         -- 'copy'
818         fs_meta[to]  = fs_meta[from]
819
820         -- update the TO parent: add entry + change meta
821         local toparent,e = splitpath(to)
822         fs_meta[toparent].directorylist[e] = fs_meta[to]
823         fs_meta[toparent].ctime = ctime
824         fs_meta[toparent].mtime = ctime
825         
826         return 0
827     else
828         return ENOENT
829     end
830 end,
831
832 unlink = function(self, path, ctime)
833     return luafs._unlink(self, path, ctime)
834 end,
835
836 _unlink = function(self, path, ctime)
837
838     local entity = fs_meta[path]
839     entity.nlink = (entity.nlink or 1) - 1
840     entity.ctime = ctime
841
842     local p,e = splitpath(path)
843     fs_meta[p].directorylist[e] = nil
844     fs_meta[p].ctime = ctime
845     fs_meta[p].mtime = ctime
846
847     -- nifty huh ;-).. : decrease links to the entry + delete *this*
848     -- reference from the tree and the meta, other references will see the
849     -- decreased nlink from that
850
851     fs_meta[path] = nil
852
853     if entity.nlink == 0 then
854         if entity.freelist then
855             luafs._addtofreelist(self, entity.freelist)
856         end
857         if entity.blockmap then
858             luafs._addtofreelist(self, (rawget(entity.blockmap, '_original')).list)
859         end
860     end
861
862     return 0
863 end,
864
865 mknod = function(self, path, mode, rdev, cuid, cgid, ctime)
866     -- only called for non-symlinks, non-directories, non-files and links as
867     -- those are handled by symlink, mkdir, create, link. This is called when
868     -- mkfifo is used to make a named pipe for instance.
869     --
870     -- FIXME: support 'plain' mknod too: S_IFBLK and S_IFCHR
871     fs_meta[path]         = new_meta(mode, cuid, cgid, ctime)
872     fs_meta[path].dev     = rdev
873
874     local parent,file = splitpath(path)
875     fs_meta[parent].directorylist[file] = fs_meta[path]
876     fs_meta[parent].ctime = ctime
877     fs_meta[parent].mtime = ctime
878     return 0
879 end,
880
881 chown = function(self, path, uid, gid, cuid, cgid, ctime)
882     local entity = fs_meta[path] 
883     if entity then
884
885         -- Funny this is.. but this appears to be ext3 on linux behavior.
886         -- However, FUSE doesn't give me e.g. -1 -1 as user root, while it
887         -- wants the ctime to be adjusted. I think this is the nitty gritty
888         -- details that makes this code rather 'not needed' anywayz..
889         --
890         -- That's the reason why tests 141, 145, 149 and 153 of pjd fail
891         -- btw...
892         if cuid ~= 0 then
893             if not (uid == MAXINT and gid == MAXINT) then
894                 entity.mode = _band(entity.mode, _bnot(S_SID))
895             end
896         end
897         if uid ~= MAXINT then entity.uid = uid end
898         if gid ~= MAXINT then entity.gid = gid end
899         entity.ctime = ctime
900         return 0
901     else
902         return ENOENT
903     end
904 end,
905
906 chmod = function(self, path, mode, ctime)
907     local entity = fs_meta[path] 
908     if entity then
909         entity.mode  = mode
910         entity.ctime = ctime
911         return 0
912     else
913         return ENOENT
914     end
915 end,
916
917 utime = function(self, path, atime, mtime)
918     local entity = fs_meta[path] 
919     if entity then
920         entity.atime = atime
921         entity.mtime = mtime
922         return 0
923     else
924         return ENOENT
925     end
926 end,
927
928
929 utimens = function(self, path, atime, mtime)
930     local entity = fs_meta[path] 
931     if entity then
932         entity.atime = atime
933         entity.mtime = mtime
934         return 0
935     else
936         return ENOENT
937     end
938 end,
939
940 access = function(self, path, mode)
941     return 0
942 end,
943
944 fsync = function(self, path, isdatasync, obj)
945     return 0
946 end,
947
948 fsyncdir = function(self, path, isdatasync, obj)
949     return 0
950 end,
951
952 fgetattr = function(self, path, obj)
953     return self:getattr(path)
954 end,
955
956 destroy = function(self, return_value_from_init)
957     journal_fh:close()
958     self:serializemeta()
959     return 0
960 end,
961
962 bmap = function(self, path, blocksize, index)
963     return 0
964 end,
965
966 getattr = function(self, path)
967     if #path > 1024 then
968         return ENAMETOOLONG
969     end
970
971     -- FIXME: All ENAMETOOLONG needs to implemented better (and correct)
972     local dir,file = splitpath(path)
973     if #file > 255 then
974         return ENAMETOOLONG
975     end
976     local x = fs_meta[path]
977     if not x then
978         return ENOENT, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil
979     end 
980     if debug then
981         print("getattr():"..x.mode..",".. x.ino..",".. (x.dev or '<dev=nil but returning 0>')
982               ..",".. (x.nlink or '<nlink=nil but returning 1>')..",".. x.uid..",".. x.gid..",".. x.size..","
983               .. x.atime..",".. x.mtime..",".. x.ctime)
984     end
985     return 0, x.mode, x.ino, x.dev or 0, x.nlink or 1, x.uid, x.gid, x.size, x.atime, x.mtime, x.ctime    
986 end,
987
988 listxattr = function(self, path, size)
989     if fs_meta[path] then
990         local s = "\0"
991         for k,v in pairs(fs_meta[path].xattr or {}) do 
992             if type(v) == "string" then
993                 s = v .. "\0" .. s
994             end
995         end
996         return 0, s
997     else
998         return ENOENT, nil
999     end
1000 end,
1001
1002 removexattr = function(self, path, name)
1003     if fs_meta[path] and fs_meta[path].xattr then
1004         fs_meta[path].xattr[name] = nil
1005         return 0
1006     else
1007         return ENOENT
1008     end
1009 end,
1010
1011 setxattr = function(self, path, name, val, flags)
1012     local e = fs_meta[path]
1013     if e then
1014         e.xattr = e.xattr or {}
1015         e.xattr[name] = val
1016         return 0
1017     else
1018         return ENOENT
1019     end
1020 end,
1021
1022 getxattr = function(self, path, name, size)
1023     local e = fs_meta[path]
1024     if fs_meta[path] then
1025         -- xattr 'name' not found is empty string ""
1026         e.xattr = e.xattr or {}
1027         return e.xattr[name] or ""
1028     else
1029         return ENOENT, ""
1030     end
1031 end,
1032
1033 statfs = function(self, path)
1034     local nr_of_free_blocks = max_block_nr - (block_nr + 1) + blocks_in_freelist
1035     return 
1036         0,
1037         BLOCKSIZE, 
1038         max_block_nr, 
1039         nr_of_free_blocks, 
1040         nr_of_free_blocks, 
1041         inode_start,
1042         MAXINT
1043 end,
1044
1045 _canonicalize_freelist = function(self)
1046     freelist_index = {}
1047     local last
1048     for i,v in pairsByKeys(freelist) do
1049         if not last then
1050             last = i
1051             push(freelist_index, last)
1052         else
1053             if i == freelist[last] + 1 then
1054                 freelist[last] = freelist[i]
1055                 freelist[i]    = nil
1056             else
1057                 last = i
1058                 push(freelist_index, last)
1059             end
1060         end
1061     end
1062     return
1063 end,
1064
1065 serializemeta = function(self)
1066
1067     -- a hash that transfers inode numbers to the first dumped path, this
1068     -- serves the purpose of making the hardlinks correct. Of course, we only
1069     -- keep them here when the number of links > 1. (Or in case a directory is
1070     -- linked, >2)
1071     local inode = {}
1072
1073     -- write the main globals first
1074     local new_meta_fh = io.open(self.metafile..'.new', 'w')
1075     new_meta_fh:write('block_nr,inode_start,blocks_in_freelist='
1076                       ..block_nr..','..inode_start..','..blocks_in_freelist..'\n')
1077
1078     -- write the freelist
1079     self:_canonicalize_freelist()
1080     local fl = {}
1081     for i, v in pairs(freelist) do
1082         push(fl, '['..i..']='..v)
1083     end
1084     new_meta_fh:write('freelist = {', join(fl, ','), '}\n')
1085     new_meta_fh:write('freelist_index = {', join(freelist_index, ','), '}\n')
1086
1087     -- loop over all filesystem entries
1088     for k,e in pairs(fs_meta) do
1089         local prefix = 'fs_meta["'..k..'"]'
1090         if inode[e.ino] then
1091
1092             -- just add a link
1093             new_meta_fh:write(prefix,' = fs_meta["',inode[e.ino],'"]\n')
1094
1095         else
1096
1097             -- save that ref for our hardlink tree check
1098             if (e.directorylist and e.nlink > 2) or (e.nlink and e.nlink > 1) then
1099                 inode[e.ino] = k
1100             end
1101
1102             -- regular values + symlink target
1103             local meta_str = {}
1104             for key, value in pairs(e) do
1105                 if type(value) == "number" then
1106                     push(meta_str, key..'='..value)
1107                 elseif type(value) == "string" then
1108                     push(meta_str, key..'='..format("%q", value))
1109                 end
1110             end
1111             new_meta_fh:write(prefix,'={', join(meta_str, ","))
1112
1113             -- metadata:xattr
1114             if e.xattr then
1115                 local xattr_str = {}
1116                 for x,v in pairs(e.xattr) do
1117                     if type(v) == 'boolean' and v == true then
1118                         push(xattr_str, '["'..x..'"]=true')
1119                         break
1120                     end
1121                     if type(v) == 'boolean' and v == false then
1122                         push(xattr_str, '["'..x..'"]=false')
1123                         break
1124                     end
1125                     push(xattr_str, '["'..x..'"]='..format('%q',v))
1126                 end
1127                 push(meta_str, 'xattr={'..join(xattr_str, ',')..'}')
1128             end
1129
1130             -- 'real' data entry stuff
1131             local t = {}
1132             if e.directorylist then
1133
1134                 -- directorylist
1135                 for d, _ in pairs(e.directorylist) do
1136                     push(t, '["'..d..'"]=true')
1137                 end
1138                 new_meta_fh:write(',directorylist={',join(t, ','),'}}\n')
1139
1140             elseif e.blockmap then
1141
1142                 -- dump the freelist
1143                 if e.freelist and next(e.freelist) then
1144                     fl = {}
1145                     for i, v in pairs(e.freelist) do
1146                         push(fl, '['..i..']='..v)
1147                     end
1148                     new_meta_fh:write(',freelist={',
1149                         join(fl, ','),
1150                     '}')
1151                 end
1152
1153                 -- dump the blockmap
1154                 new_meta_fh:write(',blockmap=list:new{',
1155                     list.tostring(e.blockmap),
1156                 '}}\n')
1157
1158             else
1159
1160                 -- was a symlink, node,.. just close the tag
1161                 new_meta_fh:write('}\n')
1162             end
1163         end
1164     end
1165     new_meta_fh:write(empty_block, empty_block)
1166     new_meta_fh:close()
1167
1168     return 0
1169 end,
1170
1171 metadev  = "/dev/loop7",
1172 metafile = "/home/tim/tmp/fs/test.lua",
1173
1174 }
1175
1176 --
1177 -- commandline option parsing/checking section
1178 --
1179 -- -s option: single threaded. multithreaded also works, but no performance
1180 -- gain (yet), in fact, it's slower.
1181 --
1182 options = {
1183     'luafs',
1184     ...
1185 }
1186 fuse_options = {
1187     '-s', 
1188     '-f', 
1189     '-oallow_other',
1190     '-odefault_permissions',
1191     '-ohard_remove', 
1192     '-oentry_timeout=0',
1193     '-onegative_timeout=0',
1194     '-oattr_timeout=0',
1195     '-ouse_ino',
1196     '-oreaddir_ino',
1197     '-omax_read=131072',
1198     '-omax_readahead=131072',
1199     '-omax_write=131072',
1200
1201 }
1202
1203 for i,w in ipairs(fuse_options) do
1204     push(options, w)
1205 end
1206
1207 -- check the mountpoint
1208 local here = lfs.currentdir()
1209 if not lfs.chdir(options[2]) then
1210     print("mountpoint "..options[2].." does not exist")
1211     os.exit(1)
1212 end
1213 lfs.chdir(here)
1214
1215 -- simple options check
1216 if select('#', ...) < 1 then
1217     print(string.format("Usage: %s <mount point> [fuse mount options]", arg[0]))
1218     os.exit(1)
1219 end
1220
1221
1222 --
1223 -- debugging section
1224 --
1225 local debug = 0
1226 for i,w in ipairs(options) do
1227     if w == '-d'  then
1228         debug = 1
1229     end
1230 end
1231 oldprint = print
1232 function say(...)
1233     return oldprint(time(), unpack(arg))
1234 end
1235 if debug == 0 then 
1236     function print() end
1237 end
1238
1239 say("start")
1240
1241 -- debug?
1242 if debug then
1243     for k, f in pairs(luafs) do
1244         if type(f) == 'function' then
1245             luafs[k] = function(self,...) 
1246                 
1247                 local d = {}
1248                 for i,v in ipairs(arg) do
1249                     d[i] = tostring(v) 
1250                 end
1251                 print("function:"..k.."(),args:"..join(d, ","))
1252
1253                 -- really call the function
1254                 return f(self, unpack(arg))
1255             end
1256         end
1257     end
1258 end
1259
1260 for i,w in ipairs(options) do
1261     print("option:"..w)
1262 end
1263
1264 --
1265 -- start the main fuse loop
1266 --
1267 print("main()")
1268 fuse.main(luafs, options)