Package fcp :: Module freenetfs
[hide private]
[frames] | no frames]

Source Code for Module fcp.freenetfs

   1  #! /usr/bin/env python 
   2  #@+leo-ver=4 
   3  #@+node:@file freenetfs.py 
   4  #@@first 
   5  """ 
   6  A FUSE-based filesystem for freenet 
   7   
   8  Written May 2006 by aum 
   9   
  10  Released under the GNU Lesser General Public License 
  11   
  12  Requires: 
  13      - python2.3 or later 
  14      - FUSE kernel module installed and loaded 
  15        (apt-get install fuse-source, crack tarball, build and install) 
  16      - python2.3-fuse 
  17      - libfuse2 
  18  """ 
  19   
  20  #@+others 
  21  #@+node:imports 
  22  import sys, os, time, stat, errno 
  23  from StringIO import StringIO 
  24  import thread 
  25  from threading import Lock 
  26  import traceback 
  27  from Queue import Queue 
  28  from hashlib import md5, sha1 
  29  from UserString import MutableString 
  30   
  31  from errno import * 
  32  from stat import * 
  33   
  34  try: 
  35      import warnings 
  36      warnings.filterwarnings('ignore', 
  37                              'Python C API version mismatch', 
  38                              RuntimeWarning, 
  39                              ) 
  40  except: 
  41      pass 
  42    
  43  import sys 
  44  from errno import * 
  45   
  46  import fcp 
  47   
  48  from fcp.xmlobject import XMLFile 
  49  from fcp.node import guessMimetype, base64encode, base64decode, uriIsPrivate 
  50   
  51  #@-node:imports 
  52  #@+node:globals 
  53  argv = sys.argv 
  54  argc = len(argv) 
  55  progname = argv[0] 
  56   
  57  fcpHost = fcp.node.defaultFCPHost 
  58  fcpPort = fcp.node.defaultFCPPort 
  59   
  60  defaultVerbosity = fcp.DETAIL 
  61   
  62  quiet = 0 
  63   
  64  myuid = os.getuid() 
  65  mygid = os.getgid() 
  66   
  67  inodes = {} 
  68  inodesNext = 1 
  69   
  70  # set this to disable hits to node, for debugging 
  71  _no_node = 0 
  72   
  73  # special filenames in freedisk toplevel dirs 
  74  freediskSpecialFiles = [ 
  75      '.privatekey', '.publickey', '.cmd', '.status', ".passwd", 
  76      ] 
  77   
  78  showAllExceptions = False 
  79   
  80  #@-node:globals 
  81  #@+node:class ErrnoWrapper 
