1 (* HCoop
Domtool (http
://hcoop
.sourceforge
.net
/)
2 * Copyright (c
) 2006, Adam Chlipala
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
.
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
.
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
.
19 (* Domain
-related primitive actions
*)
21 structure Domain
:> DOMAIN
= struct
24 case map
Int.fromString (String.fields (fn ch
=> ch
= #
".") s
) of
25 [SOME n1
, SOME n2
, SOME n3
, SOME n4
] =>
26 n1
>= 0 andalso n1
< 256 andalso n2
>= 0 andalso n2
< 256 andalso n3
>= 0 andalso n3
< 256 andalso n4
>= 0 andalso n4
< 256
29 fun isIdent ch
= Char.isLower ch
orelse Char.isDigit ch
32 size s
> 0 andalso size s
< 20
33 andalso CharVector
.all (fn ch
=> isIdent ch
orelse ch
= #
"-") s
36 size s
> 0 andalso size s
< 100
37 andalso List.all
validHost (String.fields (fn ch
=> ch
= #
".") s
)
39 fun validNode s
= List.exists (fn s
' => s
= s
') Config
.nodes
41 val _
= Env
.type_one
"ip"
45 val _
= Env
.type_one
"host"
49 val _
= Env
.type_one
"domain"
53 val _
= Env
.type_one
"node"
59 val dl
= ErrorMsg
.dummyLoc
61 val nsD
= (EString Config
.defaultNs
, dl
)
62 val serialD
= (EVar
"serialAuto", dl
)
63 val refD
= (EInt Config
.defaultRefresh
, dl
)
64 val retD
= (EInt Config
.defaultRetry
, dl
)
65 val expD
= (EInt Config
.defaultExpiry
, dl
)
66 val minD
= (EInt Config
.defaultMinimum
, dl
)
68 val soaD
= multiApp ((EVar
"soa", dl
),
70 [nsD
, serialD
, refD
, retD
, expD
, minD
])
72 val masterD
= (EApp ((EVar
"internalMaster", dl
),
73 (EString Config
.defaultNode
, dl
)),
76 val _
= Main
.registerDefault ("DNS",
77 (TBase
"dnsKind", dl
),
78 (multiApp ((EVar
"useDns", dl
),
80 [soaD
, masterD
, (EList
[], dl
)])))
82 val _
= Main
.registerDefault ("TTL",
84 (EInt Config
.Bind
.defaultTTL
, dl
))
86 type soa
= {ns
: string,
93 val serial
= fn (EVar
"serialAuto", _
) => SOME NONE
94 |
(EApp ((EVar
"serialConst", _
), n
), _
) => Option
.map
SOME (Env
.int n
)
97 val soa
= fn (EApp ((EApp ((EApp ((EApp ((EApp ((EApp
98 ((EVar
"soa", _
), ns
), _
),
104 (case (Env
.string ns
, serial sl
, Env
.int rf
,
105 Env
.int ret
, Env
.int exp
, Env
.int min
) of
106 (SOME ns
, SOME sl
, SOME rf
,
107 SOME ret
, SOME exp
, SOME min
) =>
118 ExternalMaster
of string
119 | InternalMaster
of string
121 val master
= fn (EApp ((EVar
"externalMaster", _
), e
), _
) => Option
.map
ExternalMaster (Env
.string e
)
122 |
(EApp ((EVar
"internalMaster", _
), e
), _
) => Option
.map
InternalMaster (Env
.string e
)
126 UseDns
of {soa
: soa
,
128 slaves
: string list
}
131 val dnsKind
= fn (EApp ((EApp ((EApp
132 ((EVar
"useDns", _
), sa
), _
),
135 (case (soa sa
, master mstr
, Env
.list Env
.string slaves
) of
136 (SOME sa
, SOME mstr
, SOME slaves
) =>
137 SOME (UseDns
{soa
= sa
,
143 val befores
= ref (fn (_
: string) => ())
144 val afters
= ref (fn (_
: string) => ())
146 fun registerBefore f
=
150 befores
:= (fn x
=> (old x
; f x
))
153 fun registerAfter f
=
157 afters
:= (fn x
=> (old x
; f x
))
161 val currentPath
= ref (fn (_
: string) => "")
165 fun currentDomain () = !current
167 fun domainFile
{node
, name
} = ((*print ("Opening " ^
!currentPath node ^ name ^
"\n");*)
168 TextIO.openOut (!currentPath node ^ name
))
172 val toks
= String.fields (fn ch
=> ch
= #
".") domain
174 val elems
= foldr (fn (piece
, elems
) =>
176 val elems
= piece
:: elems
180 val path
= String.concatWith
"/"
181 (Config
.resultRoot
:: node
:: rev elems
)
182 val tmpPath
= String.concatWith
"/"
183 (Config
.tmpDir
:: node
:: rev elems
)
185 (if Posix
.FileSys
.ST
.isDir
186 (Posix
.FileSys
.stat path
) then
189 (OS
.FileSys
.remove path
;
190 OS
.FileSys
.mkDir path
))
191 handle OS
.SysErr _
=> OS
.FileSys
.mkDir path
;
193 (if Posix
.FileSys
.ST
.isDir
194 (Posix
.FileSys
.stat tmpPath
) then
197 (OS
.FileSys
.remove tmpPath
;
198 OS
.FileSys
.mkDir tmpPath
))
199 handle OS
.SysErr _
=> OS
.FileSys
.mkDir tmpPath
202 app doNode Config
.nodes
;
206 fn (root
, site
) => String.concatWith
"/" (root
:: site
:: rev ("" :: elems
))
209 datatype file_action
' =
210 Add
' of {src
: string, dst
: string}
212 | Modify
' of {src
: string, dst
: string}
214 fun findDiffs (site
, dom
, acts
) =
217 val realPath
= gp (Config
.resultRoot
, site
)
218 val tmpPath
= gp (Config
.tmpDir
, site
)
220 (*val _
= print ("getDiffs(" ^ site ^
", " ^ dom ^
")... " ^ realPath ^
"; " ^ tmpPath ^
"\n")*)
222 val dir
= Posix
.FileSys
.opendir realPath
225 case Posix
.FileSys
.readdir dir
of
226 NONE
=> (Posix
.FileSys
.closedir dir
;
230 val real = OS
.Path
.joinDirFile
{dir
= realPath
,
232 val tmp
= OS
.Path
.joinDirFile
{dir
= tmpPath
,
235 if Posix
.FileSys
.ST
.isDir (Posix
.FileSys
.stat
real) then
237 else if Posix
.FileSys
.access (tmp
, []) then
238 if Slave
.shell
[Config
.diff
, " ", real, " ", tmp
] then
241 loopReal ((site
, dom
, realPath
, Modify
' {src
= tmp
, dst
= real}) :: acts
)
243 loopReal ((site
, dom
, realPath
, Delete
' real) :: acts
)
246 val acts
= loopReal acts
248 val dir
= Posix
.FileSys
.opendir tmpPath
251 case Posix
.FileSys
.readdir dir
of
252 NONE
=> (Posix
.FileSys
.closedir dir
;
256 val real = OS
.Path
.joinDirFile
{dir
= realPath
,
258 val tmp
= OS
.Path
.joinDirFile
{dir
= tmpPath
,
261 if Posix
.FileSys
.ST
.isDir (Posix
.FileSys
.stat tmp
) then
263 else if Posix
.FileSys
.access (real, []) then
266 loopTmp ((site
, dom
, realPath
, Add
' {src
= tmp
, dst
= real}) :: acts
)
269 val acts
= loopTmp acts
274 fun findAllDiffs () =
276 val dir
= Posix
.FileSys
.opendir Config
.tmpDir
277 val len
= length (String.fields (fn ch
=> ch
= #
"/") Config
.tmpDir
) + 1
279 fun exploreSites diffs
=
280 case Posix
.FileSys
.readdir dir
of
284 fun explore (dname
, diffs
) =
286 val dir
= Posix
.FileSys
.opendir dname
289 case Posix
.FileSys
.readdir dir
of
293 val fname
= OS
.Path
.joinDirFile
{dir
= dname
,
296 loop (if Posix
.FileSys
.ST
.isDir (Posix
.FileSys
.stat fname
) then
298 val dom
= String.fields (fn ch
=> ch
= #
"/") fname
299 val dom
= List.drop (dom
, len
)
300 val dom
= String.concatWith
"." (rev dom
)
302 val dname
' = OS
.Path
.joinDirFile
{dir
= dname
,
306 findDiffs (site
, dom
, diffs
))
313 before Posix
.FileSys
.closedir dir
316 explore (OS
.Path
.joinDirFile
{dir
= Config
.tmpDir
,
321 before Posix
.FileSys
.closedir dir
324 val masterNode
: string option ref
= ref NONE
325 fun dnsMaster () = !masterNode
327 val _
= Env
.containerV_one
"domain"
328 ("domain", Env
.string)
331 val kind
= Env
.env
dnsKind (evs
, "DNS")
332 val ttl
= Env
.env Env
.int (evs
, "TTL")
334 val path
= getPath dom
336 val () = (current
:= dom
;
337 currentPath
:= (fn site
=> path (Config
.tmpDir
, site
)))
339 fun saveSoa (kind
, soa
: soa
) node
=
341 val outf
= domainFile
{node
= node
, name
= "soa"}
343 TextIO.output (outf
, kind
);
344 TextIO.output (outf
, "\n");
345 TextIO.output (outf
, Int.toString ttl
);
346 TextIO.output (outf
, "\n");
347 TextIO.output (outf
, #ns soa
);
348 TextIO.output (outf
, "\n");
351 | SOME n
=> TextIO.output (outf
, Int.toString n
);
352 TextIO.output (outf
, "\n");
353 TextIO.output (outf
, Int.toString (#ref soa
));
354 TextIO.output (outf
, "\n");
355 TextIO.output (outf
, Int.toString (#ret soa
));
356 TextIO.output (outf
, "\n");
357 TextIO.output (outf
, Int.toString (#exp soa
));
358 TextIO.output (outf
, "\n");
359 TextIO.output (outf
, Int.toString (#min soa
));
360 TextIO.output (outf
, "\n");
364 fun saveNamed (kind
, soa
: soa
) node
=
366 val outf
= domainFile
{node
= node
, name
= "named.conf"}
368 TextIO.output (outf
, "\nzone \"");
369 TextIO.output (outf
, dom
);
370 TextIO.output (outf
, "\" IN {\n\ttype ");
371 TextIO.output (outf
, kind
);
372 TextIO.output (outf
, ";\n\tfile \"");
373 TextIO.output (outf
, Config
.Bind
.zonePath
);
374 TextIO.output (outf
, "/");
375 TextIO.output (outf
, dom
);
376 TextIO.output (outf
, ".zone\";\n");
378 "master" => TextIO.output (outf
, "\tallow-update { none; };\n")
379 | _
=> TextIO.output (outf
, "\tmasters { 1.2.3.4; };\n");
380 TextIO.output (outf
, "}\n");
384 fun saveBoth ks
= (saveSoa ks
; saveNamed ks
)
387 NoDns
=> masterNode
:= NONE
389 (app (saveSoa ("slave", #soa dns
)) (#slaves dns
);
390 app (saveNamed ("slave", #soa dns
)) (#slaves dns
);
392 InternalMaster node
=>
393 (masterNode
:= SOME node
;
394 saveSoa ("master", #soa dns
) node
;
395 saveNamed ("master", #soa dns
) node
)
396 | _
=> masterNode
:= NONE
);
399 fn () => !afters (!current
))
401 val () = Env
.registerPre (fn () => (ignore (Slave
.shellF ([Config
.rm
, " -rf ", Config
.tmpDir
, ""],
402 fn cl
=> "Temp file cleanup failed: " ^ cl
));
403 OS
.FileSys
.mkDir Config
.tmpDir
;
404 app (fn node
=> OS
.FileSys
.mkDir
405 (OS
.Path
.joinDirFile
{dir
= Config
.tmpDir
,
408 app (fn node
=> OS
.FileSys
.mkDir
409 (OS
.Path
.joinDirFile
{dir
= Config
.resultRoot
,
411 handle OS
.SysErr _
=> ())
414 val () = Env
.registerPost (fn () =>
416 val diffs
= findAllDiffs ()
418 val diffs
= map (fn (site
, dom
, dir
, Add
' {src
, dst
}) =>
419 (Slave
.shellF ([Config
.cp
, " ", src
, " ", dst
],
420 fn cl
=> "Copy failed: " ^ cl
);
426 |
(site
, dom
, dir
, Delete
' dst
) =>
427 (OS
.FileSys
.remove dst
428 handle OS
.SysErr _
=>
429 ErrorMsg
.error
NONE ("Delete failed for " ^ dst
);
431 {action
= Slave
.Delete
,
435 |
(site
, dom
, dir
, Modify
' {src
, dst
}) =>
436 (Slave
.shellF ([Config
.cp
, " ", src
, " ", dst
],
437 fn cl
=> "Copy failed: " ^ cl
);
439 {action
= Slave
.Modify
,
444 if !ErrorMsg
.anyErrors
then
447 Slave
.handleChanges (map #
2 diffs
);
448 ignore (Slave
.shellF ([Config
.rm
, " -rf ", Config
.tmpDir
, ""],
449 fn cl
=> "Temp file cleanup failed: " ^ cl
))