[Pkg-ocaml-maint-commits] r1778 - in
trunk/projects/approx/branches/nethttpd: . debian
Eric Cooper
ecc-guest at costa.debian.org
Wed Sep 14 21:45:41 UTC 2005
Author: ecc-guest
Date: 2005-09-14 21:45:40 +0000 (Wed, 14 Sep 2005)
New Revision: 1778
Modified:
trunk/projects/approx/branches/nethttpd/Makefile
trunk/projects/approx/branches/nethttpd/approx.ml
trunk/projects/approx/branches/nethttpd/debian/changelog
trunk/projects/approx/branches/nethttpd/debian/control
trunk/projects/approx/branches/nethttpd/gc_approx.ml
trunk/projects/approx/branches/nethttpd/url.ml
trunk/projects/approx/branches/nethttpd/url.mli
trunk/projects/approx/branches/nethttpd/util.ml
trunk/projects/approx/branches/nethttpd/util.mli
Log:
changed client to use curl subprocesses instead of ocurl
for simplicity, all downloads are cached first, then sent to the client
Modified: trunk/projects/approx/branches/nethttpd/Makefile
===================================================================
--- trunk/projects/approx/branches/nethttpd/Makefile 2005-09-12 21:51:13 UTC (rev 1777)
+++ trunk/projects/approx/branches/nethttpd/Makefile 2005-09-14 21:45:40 UTC (rev 1778)
@@ -8,8 +8,8 @@
define PROJ_server
SOURCES = util.ml config.ml default_config.ml log.ml url.ml control_file.ml release.ml server.ml version.ml approx.ml
- INCDIRS = +pcre +syslog +netstring +cgi +nethttpd +curl
- LIBS = unix pcre syslog netstring cgi nethttpd curl
+ INCDIRS = +pcre +syslog +netstring +cgi +nethttpd
+ LIBS = unix pcre syslog netstring cgi nethttpd
RESULT = approx
endef
export PROJ_server
Modified: trunk/projects/approx/branches/nethttpd/approx.ml
===================================================================
--- trunk/projects/approx/branches/nethttpd/approx.ml 2005-09-12 21:51:13 UTC (rev 1777)
+++ trunk/projects/approx/branches/nethttpd/approx.ml 2005-09-14 21:45:40 UTC (rev 1778)
@@ -46,8 +46,6 @@
error_message "Failure: %s" str
| Invalid_argument str ->
error_message "Invalid argument: %s" str
- | Curl.CurlException (_, _, str) ->
- error_message "Curl exception: %s" str
| Unix_error (err, str, "") ->
error_message "%s: %s" str (Unix.error_message err)
| Unix_error (err, str, arg) ->
@@ -69,6 +67,18 @@
info_message "Max wait: %d" max_wait;
info_message "Debug: %B" debug
+type download_status =
+ | Cached
+ | Not_modified
+ | File_not_found
+ | Wrong_size
+
+let string_of_download_status = function
+ | Cached -> "Cached"
+ | Not_modified -> "Not modified"
+ | File_not_found -> "File not found"
+ | Wrong_size -> "Wrong size"
+
let http_time t =
Netdate.format ~fmt: "%a, %d %b %Y %T GMT" (Netdate.create ~zone: 0 t)
@@ -101,16 +111,17 @@
debug_message "%s" msg;
List.iter (fun (x, y) -> debug_message " %s: %s" x y) headers
-let proxy_headers name env =
- env#set_output_header_field "Content-Type" "text/plain";
- env#set_output_header_field "Last-Modified" (http_time (file_modtime name));
- if debug then print_headers "Local response" env#output_header_fields
+let proxy_headers name =
+ ["Content-Type", "text/plain";
+ "Content-Length", Int64.to_string (file_size name);
+ "Last-Modified", http_time (file_modtime name)]
(* Deliver a file from the local cache *)
let deliver_local name env =
if not debug then info_message "%s" name;
- proxy_headers name env;
+ env#set_output_header_fields (proxy_headers name);
+ if debug then print_headers "Cache hit" env#output_header_fields;
`File (`Ok, None, cache_dir ^/ name, 0L, file_size name)
(* Return the age of a file in minutes, using the last status change
@@ -195,69 +206,57 @@
if debug then debug_message " open cache %s" name;
cache_file := name;
tmp_cache_file := in_progress name;
- cache_chan := Some (create_file !tmp_cache_file);
+ cache_chan := Some (create_file !tmp_cache_file)
with e ->
exception_message e;
error_message "Cannot cache %s" name
-let write_cache str =
+let write_cache str pos len =
match !cache_chan with
+ | Some chan -> output chan str pos len
| None -> assert false
- | Some chan ->
- output_string chan str;
- flush chan
-let close_cache ?(mod_time=0.) ?(size=(-1L)) () =
+let close_cache mod_time size =
match !cache_chan with
- | None -> assert false
| Some chan ->
- if debug then debug_message " close cache %s" !cache_file;
+ let real_name = !cache_file in
+ let tmp_name = !tmp_cache_file in
+ if debug then debug_message " close cache %s" real_name;
close_out chan;
cache_chan := None;
- try
- if size = -1L || size = file_size !tmp_cache_file then
- begin
- Sys.rename !tmp_cache_file !cache_file;
- if mod_time <> 0. then
- begin
- if debug then
- debug_message " setting mtime to %s"
- (string_of_time mod_time);
- utimes !cache_file mod_time mod_time
- end
- end
- else
- begin
- error_message "Size of %s should be %Ld" !cache_file size;
- Sys.remove !tmp_cache_file
- end
- with e ->
- (* gc_approx or another approx process might have removed .tmp file *)
- error_message "Cannot close cache file %s" !cache_file;
- exception_message e
+ if size = file_size tmp_name then
+ begin
+ Sys.rename tmp_name real_name;
+ if mod_time <> 0. then
+ begin
+ if debug then
+ debug_message " setting mtime to %s"
+ (string_of_time mod_time);
+ utimes real_name mod_time mod_time
+ end;
+ Cached
+ end
+ else
+ (* FIXME:
+ gc_approx or another approx process might have removed .tmp file *)
+ begin
+ error_message "Size of %s should be %Ld, not %Ld"
+ real_name size (file_size tmp_name);
+ Sys.remove tmp_name;
+ Wrong_size
+ end
+ | None -> assert false
let remove_cache () =
match !cache_chan with
| None -> ()
| Some chan ->
- let name = !tmp_cache_file in
+ let tmp_name = !tmp_cache_file in
close_out chan;
cache_chan := None;
- error_message "Removing %s (size: %Ld)" name (file_size name);
- Sys.remove name
+ error_message "Removing %s (size: %Ld)" tmp_name (file_size tmp_name);
+ Sys.remove tmp_name
-type download_status =
- | Delivered
- | Cached
- | Not_modified
- | File_not_found
-
-let string_of_download_status = function
- | Delivered -> "Delivered"
- | Cached -> "Cached"
- | Not_modified -> "Not modified"
- | File_not_found -> "File not found"
-
(* Update the ctime but not the mtime of the file, if it exists *)
let update_ctime name =
@@ -266,23 +265,10 @@
utimes name stats.st_atime stats.st_mtime
with Unix_error (ENOENT, "stat", _) -> ()
-let print_header str =
- let n = String.length str in
- if n >= 2 && str.[n-2] = '\r' && str.[n-1] = '\n' then
- (if n > 2 then debug_message " %s" (substring str ~until: (n-2)))
- else
- error_message "No CRLF in header: %s" str
-
-let status_re = Pcre.regexp "^HTTP/\\d+\\.\\d+ (\\d{3}) (.*?)\\s*\\r\\n"
-let header_re = Pcre.regexp "^(.*?):\\s*(.*?)\\s*\\r\\n"
-
let with_pair rex str proc =
match Pcre.extract ~rex ~full_match: false str with
- | [| a; b |] ->
- assert (not (String.contains b '\r' || String.contains b '\n'));
- proc (a, b)
- | _ ->
- assert false
+ | [| a; b |] -> proc (a, b)
+ | _ -> assert false
let once flag proc =
if not !flag then
@@ -291,112 +277,85 @@
proc ()
end
-(* Respond to a request for a file at an HTTP repository *)
+let status_re = Pcre.regexp "^HTTP/\\d+\\.\\d+ (\\d{3}) (.*?)\\s*$"
+let header_re = Pcre.regexp "^(.*?):\\s*(.*?)\\s*$"
-let serve_http url name ims env output =
- let header_list = ref [] in
- let length = ref (-1L) in
- let last_modified = ref 0. in
- let chunked = ref false in
- let add_header (header, value as pair) =
+let process_header (status, length, last_modified) str =
+ let do_status (code, _) =
+ status := int_of_string code
+ in
+ let do_header (header, value) =
match String.lowercase header with
- | "content-encoding" | "content-type" ->
- header_list := pair :: !header_list
| "content-length" ->
- header_list := pair :: !header_list;
(try length := Int64.of_string value
- with Failure _ ->
- error_message "Cannot parse Content-Length value %s" value)
+ with Failure _ ->
+ error_message "Cannot parse Content-Length value %s" value)
| "last-modified" ->
- header_list := pair :: !header_list;
- (try last_modified := Netdate.parse_epoch value;
- with Invalid_argument _ ->
- error_message "Cannot parse Last-Modified date %s" value)
- | "transfer-encoding" ->
- if String.lowercase value = "chunked" then
- chunked := true
- else
- error_message "Unknown Transfer-Encoding type %s" value
- | _ ->
- ()
+ (try last_modified := Netdate.parse_epoch value
+ with Invalid_argument _ ->
+ error_message "Cannot parse Last-Modified date %s" value)
+ | _ -> ()
in
+ if debug then debug_message " %s" str;
+ try with_pair header_re str do_header
+ with Not_found -> (* e.g., status line or CRLF *)
+ try with_pair status_re str do_status
+ with Not_found -> error_message "Unrecognized response: %s" str
+
+(* Download a file from an HTTP repository *)
+
+let download_http url name ims =
let status = ref 0 in
- let do_status (code, _) =
- status := int_of_string code
- in
- let header_callback str =
- if debug then print_header str;
- try with_pair header_re str add_header
- with Not_found -> (* e.g., status line or CRLF *)
- try with_pair status_re str do_status
- with Not_found ->
- if str <> "\r\n" then error_message "Unrecognized response: %s" str
- in
+ let length = ref (-1L) in
+ let last_modified = ref 0. in
+ let header_callback = process_header (status, length, last_modified) in
let body_seen = ref false in
- let start_transfer () =
- if not !chunked then
- (* start our response now *)
- begin
- env#set_output_header_fields !header_list;
- if debug then print_headers "Proxy response" env#output_header_fields
- end;
- open_cache name
- in
- let body_callback str =
+ let body_callback str pos len =
if !status = 200 then
begin
- once body_seen start_transfer;
- write_cache str;
- if not !chunked then
- (* stream the data back to the client as we receive it *)
- output#output_string str
+ once body_seen (fun () -> open_cache name);
+ write_cache str pos len
end
in
let headers =
if ims > 0. then [ "If-Modified-Since: " ^ http_time ims ] else []
in
- Url.iter url ~headers ~header_callback body_callback;
+ Url.download url ~headers ~header_callback body_callback;
match !status with
- | 200 ->
- close_cache ~mod_time: !last_modified ~size: !length ();
- if !chunked then Cached else Delivered
- | 304 ->
- Not_modified
- | 404 ->
- File_not_found
- | n ->
- error_message "Unexpected status code: %d" n;
- File_not_found
+ | 200 -> close_cache !last_modified !length
+ | 304 -> Not_modified
+ | 404 -> File_not_found
+ | n -> error_message "Unexpected status code: %d" n; File_not_found
-(* Respond to a request for a file at an FTP repository *)
+(* Download a file from an FTP repository *)
-let serve_ftp url name ims env output =
- let mod_time = Url.mod_time url in
+let download_ftp url name ims =
+ let status = ref 0 in
+ let length = ref (-1L) in
+ let last_modified = ref 0. in
+ let header_callback = process_header (status, length, last_modified) in
+ Url.head url header_callback;
+ let mod_time = !last_modified in
if debug then
- debug_message " ims %s mtime %s"
- (string_of_time ims) (string_of_time mod_time);
+ debug_message " ims %s mtime %s"
+ (string_of_time ims) (string_of_time mod_time);
if mod_time > ims || mod_time = 0. then
begin
- (* Since don't know the length in advance,
- we cache the whole file before delivering it to the client.
- The alternative -- using chunked transfer encoding --
- triggers a bug in APT. *)
open_cache name;
- Url.iter url write_cache;
- close_cache ~mod_time ();
- Cached
+ Url.download url write_cache;
+ close_cache !last_modified !length
end
else
Not_modified
-let serve_url url =
+let download_url url =
let meth =
try String.lowercase (substring url ~until: (String.index url ':'))
with Not_found -> invalid_arg "no method in URL"
in
match meth with
- | "http" -> serve_http url
- | "ftp" -> serve_ftp url
+ | "http" -> download_http url
+ | "ftp" -> download_ftp url
| _ -> invalid_arg "unsupported URL method"
(* Remove any files from the cache that have been invalidated
@@ -415,57 +374,62 @@
let buf = String.create len in
let rec loop () =
match input src buf 0 len with
- | 0 -> dst#flush ()
+ | 0 -> ()
| n -> dst#really_output buf 0 n; loop ()
in
loop ()
+let send_header name (cgi : Netcgi_types.cgi_activation) =
+ let fields =
+ List.map (fun (name, value) -> (name, [value])) (proxy_headers name)
+ in
+ cgi#set_header ~status: `Ok ~fields ();
+ if debug then print_headers "Cache miss" cgi#environment#output_header_fields
+
(* Similar to deliver_local, but we have to copy it ourselves *)
-let copy_from_cache name env output =
+let copy_from_cache name cgi =
wait_for_download_in_progress name;
- env#set_output_header_field "Content-Length"
- (Int64.to_string (file_size name));
- proxy_headers name env;
- with_channel open_in name (fun input -> copy input output)
+ send_header name cgi;
+ let output = cgi#output in
+ with_channel open_in name (fun input -> copy input output);
+ output#commit_work ()
-let serve_remote url name ims mod_time env output =
+let serve_remote url name ims mod_time cgi =
info_message "%s" url;
let status =
- try serve_url url name (max ims mod_time) env output
+ try download_url url name (max ims mod_time)
with e ->
remove_cache ();
exception_message e;
raise (Standard_response (`Not_found, None, Some "Download failed"))
in
- if debug then
- debug_message " status: %s" (string_of_download_status status);
+ if debug then debug_message " => %s" (string_of_download_status status);
match status with
- | Delivered ->
- cleanup_after name
| Cached ->
- copy_from_cache name env output;
+ copy_from_cache name cgi;
cleanup_after name
| Not_modified ->
update_ctime name;
if mod_time > ims then
(* the cached copy is newer than what the client has *)
- copy_from_cache name env output
+ copy_from_cache name cgi
else
raise (Standard_response (`Not_modified, None, None))
| File_not_found ->
raise (Standard_response (`Not_found, None, None))
+ | Wrong_size ->
+ raise (Standard_response (`Service_unavailable, None, Some "Wrong size"))
let remote_service url name ims mod_time =
object
method process_body env =
let cgi =
- Nethttpd_services.std_activation `Std_activation_buffered env
+ (* buffered activation runs out of memory on large downloads *)
+ Nethttpd_services.std_activation `Std_activation_unbuffered env
in
object
- method generate_response env =
- serve_remote url name ims mod_time env cgi#output;
- cgi#output#commit_work ()
+ method generate_response _ = serve_remote url name ims mod_time cgi
end
end
Modified: trunk/projects/approx/branches/nethttpd/debian/changelog
===================================================================
--- trunk/projects/approx/branches/nethttpd/debian/changelog 2005-09-12 21:51:13 UTC (rev 1777)
+++ trunk/projects/approx/branches/nethttpd/debian/changelog 2005-09-14 21:45:40 UTC (rev 1778)
@@ -1,7 +1,13 @@
+approx (1.51) unstable; urgency=low
+
+ * Changed client to use curl subprocesses instead of ocurl
+ * For simplicity, all downloads are cached first, then sent to the client
+
+ -- Eric Cooper <ecc at cmu.edu> Wed, 14 Sep 2005 09:37:54 -0400
+
approx (1.50) unstable; urgency=low
- * Changed server to use nethttpd from the ocamlnet library
- instead of ocaml-http
+ * Changed server to use nethttpd instead of ocaml-http
-- Eric Cooper <ecc at cmu.edu> Mon, 22 Aug 2005 16:53:22 -0400
Modified: trunk/projects/approx/branches/nethttpd/debian/control
===================================================================
--- trunk/projects/approx/branches/nethttpd/debian/control 2005-09-12 21:51:13 UTC (rev 1777)
+++ trunk/projects/approx/branches/nethttpd/debian/control 2005-09-14 21:45:40 UTC (rev 1778)
@@ -3,12 +3,12 @@
Priority: optional
Maintainer: Eric Cooper <ecc at cmu.edu>
Uploaders: Sven Luther <luther at debian.org>, Stefano Zacchiroli <zack at debian.org>
-Build-Depends: debhelper (>= 4.0), ocaml-nox-3.08.3, ocaml-best-compilers, ocaml-tools, libcurl-ocaml-dev (>= 0.2.0), libocamlnet-ocaml-dev (>= 1.1), libpcre-ocaml-dev, libsyslog-ocaml-dev (>= 1.2)
+Build-Depends: debhelper (>= 4.0), ocaml-nox-3.08.3, ocaml-best-compilers, ocaml-tools, libocamlnet-ocaml-dev (>= 1.1), libpcre-ocaml-dev (>= 5.10.0), libsyslog-ocaml-dev (>= 1.2)
Standards-Version: 3.6.2
Package: approx
Architecture: any
-Depends: ${shlibs:Depends}, bzip2, wget
+Depends: ${shlibs:Depends}, bzip2, curl
Description: caching proxy server for Debian archive files
Approx is an HTTP-based Debian archive server.
It fetches packages from remote repositories on demand,
Modified: trunk/projects/approx/branches/nethttpd/gc_approx.ml
===================================================================
--- trunk/projects/approx/branches/nethttpd/gc_approx.ml 2005-09-12 21:51:13 UTC (rev 1777)
+++ trunk/projects/approx/branches/nethttpd/gc_approx.ml 2005-09-14 21:45:40 UTC (rev 1778)
@@ -14,13 +14,13 @@
open Unix
let usage () =
- prerr_endline "Usage: gc_approx [options]";
- prerr_endline "Garbage-collect the approx cache";
- prerr_endline "Options:";
- prerr_endline " -f|--fast do not validate MD5 checksums";
- prerr_endline " -k|--keep do not remove files";
- prerr_endline " -q|--quiet do not print file names";
- prerr_endline " -v|--verbose print reason for removal";
+ prerr_endline "Usage: gc_approx [options]
+Garbage-collect the approx cache
+Options:
+ -f|--fast do not validate MD5 checksums
+ -k|--keep do not remove files
+ -q|--quiet do not print file names
+ -v|--verbose print reason for removal";
exit 1
let no_checksum = ref false
@@ -130,9 +130,13 @@
let download dist file package =
let url = Config.get dist ^/ file in
- prerr_string "downloading "; prerr_endline url;
+ eprintf "downloading %s\n" url; flush Pervasives.stderr;
let package' = package ^ ".tmp" in
- if Sys.command (sprintf "/usr/bin/wget -q -O %s %s" package' url) = 0 then
+ let cmd =
+ sprintf "/usr/bin/curl --silent --output %s %s"
+ package' (quoted_string url)
+ in
+ if Sys.command cmd = 0 then
Sys.rename package' package
else
failwith ("cannot download " ^ url)
@@ -141,8 +145,7 @@
if !verbose then (print_string "# "; print_endline package);
let dist, file = split_cache_pathname package in
let prefix = cache_dir ^/ dist in
- try
- Control_file.iter (mark_file prefix) package
+ try Control_file.iter (mark_file prefix) package
with Failure "decompress" ->
(* corrupt Packages file: download it and try again *)
download dist file package;
Modified: trunk/projects/approx/branches/nethttpd/url.ml
===================================================================
--- trunk/projects/approx/branches/nethttpd/url.ml 2005-09-12 21:51:13 UTC (rev 1777)
+++ trunk/projects/approx/branches/nethttpd/url.ml 2005-09-14 21:45:40 UTC (rev 1778)
@@ -2,35 +2,71 @@
Copyright (C) 2005 Eric C. Cooper <ecc at cmu.edu>
Released under the GNU General Public License *)
-open Curl
+open Util
+open Default_config
+open Log
+open Printf
-let conn =
- global_init CURLINIT_GLOBALALL;
- let conn = init () in
- set_followlocation conn true;
- set_connecttimeout conn 10;
- conn
+let head_command url =
+ sprintf "/usr/bin/curl --silent --head %s" (quoted_string url)
-(* Since the following functions share the same Curl handle,
- each one must reset any options the others might have set *)
+let iter_headers chan proc =
+ let next () =
+ try Some (input_line chan)
+ with End_of_file -> None
+ in
+ let rec loop () =
+ match next () with
+ | Some header ->
+ let n = String.length header in
+ if n > 0 && header.[n - 1] = '\r' then
+ if n > 1 then
+ begin
+ proc (String.sub header 0 (n - 1));
+ loop ()
+ end
+ else
+ (* CRLF terminates headers *)
+ ()
+ else
+ error_message "Unexpected header: %s" header
+ | None -> ()
+ in
+ loop ()
-let iter url ?(headers=[]) ?(header_callback=ignore) callback =
- set_httpget conn true;
- set_nobody conn false;
- set_url conn url;
- set_httpheader conn headers;
- set_headerfunction conn header_callback;
- set_writefunction conn callback;
- set_filetime conn false;
- perform conn
+let head url callback =
+ let cmd = head_command url in
+ if debug then debug_message "Command: %s" cmd;
+ let chan = Unix.open_process_in cmd in
+ iter_headers chan callback;
+ ignore (Unix.close_process_in chan)
-let mod_time url =
- set_httpget conn false;
- set_nobody conn true;
- set_url conn url;
- set_httpheader conn [];
- set_headerfunction conn ignore;
- set_writefunction conn ignore;
- set_filetime conn true;
- perform conn;
- get_filetime conn
+let download_command url headers headers_wanted =
+ let buf = Buffer.create 200 in
+ let add_header str h =
+ sprintf "%s --header %s" str (quoted_string h)
+ in
+ sprintf "/usr/bin/curl --silent%s%s %s"
+ (if headers_wanted then " --include" else "")
+ (List.fold_left add_header "" headers)
+ (quoted_string url)
+
+let iter_body chan proc =
+ let len = 4096 in
+ let buf = String.create len in
+ let rec loop () =
+ match input chan buf 0 len with
+ | 0 -> ()
+ | n -> proc buf 0 n; loop ()
+ in
+ loop ()
+
+let download url ?(headers=[]) ?header_callback callback =
+ let cmd = download_command url headers (header_callback <> None) in
+ if debug then debug_message "Command: %s" cmd;
+ let chan = Unix.open_process_in cmd in
+ (match header_callback with
+ | Some proc -> iter_headers chan proc
+ | None -> ());
+ iter_body chan callback;
+ ignore (Unix.close_process_in chan)
Modified: trunk/projects/approx/branches/nethttpd/url.mli
===================================================================
--- trunk/projects/approx/branches/nethttpd/url.mli 2005-09-12 21:51:13 UTC (rev 1777)
+++ trunk/projects/approx/branches/nethttpd/url.mli 2005-09-14 21:45:40 UTC (rev 1778)
@@ -2,10 +2,10 @@
Copyright (C) 2005 Eric C. Cooper <ecc at cmu.edu>
Released under the GNU General Public License *)
-val iter :
+val head : string -> (string -> unit) -> unit
+
+val download :
string ->
?headers:string list ->
?header_callback:(string -> unit) ->
- (string -> unit) -> unit
-
-val mod_time : string -> float
+ (string -> int -> int -> unit) -> unit
Modified: trunk/projects/approx/branches/nethttpd/util.ml
===================================================================
--- trunk/projects/approx/branches/nethttpd/util.ml 2005-09-12 21:51:13 UTC (rev 1777)
+++ trunk/projects/approx/branches/nethttpd/util.ml 2005-09-14 21:45:40 UTC (rev 1778)
@@ -29,6 +29,8 @@
let implode_path = String.concat "/"
+let quoted_string str = "\"" ^ String.escaped str ^ "\""
+
let (^/) = Filename.concat
let relative_path name =
Modified: trunk/projects/approx/branches/nethttpd/util.mli
===================================================================
--- trunk/projects/approx/branches/nethttpd/util.mli 2005-09-12 21:51:13 UTC (rev 1777)
+++ trunk/projects/approx/branches/nethttpd/util.mli 2005-09-14 21:45:40 UTC (rev 1778)
@@ -24,6 +24,10 @@
val implode_path : string list -> string
+(* Return a quoted string *)
+
+val quoted_string : string -> string
+
(* Infix operator to concatenate two pathname components *)
val (^/) : string -> string -> string
More information about the Pkg-ocaml-maint-commits
mailing list