82 -class ErrnoWrapper:
83
84 - def __init__(self, func):
85 self.func = func
86
87 - def __call__(self, *args, **kw):
88 try: 89 return apply(self.func, args, kw) 90 except (IOError, OSError), detail: 91 if showAllExceptions: 92 traceback.print_exc() 93 # Sometimes this is an int, sometimes an instance... 94 if hasattr(detail, "errno"): detail = detail.errno 95 return -detail
96 97 98 #@-node:class ErrnoWrapper 99 #@+node:class FreenetBaseFS
100 -class FreenetBaseFS:
101 102 #@ @+others 103 #@+node:attribs 104 multithreaded = 0 105 flags = 1 106 debug = False 107 fcpHost = fcpHost 108 fcpPort = fcpPort 109 verbosity = defaultVerbosity 110 allow_other = False 111 kernel_cache = False 112 config = os.path.join(os.path.expanduser("~"), ".freediskrc") 113 114 # Files and directories already present in the filesytem. 115 # Note - directories must end with "/" 116 117 initialFiles = [ 118 "/", 119 "/get/", 120 "/put/", 121 "/keys/", 122 "/usr/", 123 "/cmds/", 124 ] 125 126 chrFiles = [ 127 ] 128 129 #@-node:attribs 130 #@+node:__init__
131 - def __init__(self, mountpoint, *args, **kw):
132 """ 133 Create a freenetfs 134 135 Arguments: 136 - mountpoint - the dir in the filesystem at which to mount the fs 137 - other args get passed to fuse 138 139 Keywords: 140 - multithreaded - whether to run the fs multithreaded, default True 141 - fcpHost - hostname of FCP service 142 - fcpPort - port number of FCP service 143 - verbosity - defaults to fcp.DETAIL 144 - config - location of config file 145 - debug - whether to run in debug mode, default False 146 """ 147 148 self.log("FreenetBaseFS.__init__: args=%s kw=%s" % (args, kw)) 149 150 for k in ['multithreaded', 151 'fcpHost', 152 'fcpPort', 153 'verbosity', 154 'debug', 155 ]: 156 if kw.has_key(k): 157 v = kw.pop(k) 158 try: 159 v = int(v) 160 except: 161 pass 162 163 setattr(self, k, v) 164 165 self.optlist = list(args) 166 self.optdict = dict(kw) 167 168 self.mountpoint = mountpoint 169 170 #if not self.config: 171 # raise Exception("Missing 'config=filename.conf' argument") 172 173 #self.loadConfig() 174 self.setupFiles() 175 self.setupFreedisks() 176 177 # do stuff to set up your filesystem here, if you want 178 #thread.start_new_thread(self.mythread, ()) 179 180 if 0: 181 self.log("xmp.py:Xmp:mountpoint: %s" % repr(self.mountpoint)) 182 self.log("xmp.py:Xmp:unnamed mount options: %s" % self.optlist) 183 self.log("xmp.py:Xmp:named mount options: %s" % self.optdict) 184 185 try: 186 self.node = None 187 self.connectToNode() 188 except: 189 raise 190 pass
191 192 #@-node:__init__ 193 #@+node:command handlers 194 # methods which handle filesystem commands 195 196 #@+others 197 #@+node:executeCommand
198 - def executeCommand(self, cmd):
199 """ 200 Executes a single-line command that was submitted as 201 a base64-encoded filename in /cmds/ 202 """ 203 self.log("executeCommand:cmd=%s" % repr(cmd)) 204 205 try: 206 cmd, args = cmd.split(" ", 1) 207 args = args.split("|") 208 except: 209 return "error\nInvalid command %s" % repr(cmd) 210 211 method = getattr(self, "cmd_"+cmd, None) 212 if method: 213 return method(*args) 214 else: 215 return "error\nUnrecognised command %s" % repr(cmd)
216 217 #@-node:executeCommand 218 #@+node:cmd_hello
219 - def cmd_hello(self, *args):
220 221 return "ok\nhello: args=%s" % repr(args)
222 223 #@-node:cmd_hello 224 #@+node:cmd_mount
225 - def cmd_mount(self, *args):
226 """ 227 tries to mount a freedisk 228 229 arguments: 230 - diskname 231 - uri (may be public or private) 232 - password 233 """ 234 #print "mount: args=%s" % repr(args) 235 236 try: 237 name, uri, passwd = args 238 except: 239 return "error\nmount: invalid arguments %s" % repr(args) 240 241 try: 242 self.addDisk(name, uri, passwd) 243 except: 244 return "error\nmount: failed to mount disk %s" % name 245 246 return "ok\nmount: successfully mounted disk %s" % name
247 248 #@-node:cmd_mount 249 #@+node:cmd_umount
250 - def cmd_umount(self, *args):
251 """ 252 tries to unmount a freedisk 253 254 arguments: 255 - diskname 256 """ 257 #print "mount: args=%s" % repr(args) 258 259 try: 260 name = args[0] 261 except: 262 return "error\numount: invalid arguments %s" % repr(args) 263 264 try: 265 self.delDisk(name) 266 except: 267 traceback.print_exc() 268 return "error\numount: failed to unmount freedisk '%s'" % name 269 270 return "ok\numount: successfully unmounted freedisk %s" % name
271 272 #@-node:cmd_umount 273 #@+node:cmd_update
274 - def cmd_update(self, *args):
275 """ 276 Does an update of a freedisk from freenet 277 """ 278 #print "update: args=%s" % repr(args) 279 280 try: 281 name = args[0] 282 except: 283 return "error\nupdate: invalid arguments %s" % repr(args) 284 285 try: 286 self.updateDisk(name) 287 except: 288 traceback.print_exc() 289 return "error\nupdate: failed to update freedisk '%s'" % name 290 291 return "ok\nupdate: successfully updated freedisk '%s'" % name
292 293 #@-node:cmd_update 294 #@+node:cmd_commit
295 - def cmd_commit(self, *args):
296 """ 297 Does an commit of a freedisk into freenet 298 """ 299 try: 300 name = args[0] 301 except: 302 return "error\ninvalid arguments %s" % repr(args) 303 304 try: 305 uri = self.commitDisk(name) 306 except: 307 traceback.print_exc() 308 return "error\nfailed to commit freedisk '%s'" % name 309 310 return "ok\n%s" % uri
311 312 #@-node:cmd_commit 313 #@-others 314 315 #@-node:command handlers 316 #@+node:fs primitives 317 # primitives required for actual fs operations 318 319 #@+others 320 #@+node:chmod
321 - def chmod(self, path, mode):
322 323 ret = os.chmod(path, mode) 324 self.log("chmod: path=%s mode=%s\n => %s" % (path, mode, ret)) 325 return ret
326 327 #@-node:chmod 328 #@+node:chown
329 - def chown(self, path, user, group):
330 331 ret = os.chown(path, user, group) 332 self.log("chmod: path=%s user=%s group=%s\n => %s" % (path, user, group, ret)) 333 return ret
334 335 #@-node:chown 336 #@+node:fsync
337 - def fsync(self, path, isfsyncfile):
338 339 self.log("fsync: path=%s, isfsyncfile=%s" % (path, isfsyncfile)) 340 return 0
341 342 #@-node:fsync 343 #@+node:getattr
344 - def getattr(self, path):
345 346 rec = self.files.get(path, None) 347 if not rec: 348 # each of these code segments should assign a record to 'rec', 349 # or raise an IOError 350 351 # retrieving a key? 352 if path.startswith("/keys/"): 353 #@ <<generate keypair>> 354 #@+node:<<generate keypair>> 355 # generate a new keypair 356 self.connectToNode() 357 pubkey, privkey = self.node.genkey() 358 rec = self.addToCache( 359 path=path, 360 isreg=True, 361 data=pubkey+"\n"+privkey+"\n", 362 perm=0444, 363 ) 364 365 #@-node:<<generate keypair>> 366 #@nl 367 elif path.startswith("/get/"): 368 #@ <<retrieve/cache key>> 369 #@+node:<<retrieve/cache key>> 370 # check the cache 371 if _no_node: 372 print "FIXME: returning IOerror" 373 raise IOError(errno.ENOENT, path) 374 375 # get a key 376 uri = path.split("/", 2)[-1] 377 try: 378 self.connectToNode() 379 mimetype, data = self.node.get(uri) 380 rec = self.addToCache( 381 path=path, 382 isreg=True, 383 perm=0644, 384 data=data, 385 ) 386 387 except: 388 traceback.print_exc() 389 #print "ehhh?? path=%s" % path 390 raise IOError(errno.ENOENT, path) 391 392 #@-node:<<retrieve/cache key>> 393 #@nl 394 elif path.startswith("/cmds/"): 395 #@ <<base64 command>> 396 #@+node:<<base64 command>> 397 # a command has been encoded via base64 398 399 cmdBase64 = path.split("/cmds/", 1)[-1] 400 401 cmd = base64decode(cmdBase64) 402 403 result = self.executeCommand(cmd) 404 405 rec = self.addToCache(path=path, isreg=True, data=result, perm=0644) 406 407 #@-node:<<base64 command>> 408 #@nl 409 else: 410 raise IOError(errno.ENOENT, path) 411 412 self.log("getattr: path=%s" % path) 413 self.log(" mode=0%o" % rec.mode) 414 self.log(" inode=0x%x" % rec.inode) 415 self.log(" dev=0x%x" % rec.dev) 416 self.log(" nlink=0x%x" % rec.nlink) 417 self.log(" uid=%d" % rec.uid) 418 self.log(" gid=%d" % rec.gid) 419 self.log(" size=%d" % rec.size) 420 self.log(" atime=%d" % rec.atime) 421 self.log(" mtime=%d" % rec.mtime) 422 self.log(" ctime=%d" % rec.ctime) 423 self.log("rec=%s" % str(rec)) 424 425 return tuple(rec)
426 427 #@-node:getattr 428 #@+node:getdir
429 - def getdir(self, path):
430 431 rec = self.files.get(path, None) 432 433 if rec: 434 files = [os.path.split(child.path)[-1] for child in rec.children] 435 files.sort() 436 if rec.isdir: 437 if path != "/": 438 files.insert(0, "..") 439 files.insert(0, ".") 440 else: 441 self.log("Hit main fs for %s" % path) 442 files = os.listdir(path) 443 444 ret = map(lambda x: (x,0), files) 445 446 self.log("getdir: path=%s\n => %s" % (path, ret)) 447 return ret
448 449 #@-node:getdir 450 #@+node:link 458 459 #@-node:link 460 #@+node:mkdir
461 - def mkdir(self, path, mode):
462 463 self.log("mkdir: path=%s mode=%s" % (path, mode)) 464 465 # barf if directory exists 466 if self.files.has_key(path): 467 raise IOError(errno.EEXIST, path) 468 469 # barf if happening outside /usr/ 470 if not path.startswith("/usr/"): 471 raise IOError(errno.EACCES, path) 472 473 parentPath = os.path.split(path)[0] 474 475 if parentPath == '/usr': 476 # creating a new freedisk 477 478 # create the directory record 479 rec = self.addToCache(path=path, isdir=True, perm=0555) 480 481 # create the pseudo-files within it 482 for name in freediskSpecialFiles: 483 subpath = os.path.join(path, name) 484 rec = self.addToCache(path=subpath, isreg=True, perm=0644) 485 if name == '.status': 486 rec.data = "idle" 487 488 # done here 489 return 0 490 491 elif path.startswith("/usr/"): 492 # creating a dir within a freedisk 493 494 # barf if no write permission in dir 495 diskPath = "/".join(path.split("/")[:3]) 496 diskRec = self.files.get(diskPath, None) 497 #if not diskRec: 498 # self.log("mkdir: diskPath=%s" % diskPath) 499 # raise IOError(errno.ENOENT, path) 500 if diskRec and not diskRec.canwrite: 501 self.log("mkdir: diskPath=%s" % diskPath) 502 raise IOError(errno.EPERM, path) 503 504 # ok to create 505 self.addToCache(path=path, isdir=True, perm=0755) 506 507 return 0
508 509 #@-node:mkdir 510 #@+node:mknod
511 - def mknod(self, path, mode, dev):
512 """ Python has no os.mknod, so we can only do some things """ 513 514 if path == "/": 515 #return -EINVAL 516 raise IOError(errno.EEXIST, path) 517 518 parentPath = os.path.split(path)[0] 519 if parentPath in ['/', '/usr']: 520 #return -EINVAL 521 raise IOError(errno.EPERM, path) 522 523 # start key write, if needed 524 if parentPath == "/put": 525 526 # see if an existing file 527 if self.files.has_key(path): 528 raise IOError(errno.EEXIST, path) 529 530 rec = self.addToCache( 531 path=path, isreg=True, iswriting=True, 532 perm=0644) 533 ret = 0 534 535 elif path.startswith("/usr/"): 536 # creating a file in a user dir 537 538 # barf if no write permission in dir 539 diskPath = "/".join(path.split("/")[:3]) 540 diskRec = self.files.get(diskPath, None) 541 #if not diskRec: 542 # raise IOError(errno.ENOENT, path) 543 if diskRec and not diskRec.canwrite: 544 self.log("mknod: diskPath=%s" % diskPath) 545 raise IOError(errno.EPERM, path) 546 547 # create the record 548 rec = self.addToCache(path=path, isreg=True, perm=0644, 549 iswriting=True, haschanged=True) 550 ret = 0 551 552 # fall back on host os 553 #if S_ISREG(mode): 554 # file(path, "w").close() 555 # ret = 0 556 557 else: 558 #ret = -EINVAL 559 raise IOError(errno.EPERM, path) 560 561 self.log("mknod: path=%s mode=0%o dev=%s\n => %s" % ( 562 path, mode, dev, ret)) 563 564 return ret
565 566 #@-node:mknod 567 #@+node:open
568 - def open(self, path, flags):
569 570 self.log("open: path=%s flags=%s" % (path, flags)) 571 572 # see if it's an existing file 573 rec = self.files.get(path, None) 574 575 if rec: 576 # barf if not regular file 577 if not (rec.isreg or rec.ischr): 578 self.log("open: %s is not regular file" % path) 579 raise IOError(errno.EIO, "Not a regular file: %s" % path) 580 581 else: 582 # fall back to host fs 583 raise IOError(errno.ENOENT, path) 584 585 for flag in [os.O_WRONLY, os.O_RDWR, os.O_APPEND]: 586 if flags & flag: 587 self.log("open: setting iswriting for %s" % path) 588 rec.iswriting = True 589 rec.haschanged = True 590 591 self.log("open: open of %s succeeded" % path) 592 593 # seems ok 594 return 0
595 596 #@-node:open 597 #@+node:read
598 - def read(self, path, length, offset):
599 """ 600 """ 601 # forward to existing file if any 602 rec = self.files.get(path, None) 603 if rec: 604 rec.seek(offset) 605 buf = rec.read(length) 606 607 self.log("read: path=%s length=%s offset=%s\n => %s" % ( 608 path, length, offset, len(buf))) 609 #print repr(buf) 610 return buf 611 612 else: 613 # fall back on host fs 614 f = open(path, "r") 615 f.seek(offset) 616 buf = f.read(length) 617 618 self.log("read: path=%s length=%s offset=%s\n => (%s bytes)" % ( 619 path, length, offset, len(buf))) 620 621 return buf
622 623 #@-node:read 624 #@+node:readlink 630 631 #@-node:readlink 632 #@+node:release
633 - def release(self, path, flags):
634 635 rec = self.files.get(path, None) 636 if not rec: 637 return 638 639 filename = os.path.split(path)[1] 640 641 # ditch any encoded command files 642 if path.startswith("/cmds/"): 643 #print "got file %s" % path 644 rec = self.files.get(path, None) 645 if rec: 646 self.delFromCache(rec) 647 else: 648 print "eh? not in cache" 649 650 # if writing, save the thing 651 elif rec.iswriting: 652 653 self.log("release: %s: iswriting=True" % path) 654 655 # what uri? 656 rec.iswriting = False 657 658 print "Release: path=%s" % path 659 660 if path.startswith("/put/"): 661 #@ <<insert to freenet>> 662 #@+node:<<insert to freenet>> 663 # insert directly to freenet as a key 664 665 uri = os.path.split(path)[1] 666 667 # frigs to allow fancy CHK@ inserts 668 if uri.startswith("CHK@"): 669 putUri = "CHK@" 670 else: 671 putUri = uri 672 673 ext = os.path.splitext(uri)[1] 674 675 try: 676 self.log("release: inserting %s" % uri) 677 678 mimetype = fcp.node.guessMimetype(path) 679 data = rec.data 680 681 # empty the pseudo-file till a result is through 682 rec.data = 'inserting' 683 684 self.connectToNode() 685 686 #print "FIXME: data=%s" % repr(data) 687 688 if _no_node: 689 print "FIXME: not inserting" 690 getUri = "NO_URI" 691 else: 692 # perform the insert 693 getUri = self.node.put( 694 putUri, 695 data=data, 696 mimetype=mimetype) 697 698 # strip 'freenet:' prefix 699 if getUri.startswith("freenet:"): 700 getUri = getUri[8:] 701 702 # restore file extension 703 if getUri.startswith("CHK@"): 704 getUri += ext 705 706 # now cache the read-back 707 self.addToCache( 708 path="/get/"+getUri, 709 data=data, 710 perm=0444, 711 isreg=True, 712 ) 713 714 # and adjust the written file to reveal read uri 715 rec.data = getUri 716 717 self.log("release: inserted %s as %s ok" % ( 718 uri, mimetype)) 719 720 except: 721 traceback.print_exc() 722 rec.data = 'failed' 723 self.log("release: insert of %s failed" % uri) 724 raise IOError(errno.EIO, "Failed to insert") 725 self.log("release: done with insertion") 726 727 #@-node:<<insert to freenet>> 728 #@nl 729 730 elif path.startswith("/usr/"): 731 #@ <<write to freedisk>> 732 #@+node:<<write to freedisk>> 733 # releasing a file being written into a freedisk 734 735 bits = path.split("/") 736 737 self.log("release: bits=%s" % str(bits)) 738 739 if bits[0] == '' and bits[1] == 'usr': 740 diskName = bits[2] 741 fileName = bits[3] 742 743 self.log("diskName=%s fileName=%s" % (diskName, fileName)) 744 745 if fileName == '.privatekey': 746 # written a private key, make the directory writeable 747 parentPath = os.path.split(path)[0] 748 parentRec = self.files[parentPath] 749 parentRec.canwrite = True 750 self.log("release: got privkey, mark dir %s read/write" % parentRec) 751 752 elif fileName == '.cmd': 753 # wrote a command 754 755 self.log("got release of .cmd") 756 757 cmd = rec.data.strip() 758 rec.data = "" 759 760 self.log("release: cmd=%s" % cmd) 761 762 # execute according to command 763 if cmd == 'commit': 764 self.commitDisk(diskName) 765 elif cmd == 'update': 766 self.updateDisk(diskName) 767 elif cmd == 'merge': 768 self.mergeDisk(diskName) 769 770 #@-node:<<write to freedisk>> 771 #@nl 772 773 774 self.log("release: path=%s flags=%s" % (path, flags)) 775 return 0
776 #@-node:release 777 #@+node:rename
778 - def rename(self, path, path1):
779 780 rec = self.files.get(path, None) 781 if not rec: 782 raise IOError(errno.ENOENT, path) 783 784 del self.files[path] 785 self.files[path1] = rec 786 rec.haschanged = True 787 ret = 0 788 789 self.log("rename: path=%s path1=%s\n => %s" % (path, path1, ret)) 790 return ret
791 792 #@-node:rename 793 #@+node:rmdir
794 - def rmdir(self, path):
795 796 self.log("rmdir: path=%s" % path) 797 798 rec = self.files.get(path, None) 799 800 # barf if no such directory 801 if not rec: 802 raise IOError(errno.ENOENT, path) 803 804 # barf if not a directory 805 if not rec.isdir: 806 raise IOError(errno.ENOTDIR, path) 807 808 # barf if not within freedisk mounts 809 if not path.startswith("/usr/"): 810 raise IOError(errno.EACCES, path) 811 812 # seek the freedisk record 813 bits = path.split("/") 814 diskPath = "/".join(bits[:3]) 815 diskRec = self.files.get(diskPath, None) 816 817 # barf if nonexistent 818 if not diskRec: 819 raise IOError(errno.ENOENT, path) 820 821 # if a freedisk root, just delete 822 if path == diskPath: 823 # remove directory record 824 self.delFromCache(rec) 825 826 # and remove children 827 for k in self.files.keys(): 828 if k.startswith(path+"/"): 829 del self.files[k] 830 831 return 0 832 833 # now, it's a subdir within a freedisk 834 835 # barf if non-empty 836 if rec.children: 837 raise IOError(errno.ENOTEMPTY, path) 838 839 # now, at last, can remove 840 self.delFromCache(rec) 841 ret = 0 842 843 self.log("rmdir: => %s" % ret) 844 845 return ret
846 847 #@-node:rmdir 848 #@+node:statfs
849 - def statfs(self):
850 """ 851 Should return a tuple with the following 6 elements: 852 - blocksize - size of file blocks, in bytes 853 - totalblocks - total number of blocks in the filesystem 854 - freeblocks - number of free blocks 855 - totalfiles - total number of file inodes 856 - freefiles - nunber of free file inodes 857 858 Feel free to set any of the above values to 0, which tells 859 the kernel that the info is not available. 860 """ 861 self.log("statfs: returning fictitious values") 862 blocks_size = 1024 863 blocks = 100000 864 blocks_free = 25000 865 files = 100000 866 files_free = 60000 867 namelen = 80 868 869 return (blocks_size, blocks, blocks_free, files, files_free, namelen)
870 871 #@-node:statfs 872 #@+node:symlink 880 881 #@-node:symlink 882 #@+node:truncate
883 - def truncate(self, path, size):
884 885 self.log("truncate: path=%s size=%s" % (path, size)) 886 887 if not path.startswith("/usr/"): 888 raise IOError(errno.EPERM, path) 889 890 parentPath, filename = os.path.split(path) 891 892 if os.path.split(parentPath)[0] != "/usr": 893 raise IOError(errno.EPERM, path) 894 895 rec = self.files.get(path, None) 896 if not rec: 897 raise IOError(errno.ENOENT, path) 898 899 # barf at readonly files 900 if filename == '.status': 901 raise IOError(errno.EPERM, path) 902 903 rec.data = "" 904 rec.haschanged = True 905 906 ret = 0 907 908 self.log("truncate: => %s" % ret) 909 910 return ret
911 912 #@-node:truncate 913 #@+node:unlink 965 966 #@-node:unlink 967 #@+node:utime
968 - def utime(self, path, times):
969 970 ret = os.utime(path, times) 971 self.log("utime: path=%s times=%s\n => %s" % (path, times, ret)) 972 return ret
973 974 #@-node:utime 975 #@+node:write
976 - def write(self, path, buf, off):
977 978 dataLen = len(buf) 979 980 rec = self.files.get(path, None) 981 if rec: 982 # write to existing 'file' 983 rec.seek(off) 984 rec.write(buf) 985 rec.hasdata = True 986 else: 987 f = open(path, "r+") 988 f.seek(off) 989 nwritten = f.write(buf) 990 f.flush() 991 992 self.log("write: path=%s buf=[%s bytes] off=%s" % (path, len(buf), off)) 993 994 #return nwritten 995 return dataLen
996 997 #@-node:write 998 #@-others 999 1000 #@-node:fs primitives 1001 #@+node:freedisk methods 1002 # methods for freedisk operations 1003 1004 #@+others 1005 #@+node:setupFreedisks
1006 - def setupFreedisks(self):
1007 """ 1008 Initialises the freedisks 1009 """ 1010 self.freedisks = {}
1011 1012 #@-node:setupFreedisks 1013 #@+node:addDisk
1014 - def addDisk(self, name, uri, passwd):
1015 """ 1016 Adds (mounts) a freedisk within freenetfs 1017 1018 Arguments: 1019 - name - name of disk - will be mounted in as /usr/<name> 1020 - uri - a public or private SSK key URI. Parsing of the key will 1021 reveal whether it's public or private. If public, the freedisk 1022 will be mounted read-only. If private, the freedisk will be 1023 mounted read/write 1024 - passwd - the encryption password for the disk, or empty string 1025 if the disk is to be unencrypted 1026 """ 1027 print "addDisk: name=%s uri=%s passwd=%s" % (name, uri, passwd) 1028 1029 diskPath = "/usr/" + name 1030 rec = self.addToCache(path=diskPath, isdir=True, perm=0755, canwrite=True) 1031 disk = Freedisk(rec) 1032 self.freedisks[name] = disk 1033 1034 if uriIsPrivate(uri): 1035 privKey = uri 1036 pubKey = self.node.invertprivate(uri) 1037 else: 1038 privKey = None 1039 pubKey = uri 1040 1041 disk.privKey = privKey 1042 disk.pubKey = pubKey
1043 1044 #print "addDisk: done" 1045 1046 #@-node:addDisk 1047 #@+node:delDisk
1048 - def delDisk(self, name):
1049 """ 1050 drops a freedisk mount 1051 1052 Arguments: 1053 - name - the name of the disk 1054 """ 1055 diskPath = "/usr/" + name 1056 rec = self.freedisks.pop(diskPath) 1057 self.delFromCache(rec)
1058 1059 #@-node:delDisk 1060 #@+node:commitDisk
1061 - def commitDisk(self, name):
1062 """ 1063 synchronises a freedisk TO freenet 1064 1065 Arguments: 1066 - name - the name of the disk 1067 """ 1068 self.log("commitDisk: disk=%s" % name) 1069 1070 startTime = time.time() 1071 1072 # get the freedisk root's record, barf if nonexistent 1073 diskRec = self.freedisks.get(name, None) 1074 if not diskRec: 1075 self.log("commitDisk: no such disk '%s'" % name) 1076 return "No such disk '%s'" % name 1077 1078 # and the file record and path 1079 rootRec = diskRec.root 1080 rootPath = rootRec.path 1081 1082 # get private key, if any 1083 privKey = diskRec.privKey 1084 if not privKey: 1085 # no private key - disk was mounted readonly with only a pubkey 1086 raise IOError(errno.EIO, "Disk %s is read-only" % name) 1087 1088 # and pubkey 1089 pubKey = diskRec.pubKey 1090 1091 # process the private key to needed format 1092 privKey = privKey.split("freenet:")[-1] 1093 privKey = privKey.replace("SSK@", "USK@").split("/")[0] + "/" + name + "/0" 1094 1095 self.log("commit: privKey=%s" % privKey) 1096 1097 self.log("commitDisk: checking files in %s" % rootPath) 1098 1099 # update status 1100 #statusFile.data = "committing\nAnalysing files\n" 1101 1102 # get list of records of files within this freedisk 1103 fileRecs = [] 1104 for f in self.files.keys(): 1105 # is file/dir within the freedisk? 1106 if f.startswith(rootPath+"/"): 1107 # yes, get its record 1108 fileRec = self.files[f] 1109 1110 # is it a file, and not a special file? 1111 if fileRec.isfile \ 1112 and (os.path.split(f)[1] not in freediskSpecialFiles): 1113 # yes, grab it 1114 fileRecs.append(fileRec) 1115 1116 # now sort them by path 1117 fileRecs.sort(lambda r1, r2: cmp(r1.path, r2.path)) 1118 1119 # make sure we have a node to talk to 1120 self.connectToNode() 1121 node = self.node 1122 1123 # determine CHKs for all these jobs 1124 for rec in fileRecs: 1125 rec.mimetype = guessMimetype(rec.path) 1126 rec.uri = node.put( 1127 "CHK@file", 1128 data=rec.data, 1129 chkonly=True, 1130 mimetype=rec.mimetype) 1131 1132 # now insert all these files 1133 maxJobs = 5 1134 jobsWaiting = fileRecs[:] 1135 jobsRunning = [] 1136 jobsDone = [] 1137 1138 # now, create the manifest XML file 1139 manifest = XMLFile(root="freedisk") 1140 root = manifest.root 1141 for rec in jobsWaiting: 1142 fileNode = root._addNode("file") 1143 fileNode.path = rec.path 1144 fileNode.uri = rec.uri 1145 try: 1146 fileNode.mimetype = rec.mimetype 1147 except: 1148 fileNode.mimetype = "text/plain" 1149 fileNode.hash = sha1(rec.data).hexdigest() 1150 1151 # and create an index.html to make it freesite-compatible 1152 indexLines = [ 1153 "<html><head><title>This is a freedisk</title></head><body>", 1154 "<h1>freedisk: %s" % name, 1155 "<table cellspacing=0 cellpadding=3 border=1>" 1156 "<tr>", 1157 "<td><b>Size</b></td>", 1158 "<td><b>Filename</b></td>", 1159 "<td><b>URI</b></td>", 1160 "</tr>", 1161 ] 1162 for rec in fileRecs: 1163 indexLines.append("<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ( 1164 rec.size, rec.path, rec.uri)) 1165 indexLines.append("</table></body></html>\n") 1166 indexHtml = "\n".join(indexLines) 1167 1168 # and add the manifest as a waiting job 1169 manifestJob = node.put( 1170 privKey, 1171 data=manifest.toxml(), 1172 mimetype="text/xml", 1173 async=True, 1174 ) 1175 1176 #jobsRunning.append(manifestJob) 1177 #manifestUri = manifestJob.wait() 1178 #print "manifestUri=%s" % manifestUri 1179 #time.sleep(6) 1180 1181 # the big insert/wait loop 1182 while jobsWaiting or jobsRunning: 1183 nWaiting = len(jobsWaiting) 1184 nRunning = len(jobsRunning) 1185 self.log("commit: %s waiting, %s running" % (nWaiting,nRunning)) 1186 1187 # launch jobs, if available, and if spare slots 1188 while len(jobsRunning) < maxJobs and jobsWaiting: 1189 1190 rec = jobsWaiting.pop(0) 1191 1192 # if record has data, insert it, otherwise take as done 1193 if rec.hasdata: 1194 uri = rec.uri 1195 if not uri: 1196 uri = "CHK@somefile" + os.path.splitext(rec.path)[1] 1197 job = node.put(uri, data=rec.data, async=True) 1198 rec.job = job 1199 jobsRunning.append(rec) 1200 else: 1201 # record should already have the hash, uri, mimetype 1202 jobsDone.append(rec) 1203 1204 # check running jobs 1205 for rec in jobsRunning: 1206 if rec == manifestJob: 1207 job = rec 1208 else: 1209 job = rec.job 1210 1211 if job.isComplete(): 1212 jobsRunning.remove(rec) 1213 1214 uri = job.wait() 1215 1216 if job != manifestJob: 1217 rec.uri = uri 1218 rec.job = None 1219 jobsDone.append(rec) 1220 1221 # breathe!! 1222 if jobsRunning: 1223 time.sleep(5) 1224 else: 1225 time.sleep(1) 1226 1227 manifestUri = manifestJob.wait() 1228 self.log("commitDisk: done, manifestUri=%s" % manifestUri) 1229 1230 #pubKeyFile.data = manifestJob.uri 1231 1232 endTime = time.time() 1233 commitTime = endTime - startTime 1234 1235 self.log("commitDisk: commit completed in %s seconds" % commitTime) 1236 1237 return manifestUri
1238 1239 #@-node:commitDisk 1240 #@+node:updateDisk
1241 - def updateDisk(self, name):
1242 """ 1243 synchronises a freedisk FROM freenet 1244 1245 Arguments: 1246 - name - the name of the disk 1247 """ 1248 self.log("updateDisk: disk=%s" % name) 1249 1250 startTime = time.time() 1251 1252 # get the freedisk root's record, barf if nonexistent 1253 diskRec = self.freedisks.get(name, None) 1254 if not diskRec: 1255 self.log("commitDisk: no such disk '%s'" % name) 1256 return "No such disk '%s'" % name 1257 1258 rootRec = diskRec.root 1259 1260 # and get the public key, sans 'freenet:' 1261 pubKey = rootRec.pubKey 1262 1263 pubKey = pubKey.split("freenet:")[-1] 1264 1265 # process further 1266 pubKey = privKey.replace("SSK@", "USK@").split("/")[0] + "/" + name + "/0" 1267 1268 self.log("update: pubKey=%s" % pubKey)
1269 1270 # fetch manifest 1271 1272 # mark disk as readonly 1273 1274 # for each entry in manifest 1275 # if not localfile has changed 1276 # replace the file record 1277 1278 #@-node:updateDisk 1279 #@+node:getManifest
1280 - def getManifest(self, name):
1281 """ 1282 Retrieves the manifest of a given disk 1283 """
1284 #@-node:getManifest 1285 #@+node:putManifest
1286 - def putManifest(self, name):
1287 """ 1288 Inserts a freedisk manifest into freenet 1289 """
1290 #@-node:putManifest 1291 #@-others 1292 1293 #@-node:freedisk methods 1294 #@+node:util methods 1295 # utility methods 1296 1297 #@+others 1298 #@+node:setupFiles
1299 - def setupFiles(self):
1300 """ 1301 Create initial file/directory layout, according 1302 to attributes 'initialFiles' and 'chrFiles' 1303 """ 1304 # easy map of files 1305 self.files = {} 1306 1307 # now create records for initial files 1308 for path in self.initialFiles: 1309 1310 # initial attribs 1311 isReg = isDir = isChr = isSock = isFifo = False 1312 perm = size = 0 1313 1314 # determine file type 1315 if path.endswith("/"): 1316 isDir = True 1317 path = path[:-1] 1318 if not path: 1319 path = "/" 1320 elif path in self.chrFiles: 1321 # it's a char file 1322 #isChr = True 1323 isReg = True 1324 perm |= 0666 1325 size = 1024 1326 else: 1327 # by default, it's a regular file 1328 isReg = True 1329 1330 # create permissions field 1331 if isDir: 1332 perm |= 0755 1333 size = 2 1334 else: 1335 perm |= 0444 1336 1337 # create record for this path 1338 self.addToCache( 1339 path=path, 1340 perm=perm, 1341 size=size, 1342 isdir=isDir, isreg=isReg, ischr=isChr, 1343 issock=isSock, isfifo=isFifo, 1344 )
1345 1346 #@-node:setupFiles 1347 #@+node:connectToNode
1348 - def connectToNode(self):
1349 """ 1350 Attempts a connection to an fcp node 1351 """ 1352 if self.node: 1353 return 1354 1355 #self.verbosity = fcp.DETAIL 1356 1357 self.log("connectToNode: verbosity=%s" % self.verbosity) 1358 1359 try: 1360 self.node = fcp.FCPNode(host=self.fcpHost, 1361 port=self.fcpPort, 1362 verbosity=self.verbosity) 1363 except: 1364 raise IOError(errno.EIO, "Failed to reach FCP service at %s:%s" % ( 1365 self.fcpHost, self.fcpPort))
1366 1367 #self.log("pubkey=%s" % self.pubkey) 1368 #self.log("privkey=%s" % self.privkey) 1369 #self.log("cachedir=%s" % self.cachedir) 1370 1371 #@-node:connectToNode 1372 #@+node:mythread
1373 - def mythread(self):
1374 1375 """ 1376 The beauty of the FUSE python implementation is that with the python interp 1377 running in foreground, you can have threads 1378 """ 1379 self.log("mythread: started")
1380 #while 1: 1381 # time.sleep(120) 1382 # print "mythread: ticking" 1383 1384 #@-node:mythread 1385 #@+node:hashpath
1386 - def hashpath(self, path):
1387 1388 return sha1(path).hexdigest()
1389 1390 #@-node:hashpath 1391 #@+node:addToCache
1392 - def addToCache(self, rec=None, **kw):
1393 """ 1394 Tries to 'cache' a given file/dir record, and 1395 adds it to parent dir 1396 """ 1397 if rec == None: 1398 rec = FileRecord(self, **kw) 1399 1400 path = rec.path 1401 1402 # barf if file/dir already exists 1403 if self.files.has_key(path): 1404 self.log("addToCache: already got %s !!!" % path) 1405 return 1406 1407 #print "path=%s" % path 1408 1409 # if not root, add to parent 1410 if path != '/': 1411 parentPath = os.path.split(path)[0] 1412 parentRec = self.files.get(parentPath, None) 1413 parentRec.addChild(rec) 1414 if not parentRec: 1415 self.log("addToCache: no parent of %s ?!?!" % path) 1416 return 1417 1418 # ok, add to our table 1419 self.files[path] = rec 1420 1421 # done 1422 return rec
1423 1424 #@-node:addToCache 1425 #@+node:delFromCache
1426 - def delFromCache(self, rec):
1427 """ 1428 Tries to remove file/dir record from cache 1429 """ 1430 if isinstance(rec, str): 1431 path = rec 1432 rec = self.files.get(path, None) 1433 if not rec: 1434 print "delFromCache: no such path %s" % path 1435 return 1436 else: 1437 path = rec.path 1438 1439 parentPath = os.path.split(path)[0] 1440 1441 if self.files.has_key(path): 1442 rec = self.files[path] 1443 del self.files[path] 1444 for child in rec.children: 1445 self.delFromCache(child) 1446 1447 parentRec = self.files.get(parentPath, None) 1448 if parentRec: 1449 parentRec.delChild(rec)
1450 1451 #@-node:delFromCache 1452 #@+node:statFromKw
1453 - def statFromKw(self, **kw):
1454 """ 1455 Constructs a stat tuple from keywords 1456 """ 1457 tup = [0] * 10 1458 1459 # build mode mask 1460 mode = kw.get('mode', 0) 1461 if kw.get('isdir', False): 1462 mode |= stat.S_IFDIR 1463 if kw.get('ischr', False): 1464 mode |= stat.S_IFCHR 1465 if kw.get('isblk', False): 1466 mode |= stat.S_IFBLK 1467 if kw.get('isreg', False): 1468 mode |= stat.S_IFREG 1469 if kw.get('isfifo', False): 1470 mode |= stat.S_IFIFO 1471 if kw.get('islink', False): 1472 mode |= stat.S_IFLNK 1473 if kw.get('issock', False): 1474 mode |= stat.S_IFSOCK 1475 1476 path = kw['path'] 1477 1478 # get inode number 1479 inode = self.pathToInode(path) 1480 1481 dev = 0 1482 1483 nlink = 1 1484 uid = myuid 1485 gid = mygid 1486 size = 0 1487 atime = mtime = ctime = timeNow() 1488 1489 return (mode, inode, dev, nlink, uid, gid, size, atime, mtime, ctime)
1490 1491 # st_mode, st_ino, st_dev, st_nlink, 1492 # st_uid, st_gid, st_size, 1493 # st_atime, st_mtime, st_ctime 1494 1495 #@-node:statFromKw 1496 #@+node:statToDict
1497 - def statToDict(self, info):
1498 """ 1499 Converts a tuple returned by a stat call into 1500 a dict with keys: 1501 1502 - isdir 1503 - ischr 1504 - isblk 1505 - isreg 1506 - isfifo 1507 - islnk 1508 - issock 1509 - mode 1510 - inode 1511 - dev 1512 - nlink 1513 - uid 1514 - gid 1515 - size 1516 - atime 1517 - mtime 1518 - ctime 1519 """ 1520 print "statToDict: info=%s" % str(info) 1521 1522 mode = info[stat.ST_MODE] 1523 return { 1524 'isdir' : stat.S_ISDIR(mode), 1525 'ischr' : stat.S_ISCHR(mode), 1526 'isblk' : stat.S_ISBLK(mode), 1527 'isreg' : stat.S_ISREG(mode), 1528 'isfifo' : stat.S_ISFIFO(mode), 1529 'islink' : stat.S_ISLNK(mode), 1530 'issock' : stat.S_ISSOCK(mode), 1531 'mode' : mode, 1532 'inode' : info[stat.ST_INO], 1533 'dev' : info[stat.ST_DEV], 1534 'nlink' : info[stat.ST_NLINK], 1535 'uid' : info[stat.ST_UID], 1536 'gid' : info[stat.ST_GID], 1537 'size' : info[stat.ST_SIZE], 1538 'atime' : info[stat.ST_ATIME], 1539 'mtime' : info[stat.ST_MTIME], 1540 'ctime' : info[stat.ST_CTIME], 1541 }
1542 1543 #@-node:statToDict 1544 #@+node:getReadURI
1545 - def getReadURI(self, path):
1546 """ 1547 Converts to a pathname to a freenet URI for insertion, 1548 using public key 1549 """ 1550 return self.pubkey + self.hashpath(path) + "/0"
1551 1552 #@-node:getReadURI 1553 #@+node:getWriteURI
1554 - def getWriteURI(self, path):
1555 """ 1556 Converts to a pathname to a freenet URI for insertion, 1557 using private key if any 1558 """ 1559 if not self.privkey: 1560 raise Exception("cannot write: no private key") 1561 1562 return self.privkey + self.hashpath(path) + "/0"
1563 1564 #@-node:getWriteURI 1565 #@+node:log
1566 - def log(self, msg):
1567 #if not quiet: 1568 # print "freedisk:"+msg 1569 file("/tmp/freedisk.log", "a").write(msg+"\n")
1570 1571 #@-node:log 1572 #@-others 1573 1574 #@-node:util methods 1575 #@+node:deprecated methods 1576 # deprecated methods 1577 1578 #@+others 1579 #@+node:__getDirStat
1580 - def __getDirStat(self, path):
1581 """ 1582 returns a stat tuple for given path 1583 """ 1584 return FileRecord(mode=0700, path=path, isdir=True)
1585 1586 #@-node:__getDirStat 1587 #@+node:_loadConfig
1588 - def _loadConfig(self):
1589 """ 1590 The 'physical device' argument to mount should be the pathname 1591 of a configuration file, with 'name=val' lines, including the 1592 following items: 1593 - publickey=<freenet public key URI> 1594 - privatekey=<freenet private key URI> (optional, without which we 1595 will have the fs mounted readonly 1596 """ 1597 opts = {} 1598 1599 # build a dict of all the 'name=value' pairs in config file 1600 for line in [l.strip() for l in file(self.config).readlines()]: 1601 if line == '' or line.startswith("#"): 1602 continue 1603 try: 1604 name, val = line.split("=", 1) 1605 opts[name.strip()] = val.strip() 1606 except: 1607 pass 1608 1609 # mandate a pubkey 1610 try: 1611 self.pubkey = opts['pubkey'].replace("SSK@", "USK@").split("/")[0] + "/" 1612 except: 1613 raise Exception("Config file %s: missing or invalid publickey" \ 1614 % self.configfile) 1615 1616 # accept optional privkey 1617 if opts.has_key("privkey"): 1618 1619 try: 1620 self.privkey = opts['privkey'].replace("SSK@", 1621 "USK@").split("/")[0] + "/" 1622 except: 1623 raise Exception("Config file %s: invalid privkey" \ 1624 % self.configfile) 1625 1626 # mandate cachepath 1627 try: 1628 self.cachedir = opts['cachedir'] 1629 if not os.path.isdir(self.cachedir): 1630 self.log("Creating cache directory %s" % self.cachedir) 1631 os.makedirs(self.cachedir) 1632 #raise hell 1633 except: 1634 raise Exception("config file %s: missing or invalid cache directory" \ 1635 % self.configfile)
1636 1637 #@-node:_loadConfig 1638 #@-others 1639 1640 #@-node:deprecated methods 1641 #@-others 1642 1643 #@-node:class FreenetBaseFS 1644 #@+node:class Freedisk
1645 -class Freedisk:
1646 """ 1647 Encapsulates a freedisk 1648 """ 1649 #@ @+others 1650 #@+node:__init__
1651 - def __init__(self, rootrec):
1652 1653 self.root = rootrec
1654 1655 #@-node:__init__ 1656 #@-others 1657 1658 #@-node:class Freedisk 1659 #@+node:class FreenetFuseFS
1660 -class FreenetFuseFS(FreenetBaseFS):
1661 """ 1662 Interfaces with FUSE 1663 """ 1664 #@ @+others 1665 #@+node:attribs 1666 _attrs = ['getattr', 'readlink', 'getdir', 'mknod', 'mkdir', 1667 'unlink', 'rmdir', 'symlink', 'rename', 'link', 'chmod', 1668 'chown', 'truncate', 'utime', 'open', 'read', 'write', 'release', 1669 'statfs', 'fsync'] 1670 1671 #@-node:attribs 1672 #@+node:run
1673 - def run(self):
1674 1675 import _fuse 1676 1677 d = {'mountpoint': self.mountpoint, 1678 'multithreaded': self.multithreaded, 1679 } 1680 1681 #print "run: d=%s" % str(d) 1682 1683 if self.debug: 1684 d['lopts'] = 'debug' 1685 1686 k=[] 1687 for opt in ['allow_other', 'kernel_cache']: 1688 if getattr(self, opt): 1689 k.append(opt) 1690 if k: 1691 d['kopts'] = ",".join(k) 1692 1693 for a in self._attrs: 1694 if hasattr(self,a): 1695 d[a] = ErrnoWrapper(getattr(self, a)) 1696 1697 #thread.start_new_thread(self.tickThread, ()) 1698 1699 _fuse.main(**d)
1700 1701 #@-node:run 1702 #@+node:GetContent
1703 - def GetContext(self):
1704 print "GetContext: called" 1705 return _fuse.FuseGetContext(self)
1706 1707 #@-node:GetContent 1708 #@+node:Invalidate
1709 - def Invalidate(self, path):
1710 print "Invalidate: called" 1711 return _fuse.FuseInvalidate(self, path)
1712 1713 #@-node:Invalidate 1714 #@+node:tickThread
1715 - def tickThread(self, *args, **kw):
1716 1717 print "tickThread: starting" 1718 i = 0 1719 while True: 1720 print "tickThread: n=%s" % i 1721 time.sleep(10) 1722 i += 1
1723 1724 #@-node:tickThread 1725 #@-others 1726 #@-node:class FreenetFuseFS 1727 #@+node:class FileRecord
1728 -class FileRecord(list):
1729 """ 1730 Encapsulates the info for a file, and can 1731 be returned by getattr 1732 """ 1733 #@ @+others 1734 #@+node:attribs 1735 # default attribs, can be overwritten by constructor keywords 1736 haschanged = False 1737 hasdata = False 1738 canwrite = False 1739 iswriting = False 1740 uri = None 1741 1742 #@-node:attribs 1743 #@+node:__init__
1744 - def __init__(self, fs, statrec=None, **kw):
1745 """ 1746 """ 1747 # copy keywords cos we'll be popping them 1748 kw = dict(kw) 1749 1750 # save fs ref 1751 self.fs = fs 1752 1753 # got a statrec arg? 1754 if statrec: 1755 # yes, extract main items 1756 dev = statrec[stat.ST_DEV] 1757 nlink = statrec[stat.ST_NLINK] 1758 uid = statrec[stat.ST_UID] 1759 gid = statrec[stat.ST_GID] 1760 size = statrec[stat.ST_SIZE] 1761 else: 1762 # no, fudge a new one 1763 statrec = [0,0,0,0,0,0,0,0,0,0] 1764 dev = 0 1765 nlink = 1 1766 uid = myuid 1767 gid = mygid 1768 size = 0 1769 1770 # convert tuple to list if need be 1771 if not hasattr(statrec, '__setitem__'): 1772 statrec = list(statrec) 1773 1774 # build mode mask 1775 mode = kw.pop('mode', 0) 1776 if kw.pop('isdir', False): 1777 mode |= stat.S_IFDIR 1778 if kw.pop('ischr', False): 1779 mode |= stat.S_IFCHR 1780 if kw.pop('isblk', False): 1781 mode |= stat.S_IFBLK 1782 if kw.pop('isreg', False): 1783 mode |= stat.S_IFREG 1784 if kw.pop('isfifo', False): 1785 mode |= stat.S_IFIFO 1786 if kw.pop('islink', False): 1787 mode |= stat.S_IFLNK 1788 if kw.pop('issock', False): 1789 mode |= stat.S_IFSOCK 1790 1791 # handle non-file-related keywords 1792 perm = kw.pop('perm', 0) 1793 mode |= perm 1794 1795 # set path 1796 path = kw.pop('path') 1797 self.path = path 1798 1799 # set up data stream 1800 if kw.has_key("data"): 1801 self.stream = StringIO(kw.pop('data')) 1802 self.hasdata = True 1803 else: 1804 self.stream = StringIO() 1805 1806 # find parent, if any 1807 if path == '/': 1808 self.parent = None 1809 else: 1810 parentPath = os.path.split(path)[0] 1811 parentRec = fs.files[parentPath] 1812 self.parent = parentRec 1813 1814 # child files/dirs 1815 self.children = [] 1816 1817 # get inode number 1818 inode = pathToInode(path) 1819 1820 #size = kw.get('size', 0) 1821 now = timeNow() 1822 atime = kw.pop('atime', now) 1823 mtime = kw.pop('mtime', now) 1824 ctime = kw.pop('ctime', now) 1825 1826 #print "statrec[stat.ST_MODE]=%s" % statrec[stat.ST_MODE] 1827 #print "mode=%s" % mode 1828 1829 statrec[stat.ST_MODE] |= mode 1830 statrec[stat.ST_INO] = inode 1831 statrec[stat.ST_DEV] = dev 1832 statrec[stat.ST_NLINK] = nlink 1833 statrec[stat.ST_UID] = uid 1834 statrec[stat.ST_GID] = gid 1835 1836 statrec[stat.ST_SIZE] = len(self.stream.getvalue()) 1837 1838 statrec[stat.ST_ATIME] = atime 1839 statrec[stat.ST_MTIME] = atime 1840 statrec[stat.ST_CTIME] = atime 1841 1842 # throw remaining keywords into instance's attribs 1843 self.__dict__.update(kw) 1844 1845 # finally, parent constructor, now that we have a complete stat list 1846 list.__init__(self, statrec) 1847 1848 if self.isdir: 1849 self.size = 2
1850 1851 #@-node:__init__ 1852 #@+node:__getattr__
1853 - def __getattr__(self, attr):
1854 """ 1855 Support read of pseudo-attributes: 1856 - mode, isdir, ischr, isblk, isreg, isfifo, islnk, issock, 1857 - inode, dev, nlink, uid, gid, size, atime, mtime, ctime 1858 """ 1859 if attr == 'mode': 1860 return self[stat.ST_MODE] 1861 1862 if attr == 'isdir': 1863 return stat.S_ISDIR(self.mode) 1864 1865 if attr == 'ischr': 1866 return stat.S_ISCHR(self.mode) 1867 1868 if attr == 'isblk': 1869 return stat.S_ISBLK(self.mode) 1870 1871 if attr in ['isreg', 'isfile']: 1872 return stat.S_ISREG(self.mode) 1873 1874 if attr == 'isfifo': 1875 return stat.S_ISFIFO(self.mode) 1876 1877 if attr == 'islnk': 1878 return stat.S_ISLNK(self.mode) 1879 1880 if attr == 'issock': 1881 return stat.S_ISSOCK(self.mode) 1882 1883 if attr == 'inode': 1884 return self[stat.ST_INO] 1885 1886 if attr == 'dev': 1887 return self[stat.ST_DEV] 1888 1889 if attr == 'nlink': 1890 return self[stat.ST_NLINK] 1891 1892 if attr == 'uid': 1893 return self[stat.ST_UID] 1894 1895 if attr == 'gid': 1896 return self[stat.ST_GID] 1897 1898 if attr == 'size': 1899 return self[stat.ST_SIZE] 1900 1901 if attr == 'atime': 1902 return self[stat.ST_ATIME] 1903 1904 if attr == 'mtime': 1905 return self[stat.ST_ATIME] 1906 1907 if attr == 'ctime': 1908 return self[stat.ST_ATIME] 1909 1910 if attr == 'data': 1911 return self.stream.getvalue() 1912 1913 try: 1914 return getattr(self.stream, attr) 1915 except: 1916 pass 1917 1918 raise AttributeError(attr)
1919 1920 #@-node:__getattr__ 1921 #@+node:__setattr__
1922 - def __setattr__(self, attr, val):
1923 """ 1924 Support write of pseudo-attributes: 1925 - mode, isdir, ischr, isblk, isreg, isfifo, islnk, issock, 1926 - inode, dev, nlink, uid, gid, size, atime, mtime, ctime 1927 """ 1928 if attr == 'isdir': 1929 if val: 1930 self[stat.ST_MODE] |= stat.S_IFDIR 1931 else: 1932 self[stat.ST_MODE] &= ~stat.S_IFDIR 1933 elif attr == 'ischr': 1934 if val: 1935 self[stat.ST_MODE] |= stat.S_IFCHR 1936 else: 1937 self[stat.ST_MODE] &= ~stat.S_IFCHR 1938 elif attr == 'isblk': 1939 if val: 1940 self[stat.ST_MODE] |= stat.S_IFBLK 1941 else: 1942 self[stat.ST_MODE] &= ~stat.S_IFBLK 1943 elif attr in ['isreg', 'isfile']: 1944 if val: 1945 self[stat.ST_MODE] |= stat.S_IFREG 1946 else: 1947 self[stat.ST_MODE] &= ~stat.S_IFREG 1948 elif attr == 'isfifo': 1949 if val: 1950 self[stat.ST_MODE] |= stat.S_IFIFO 1951 else: 1952 self[stat.ST_MODE] &= ~stat.S_IFIFO 1953 elif attr == 'islnk': 1954 if val: 1955 self[stat.ST_MODE] |= stat.S_IFLNK 1956 else: 1957 self[stat.ST_MODE] &= ~stat.S_IFLNK 1958 elif attr == 'issock': 1959 if val: 1960 self[stat.ST_MODE] |= stat.S_IFSOCK 1961 else: 1962 self[stat.ST_MODE] &= ~stat.S_IFSOCK 1963 1964 elif attr == 'mode': 1965 self[stat.ST_MODE] = val 1966 elif attr == 'inode': 1967 self[stat.ST_IMO] = val 1968 elif attr == 'dev': 1969 self[stat.ST_DEV] = val 1970 elif attr == 'nlink': 1971 self[stat.ST_NLINK] = val 1972 elif attr == 'uid': 1973 self[stat.ST_UID] = val 1974 elif attr == 'gid': 1975 self[stat.ST_GID] = val 1976 elif attr == 'size': 1977 self[stat.ST_SIZE] = val 1978 elif attr == 'atime': 1979 self[stat.ST_ATIME] = val 1980 elif attr == 'mtime': 1981 self[stat.ST_MTIME] = val 1982 elif attr == 'ctime': 1983 self[stat.ST_CTIME] = val 1984 1985 elif attr == 'data': 1986 oldPos = self.stream.tell() 1987 self.stream = StringIO(val) 1988 self.stream.seek(min(oldPos, len(val))) 1989 self.size = len(val) 1990 1991 else: 1992 self.__dict__[attr] = val
1993 1994 #@-node:__setattr__ 1995 #@+node:write
1996 - def write(self, buf):
1997 1998 self.stream.write(buf) 1999 self.size = len(self.stream.getvalue())
2000 2001 #@-node:write 2002 #@+node:addChild
2003 - def addChild(self, rec):
2004 """ 2005 Adds a child file rec as a child of this rec 2006 """ 2007 if not isinstance(rec, FileRecord): 2008 raise Exception("Not a FileRecord: %s" % rec) 2009 2010 self.children.append(rec) 2011 self.size += 1
2012 2013 #print "addChild: path=%s size=%s" % (self.path, self.size) 2014 2015 #@-node:addChild 2016 #@+node:delChild
2017 - def delChild(self, rec):
2018 """ 2019 Tries to remove a child entry 2020 """ 2021 if rec in self.children: 2022 self.children.remove(rec) 2023 self.size -= 1 2024 2025 else: 2026 print "eh? trying to remove %s from %s" % (rec.path, self.path)
2027 2028 #print "delChild: path=%s size=%s" % (self.path, self.size) 2029 2030 #@-node:delChild 2031 #@-others 2032 2033 #@-node:class FileRecord 2034 #@+node:class FreediskMgr
2035 -class FreediskMgr:
2036 """ 2037 Gateway for mirroring a local directory to/from freenet 2038 """ 2039 #@ @+others 2040 #@+node:__init__
2041 - def __init__(self, **kw):
2042 """ 2043 Creates a freediskmgr object 2044 2045 Keywords: 2046 - name - mandatory - the name of the disk 2047 - fcpNode - mandatory - an FCPNode instance 2048 - root - mandatory - the root directory 2049 - publicKey - the freenet public key URI 2050 - privateKey - the freenet private key URI 2051 Notes: 2052 - exactly one of publicKey, privateKey keywords must be given 2053 """
2054 2055 #@-node:__init__ 2056 #@+node:update
2057 - def update(self):
2058 """ 2059 Update from freenet to local directory 2060 """
2061 2062 #@-node:update 2063 #@+node:commit
2064 - def commit(self):
2065 """ 2066 commit from local directory into freenet 2067 """
2068 2069 #@-node:commit 2070 #@-others 2071 2072 #@-node:class FreediskMgr 2073 #@+node:pathToInode
2074 -def pathToInode(path):
2075 """ 2076 Comes up with a unique inode number given a path 2077 """ 2078 # try for existing known path/inode 2079 inode = inodes.get(path, None) 2080 if inode != None: 2081 return inode 2082 2083 # try hashing the path to 32bit 2084 inode = int(md5(path).hexdigest()[:7], 16) 2085 2086 # and ensure it's unique 2087 while inodes.has_key(inode): 2088 inode += 1 2089 2090 # register it 2091 inodes[path] = inode 2092 2093 # done 2094 return inode
2095 2096 #@-node:pathToInode 2097 #@+node:timeNow
2098 -def timeNow():
2099 return int(time.time()) & 0xffffffffL
2100 2101 #@-node:timeNow 2102 #@+node:usage
2103 -def usage(msg, ret=1):
2104 2105 print "Usage: %s mountpoint -o args" % progname 2106 2107 sys.exit(ret)
2108 2109 #@-node:usage 2110 #@+node:main
2111 -def main():
2112 2113 kw = {} 2114 args = [] 2115 2116 if argc != 5: 2117 usage("Bad argument count") 2118 2119 mountpoint = argv[2] 2120 2121 for o in argv[4].split(","): 2122 try: 2123 k, v = o.split("=", 1) 2124 kw[k] = v 2125 except: 2126 args.append(o) 2127 2128 kw['multithreaded'] = True 2129 #kw['multithreaded'] = False 2130 print "main: kw=%s" % str(kw) 2131 2132 2133 if os.fork() == 0: 2134 server = FreenetFuseFS(mountpoint, *args, **kw) 2135 server.run()
2136 2137 2138 #@-node:main 2139 #@+node:mainline 2140 if __name__ == '__main__': 2141 2142 main() 2143 #@-node:mainline 2144 #@-others 2145 2146 #@-node:@file freenetfs.py 2147 #@-leo 2148