Preliminary regeneration support
[hcoop/domtool2.git] / src / main.sml
CommitLineData
234b917a
AC
1(* HCoop Domtool (http://hcoop.sourceforge.net/)
2 * Copyright (c) 2006, Adam Chlipala
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
dac62e84 17 *)
234b917a
AC
18
19(* Main interface *)
20
21structure Main :> MAIN = struct
22
36e42cb8 23open Ast MsgTypes Print
234b917a 24
6ae327f8
AC
25structure SM = StringMap
26
aa56e112 27fun init () = Acl.read Config.aclFile
234b917a 28
d189ec0e 29fun check' G fname =
a3698041
AC
30 let
31 val prog = Parse.parse fname
32 in
33 if !ErrorMsg.anyErrors then
d189ec0e 34 G
a3698041 35 else
aa56e112 36 Tycheck.checkFile G (Defaults.tInit ()) prog
a3698041
AC
37 end
38
d189ec0e 39fun basis () =
234b917a 40 let
d189ec0e
AC
41 val dir = Posix.FileSys.opendir Config.libRoot
42
43 fun loop files =
44 case Posix.FileSys.readdir dir of
d612d62c
AC
45 NONE => (Posix.FileSys.closedir dir;
46 files)
d189ec0e
AC
47 | SOME fname =>
48 if String.isSuffix ".dtl" fname then
d612d62c
AC
49 loop (OS.Path.joinDirFile {dir = Config.libRoot,
50 file = fname}
d189ec0e
AC
51 :: files)
52 else
53 loop files
54
55 val files = loop []
c53e82e4 56 val (_, files) = Order.order NONE files
d189ec0e 57 in
6ae327f8
AC
58 if !ErrorMsg.anyErrors then
59 Env.empty
60 else
b3159a70
AC
61 (Tycheck.allowExterns ();
62 foldl (fn (fname, G) => check' G fname) Env.empty files
63 before Tycheck.disallowExterns ())
d189ec0e
AC
64 end
65
66fun check fname =
67 let
68 val _ = ErrorMsg.reset ()
12adf55a 69 val _ = Env.preTycheck ()
d189ec0e
AC
70
71 val b = basis ()
234b917a
AC
72 in
73 if !ErrorMsg.anyErrors then
36e42cb8 74 raise ErrorMsg.Error
234b917a
AC
75 else
76 let
b3159a70 77 val _ = Tycheck.disallowExterns ()
7f012ffd 78 val _ = ErrorMsg.reset ()
d189ec0e 79 val prog = Parse.parse fname
234b917a 80 in
492c1cff 81 if !ErrorMsg.anyErrors then
36e42cb8 82 raise ErrorMsg.Error
492c1cff 83 else
d189ec0e 84 let
aa56e112 85 val G' = Tycheck.checkFile b (Defaults.tInit ()) prog
d189ec0e 86 in
36e42cb8
AC
87 if !ErrorMsg.anyErrors then
88 raise ErrorMsg.Error
89 else
90 (G', #3 prog)
d189ec0e 91 end
234b917a
AC
92 end
93 end
94
c53e82e4
AC
95val notTmp = CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"." orelse ch = #"_" orelse ch = #"-")
96
97fun checkDir dname =
98 let
99 val b = basis ()
100
101 val dir = Posix.FileSys.opendir dname
102
103 fun loop files =
104 case Posix.FileSys.readdir dir of
105 NONE => (Posix.FileSys.closedir dir;
106 files)
107 | SOME fname =>
108 if notTmp fname then
109 loop (OS.Path.joinDirFile {dir = dname,
110 file = fname}
111 :: files)
112 else
113 loop files
114
115 val files = loop []
116 val (_, files) = Order.order (SOME b) files
117 in
118 if !ErrorMsg.anyErrors then
1824f573 119 raise ErrorMsg.Error
c53e82e4
AC
120 else
121 (foldl (fn (fname, G) => check' G fname) b files;
1824f573
AC
122 if !ErrorMsg.anyErrors then
123 raise ErrorMsg.Error
124 else
125 ())
c53e82e4
AC
126 end
127
d189ec0e 128fun reduce fname =
a3698041 129 let
d189ec0e 130 val (G, body) = check fname
a3698041
AC
131 in
132 if !ErrorMsg.anyErrors then
d189ec0e 133 NONE
a3698041 134 else
d189ec0e
AC
135 case body of
136 SOME body =>
137 let
138 val body' = Reduce.reduceExp G body
139 in
140 (*printd (PD.hovBox (PD.PPS.Rel 0,
141 [PD.string "Result:",
142 PD.space 1,
143 p_exp body']))*)
144 SOME body'
145 end
146 | _ => NONE
a3698041
AC
147 end
148
d189ec0e
AC
149fun eval fname =
150 case reduce fname of
151 (SOME body') =>
152 if !ErrorMsg.anyErrors then
36e42cb8 153 raise ErrorMsg.Error
d189ec0e 154 else
aa56e112 155 Eval.exec (Defaults.eInit ()) body'
36e42cb8 156 | NONE => raise ErrorMsg.Error
d189ec0e 157
1824f573
AC
158fun eval' fname =
159 case reduce fname of
160 (SOME body') =>
161 if !ErrorMsg.anyErrors then
162 raise ErrorMsg.Error
163 else
164 ignore (Eval.exec' (Defaults.eInit ()) body')
165 | NONE => raise ErrorMsg.Error
166
3b267643
AC
167val dispatcher =
168 Config.dispatcher ^ ":" ^ Int.toString Config.dispatcherPort
559e89e9 169
5ee41dd0 170fun requestContext f =
07cc384c 171 let
a56cc2c3
AC
172 val uid = Posix.ProcEnv.getuid ()
173 val user = Posix.SysDB.Passwd.name (Posix.SysDB.getpwuid uid)
5ee41dd0 174
a56cc2c3
AC
175 val () = Acl.read Config.aclFile
176 val () = Domain.setUser user
5ee41dd0
AC
177
178 val () = f ()
aa56e112 179
aa56e112 180 val context = OpenSSL.context (Config.certDir ^ "/" ^ user ^ ".pem",
a088cea6 181 Config.keyDir ^ "/" ^ user ^ "/key.pem",
3b267643 182 Config.trustStore)
5ee41dd0
AC
183 in
184 (user, context)
185 end
07cc384c 186
5ee41dd0
AC
187fun requestBio f =
188 let
189 val (user, context) = requestContext f
190 in
191 (user, OpenSSL.connect (context, dispatcher))
192 end
193
194fun request fname =
195 let
196 val (user, bio) = requestBio (fn () => ignore (check fname))
559e89e9 197
3b267643
AC
198 val inf = TextIO.openIn fname
199
36e42cb8 200 fun loop lines =
3b267643 201 case TextIO.inputLine inf of
36e42cb8
AC
202 NONE => String.concat (List.rev lines)
203 | SOME line => loop (line :: lines)
204
205 val code = loop []
559e89e9 206 in
3b267643 207 TextIO.closeIn inf;
36e42cb8
AC
208 Msg.send (bio, MsgConfig code);
209 case Msg.recv bio of
210 NONE => print "Server closed connection unexpectedly.\n"
211 | SOME m =>
212 case m of
213 MsgOk => print "Configuration succeeded.\n"
214 | MsgError s => print ("Configuration failed: " ^ s ^ "\n")
215 | _ => print "Unexpected server reply.\n";
3b267643 216 OpenSSL.close bio
559e89e9 217 end
aa56e112 218 handle ErrorMsg.Error => ()
559e89e9 219
c53e82e4
AC
220fun requestDir dname =
221 let
1824f573
AC
222 val _ = ErrorMsg.reset ()
223
224 val (user, bio) = requestBio (fn () => checkDir dname)
c53e82e4
AC
225
226 val b = basis ()
227
228 val dir = Posix.FileSys.opendir dname
229
230 fun loop files =
231 case Posix.FileSys.readdir dir of
232 NONE => (Posix.FileSys.closedir dir;
233 files)
234 | SOME fname =>
235 if notTmp fname then
236 loop (OS.Path.joinDirFile {dir = dname,
237 file = fname}
238 :: files)
239 else
240 loop files
241
242 val files = loop []
243 val (_, files) = Order.order (SOME b) files
244
245 val _ = if !ErrorMsg.anyErrors then
246 raise ErrorMsg.Error
247 else
248 ()
249
250 val codes = map (fn fname =>
251 let
252 val inf = TextIO.openIn fname
253
254 fun loop lines =
255 case TextIO.inputLine inf of
256 NONE => String.concat (rev lines)
257 | SOME line => loop (line :: lines)
258 in
259 loop []
260 before TextIO.closeIn inf
261 end) files
262 in
1824f573
AC
263 if !ErrorMsg.anyErrors then
264 ()
265 else
266 (Msg.send (bio, MsgMultiConfig codes);
267 case Msg.recv bio of
268 NONE => print "Server closed connection unexpectedly.\n"
269 | SOME m =>
270 case m of
271 MsgOk => print "Configuration succeeded.\n"
272 | MsgError s => print ("Configuration failed: " ^ s ^ "\n")
273 | _ => print "Unexpected server reply.\n";
274 OpenSSL.close bio)
c53e82e4
AC
275 end
276 handle ErrorMsg.Error => ()
277
5ee41dd0
AC
278fun requestGrant acl =
279 let
280 val (user, bio) = requestBio (fn () => ())
281 in
282 Msg.send (bio, MsgGrant acl);
283 case Msg.recv bio of
284 NONE => print "Server closed connection unexpectedly.\n"
285 | SOME m =>
286 case m of
287 MsgOk => print "Grant succeeded.\n"
288 | MsgError s => print ("Grant failed: " ^ s ^ "\n")
289 | _ => print "Unexpected server reply.\n";
290 OpenSSL.close bio
291 end
292
411a85f2
AC
293fun requestRevoke acl =
294 let
295 val (user, bio) = requestBio (fn () => ())
296 in
297 Msg.send (bio, MsgRevoke acl);
298 case Msg.recv bio of
299 NONE => print "Server closed connection unexpectedly.\n"
300 | SOME m =>
301 case m of
302 MsgOk => print "Revoke succeeded.\n"
303 | MsgError s => print ("Revoke failed: " ^ s ^ "\n")
304 | _ => print "Unexpected server reply.\n";
305 OpenSSL.close bio
306 end
307
08a04eb4
AC
308fun requestListPerms user =
309 let
310 val (_, bio) = requestBio (fn () => ())
311 in
312 Msg.send (bio, MsgListPerms user);
313 (case Msg.recv bio of
314 NONE => (print "Server closed connection unexpectedly.\n";
315 NONE)
316 | SOME m =>
317 case m of
318 MsgPerms perms => SOME perms
319 | MsgError s => (print ("Listing failed: " ^ s ^ "\n");
320 NONE)
321 | _ => (print "Unexpected server reply.\n";
322 NONE))
323 before OpenSSL.close bio
324 end
325
094877b1
AC
326fun requestWhoHas perm =
327 let
328 val (_, bio) = requestBio (fn () => ())
329 in
330 Msg.send (bio, MsgWhoHas perm);
331 (case Msg.recv bio of
332 NONE => (print "Server closed connection unexpectedly.\n";
333 NONE)
334 | SOME m =>
335 case m of
336 MsgWhoHasResponse users => SOME users
337 | MsgError s => (print ("whohas failed: " ^ s ^ "\n");
338 NONE)
339 | _ => (print "Unexpected server reply.\n";
340 NONE))
341 before OpenSSL.close bio
342 end
343
1824f573
AC
344fun requestRegen () =
345 let
346 val (_, bio) = requestBio (fn () => ())
347 in
348 Msg.send (bio, MsgRegenerate);
349 case Msg.recv bio of
350 NONE => print "Server closed connection unexpectedly.\n"
351 | SOME m =>
352 case m of
353 MsgOk => print "Regeneration succeeded.\n"
354 | MsgError s => print ("Regeneration failed: " ^ s ^ "\n")
355 | _ => print "Unexpected server reply.\n";
356 OpenSSL.close bio
357 end
358
c189cbe9
AC
359fun requestRmdom dom =
360 let
361 val (_, bio) = requestBio (fn () => ())
362 in
363 Msg.send (bio, MsgRmdom dom);
364 case Msg.recv bio of
365 NONE => print "Server closed connection unexpectedly.\n"
366 | SOME m =>
367 case m of
368 MsgOk => print "Removal succeeded.\n"
369 | MsgError s => print ("Removal failed: " ^ s ^ "\n")
370 | _ => print "Unexpected server reply.\n";
371 OpenSSL.close bio
372 end
373
1824f573
AC
374fun regenerate () =
375 let
376 val b = basis ()
377 val _ = Tycheck.disallowExterns ()
378
379 fun doUser user =
380 let
381 val _ = Domain.setUser user
382 val _ = ErrorMsg.reset ()
383
384 val dname = Config.domtoolDir user
385
386 val dir = Posix.FileSys.opendir dname
387
388 fun loop files =
389 case Posix.FileSys.readdir dir of
390 NONE => (Posix.FileSys.closedir dir;
391 files)
392 | SOME fname =>
393 if notTmp fname then
394 loop (OS.Path.joinDirFile {dir = dname,
395 file = fname}
396 :: files)
397 else
398 loop files
399
400 val files = loop []
401 val (_, files) = Order.order (SOME b) files
402 in
403 if !ErrorMsg.anyErrors then
404 print ("User " ^ user ^ "'s configuration has errors!\n")
405 else
406 app eval' files
407 end
408 handle IO.Io _ => ()
409 | OS.SysErr (s, _) => print ("System error processing user " ^ user ^ ": " ^ s ^ "\n")
410 in
411 Env.pre ();
412 app doUser (Acl.users ());
413 Env.post ()
414 end
415
3b267643 416fun service () =
07cc384c 417 let
aa56e112
AC
418 val () = Acl.read Config.aclFile
419
3b267643
AC
420 val context = OpenSSL.context (Config.serverCert,
421 Config.serverKey,
422 Config.trustStore)
36e42cb8 423 val _ = Domain.set_context context
3b267643 424
60534712 425 val sock = OpenSSL.listen (context, Config.dispatcherPort)
3b267643
AC
426
427 fun loop () =
60534712 428 case OpenSSL.accept sock of
3b267643
AC
429 NONE => ()
430 | SOME bio =>
431 let
aa56e112
AC
432 val user = OpenSSL.peerCN bio
433 val () = print ("\nConnection from " ^ user ^ "\n")
434 val () = Domain.setUser user
435
c53e82e4
AC
436 fun doConfig codes =
437 let
438 val _ = print "Configuration:\n"
439 val _ = app (fn s => (print s; print "\n")) codes
440 val _ = print "\n"
441
442 val outname = OS.FileSys.tmpName ()
443
444 fun doOne code =
445 let
446 val outf = TextIO.openOut outname
447 in
448 TextIO.output (outf, code);
449 TextIO.closeOut outf;
1824f573 450 eval' outname
c53e82e4
AC
451 end
452 in
1824f573
AC
453 (Env.pre ();
454 app doOne codes;
455 Env.post ();
c53e82e4
AC
456 Msg.send (bio, MsgOk))
457 handle ErrorMsg.Error =>
458 (print "Compilation error\n";
459 Msg.send (bio,
460 MsgError "Error during configuration evaluation"))
461 | OpenSSL.OpenSSL s =>
462 (print "OpenSSL error\n";
463 Msg.send (bio,
464 MsgError
465 ("Error during configuration evaluation: "
466 ^ s)));
467 OS.FileSys.remove outname;
468 (ignore (OpenSSL.readChar bio);
469 OpenSSL.close bio)
470 handle OpenSSL.OpenSSL _ => ();
471 loop ()
472 end
473
36e42cb8
AC
474 fun cmdLoop () =
475 case Msg.recv bio of
476 NONE => (OpenSSL.close bio
477 handle OpenSSL.OpenSSL _ => ();
478 loop ())
479 | SOME m =>
480 case m of
c53e82e4
AC
481 MsgConfig code => doConfig [code]
482 | MsgMultiConfig codes => doConfig codes
5ee41dd0
AC
483
484 | MsgGrant acl =>
be1bea4c 485 if Acl.query {user = user, class = "priv", value = "all"} then
5ee41dd0
AC
486 ((Acl.grant acl;
487 Acl.write Config.aclFile;
411a85f2
AC
488 Msg.send (bio, MsgOk);
489 print ("Granted permission " ^ #value acl ^ " to " ^ #user acl ^ " in " ^ #class acl ^ ".\n"))
5ee41dd0
AC
490 handle OpenSSL.OpenSSL s =>
491 (print "OpenSSL error\n";
492 Msg.send (bio,
493 MsgError
494 ("Error during granting: "
495 ^ s)));
496 (ignore (OpenSSL.readChar bio);
497 OpenSSL.close bio)
498 handle OpenSSL.OpenSSL _ => ();
499 loop ())
500 else
501 ((Msg.send (bio, MsgError "Not authorized to grant privileges");
411a85f2
AC
502 print "Unauthorized user asked to grant a permission!\n";
503 ignore (OpenSSL.readChar bio);
504 OpenSSL.close bio)
505 handle OpenSSL.OpenSSL _ => ();
506 loop ())
507
508 | MsgRevoke acl =>
be1bea4c 509 if Acl.query {user = user, class = "priv", value = "all"} then
411a85f2
AC
510 ((Acl.revoke acl;
511 Acl.write Config.aclFile;
512 Msg.send (bio, MsgOk);
513 print ("Revoked permission " ^ #value acl ^ " from " ^ #user acl ^ " in " ^ #class acl ^ ".\n"))
514 handle OpenSSL.OpenSSL s =>
515 (print "OpenSSL error\n";
516 Msg.send (bio,
517 MsgError
518 ("Error during revocation: "
519 ^ s)));
520 (ignore (OpenSSL.readChar bio);
521 OpenSSL.close bio)
522 handle OpenSSL.OpenSSL _ => ();
523 loop ())
524 else
525 ((Msg.send (bio, MsgError "Not authorized to revoke privileges");
526 print "Unauthorized user asked to revoke a permission!\n";
5ee41dd0
AC
527 ignore (OpenSSL.readChar bio);
528 OpenSSL.close bio)
529 handle OpenSSL.OpenSSL _ => ();
530 loop ())
531
08a04eb4
AC
532 | MsgListPerms user =>
533 ((Msg.send (bio, MsgPerms (Acl.queryAll user));
534 print ("Sent permission list for user " ^ user ^ ".\n"))
535 handle OpenSSL.OpenSSL s =>
536 (print "OpenSSL error\n";
537 Msg.send (bio,
538 MsgError
539 ("Error during permission listing: "
540 ^ s)));
541 (ignore (OpenSSL.readChar bio);
542 OpenSSL.close bio)
543 handle OpenSSL.OpenSSL _ => ();
544 loop ())
545
094877b1
AC
546 | MsgWhoHas perm =>
547 ((Msg.send (bio, MsgWhoHasResponse (Acl.whoHas perm));
548 print ("Sent whohas response for " ^ #class perm ^ " / " ^ #value perm ^ ".\n"))
549 handle OpenSSL.OpenSSL s =>
550 (print "OpenSSL error\n";
551 Msg.send (bio,
552 MsgError
553 ("Error during whohas: "
554 ^ s)));
555 (ignore (OpenSSL.readChar bio);
556 OpenSSL.close bio)
557 handle OpenSSL.OpenSSL _ => ();
558 loop ())
559
c189cbe9
AC
560 | MsgRmdom dom =>
561 if Acl.query {user = user, class = "priv", value = "all"}
562 orelse Acl.query {user = user, class = "domain", value = dom} then
563 ((Domain.rmdom dom;
564 Msg.send (bio, MsgOk);
565 print ("Removed domain " ^ dom ^ ".\n"))
566 handle OpenSSL.OpenSSL s =>
567 (print "OpenSSL error\n";
568 Msg.send (bio,
569 MsgError
570 ("Error during revocation: "
571 ^ s)));
572 (ignore (OpenSSL.readChar bio);
573 OpenSSL.close bio)
574 handle OpenSSL.OpenSSL _ => ();
575 loop ())
576 else
577 ((Msg.send (bio, MsgError "Not authorized to remove that domain");
578 print "Unauthorized user asked to remove a domain!\n";
579 ignore (OpenSSL.readChar bio);
580 OpenSSL.close bio)
581 handle OpenSSL.OpenSSL _ => ();
1824f573
AC
582 loop ())
583
584 | MsgRegenerate =>
585 if Acl.query {user = user, class = "priv", value = "regen"}
586 orelse Acl.query {user = user, class = "priv", value = "all"} then
587 ((regenerate ();
588 Msg.send (bio, MsgOk);
589 print "Regenerated all configuration.\n")
590 handle OpenSSL.OpenSSL s =>
591 (print "OpenSSL error\n";
592 Msg.send (bio,
593 MsgError
594 ("Error during regeneration: "
595 ^ s)));
596 (ignore (OpenSSL.readChar bio);
597 OpenSSL.close bio)
598 handle OpenSSL.OpenSSL _ => ();
599 loop ())
600 else
601 ((Msg.send (bio, MsgError "Not authorized to regeneration");
602 print "Unauthorized user asked to regenerate!\n";
603 ignore (OpenSSL.readChar bio);
604 OpenSSL.close bio)
605 handle OpenSSL.OpenSSL _ => ();
c189cbe9
AC
606 loop ())
607
36e42cb8
AC
608 | _ =>
609 (Msg.send (bio, MsgError "Unexpected command")
610 handle OpenSSL.OpenSSL _ => ();
611 OpenSSL.close bio
612 handle OpenSSL.OpenSSL _ => ();
613 loop ())
614 in
615 cmdLoop ()
616 end
97665758
AC
617 handle OpenSSL.OpenSSL s =>
618 (print ("OpenSSL error: " ^ s ^ "\n");
619 OpenSSL.close bio
620 handle OpenSSL.OpenSSL _ => ();
621 loop ())
622 | OS.SysErr (s, _) =>
623 (print ("System error: " ^ s ^ "\n");
624 OpenSSL.close bio
625 handle OpenSSL.OpenSSL _ => ();
626 loop ())
36e42cb8 627 in
361a1e7f 628 print "Listening for connections....\n";
36e42cb8
AC
629 loop ();
630 OpenSSL.shutdown sock
631 end
632
633fun slave () =
634 let
6e62228d 635 val host = Slave.hostname ()
36e42cb8
AC
636
637 val context = OpenSSL.context (Config.certDir ^ "/" ^ host ^ ".pem",
a088cea6 638 Config.keyDir ^ "/" ^ host ^ "/key.pem",
36e42cb8
AC
639 Config.trustStore)
640
641 val sock = OpenSSL.listen (context, Config.slavePort)
642
643 fun loop () =
644 case OpenSSL.accept sock of
645 NONE => ()
646 | SOME bio =>
647 let
648 val peer = OpenSSL.peerCN bio
649 val () = print ("\nConnection from " ^ peer ^ "\n")
3b267643 650 in
36e42cb8
AC
651 if peer <> Config.dispatcherName then
652 (print "Not authorized!\n";
653 OpenSSL.close bio;
654 loop ())
655 else let
656 fun loop' files =
657 case Msg.recv bio of
658 NONE => print "Dispatcher closed connection unexpectedly\n"
659 | SOME m =>
660 case m of
661 MsgFile file => loop' (file :: files)
662 | MsgDoFiles => (Slave.handleChanges files;
663 Msg.send (bio, MsgOk))
664 | _ => (print "Dispatcher sent unexpected command\n";
665 Msg.send (bio, MsgError "Unexpected command"))
666 in
667 loop' [];
668 ignore (OpenSSL.readChar bio);
669 OpenSSL.close bio;
670 loop ()
671 end
3196000d
AC
672 end handle OpenSSL.OpenSSL s =>
673 (print ("OpenSSL error: "^ s ^ "\n");
674 OpenSSL.close bio
675 handle OpenSSL.OpenSSL _ => ();
676 loop ())
7af7d4cb
AC
677 | OS.SysErr (s, _) =>
678 (print ("System error: "^ s ^ "\n");
679 OpenSSL.close bio
680 handle OpenSSL.OpenSSL _ => ();
681 loop ())
07cc384c 682 in
3b267643
AC
683 loop ();
684 OpenSSL.shutdown sock
07cc384c
AC
685 end
686
3196000d
AC
687fun autodocBasis outdir =
688 let
689 val dir = Posix.FileSys.opendir Config.libRoot
690
691 fun loop files =
692 case Posix.FileSys.readdir dir of
693 NONE => (Posix.FileSys.closedir dir;
694 files)
695 | SOME fname =>
696 if String.isSuffix ".dtl" fname then
697 loop (OS.Path.joinDirFile {dir = Config.libRoot,
698 file = fname}
699 :: files)
700 else
701 loop files
702
703 val files = loop []
704 in
705 Autodoc.autodoc {outdir = outdir, infiles = files}
706 end
707
234b917a 708end