1
2
3
4
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
21
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
52
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
71 _no_node = 0
72
73
74 freediskSpecialFiles = [
75 '.privatekey', '.publickey', '.cmd', '.status', ".passwd",
76 ]
77
78 showAllExceptions = False
79
80
81
83
86
88 try:
89 return apply(self.func, args, kw)
90 except (IOError, OSError), detail:
91 if showAllExceptions:
92 traceback.print_exc()
93
94 if hasattr(detail, "errno"): detail = detail.errno
95 return -detail
96
97
98
99
101
102
103
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
115
116
117 initialFiles = [
118 "/",
119 "/get/",
120 "/put/",
121 "/keys/",
122 "/usr/",
123 "/cmds/",
124 ]
125
126 chrFiles = [
127 ]
128
129
130
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
171
172
173
174 self.setupFiles()
175 self.setupFreedisks()
176
177
178
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
193
194
195
196
197
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
218
220
221 return "ok\nhello: args=%s" % repr(args)
222
223
224
226 """
227 tries to mount a freedisk
228
229 arguments:
230 - diskname
231 - uri (may be public or private)
232 - password
233 """
234
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
249
251 """
252 tries to unmount a freedisk
253
254 arguments:
255 - diskname
256 """
257
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
273
275 """
276 Does an update of a freedisk from freenet
277 """
278
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
294
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
313
314
315
316
317
318
319
320
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
328
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
336
337 - def fsync(self, path, isfsyncfile):
338
339 self.log("fsync: path=%s, isfsyncfile=%s" % (path, isfsyncfile))
340 return 0
341
342
343
345
346 rec = self.files.get(path, None)
347 if not rec:
348
349
350
351
352 if path.startswith("/keys/"):
353
354
355
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
366
367 elif path.startswith("/get/"):
368
369
370
371 if _no_node:
372 print "FIXME: returning IOerror"
373 raise IOError(errno.ENOENT, path)
374
375
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
390 raise IOError(errno.ENOENT, path)
391
392
393
394 elif path.startswith("/cmds/"):
395
396
397
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
408
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
428
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
450
451 - def link(self, path, path1):
452
453 raise IOError(errno.EPERM, path)
454
455 ret = os.link(path, path1)
456 self.log("link: path=%s path1=%s\n => %s" % (path, path1, ret))
457 return ret
458
459
460
461 - def mkdir(self, path, mode):
462
463 self.log("mkdir: path=%s mode=%s" % (path, mode))
464
465
466 if self.files.has_key(path):
467 raise IOError(errno.EEXIST, path)
468
469
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
477
478
479 rec = self.addToCache(path=path, isdir=True, perm=0555)
480
481
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
489 return 0
490
491 elif path.startswith("/usr/"):
492
493
494
495 diskPath = "/".join(path.split("/")[:3])
496 diskRec = self.files.get(diskPath, None)
497
498
499
500 if diskRec and not diskRec.canwrite:
501 self.log("mkdir: diskPath=%s" % diskPath)
502 raise IOError(errno.EPERM, path)
503
504
505 self.addToCache(path=path, isdir=True, perm=0755)
506
507 return 0
508
509
510
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
516 raise IOError(errno.EEXIST, path)
517
518 parentPath = os.path.split(path)[0]
519 if parentPath in ['/', '/usr']:
520
521 raise IOError(errno.EPERM, path)
522
523
524 if parentPath == "/put":
525
526
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
537
538
539 diskPath = "/".join(path.split("/")[:3])
540 diskRec = self.files.get(diskPath, None)
541
542
543 if diskRec and not diskRec.canwrite:
544 self.log("mknod: diskPath=%s" % diskPath)
545 raise IOError(errno.EPERM, path)
546
547
548 rec = self.addToCache(path=path, isreg=True, perm=0644,
549 iswriting=True, haschanged=True)
550 ret = 0
551
552
553
554
555
556
557 else:
558
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
567
568 - def open(self, path, flags):
569
570 self.log("open: path=%s flags=%s" % (path, flags))
571
572
573 rec = self.files.get(path, None)
574
575 if rec:
576
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
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
594 return 0
595
596
597
598 - def read(self, path, length, offset):
599 """
600 """
601
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
610 return buf
611
612 else:
613
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
624
626
627 ret = os.readlink(path)
628 self.log("readlink: path=%s\n => %s" % (path, ret))
629 return ret
630
631
632
634
635 rec = self.files.get(path, None)
636 if not rec:
637 return
638
639 filename = os.path.split(path)[1]
640
641
642 if path.startswith("/cmds/"):
643
644 rec = self.files.get(path, None)
645 if rec:
646 self.delFromCache(rec)
647 else:
648 print "eh? not in cache"
649
650
651 elif rec.iswriting:
652
653 self.log("release: %s: iswriting=True" % path)
654
655
656 rec.iswriting = False
657
658 print "Release: path=%s" % path
659
660 if path.startswith("/put/"):
661
662
663
664
665 uri = os.path.split(path)[1]
666
667
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
682 rec.data = 'inserting'
683
684 self.connectToNode()
685
686
687
688 if _no_node:
689 print "FIXME: not inserting"
690 getUri = "NO_URI"
691 else:
692
693 getUri = self.node.put(
694 putUri,
695 data=data,
696 mimetype=mimetype)
697
698
699 if getUri.startswith("freenet:"):
700 getUri = getUri[8:]
701
702
703 if getUri.startswith("CHK@"):
704 getUri += ext
705
706
707 self.addToCache(
708 path="/get/"+getUri,
709 data=data,
710 perm=0444,
711 isreg=True,
712 )
713
714
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
728
729
730 elif path.startswith("/usr/"):
731
732
733
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
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
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
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
771
772
773
774 self.log("release: path=%s flags=%s" % (path, flags))
775 return 0
776
777
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
793
795
796 self.log("rmdir: path=%s" % path)
797
798 rec = self.files.get(path, None)
799
800
801 if not rec:
802 raise IOError(errno.ENOENT, path)
803
804
805 if not rec.isdir:
806 raise IOError(errno.ENOTDIR, path)
807
808
809 if not path.startswith("/usr/"):
810 raise IOError(errno.EACCES, path)
811
812
813 bits = path.split("/")
814 diskPath = "/".join(bits[:3])
815 diskRec = self.files.get(diskPath, None)
816
817
818 if not diskRec:
819 raise IOError(errno.ENOENT, path)
820
821
822 if path == diskPath:
823
824 self.delFromCache(rec)
825
826
827 for k in self.files.keys():
828 if k.startswith(path+"/"):
829 del self.files[k]
830
831 return 0
832
833
834
835
836 if rec.children:
837 raise IOError(errno.ENOTEMPTY, path)
838
839
840 self.delFromCache(rec)
841 ret = 0
842
843 self.log("rmdir: => %s" % ret)
844
845 return ret
846
847
848
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
872
874
875 raise IOError(errno.EPERM, path)
876
877 ret = os.symlink(path, path1)
878 self.log("symlink: path=%s path1=%s\n => %s" % (path, path1, ret))
879 return ret
880
881
882
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
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
913
915
916 self.log("unlink: path=%s" % path)
917
918
919 if path.startswith("/get/") \
920 or path.startswith("/put/") \
921 or path.startswith("/keys/"):
922 rec = self.files.get(path, None)
923 if not rec:
924 raise IOError(2, path)
925 self.delFromCache(rec)
926 return 0
927
928 if path.startswith("/usr"):
929
930
931
932 rec = self.files.get(path, None)
933 if not rec:
934 raise IOError(errno.ENOENT, path)
935
936
937 if rec.isdir:
938 raise IOError(errno.EISDIR, path)
939
940
941 bits = path.split("/")[2:]
942 diskPath = "/".join(path.split("/")[:3])
943 if len(bits) == 2 and bits[1] in freediskSpecialFiles:
944 raise IOError(errno.EACCES, path)
945
946
947 diskRec = self.files.get(diskPath, None)
948 if not diskRec:
949 raise IOError(errno.ENOENT, path)
950
951
952 if not diskRec.canwrite:
953 raise IOError(errno.EACCES, path)
954
955
956 self.delFromCache(rec)
957
958 ret = 0
959 else:
960 raise IOError(errno.ENOENT, path)
961
962
963 self.log("unlink: => %s" % ret)
964 return ret
965
966
967
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
975
976 - def write(self, path, buf, off):
977
978 dataLen = len(buf)
979
980 rec = self.files.get(path, None)
981 if rec:
982
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
995 return dataLen
996
997
998
999
1000
1001
1002
1003
1004
1005
1007 """
1008 Initialises the freedisks
1009 """
1010 self.freedisks = {}
1011
1012
1013
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
1045
1046
1047
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
1060
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
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
1079 rootRec = diskRec.root
1080 rootPath = rootRec.path
1081
1082
1083 privKey = diskRec.privKey
1084 if not privKey:
1085
1086 raise IOError(errno.EIO, "Disk %s is read-only" % name)
1087
1088
1089 pubKey = diskRec.pubKey
1090
1091
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
1100
1101
1102
1103 fileRecs = []
1104 for f in self.files.keys():
1105
1106 if f.startswith(rootPath+"/"):
1107
1108 fileRec = self.files[f]
1109
1110
1111 if fileRec.isfile \
1112 and (os.path.split(f)[1] not in freediskSpecialFiles):
1113
1114 fileRecs.append(fileRec)
1115
1116
1117 fileRecs.sort(lambda r1, r2: cmp(r1.path, r2.path))
1118
1119
1120 self.connectToNode()
1121 node = self.node
1122
1123
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
1133 maxJobs = 5
1134 jobsWaiting = fileRecs[:]
1135 jobsRunning = []
1136 jobsDone = []
1137
1138
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
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
1169 manifestJob = node.put(
1170 privKey,
1171 data=manifest.toxml(),
1172 mimetype="text/xml",
1173 async=True,
1174 )
1175
1176
1177
1178
1179
1180
1181
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
1188 while len(jobsRunning) < maxJobs and jobsWaiting:
1189
1190 rec = jobsWaiting.pop(0)
1191
1192
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
1202 jobsDone.append(rec)
1203
1204
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
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
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
1240
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
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
1261 pubKey = rootRec.pubKey
1262
1263 pubKey = pubKey.split("freenet:")[-1]
1264
1265
1266 pubKey = privKey.replace("SSK@", "USK@").split("/")[0] + "/" + name + "/0"
1267
1268 self.log("update: pubKey=%s" % pubKey)
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1281 """
1282 Retrieves the manifest of a given disk
1283 """
1284
1285
1287 """
1288 Inserts a freedisk manifest into freenet
1289 """
1290
1291
1292
1293
1294
1295
1296
1297
1298
1300 """
1301 Create initial file/directory layout, according
1302 to attributes 'initialFiles' and 'chrFiles'
1303 """
1304
1305 self.files = {}
1306
1307
1308 for path in self.initialFiles:
1309
1310
1311 isReg = isDir = isChr = isSock = isFifo = False
1312 perm = size = 0
1313
1314
1315 if path.endswith("/"):
1316 isDir = True
1317 path = path[:-1]
1318 if not path:
1319 path = "/"
1320 elif path in self.chrFiles:
1321
1322
1323 isReg = True
1324 perm |= 0666
1325 size = 1024
1326 else:
1327
1328 isReg = True
1329
1330
1331 if isDir:
1332 perm |= 0755
1333 size = 2
1334 else:
1335 perm |= 0444
1336
1337
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
1347
1349 """
1350 Attempts a connection to an fcp node
1351 """
1352 if self.node:
1353 return
1354
1355
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
1368
1369
1370
1371
1372
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
1381
1382
1383
1384
1385
1387
1388 return sha1(path).hexdigest()
1389
1390
1391
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
1403 if self.files.has_key(path):
1404 self.log("addToCache: already got %s !!!" % path)
1405 return
1406
1407
1408
1409
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
1419 self.files[path] = rec
1420
1421
1422 return rec
1423
1424
1425
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
1452
1454 """
1455 Constructs a stat tuple from keywords
1456 """
1457 tup = [0] * 10
1458
1459
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
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
1492
1493
1494
1495
1496
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
1544
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
1553
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
1565
1566 - def log(self, msg):
1567
1568
1569 file("/tmp/freedisk.log", "a").write(msg+"\n")
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1581 """
1582 returns a stat tuple for given path
1583 """
1584 return FileRecord(mode=0700, path=path, isdir=True)
1585
1586
1587
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
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
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
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
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
1633 except:
1634 raise Exception("config file %s: missing or invalid cache directory" \
1635 % self.configfile)
1636
1637
1638
1639
1640
1641
1642
1643
1644
1646 """
1647 Encapsulates a freedisk
1648 """
1649
1650
1652
1653 self.root = rootrec
1654
1655
1656
1657
1658
1659
1661 """
1662 Interfaces with FUSE
1663 """
1664
1665
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
1672
1674
1675 import _fuse
1676
1677 d = {'mountpoint': self.mountpoint,
1678 'multithreaded': self.multithreaded,
1679 }
1680
1681
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
1698
1699 _fuse.main(**d)
1700
1701
1702
1703 - def GetContext(self):
1704 print "GetContext: called"
1705 return _fuse.FuseGetContext(self)
1706
1707
1708
1710 print "Invalidate: called"
1711 return _fuse.FuseInvalidate(self, path)
1712
1713
1714
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
1725
1726
1727
1729 """
1730 Encapsulates the info for a file, and can
1731 be returned by getattr
1732 """
1733
1734
1735
1736 haschanged = False
1737 hasdata = False
1738 canwrite = False
1739 iswriting = False
1740 uri = None
1741
1742
1743
1744 - def __init__(self, fs, statrec=None, **kw):
1745 """
1746 """
1747
1748 kw = dict(kw)
1749
1750
1751 self.fs = fs
1752
1753
1754 if statrec:
1755
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
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
1771 if not hasattr(statrec, '__setitem__'):
1772 statrec = list(statrec)
1773
1774
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
1792 perm = kw.pop('perm', 0)
1793 mode |= perm
1794
1795
1796 path = kw.pop('path')
1797 self.path = path
1798
1799
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
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
1815 self.children = []
1816
1817
1818 inode = pathToInode(path)
1819
1820
1821 now = timeNow()
1822 atime = kw.pop('atime', now)
1823 mtime = kw.pop('mtime', now)
1824 ctime = kw.pop('ctime', now)
1825
1826
1827
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
1843 self.__dict__.update(kw)
1844
1845
1846 list.__init__(self, statrec)
1847
1848 if self.isdir:
1849 self.size = 2
1850
1851
1852
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
1921
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
1995
1997
1998 self.stream.write(buf)
1999 self.size = len(self.stream.getvalue())
2000
2001
2002
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
2014
2015
2016
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
2029
2030
2031
2032
2033
2034
2036 """
2037 Gateway for mirroring a local directory to/from freenet
2038 """
2039
2040
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
2056
2058 """
2059 Update from freenet to local directory
2060 """
2061
2062
2063
2065 """
2066 commit from local directory into freenet
2067 """
2068
2069
2070
2071
2072
2073
2075 """
2076 Comes up with a unique inode number given a path
2077 """
2078
2079 inode = inodes.get(path, None)
2080 if inode != None:
2081 return inode
2082
2083
2084 inode = int(md5(path).hexdigest()[:7], 16)
2085
2086
2087 while inodes.has_key(inode):
2088 inode += 1
2089
2090
2091 inodes[path] = inode
2092
2093
2094 return inode
2095
2096
2097
2099 return int(time.time()) & 0xffffffffL
2100
2101
2102
2104
2105 print "Usage: %s mountpoint -o args" % progname
2106
2107 sys.exit(ret)
2108
2109
2110
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
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
2139
2140 if __name__ == '__main__':
2141
2142 main()
2143
2144
2145
2146
2147
2148