1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
|
\input lisp-tutorial.tex
@setfilename lisp-tutorial.info
@settitle Programming Lisp translators
@copying
Copyright (C) 2008
Free Software Foundation, Inc.
@quotation
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.1 or
any later version published by the Free Software Foundation; with no
Invariant Sections, with the Front-Cover texts being ``A GNU
Manual'', and with the Back-Cover Texts as in (a) below. A copy of the
license is included in the section entitled ``GNU Free Documentation
License'' in the Emacs manual.
(a) The FSF's Back-Cover Text is: ``You have freedom to copy and modify
this GNU Manual, like GNU software. Copies published by the Free
Software Foundation raise funds for GNU development.''
This document is part of a collection distributed under the GNU Free
Documentation License. If you want to distribute this document
separately from the collection, you can do so by adding a copy of the
license to the document, as described in section 6 of the license.
@end quotation
@end copying
@dircategory Programming Lisp translators
@direntry
* Lisp translators (lisp translators). How to program Lisp translators.
@end direntry
@titlepage
@title Programming Lisp Translators
@author Flavio Cruz
@page
@insertcopying
@end titlepage
@contents
@node Top
@top Introduction
This document will introduce you to a new Hurd translator library that can be used to easily implement new file system servers.
The main advantages of this library compared to libnetfs or libtrivfs, are:
@itemize @bullet
@item
A much more fast development - What can take weeks to do in libnetfs, can be done in a few hours or less.
@item
Easy development - You can put more focus on high level details of your translators and less on the little details that arise when writing C based translators.
@item
Use of the Common Lisp language - Programming in Lisp is great fun.
@item
Prototyping - You can test and validate new translator ideas and then build a fast implementation in C.
@end itemize
Of course, there are also some drawbacks:
@itemize @bullet
@item
Efficiency - Don't expect to run your Lisp based translators as fast as the C based ones.
@item
Disk based translators - Don't expect to write the next generation ext file system.
@end itemize
Given these pros and cons, it's easy to deduce that this library is more suited to implement virtual file systems. Examples are:
@itemize @bullet
@item
Translators were data is located in a local file (like zipfs, tarfs, rarfs, ...)
@item
Single file translators (that do content filtering, output of a command, etc).
@item
Network based file systems (ftpfs, httpfs, ircfs, ...)
@item
Proxy file systems (like hostmux, usermux, etc..).
@end itemize
@menu
* Dependencies:: What needs to be installed before using the library.
* Installation:: How to install the library.
* Using the provided translators:: How to use the unzip, irc, and mux translators
* A /dev/zero like translator:: Introductory example.
* A more complex example:: A more comprehensive example.
* Conclusion:: Conclusion text.
@end menu
@node Dependencies, Installation, Top, Top
@chapter Dependencies
Right now, only CLISP is supported. Porting to new Lisp implementations will happen has soon they run and compile sanely on the Hurd operating system (especially SBCL ;-)).
You must also have these asdf packages installed on your system:
@itemize @bullet
@item
@uref{http://www.common-lisp.net/project/cffi/,CFFI}
It can be installed on Debian GNU/Hurd with @command{apt-get install cl-cffi}.
@item
@uref{http://www.weitz.de/flexi-streams/,flexi-streams}
Can also be installed with @command{apt-get install cl-flexi-streams}.
@item
@uref{http://www.cliki.net/trivial-garbage?v=20,trivial-garbage}
Not apt-get installable, but please grab the tarball from the project web page and install it as described:
@example
# cd /usr/share/common-lisp/source
# tar zxvf <path to trivial_garbage.tar.gz>
# ln -sf $PWD/trivial-garbage_0.16/trivial-garbage.asd ../systems/
@end example
@end itemize
@node Installation, A /dev/zero like translator, Dependencies, Top
@chapter Installation
To install the translator library, grab the Hurd source tree from the branch @code{flaviocruz-soc2008-lisp-branch} and build it:
@example
# cvs -z3 -d:pserver:anonymous@@cvs.savannah.gnu.org:/sources/hurd co -r flaviocruz-soc2008-lisp-branch hurd
# mkdir hurd-build
# cd hurd-build
# ../hurd/configure --prefix=/usr --enable-lisp
# make
# make install
@end example
With everything in place I recommend you to compile everything before using the library. In the CLISP read-eval-print loop do:
@example
* (asdf:operate 'asdf:load-op 'tree-translator)
@end example
@node Using the provided translators, A /dev/zero like translator, Installation, Top
@chapter Using the provided translators
This library already provides some Lisp translators that can be used production wise.
The more noteworthy translators are as follow:
@itemize @bullet
@item unzip
This translator will sit on a zip file node and expose the file contents as a traditional directory, where the packed files can be read and write as any other file in the system.
To startup this translator
@example
$ settrans -a file.zip /hurd/unzip.lisp
@end example
If you change the contents of an archived file the translator, when shutdown, will zip the original file, saving the new modifications along the way.
@item ircfs
The ircfs translator is a simple IRC client that enables you to listen for private and channel messages, sending messages, join channels and kick people, among others. Everything using your beloved file system interface.
To launch this translator, you should type:
@example
$ settrans -ac irc /hurd/irc.lisp your_nickname irc_server
$ ls irc/
notice
@end example
The first file the translator will create, is the @file{notice} file, it contains the server notices and messages.
Also, you may want to join a channel:
@example
$ mkdir hurd
$ ls hurd/
conversation kick topic users
@end example
Each channel has four files:
@itemize @bullet
@item conversation
Contains the channel's conversation log, for instance you can @command{tail -f conversation} to wait for new messages. To send a channel message, @command{echo "hello" > conversation}.
@item kick
Use this file to kick people on the channel @command{echo "user_you_dont_like" > kick}.
@item topic
Contains the channel topic.
@item users
Contains a list of the channel's users.
@end itemize
To part from a channel just remove the channel directory.
To make private conversations:
@example
$ pwd
.../irc
$ touch nick
@end example
That @file{nick} file will act just like a channel's @file{conversation} file.
@item mux
The mux translator acts like a directory organizer. For example, you may want to organize your mp3 files in a per artist fashion, for that purpose you should write a script that outputs the mp3 artist and a file that lists all the files you want to classify, and then the translator will create virtual directories and proxies for the real files.
@example
$ settrans -ac organized /hurd/mux.lisp list-of-mp3s.txt ./classify-mp3.sh
@end example
@end itemize
@node A /dev/zero like translator, A more complex example, Using the provided translators, Top
@chapter A /dev/zero like translator
Now that we can really starting doing some interesting things, we will start with a basic translator that can easily introduce ourselves to important concepts and at the same time don't be overly complex.
The translator we will be implementing is very similar to the @file{/dev/zero} file that probably any Unix like system implements. This file ignores everything that is written to it and when it is read it only gives us zeroes. It's also very similar to the @uref{www.gnu.org/software/hurd/hacking-guide/hhg.html,HHG's} hurd-one.c example.
Before getting into actual code we will properly package the new translator, using the ASDF method. Let's create a file named @file{zero-translator.asd} with the following content:
@lisp
(defpackage #:zero-translator-asd
(:use :cl :asdf))
(in-package :zero-translator-asd)
(defsystem zero-translator
:name "zero-translator"
:version "0.0.0"
:maintainer "Your name"
:author "Your name"
:license "GPL v3.0"
:description "/dev/zero translator."
:depends-on (:hurd-translator)
:components ((:file "zero")))
@end lisp
As you can see, this ASDF package has only one file (@file{zero.lisp}) and only depends on the hurd-translator ASDF package, which is the basic translator library that we will be using. If you need to use more ASDF packages please declare them here.
Now to the @file{zero.lisp} file. At the top we put:
@lisp
(defpackage :zero-translator
(:use :cl :hurd-common :mach
:hurd :hurd-translator))
(in-package :zero-translator)
@end lisp
This basically tells the Lisp system to create a new package which uses the Hurd translator library (hurd-common, mach, hurd, hurd-translator and tree-translator packages are all from this library) and then sets the currently used package to the zero-translator package.
Now inside the new package, we must define a new @code{translator} class, so that we can customize the translator methods to this new class. These translator methods act like hooks/callbacks that run whatever some request is made upon the server.
@lisp
(defclass zero-translator (translator)
()
(:documentation "The zero-translator."))
@end lisp
This definition is remarkably simple, it just tells that the @code{translator} class is the base class of the @code{zero-translator} class and doesn't add any new slots.
If you want to see how the translator class is defined please look at the file @file{translator/class.lisp}. The important slots stored on this class follow:
@itemize @bullet
@item
underlying-node
It's the port to the underlying node where the translator is set. You can execute RPC's on it to request some information you need. For RPCs look at @file{hurd/io/}, @file{hurd/fs/} and @file{hurd/fsys}.
@item
root
It's simply the translator's root node. If the translator simply exposes a new regular file this represents that file, if it's a directory it will refer to a directory node.
@item
statfs
Contains file system meta data that should be set by the translator writer. Look at @file{common/statfs.lisp} for methods you can use on this object.
@item
storage
Type of storage for this file system, defaults to :memory. Look at @file{hurd/fs/storage.lisp} for more types.
@item
options
Currently used file system options that can be set using the fsysopts utility. It's a translator-options object and it's implemented in @file{hurd/translator-options.lisp}.
@item
name and version
Translator name and 3 number list with version. It's used in request to the io-server-version RPC.
@end itemize
Now, you also must know what kinds of methods can be applied to a translator object, so that you can get the desired translator behavior. All these callbacks are extensively documented and described in the file @file{translator/api.lisp} along with some good default implementations.
If you want to dig at a lower level, it's also possible to define how core RPC's like fsys-getroot and dir-lookup work, specializing the do-fsys-getroot and do-dir-lookup (look at @file{translator/interfaces/fsys-getroot.lisp} and @file{translator/interfaces/dir-lookup.lisp}, respectively).
And if these methods don't satisfy your needs, you can even replace the lower level callbacks (implemented at @file{translator/interfaces/}), but be aware that you must deal with nasty things, like pointers, memory, etc and you must also be knowledgeable in using CFFI to deal with foreign calls.
Alright, let's forget about that and get to the good stuff!
Now that you have some basic understanding how everything fits together, we should begin implementing our first translator method: @code{make-root-node}.
This method is only called right at the beginning of execution and it wants us to create a new 'node' object that will represent the initial translator file, which can be anything: a regular file, directory, character device, etc.
To define translator methods we use the define-callback macro, which has following syntax:
@lisp
(define-callback <method-name> <translator-type> <argument list> <body>)
@end lisp
Which is transformed (as you may be guessing) into this:
@lisp
(defmethod <method-name> ((translator <translator-type>) <argument list>)
<body>)
@end lisp
As you noted, the translator argument is always present in translator methods! It represents your yet to be created, translator object, of the class @code{zero-translator}. Apart from that 'hidden' argument, you can also use the *translator* dynamic variable that references the exact same object, but can be accessed anytime and anywhere you want.
Before implementing the @code{make-root-node} callback, you need some basic knowledge of the @code{node} class. It's implemented in the file @file{translator/node.lisp} and has the following slots:
@itemize @bullet
@item
owner
File owner. Indicates the owner process ID and can be changed through the io-mod-owner RPC. Defaults to 0.
@item
stat
This is a very important slot. It represents the node meta data and it's the same thing as the C @code{struct stat}.
You should really know how to retrieve and change the @code{stat} information. Please look at @file{common/stat.lisp} to have a basic idea. Also note that the @code{stat} meta data also contains a mode object, which itself, describes node permissions, file type, among other things. Look at @file{common/mode.lisp} to see what methods are available to you.
Important note: every method applicable to a @code{mode} object can also be directly applied to a @code{stat} object.
@item
box
Translator box, can contain the current passive or/and active translator set on this node.
@item
nusers
Number of users using this node. When it drops to 0 the method @code{report-no-users} is called and when it gets from the 'no users' state to a new user, the method @code{report-new-user} is called.
@item
link
Contains a string indicating the file this node symbolic links to. Only makes sense when the node mode type is a link.
@end itemize
Alright, here is the @code{make-root-node} method:
@lisp
(define-callback make-root-node zero-translator
(underlying-node underlying-stat)
(declare (ignore underlying-node))
(let ((mode (make-mode :perms '((:owner :read :write)
(:group :read :write)
(:others :read :write))
:type :chr)))
(make-instance 'node
:stat (make-stat underlying-stat
:mode mode))))
@end lisp
The first thing we must notice here are the arguments. This method accepts the underlying node port and @code{underlying-stat}, which refers to the @code{stat} meta data from the underlying node where the translator is being set.
The first thing we do, is creating a sane @code{mode} meta data, indicating the types of permissions we want for the node and then the file type, in this case character device @code{:chr}. Other possible types are:
@itemize @bullet
@item
:dir
Directory.
@item
:chr
Character device.
@item
:blk
Block device.
@item
:reg
Regular file.
@item
:lnk
Symbolic link.
@item
sock
Socket.
@end itemize
With the mode object created, we then create a @code{node} instance, indicating the @code{stat} object we want for that node, with @code{make-stat}.
@code{make-stat} accepts an old @code{stat} object, and so the meta data will be copied to the new @code{stat} object. It means that this new @code{stat} object will be equal to @code{underlying-stat}, except the mode object which will be the node we created early. @code{make-stat} can also accept other arguments, please see @file{common/stat.lisp}.
In the end, the newly created node is returned and put in the @code{root-node} translator slot, so that you can access it later on, if you need.
What else needs to be implemented? :-) Ah, file writing!
File writing is pretty simple on this translator, we only need to ignore everything we get. The method that deals with file writing is @code{write-file}.
@lisp
(define-callback write-file zero-translator
(node user offset stream amount)
(declare (ignore translator offset amount))
(when (has-access-p node user :write)
; Empty the stream to look like we used it all.
(loop while (read-byte stream nil))
t))
@end lisp
Here, we can see that @code{write-file} gets lots of arguments, which I will explain:
@itemize @bullet
@item
node
It's the node the user wants to write in.
@item
user
The user making the write request. It's an @code{iouser} object contain the @code{uid} and @code{gid} sets from that user. Implemented at @file{hurd/iohelp/iouser.lisp}.
@item
offset
The place in the file the user wants to start writing to.
@item
stream
An input byte stream containing the data to be wrote, you can used @code{read-byte to get the data}, or @code{read-sequence}.
@item
amount
Amount of bytes present in this stream. Very useful if you want to put data into an array:
@lisp
(let ((my-array (make-array amount :element-type '(unsigned-byte 8))))
(read-sequence my-array stream))
@end lisp
You can even convert it to string form if you need to:
@lisp
(octets-to-string my-array)
@end lisp
@end itemize
How can the library know how much has been wrote? Well, simple looking at how much you read from the stream. So, if you read everything from the stream, the library will get the idea that everything was used. Finally we return T meaning success.
How about file reading? @code{read-file} is the method for reading nodes.
@lisp
(define-callback read-file zero-translator
(node user start amount stream)
(declare (ignore translator start))
(when (has-access-p node user :read)
(loop for i from 0 below amount
do (write-byte 0 stream))
t))
@end lisp
The arguments, explained:
@itemize @bullet
@item
node
The node where the user wants to read.
@item
user
The user making the request.
@item
start
The file position the user wants to start reading at.
@item
amount
Amount of data the user wants to read. You don't need to provide that amount of data, only what you have or want deliberately.
@item
stream
Output stream were byte data should be written to.
@end itemize
As we said earlier, the translator should give back only zeroes to read requests, because of that we simply write 'amount' zeroes to the output stream, and then return T signaling success.
What's missing? Ah! Start-up code:
@lisp
(defun main ()
(run-translator (make-instance 'zero-translator
:name "zero-translator")))
(main)
@end lisp
Here we just create a new zero-translator instance and then run it. Boring, isn't it?
And that's it! Let's wrap everything with a new file: @file{run-zero.lisp}:
@example
#!/usr/bin/run-lisp-trans
(asdf:operate 'asdf:load-op 'zero-translator)
@end example
Now we can run the translator:
@example
$ chmod +x run-zero.lisp
$ settrans -ac zero ./run-zero.lisp
@end example
And then you verify that it's really a character device:
@example
$ ls -l zero
crw-rw-rw- 2 root root 0, 0 Aug 14 15:09 zero
@end example
You can also do some tests. Run a CLISP instance and do this:
@example
* (asdf:operate 'asdf:load-op 'hurd)
* (use-package :hurd)
* (defvar *p* (file-name-lookup "zero" :flags '(:read :write)))
* (io-read *p* :amount 5)
#(0 0 0 0 0)
* (io-write *p* #(1 2))
2
* (use-package :mach)
* (port-deallocate *p*)
T
@end example
As expected.
Now, remove the translator:
@example
$ settrans -g zero
@end example
@node A more complex example, Conclusion, A /dev/zero like translator, Top
@chapter A more complex example
Now we'll try to implement a more complex translator example.
The translator we are going implement will provide directory listing and the traditional file creation, writing, etc.
At first it should create a directory structure mirroring Lisp lists. Here's an example:
@lisp
(:dir "root"
(:file "a" "abcdefghijklmnopqrstuvwxyz")
(:file "b" "123456789012345678901234567890")
(:file "c" "")
(:file "d" "456")
(:dir "dir1"
(:file "k" "1a2b3c")
(:file "a" "")
(:file "b" "a")
(:file "c" "c"))
(:dir "dir2")
(:link "f" "c")
(:dir "dir3"
(:file "a" "12345"))
(:link "g" "g") ; circular link
(:link "h" "/usr"))
@end lisp
Our translator node must have a directory named "root" and then some files and links on it. The third list argument for regular files describe file contents and the third argument for symbolic links describe the link target.
First, let's describe this new translator class (we'll name it @code{test-translator}):
@lisp
(defclass test-translator (tree-translator)
((file-stat :initarg :file-stat
:initform nil
:accessor file-stat)
(dir-stat :initarg :dir-stat
:initform nil
:accessor dir-stat)))
@end lisp
You might be wondering what's up with that @code{tree-translator} business. We'll, the tree-translator is a special class of translators that do some work for us. The @code{tree-translator} has the special ability of implementing all the directory lookup and listing automatically, without the need for us to do that. If you want to see what methods are implemented please see @file{tree-translator/class.lisp}.
It also extends the base node class with two new node types:
@itemize @bullet
@item
entry
Is a node with the information about the parent directory.
@item
dir-entry
Is an @code{entry} and also contains the directory entries.
@end itemize
Please check the file @file{tree-translator/dir.lisp} for methods on directories.
Some important meta data is also managed by this type of translator, specially the st-nlink and st-ino fields for the @code{stat} object.
In our @code{test-translator} we put the @code{file-stat} and @code{dir-stat} slots. They act like a template meta data for new nodes.
Another thing you should take note is that the previously used method @code{make-root-node} is already implemented for us in @code{tree-translator}, in addition, it creates a new method named @code{fill-root-node} with the root node as an argument. In this method you should fill your root directory with the stuff you want.
For this case, this might be implemented like this:
@lisp
(define-callback fill-root-node test-translator
((node dir-entry))
(setf (file-stat translator)
(make-stat (stat node)
:mode (make-mode :perms '((:owner :read)
(:group :read)))
:type :reg)
(dir-stat translator)
(make-stat (stat node)
:mode (make-mode :perms '((:owner :read :exec)
(:group :read :exec)))
:type :dir))
(%fill-node translator (with-open-file (s +file+)
(read stream))
node))
@end lisp
As you can see it defines the translator slots with fresh meta data templates and then executes the function @code{%fill-node} with these arguments: the translator, the directory structure (as read from the @code{+file+}) and the root node.
@lisp
(defun %fill-node (translator ls node)
(let ((type (first ls))
(name (second ls))
(args (rest (rest ls))))
(case type
(:dir
(let ((dir (make-instance 'dir-entry
:stat (make-stat (dir-stat translator))
:parent node)))
(add-entry node dir name)
(loop for item in args
do (%fill-node translator item dir))))
(:file
(let* ((data (first args))
(file (make-instance 'test-entry
:stat (make-stat
(file-stat translator)
:size (length data))
:parent node
:data (%read-file-data data))))
(add-entry node file name)))
(:link
(let ((target (first args))
(new (make-instance 'entry
:stat (make-stat
(file-stat translator)
:type :lnk)
:parent node)))
(setf (link new) target)
(add-entry node new name))))))
@end lisp
This is a recursive function. It checks the requested file type, creates the specific node with that type and then puts it in the directory as a new directory entry.
You should always give the :parent argument when you are creating new @code{entry}'s.
Take special attention for the symbolic links case "@code{(setf (link new) target)}". It defines the link (as a string) this node points to, but it also sets the file type to :lnk, even if it was not specified in @code{make-stat}.
You may also be wondering what's that @code{test-entry} class, well, it's just a new kind of @code{node}.
@lisp
(defclass test-entry (entry)
((contents :initarg :data
:initform (%create-data-array 0 nil)
:accessor data)))
@end lisp
In this case we add a new slot that contains the file contents. And what does @code{%create-data-array} do? It just creates a new adjustable array:
@lisp
(defun %create-data-array (size contents)
(make-array size
:initial-contents contents
:adjustable t
:fill-pointer t
:element-type '(unsigned-byte 8)))
@end lisp
You can use the CLOS dispatch system to specialize node behaviors, for example, you can have various node types that react differently to the @code{read-file} method. For example, one node type would always return the data in upper case and in some others all in lower case.
As for that @code{%read-file-data} function, here it is:
@lisp
(defun %read-file-data (str)
(%create-data-array (length str)
(loop for char across str
collect (char-code char))))
@end lisp
It takes the data string and converts it to an @code{unsigned-byte} array.
Now that the directory structure is being created, you need to start exposing the file contents with @code{read-file} (just like in the first example). Please also keep in mind that you should always keep the @code{st-size stat} field updated after a refresh-node method call. Sometimes the translator clients want to know how much data there is to read, and the file size is an important factor in this case, when it's being compared to an internally saved file offset.
@lisp
(define-callback read-file test-translator
(node user start amount stream)
(when (has-access-p node user :read)
(let* ((size (stat-get (stat node) 'st-size))
(size-res (- size start)))
(unless (plusp size-res)
(return-from read-file t))
(let* ((total (min size-res amount))
(end (+ start total)))
(write-sequence (subseq (data node) start end)
stream)
t))))
@end lisp
In the @code{read-file} method we check if the user has read access to that node and then calculate how much data there is left to read from the 'start' position. When we know that, we just write a sub sequence of the file contents to the output stream and then return T to report success.
Another good thing to provide in translators (specially those with write support) is file truncating. For that, the library has the @code{file-change-size} method.
@lisp
(define-callback file-change-size test-translator
(node user new-size)
(when (is-dir-p (stat node))
(return-from file-change-size :is-a-directory))
(when (has-access-p node user :write)
(adjust-array (data node) new-size :fill-pointer t)
(setf (stat-get (stat node) 'st-size) new-size)
t))
@end lisp
First we verify if the file is a directory, and then if the requesting user has write access. When these tests are passed we can just adjust the file contents array and define a new size for that file in the @code{stat} object.
And now for file writing!
@lisp
(defun %read-sequence (stream amount)
(let ((arr (make-array amount
:element-type '(unsigned-byte 8))))
(read-sequence arr stream)
arr))
(define-callback write-file test-translator
(node user offset stream amount)
(unless (has-access-p node user :write)
(return-from write-file nil))
(when (is-dir-p (stat node))
(return-from write-file :is-a-directory))
(let* ((size (stat-get (stat node) 'st-size))
(arr (%read-sequence stream amount))
(final-size (max (+ amount offset) size)))
(unless (= final-size size)
(adjust-array (data node)
final-size
:fill-pointer t))
(replace (data node) arr :start1 offset)
; Update stat size.
(setf (stat-get (stat node) 'st-size) final-size)
t))
@end lisp
Well, let's see. We first do some boring tests and then we read all data from the stream with @code{%read-sequence}, after that we need to know how much data the file will now have, and then we have three possible situations: the data will just be replaced, the file will get new data, and both. Anyway, we simply just need to know the maximum of the current size and the offset plus the new data.
After knowing the final size and if the file will grow we adjust the array according to this new size. Then simply replace the current array data with the stream data and update the file size.
Now, you'll probably want to support file creation, for that, you must implement the @code{create-file} method.
@lisp
(define-callback create-file test-translator
(node user filename mode)
(unless (has-access-p node user :write)
(return-from create-file nil))
(let ((entry (make-instance 'test-entry
:stat (make-stat (stat node)
:mode mode
:size 0)
:parent node)))
(add-entry node entry filename)
entry))
@end lisp
The create-file method has 4 arguments:
@itemize @bullet
@item
node
The directory where the user wants to create the file.
@item
user
The user creating the file.
@item
filename
The new file name.
@item
mode
Permission bits for that file.
@end itemize
As you can see, we just create a new @code{test-entry} instance with @code{node} as the parent directory and then we add this new node to the parent directory as a new directory entry.
How about hard link creation? It's already implemented by default in the @code{tree-translator}. It works by simply using the same node reference and adding a new point of access using a new filename.
And symbolic link creation? Three methods must be implemented for it to work:
@itemize @bullet
@item
@code{allow-link-p}
Allows link reading, implemented by default by the @code{translator} class.
@item
@code{create-symlink}
Turns a node into a symlink, implemented by default by the @code{translator} class.
@item
@code{create-anonymous-file}
Not implemented. It must create a detached node. Detached means that file will have a parent directory but will not be present when listed, and it will exist as a Lisp object that will be hard linked to a new symlink node. See @file{sysdeps/mach/hurd/symlink.c} from glibc's source code for more insights and reasons for this.
@end itemize
Here's a simple implementation for @code{create-anonymous-file}:
@lisp
(define-callback create-anonymous-file test-translator
(node user mode)
(when (can-modify-dir-p node user)
(make-instance 'test-entry
:stat (make-stat (stat node)
:mode mode)
:parent node)))
@end lisp
Here @code{node} represents the parent directory and @code{mode} the permission bits.
How about the start-up code? First we need to get the file from the arguments that will be passed to our program when it is launched. This file contains the mentioned directory structure.
@lisp
(unless (= (length ext:*args*) 1)
(error "You must provide one argument with a spec file."))
(defconstant +file+ (first ext:*args*))
@end lisp
Then, the usual main function:
@lisp
(defun main ()
(let ((translator
(make-instance 'test-translator
:name "test-translator"
:version (list 1 2 3))))
(run-translator translator)))
(main)
@end lisp
Please see @file{examples/test.lisp}, @file{examples/test-translator.asd} and @file{examples/run-test.lisp} for the complete example.
To see it running:
@example
$ settrans -ac foo ./run-test.lisp data/test.lisp
$ cd foo
$ ls
root
$ cd root
$ ls -l
total 40
-r--r----- 1 root root 26 Aug 14 19:13 a
-r--r----- 1 root root 30 Aug 14 19:13 b
-r--r----- 1 root root 0 Aug 14 19:13 c
-r--r----- 1 root root 3 Aug 14 19:13 d
dr-xr-x--- 6 root root 0 Aug 14 19:13 dir1
dr-xr-x--- 2 root root 0 Aug 14 19:13 dir2
dr-xr-x--- 3 root root 0 Aug 14 19:13 dir3
lr--r----- 1 root root 1 Aug 14 19:13 f -> c
lr--r----- 1 root root 1 Aug 14 19:13 g -> g
lr--r----- 1 root root 4 Aug 14 19:13 h -> /usr
@end example
@node Conclusion,, A more complex example, Top
@chapter Conclusion
Now that you got some taste of what's like to program Lisp translators, I invite you to read more complex translator examples that are present in the package, namely the zip and irc translators.
Happy hacking!
@bye
|