tutorial.texi 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927
  1. \input lisp-tutorial.tex
  2. @setfilename lisp-tutorial.info
  3. @settitle Programming Lisp translators
  4. @copying
  5. Copyright (C) 2008
  6. Free Software Foundation, Inc.
  7. @quotation
  8. Permission is granted to copy, distribute and/or modify this document
  9. under the terms of the GNU Free Documentation License, Version 1.1 or
  10. any later version published by the Free Software Foundation; with no
  11. Invariant Sections, with the Front-Cover texts being ``A GNU
  12. Manual'', and with the Back-Cover Texts as in (a) below. A copy of the
  13. license is included in the section entitled ``GNU Free Documentation
  14. License'' in the Emacs manual.
  15. (a) The FSF's Back-Cover Text is: ``You have freedom to copy and modify
  16. this GNU Manual, like GNU software. Copies published by the Free
  17. Software Foundation raise funds for GNU development.''
  18. This document is part of a collection distributed under the GNU Free
  19. Documentation License. If you want to distribute this document
  20. separately from the collection, you can do so by adding a copy of the
  21. license to the document, as described in section 6 of the license.
  22. @end quotation
  23. @end copying
  24. @dircategory Programming Lisp translators
  25. @direntry
  26. * Lisp translators (lisp translators). How to program Lisp translators.
  27. @end direntry
  28. @titlepage
  29. @title Programming Lisp Translators
  30. @author Flavio Cruz
  31. @page
  32. @insertcopying
  33. @end titlepage
  34. @contents
  35. @node Top
  36. @top Introduction
  37. This document will introduce you to a new Hurd translator library that can be used to easily implement new file system servers.
  38. The main advantages of this library compared to libnetfs or libtrivfs, are:
  39. @itemize @bullet
  40. @item
  41. A much more fast development - What can take weeks to do in libnetfs, can be done in a few hours or less.
  42. @item
  43. 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.
  44. @item
  45. Use of the Common Lisp language - Programming in Lisp is great fun.
  46. @item
  47. Prototyping - You can test and validate new translator ideas and then build a fast implementation in C.
  48. @end itemize
  49. Of course, there are also some drawbacks:
  50. @itemize @bullet
  51. @item
  52. Efficiency - Don't expect to run your Lisp based translators as fast as the C based ones.
  53. @item
  54. Disk based translators - Don't expect to write the next generation ext file system.
  55. @end itemize
  56. Given these pros and cons, it's easy to deduce that this library is more suited to implement virtual file systems. Examples are:
  57. @itemize @bullet
  58. @item
  59. Translators were data is located in a local file (like zipfs, tarfs, rarfs, ...)
  60. @item
  61. Single file translators (that do content filtering, output of a command, etc).
  62. @item
  63. Network based file systems (ftpfs, httpfs, ircfs, ...)
  64. @item
  65. Proxy file systems (like hostmux, usermux, etc..).
  66. @end itemize
  67. @menu
  68. * Dependencies:: What needs to be installed before using the library.
  69. * Installation:: How to install the library.
  70. * Using the provided translators:: How to use the unzip, irc, and mux translators
  71. * A /dev/zero like translator:: Introductory example.
  72. * A more complex example:: A more comprehensive example.
  73. * Conclusion:: Conclusion text.
  74. @end menu
  75. @node Dependencies, Installation, Top, Top
  76. @chapter Dependencies
  77. 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 ;-)).
  78. You must also have these asdf packages installed on your system:
  79. @itemize @bullet
  80. @item
  81. @uref{http://www.common-lisp.net/project/cffi/,CFFI}
  82. It can be installed on Debian GNU/Hurd with @command{apt-get install cl-cffi}.
  83. @item
  84. @uref{http://www.weitz.de/flexi-streams/,flexi-streams}
  85. Can also be installed with @command{apt-get install cl-flexi-streams}.
  86. @item
  87. @uref{http://www.cliki.net/trivial-garbage?v=20,trivial-garbage}
  88. Not apt-get installable, but please grab the tarball from the project web page and install it as described:
  89. @example
  90. # cd /usr/share/common-lisp/source
  91. # tar zxvf <path to trivial_garbage.tar.gz>
  92. # ln -sf $PWD/trivial-garbage_0.16/trivial-garbage.asd ../systems/
  93. @end example
  94. @end itemize
  95. @node Installation, A /dev/zero like translator, Dependencies, Top
  96. @chapter Installation
  97. To install the translator library, grab the Hurd source tree from the branch @code{flaviocruz-soc2008-lisp-branch} and build it:
  98. @example
  99. # cvs -z3 -d:pserver:anonymous@@cvs.savannah.gnu.org:/sources/hurd co -r flaviocruz-soc2008-lisp-branch hurd
  100. # mkdir hurd-build
  101. # cd hurd-build
  102. # ../hurd/configure --prefix=/usr --enable-lisp
  103. # make
  104. # make install
  105. @end example
  106. With everything in place I recommend you to compile everything before using the library. In the CLISP read-eval-print loop do:
  107. @example
  108. * (asdf:operate 'asdf:load-op 'tree-translator)
  109. @end example
  110. @node Using the provided translators, A /dev/zero like translator, Installation, Top
  111. @chapter Using the provided translators
  112. This library already provides some Lisp translators that can be used production wise.
  113. The more noteworthy translators are as follow:
  114. @itemize @bullet
  115. @item unzip
  116. 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.
  117. To startup this translator
  118. @example
  119. $ settrans -a file.zip /hurd/unzip.lisp
  120. @end example
  121. 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.
  122. @item ircfs
  123. 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.
  124. To launch this translator, you should type:
  125. @example
  126. $ settrans -ac irc /hurd/irc.lisp your_nickname irc_server
  127. $ ls irc/
  128. notice
  129. @end example
  130. The first file the translator will create, is the @file{notice} file, it contains the server notices and messages.
  131. Also, you may want to join a channel:
  132. @example
  133. $ mkdir hurd
  134. $ ls hurd/
  135. conversation kick topic users
  136. @end example
  137. Each channel has four files:
  138. @itemize @bullet
  139. @item conversation
  140. 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}.
  141. @item kick
  142. Use this file to kick people on the channel @command{echo "user_you_dont_like" > kick}.
  143. @item topic
  144. Contains the channel topic.
  145. @item users
  146. Contains a list of the channel's users.
  147. @end itemize
  148. To part from a channel just remove the channel directory.
  149. To make private conversations:
  150. @example
  151. $ pwd
  152. .../irc
  153. $ touch nick
  154. @end example
  155. That @file{nick} file will act just like a channel's @file{conversation} file.
  156. @item mux
  157. 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.
  158. @example
  159. $ settrans -ac organized /hurd/mux.lisp list-of-mp3s.txt ./classify-mp3.sh
  160. @end example
  161. @end itemize
  162. @node A /dev/zero like translator, A more complex example, Using the provided translators, Top
  163. @chapter A /dev/zero like translator
  164. 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.
  165. 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.
  166. 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:
  167. @lisp
  168. (defpackage #:zero-translator-asd
  169. (:use :cl :asdf))
  170. (in-package :zero-translator-asd)
  171. (defsystem zero-translator
  172. :name "zero-translator"
  173. :version "0.0.0"
  174. :maintainer "Your name"
  175. :author "Your name"
  176. :license "GPL v3.0"
  177. :description "/dev/zero translator."
  178. :depends-on (:hurd-translator)
  179. :components ((:file "zero")))
  180. @end lisp
  181. 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.
  182. Now to the @file{zero.lisp} file. At the top we put:
  183. @lisp
  184. (defpackage :zero-translator
  185. (:use :cl :hurd-common :mach
  186. :hurd :hurd-translator))
  187. (in-package :zero-translator)
  188. @end lisp
  189. 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.
  190. 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.
  191. @lisp
  192. (defclass zero-translator (translator)
  193. ()
  194. (:documentation "The zero-translator."))
  195. @end lisp
  196. 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.
  197. 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:
  198. @itemize @bullet
  199. @item
  200. underlying-node
  201. 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}.
  202. @item
  203. root
  204. 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.
  205. @item
  206. statfs
  207. 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.
  208. @item
  209. storage
  210. Type of storage for this file system, defaults to :memory. Look at @file{hurd/fs/storage.lisp} for more types.
  211. @item
  212. options
  213. 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}.
  214. @item
  215. name and version
  216. Translator name and 3 number list with version. It's used in request to the io-server-version RPC.
  217. @end itemize
  218. 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.
  219. 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).
  220. 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.
  221. Alright, let's forget about that and get to the good stuff!
  222. Now that you have some basic understanding how everything fits together, we should begin implementing our first translator method: @code{make-root-node}.
  223. 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.
  224. To define translator methods we use the define-callback macro, which has following syntax:
  225. @lisp
  226. (define-callback <method-name> <translator-type> <argument list> <body>)
  227. @end lisp
  228. Which is transformed (as you may be guessing) into this:
  229. @lisp
  230. (defmethod <method-name> ((translator <translator-type>) <argument list>)
  231. <body>)
  232. @end lisp
  233. 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.
  234. 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:
  235. @itemize @bullet
  236. @item
  237. owner
  238. File owner. Indicates the owner process ID and can be changed through the io-mod-owner RPC. Defaults to 0.
  239. @item
  240. stat
  241. This is a very important slot. It represents the node meta data and it's the same thing as the C @code{struct stat}.
  242. 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.
  243. Important note: every method applicable to a @code{mode} object can also be directly applied to a @code{stat} object.
  244. @item
  245. box
  246. Translator box, can contain the current passive or/and active translator set on this node.
  247. @item
  248. nusers
  249. 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.
  250. @item
  251. link
  252. Contains a string indicating the file this node symbolic links to. Only makes sense when the node mode type is a link.
  253. @end itemize
  254. Alright, here is the @code{make-root-node} method:
  255. @lisp
  256. (define-callback make-root-node zero-translator
  257. (underlying-node underlying-stat)
  258. (declare (ignore underlying-node))
  259. (let ((mode (make-mode :perms '((:owner :read :write)
  260. (:group :read :write)
  261. (:others :read :write))
  262. :type :chr)))
  263. (make-instance 'node
  264. :stat (make-stat underlying-stat
  265. :mode mode))))
  266. @end lisp
  267. 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.
  268. 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:
  269. @itemize @bullet
  270. @item
  271. :dir
  272. Directory.
  273. @item
  274. :chr
  275. Character device.
  276. @item
  277. :blk
  278. Block device.
  279. @item
  280. :reg
  281. Regular file.
  282. @item
  283. :lnk
  284. Symbolic link.
  285. @item
  286. sock
  287. Socket.
  288. @end itemize
  289. 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}.
  290. @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}.
  291. 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.
  292. What else needs to be implemented? :-) Ah, file writing!
  293. 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}.
  294. @lisp
  295. (define-callback write-file zero-translator
  296. (node user offset stream amount)
  297. (declare (ignore translator offset amount))
  298. (when (has-access-p node user :write)
  299. ; Empty the stream to look like we used it all.
  300. (loop while (read-byte stream nil))
  301. t))
  302. @end lisp
  303. Here, we can see that @code{write-file} gets lots of arguments, which I will explain:
  304. @itemize @bullet
  305. @item
  306. node
  307. It's the node the user wants to write in.
  308. @item
  309. user
  310. 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}.
  311. @item
  312. offset
  313. The place in the file the user wants to start writing to.
  314. @item
  315. stream
  316. An input byte stream containing the data to be wrote, you can used @code{read-byte to get the data}, or @code{read-sequence}.
  317. @item
  318. amount
  319. Amount of bytes present in this stream. Very useful if you want to put data into an array:
  320. @lisp
  321. (let ((my-array (make-array amount :element-type '(unsigned-byte 8))))
  322. (read-sequence my-array stream))
  323. @end lisp
  324. You can even convert it to string form if you need to:
  325. @lisp
  326. (octets-to-string my-array)
  327. @end lisp
  328. @end itemize
  329. 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.
  330. How about file reading? @code{read-file} is the method for reading nodes.
  331. @lisp
  332. (define-callback read-file zero-translator
  333. (node user start amount stream)
  334. (declare (ignore translator start))
  335. (when (has-access-p node user :read)
  336. (loop for i from 0 below amount
  337. do (write-byte 0 stream))
  338. t))
  339. @end lisp
  340. The arguments, explained:
  341. @itemize @bullet
  342. @item
  343. node
  344. The node where the user wants to read.
  345. @item
  346. user
  347. The user making the request.
  348. @item
  349. start
  350. The file position the user wants to start reading at.
  351. @item
  352. amount
  353. 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.
  354. @item
  355. stream
  356. Output stream were byte data should be written to.
  357. @end itemize
  358. 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.
  359. What's missing? Ah! Start-up code:
  360. @lisp
  361. (defun main ()
  362. (run-translator (make-instance 'zero-translator
  363. :name "zero-translator")))
  364. (main)
  365. @end lisp
  366. Here we just create a new zero-translator instance and then run it. Boring, isn't it?
  367. And that's it! Let's wrap everything with a new file: @file{run-zero.lisp}:
  368. @example
  369. #!/usr/bin/run-lisp-trans
  370. (asdf:operate 'asdf:load-op 'zero-translator)
  371. @end example
  372. Now we can run the translator:
  373. @example
  374. $ chmod +x run-zero.lisp
  375. $ settrans -ac zero ./run-zero.lisp
  376. @end example
  377. And then you verify that it's really a character device:
  378. @example
  379. $ ls -l zero
  380. crw-rw-rw- 2 root root 0, 0 Aug 14 15:09 zero
  381. @end example
  382. You can also do some tests. Run a CLISP instance and do this:
  383. @example
  384. * (asdf:operate 'asdf:load-op 'hurd)
  385. * (use-package :hurd)
  386. * (defvar *p* (file-name-lookup "zero" :flags '(:read :write)))
  387. * (io-read *p* :amount 5)
  388. #(0 0 0 0 0)
  389. * (io-write *p* #(1 2))
  390. 2
  391. * (use-package :mach)
  392. * (port-deallocate *p*)
  393. T
  394. @end example
  395. As expected.
  396. Now, remove the translator:
  397. @example
  398. $ settrans -g zero
  399. @end example
  400. @node A more complex example, Conclusion, A /dev/zero like translator, Top
  401. @chapter A more complex example
  402. Now we'll try to implement a more complex translator example.
  403. The translator we are going implement will provide directory listing and the traditional file creation, writing, etc.
  404. At first it should create a directory structure mirroring Lisp lists. Here's an example:
  405. @lisp
  406. (:dir "root"
  407. (:file "a" "abcdefghijklmnopqrstuvwxyz")
  408. (:file "b" "123456789012345678901234567890")
  409. (:file "c" "")
  410. (:file "d" "456")
  411. (:dir "dir1"
  412. (:file "k" "1a2b3c")
  413. (:file "a" "")
  414. (:file "b" "a")
  415. (:file "c" "c"))
  416. (:dir "dir2")
  417. (:link "f" "c")
  418. (:dir "dir3"
  419. (:file "a" "12345"))
  420. (:link "g" "g") ; circular link
  421. (:link "h" "/usr"))
  422. @end lisp
  423. 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.
  424. First, let's describe this new translator class (we'll name it @code{test-translator}):
  425. @lisp
  426. (defclass test-translator (tree-translator)
  427. ((file-stat :initarg :file-stat
  428. :initform nil
  429. :accessor file-stat)
  430. (dir-stat :initarg :dir-stat
  431. :initform nil
  432. :accessor dir-stat)))
  433. @end lisp
  434. 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}.
  435. It also extends the base node class with two new node types:
  436. @itemize @bullet
  437. @item
  438. entry
  439. Is a node with the information about the parent directory.
  440. @item
  441. dir-entry
  442. Is an @code{entry} and also contains the directory entries.
  443. @end itemize
  444. Please check the file @file{tree-translator/dir.lisp} for methods on directories.
  445. 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.
  446. 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.
  447. 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.
  448. For this case, this might be implemented like this:
  449. @lisp
  450. (define-callback fill-root-node test-translator
  451. ((node dir-entry))
  452. (setf (file-stat translator)
  453. (make-stat (stat node)
  454. :mode (make-mode :perms '((:owner :read)
  455. (:group :read)))
  456. :type :reg)
  457. (dir-stat translator)
  458. (make-stat (stat node)
  459. :mode (make-mode :perms '((:owner :read :exec)
  460. (:group :read :exec)))
  461. :type :dir))
  462. (%fill-node translator (with-open-file (s +file+)
  463. (read stream))
  464. node))
  465. @end lisp
  466. 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.
  467. @lisp
  468. (defun %fill-node (translator ls node)
  469. (let ((type (first ls))
  470. (name (second ls))
  471. (args (rest (rest ls))))
  472. (case type
  473. (:dir
  474. (let ((dir (make-instance 'dir-entry
  475. :stat (make-stat (dir-stat translator))
  476. :parent node)))
  477. (add-entry node dir name)
  478. (loop for item in args
  479. do (%fill-node translator item dir))))
  480. (:file
  481. (let* ((data (first args))
  482. (file (make-instance 'test-entry
  483. :stat (make-stat
  484. (file-stat translator)
  485. :size (length data))
  486. :parent node
  487. :data (%read-file-data data))))
  488. (add-entry node file name)))
  489. (:link
  490. (let ((target (first args))
  491. (new (make-instance 'entry
  492. :stat (make-stat
  493. (file-stat translator)
  494. :type :lnk)
  495. :parent node)))
  496. (setf (link new) target)
  497. (add-entry node new name))))))
  498. @end lisp
  499. 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.
  500. You should always give the :parent argument when you are creating new @code{entry}'s.
  501. 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}.
  502. You may also be wondering what's that @code{test-entry} class, well, it's just a new kind of @code{node}.
  503. @lisp
  504. (defclass test-entry (entry)
  505. ((contents :initarg :data
  506. :initform (%create-data-array 0 nil)
  507. :accessor data)))
  508. @end lisp
  509. 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:
  510. @lisp
  511. (defun %create-data-array (size contents)
  512. (make-array size
  513. :initial-contents contents
  514. :adjustable t
  515. :fill-pointer t
  516. :element-type '(unsigned-byte 8)))
  517. @end lisp
  518. 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.
  519. As for that @code{%read-file-data} function, here it is:
  520. @lisp
  521. (defun %read-file-data (str)
  522. (%create-data-array (length str)
  523. (loop for char across str
  524. collect (char-code char))))
  525. @end lisp
  526. It takes the data string and converts it to an @code{unsigned-byte} array.
  527. 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.
  528. @lisp
  529. (define-callback read-file test-translator
  530. (node user start amount stream)
  531. (when (has-access-p node user :read)
  532. (let* ((size (stat-get (stat node) 'st-size))
  533. (size-res (- size start)))
  534. (unless (plusp size-res)
  535. (return-from read-file t))
  536. (let* ((total (min size-res amount))
  537. (end (+ start total)))
  538. (write-sequence (subseq (data node) start end)
  539. stream)
  540. t))))
  541. @end lisp
  542. 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.
  543. 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.
  544. @lisp
  545. (define-callback file-change-size test-translator
  546. (node user new-size)
  547. (when (is-dir-p (stat node))
  548. (return-from file-change-size :is-a-directory))
  549. (when (has-access-p node user :write)
  550. (adjust-array (data node) new-size :fill-pointer t)
  551. (setf (stat-get (stat node) 'st-size) new-size)
  552. t))
  553. @end lisp
  554. 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.
  555. And now for file writing!
  556. @lisp
  557. (defun %read-sequence (stream amount)
  558. (let ((arr (make-array amount
  559. :element-type '(unsigned-byte 8))))
  560. (read-sequence arr stream)
  561. arr))
  562. (define-callback write-file test-translator
  563. (node user offset stream amount)
  564. (unless (has-access-p node user :write)
  565. (return-from write-file nil))
  566. (when (is-dir-p (stat node))
  567. (return-from write-file :is-a-directory))
  568. (let* ((size (stat-get (stat node) 'st-size))
  569. (arr (%read-sequence stream amount))
  570. (final-size (max (+ amount offset) size)))
  571. (unless (= final-size size)
  572. (adjust-array (data node)
  573. final-size
  574. :fill-pointer t))
  575. (replace (data node) arr :start1 offset)
  576. ; Update stat size.
  577. (setf (stat-get (stat node) 'st-size) final-size)
  578. t))
  579. @end lisp
  580. 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.
  581. 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.
  582. Now, you'll probably want to support file creation, for that, you must implement the @code{create-file} method.
  583. @lisp
  584. (define-callback create-file test-translator
  585. (node user filename mode)
  586. (unless (has-access-p node user :write)
  587. (return-from create-file nil))
  588. (let ((entry (make-instance 'test-entry
  589. :stat (make-stat (stat node)
  590. :mode mode
  591. :size 0)
  592. :parent node)))
  593. (add-entry node entry filename)
  594. entry))
  595. @end lisp
  596. The create-file method has 4 arguments:
  597. @itemize @bullet
  598. @item
  599. node
  600. The directory where the user wants to create the file.
  601. @item
  602. user
  603. The user creating the file.
  604. @item
  605. filename
  606. The new file name.
  607. @item
  608. mode
  609. Permission bits for that file.
  610. @end itemize
  611. 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.
  612. 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.
  613. And symbolic link creation? Three methods must be implemented for it to work:
  614. @itemize @bullet
  615. @item
  616. @code{allow-link-p}
  617. Allows link reading, implemented by default by the @code{translator} class.
  618. @item
  619. @code{create-symlink}
  620. Turns a node into a symlink, implemented by default by the @code{translator} class.
  621. @item
  622. @code{create-anonymous-file}
  623. 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.
  624. @end itemize
  625. Here's a simple implementation for @code{create-anonymous-file}:
  626. @lisp
  627. (define-callback create-anonymous-file test-translator
  628. (node user mode)
  629. (when (can-modify-dir-p node user)
  630. (make-instance 'test-entry
  631. :stat (make-stat (stat node)
  632. :mode mode)
  633. :parent node)))
  634. @end lisp
  635. Here @code{node} represents the parent directory and @code{mode} the permission bits.
  636. 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.
  637. @lisp
  638. (unless (= (length ext:*args*) 1)
  639. (error "You must provide one argument with a spec file."))
  640. (defconstant +file+ (first ext:*args*))
  641. @end lisp
  642. Then, the usual main function:
  643. @lisp
  644. (defun main ()
  645. (let ((translator
  646. (make-instance 'test-translator
  647. :name "test-translator"
  648. :version (list 1 2 3))))
  649. (run-translator translator)))
  650. (main)
  651. @end lisp
  652. Please see @file{examples/test.lisp}, @file{examples/test-translator.asd} and @file{examples/run-test.lisp} for the complete example.
  653. To see it running:
  654. @example
  655. $ settrans -ac foo ./run-test.lisp data/test.lisp
  656. $ cd foo
  657. $ ls
  658. root
  659. $ cd root
  660. $ ls -l
  661. total 40
  662. -r--r----- 1 root root 26 Aug 14 19:13 a
  663. -r--r----- 1 root root 30 Aug 14 19:13 b
  664. -r--r----- 1 root root 0 Aug 14 19:13 c
  665. -r--r----- 1 root root 3 Aug 14 19:13 d
  666. dr-xr-x--- 6 root root 0 Aug 14 19:13 dir1
  667. dr-xr-x--- 2 root root 0 Aug 14 19:13 dir2
  668. dr-xr-x--- 3 root root 0 Aug 14 19:13 dir3
  669. lr--r----- 1 root root 1 Aug 14 19:13 f -> c
  670. lr--r----- 1 root root 1 Aug 14 19:13 g -> g
  671. lr--r----- 1 root root 4 Aug 14 19:13 h -> /usr
  672. @end example
  673. @node Conclusion,, A more complex example, Top
  674. @chapter Conclusion
  675. 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.
  676. Happy hacking!
  677. @bye