/* * Extc : C common OCaml bindings * Copyright (c)2004-2017 Nicolas Cannasse * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #ifdef _WIN32 # include # include #else # include # include # include # include # include # include # include # include # include # include # include #endif #ifdef __APPLE__ # include # include # include #include #include #endif #ifdef __FreeBSD__ # include # include # include #endif #ifndef CLK_TCK # define CLK_TCK 100 #endif /** * Converts an OCaml value to a C pointer for a z_stream. * * @param v {value} An OCaml value * @return {z_streamp} A pointer for a z_stream */ #define ZStreamP_val(v) (*((z_streamp *) Data_custom_val(v))) /** * Converts an OCaml `Extc.zflush` value to an allowed flush value for _zlib_. * * It may raise the following OCaml exception: * - Failure: Unknown zflush value. * * Make sure to update this function when refactoring OCaml's `Extc.zflush` type. The integer value * of OCaml's `Extc.zflush` is the 0-based index of the position of the constructor in the type * definition. * * See: * https://github.com/HaxeFoundation/haxe-debian/blob/31cb4aaab9f6770d058883a1c5b97e36c8ec5d71/libs/extc/extc.ml#L22 * https://github.com/madler/zlib/blob/cacf7f1d4e3d44d871b605da3b647f07d718623f/zlib.h#L168 * * @param zflush_val {value} OCaml `Extc.zflush` * @return {int} C int representing an allowed flush value for _zlib_ */ int Zflush_val(value zflush_val) { switch (Int_val(zflush_val)) { case 0: return Z_NO_FLUSH; case 1: return Z_PARTIAL_FLUSH; case 2: return Z_SYNC_FLUSH; case 3: return Z_FULL_FLUSH; case 4: return Z_FINISH; // TODO: support Z_BLOCK and Z_TREE // TODO: append the received value default: failwith("Error in `Zflush_val` (extc_stubs.c): Unknown zflush value"); } assert(0); } /** * Converts an allowed flush value for _zlib_ to an OCaml `Extc.zflush` value. * * Make sure to update this function when refactoring OCaml's `Extc.zflush` type. The integer value * of OCaml's `Extc.zflush` is the 0-based index of the position of the constructor in the type * definition. * * See: * https://github.com/madler/zlib/blob/cacf7f1d4e3d44d871b605da3b647f07d718623f/zlib.h#L168 * https://github.com/HaxeFoundation/haxe-debian/blob/31cb4aaab9f6770d058883a1c5b97e36c8ec5d71/libs/extc/extc.ml#L22 * * @param {int} C int representing an allowed flush value for _zlib_ * @return {value} OCaml `Extc.zflush` */ value val_Zflush(int zflush) { switch (zflush) { case Z_NO_FLUSH: return Val_int(0); case Z_PARTIAL_FLUSH: return Val_int(1); case Z_SYNC_FLUSH: return Val_int(2); case Z_FULL_FLUSH: return Val_int(3); case Z_FINISH: return Val_int(4); // TODO: support Z_BLOCK and Z_TREE } assert(0); } /** * Free the memory of the pointer contained in the supplied OCaml value `caml_z_stream_pointer`. * * @param z_streamp_val {value} An OCaml value containing a z_stream pointer to the memory to free. */ void zlib_free_stream(value z_streamp_val) { caml_stat_free(ZStreamP_val(z_streamp_val)); ZStreamP_val(z_streamp_val) = NULL; } /** * Define the custom operations for a z_stream. This ensures that the memory owned * by the z_stream pointer is freed. * * See: * https://github.com/ocaml/ocaml/blob/70d880a41a82aae1ebd428fd38100e8467f8535a/byterun/caml/custom.h#L25 */ static struct custom_operations zlib_stream_ops = { // identifier "z_stream_ops", // finalize &zlib_free_stream, // compare NULL, // hash NULL, // serialize NULL, // compare_ext NULL }; /** * Create an OCaml value containing a new z_stream pointer. * * This function may raise the following OCaml exception: * - Out_of_memory exception * * @return {value} An OCaml value containing a new z_stream pointer. */ value zlib_new_stream() { value z_streamp_val = caml_alloc_custom(&zlib_stream_ops, sizeof(z_streamp), 0, 1); ZStreamP_val(z_streamp_val) = caml_stat_alloc(sizeof(z_stream)); ZStreamP_val(z_streamp_val)->zalloc = NULL; ZStreamP_val(z_streamp_val)->zfree = NULL; ZStreamP_val(z_streamp_val)->opaque = NULL; ZStreamP_val(z_streamp_val)->next_in = NULL; ZStreamP_val(z_streamp_val)->next_out = NULL; return z_streamp_val; } /** * OCaml binding for _zlib_'s `deflateInit2` function. * * This creates a new stream and initializes it for deflate. * * This function may raise the following OCaml exceptions: * - Out_of_memory exception * - Failure exception: Invalid parameters * - Failure exception: Invalid version * - Failure exception: Unknown zlib return code * * See: * https://github.com/madler/zlib/blob/cacf7f1d4e3d44d871b605da3b647f07d718623f/zlib.h#L538 * * @param levelVal {value} OCaml `int`: the compression level, must be in the range 0..9. * 0 gives no compression at all, 1 the best speed, 9 the best compression. * @param windowBitsVal {value} OCaml `int`: base two logarithm of the window size (size of the * history buffer) used by _zlib_. It should be in the range 9..15 for this version of _zlib_. * It can also be in the range -15..-8 (the absolute value is used) for raw deflate. * Finally, it can be greater than 15 for gzip encoding. See _zlib_'s documentation for * `deflateInit2` for the exact documentation. * @return {value} An OCaml value representing the new stream, initialized for deflate. */ CAMLprim value zlib_deflate_init2(value level_val, value window_bits_val) { int level = Int_val(level_val); int window_bits = Int_val(window_bits_val); value z_streamp_val = zlib_new_stream(); z_streamp stream = ZStreamP_val(z_streamp_val); int deflate_init2_result = deflateInit2( stream, level, Z_DEFLATED, // method window_bits, 8, // memLevel Z_DEFAULT_STRATEGY // strategy ); if (deflate_init2_result == Z_OK) { return z_streamp_val; } switch (deflate_init2_result) { case Z_MEM_ERROR: caml_raise_out_of_memory(); break; case Z_STREAM_ERROR: // TODO: use stream->msg to get _zlib_'s text message failwith("Error in `zlib_deflate_init2` (extc_stubs.c): call to `deflateInit2` failed: Z_STREAM_ERROR"); break; case Z_VERSION_ERROR: // TODO: use stream->msg to get _zlib_'s text message failwith("Error in `zlib_deflate_init2` (extc_stubs.c): call to `deflateInit2` failed: Z_VERSION_ERROR"); break; default: failwith("Error in `zlib_deflate_init2` (extc_stubs.c): unknown return code from `deflateInit2`"); } assert(0); } /** * OCaml binding for _zlib_'s `deflate` function. * * Compresses as much data as possible, and stops when the input buffer becomes empty or the output * buffer becomes full. * * This function may raise the following OCaml exceptions: * - Out_of_memory exception * - Failure exception: Invalid parameters * - Failure exception: Invalid version * - Failure exception: Unknown zlib return code * * See: * https://github.com/madler/zlib/blob/cacf7f1d4e3d44d871b605da3b647f07d718623f/zlib.h#L250 * * @param stream_val {value} OCaml `Extc.zstream`: value containing a z_stream pointer to a deflate * stream. * @param src {value} OCaml `bytes`: Source buffer * @param spos {value} OCaml `int`: Index of the inclusive start offset of the source. * @param slen {value} OCaml `int`: Length of the data to read from the source buffer, from spos. * @param dst {value} OCaml `bytes`: Source buffer * @param dpos {value} OCaml `int`: Index of the inclusive start offset of the source. * @param dlen {value} OCaml `int`: Length of the data to read from the source buffer, from spos. * @param flush_val {value} OCaml `Extc.zflush`: Controls the flush logic. See _zlib_'s * documentation. * @return {value} OCaml `Extc.reslut`. */ CAMLprim value zlib_deflate(value stream_val, value src, value spos, value slen, value dst, value dpos, value dlen, value flush_val) { z_streamp stream = ZStreamP_val(stream_val); int flush = Zflush_val(flush_val); stream->next_in = (Bytef*)(String_val(src) + Int_val(spos)); stream->next_out = (Bytef*)(String_val(dst) + Int_val(dpos)); stream->avail_in = Int_val(slen); stream->avail_out = Int_val(dlen); int deflate_result = deflate(stream, flush); if (deflate_result == Z_OK || deflate_result == Z_STREAM_END) { stream->next_in = NULL; stream->next_out = NULL; value zresult = alloc_small(3, 0); // z_finish Field(zresult, 0) = Val_bool(deflate_result == Z_STREAM_END); // z_read Field(zresult, 1) = Val_int(Int_val(slen) - stream->avail_in); // z_wrote Field(zresult, 2) = Val_int(Int_val(dlen) - stream->avail_out); return zresult; } switch (deflate_result) { case Z_MEM_ERROR: caml_raise_out_of_memory(); break; case Z_STREAM_ERROR: // TODO: use stream->msg to get _zlib_'s text message failwith("Error in `zlib_deflate` (extc_stubs.c): call to `deflate` failed: Z_STREAM_ERROR"); break; case Z_BUF_ERROR: // TODO: use stream->msg to get _zlib_'s text message failwith("Error in `zlib_deflate` (extc_stubs.c): call to `deflate` failed: Z_BUF_ERROR"); break; default: failwith("Error in `zlib_deflate` (extc_stubs.c): unknown return code from `deflate`"); } assert(0); } CAMLprim value zlib_deflate_bytecode(value *arg, int nargs) { return zlib_deflate(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7]); } CAMLprim value zlib_deflate_end(value zv) { if( deflateEnd(ZStreamP_val(zv)) != 0 ) failwith("zlib_deflate_end"); return Val_unit; } CAMLprim value zlib_inflate_init(value wbits) { value z = zlib_new_stream(); if( inflateInit2(ZStreamP_val(z),Int_val(wbits)) != Z_OK ) failwith("zlib_inflate_init"); return z; } CAMLprim value zlib_inflate( value zv, value src, value spos, value slen, value dst, value dpos, value dlen, value flush ) { z_streamp z = ZStreamP_val(zv); value res; int r; z->next_in = (Bytef*)(String_val(src) + Int_val(spos)); z->next_out = (Bytef*)(String_val(dst) + Int_val(dpos)); z->avail_in = Int_val(slen); z->avail_out = Int_val(dlen); if( (r = inflate(z,Int_val(flush))) < 0 ) failwith("zlib_inflate"); z->next_in = NULL; z->next_out = NULL; res = alloc_small(3, 0); Field(res, 0) = Val_bool(r == Z_STREAM_END); Field(res, 1) = Val_int(Int_val(slen) - z->avail_in); Field(res, 2) = Val_int(Int_val(dlen) - z->avail_out); return res; } CAMLprim value zlib_inflate_bytecode(value * arg, int nargs) { return zlib_inflate(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6],arg[7]); } CAMLprim value zlib_inflate_end(value zv) { if( inflateEnd(ZStreamP_val(zv)) != 0 ) failwith("zlib_inflate_end"); return Val_unit; } CAMLprim value zlib_deflate_bound(value zv,value len) { return Val_int(deflateBound(ZStreamP_val(zv),Int_val(len))); } CAMLprim value zlib_crc32( value src, value len ) { CAMLparam2(src,len); CAMLlocal1(result); uLong crc = crc32(0L, (Bytef*)(String_val(src)), Int_val(len)); result = caml_copy_int32(crc); CAMLreturn(result); } CAMLprim value executable_path(value u) { #ifdef _WIN32 char path[MAX_PATH]; if( GetModuleFileName(NULL,path,MAX_PATH) == 0 ) failwith("executable_path"); return caml_copy_string(path); #elif __APPLE__ char path[MAXPATHLEN+1]; uint32_t path_len = MAXPATHLEN; if ( _NSGetExecutablePath(path, &path_len) ) failwith("executable_path"); return caml_copy_string(path); #elif __FreeBSD__ char path[PATH_MAX]; int error, name[4]; size_t len; name[0] = CTL_KERN; name[1] = KERN_PROC; name[2] = KERN_PROC_PATHNAME; name[3] = (int)getpid(); len = sizeof(path); error = sysctl(name, 4, path, &len, NULL, 0); if( error < 0 ) failwith("executable_path"); return caml_copy_string(path); #else char path[PATH_MAX]; int length = readlink("/proc/self/exe", path, sizeof(path)); if( length < 0 || length >= PATH_MAX ) { const char *p = getenv("_"); if( p != NULL ) return caml_copy_string(p); else failwith("executable_path"); } path[length] = '\0'; return caml_copy_string(path); #endif } CAMLprim value get_full_path( value f ) { #ifdef _WIN32 char path[MAX_PATH]; if( GetFullPathName(String_val(f),MAX_PATH,path,NULL) == 0 ) failwith("get_full_path"); return caml_copy_string(path); #else char path[4096]; if( realpath(String_val(f),path) == NULL ) failwith("get_full_path"); return caml_copy_string(path); #endif } CAMLprim value get_real_path( value path ) { #ifdef _WIN32 const char sep = '\\'; size_t len, i, last; WIN32_FIND_DATA data; HANDLE handle; char out[MAX_PATH]; // this will ensure the full class path with proper casing if( GetFullPathName(String_val(path),MAX_PATH,out,NULL) == 0 ) failwith("get_real_path"); len = strlen(out); i = 0; if (len >= 2 && out[1] == ':') { // convert drive letter to uppercase if (out[0] >= 'a' && out[0] <= 'z') out[0] += 'A' - 'a'; if (len >= 3 && out[2] == sep) i = 3; else i = 2; } last = i; while (i < len) { // skip until separator while (i < len && out[i] != sep) i++; // temporarily strip string to last found component out[i] = 0; // get actual file/dir name with proper case if ((handle = FindFirstFile(out, &data)) != INVALID_HANDLE_VALUE) { int klen = strlen(data.cFileName); // a ~ was expanded ! if( klen != i - last ) { int d = klen - (i - last); memmove(out + i + d, out + i, len - i + 1); len += d; i += d; } // replace the component with proper case memcpy(out + last, data.cFileName, klen + 1); FindClose(handle); } // if we're not at the end, restore the path if (i < len) out[i] = sep; // advance i++; last = i; } return caml_copy_string(out); #else return path; #endif } #ifndef _WIN32 #define TimeSpecToSeconds(ts) (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0 #endif CAMLprim value sys_time() { #ifdef _WIN32 #define EPOCH_DIFF (134774*24*60*60.0) static LARGE_INTEGER freq; static int freq_init = -1; LARGE_INTEGER counter; if( freq_init == -1 ) freq_init = QueryPerformanceFrequency(&freq); if( !freq_init || !QueryPerformanceCounter(&counter) ) { SYSTEMTIME t; FILETIME ft; ULARGE_INTEGER ui; GetSystemTime(&t); if( !SystemTimeToFileTime(&t,&ft) ) failwith("sys_cpu_time"); ui.LowPart = ft.dwLowDateTime; ui.HighPart = ft.dwHighDateTime; return caml_copy_double( ((double)ui.QuadPart) / 10000000.0 - EPOCH_DIFF ); } return caml_copy_double( ((double)counter.QuadPart) / ((double)freq.QuadPart) ); #elif __APPLE__ uint64_t time; uint64_t elapsedNano; static mach_timebase_info_data_t sTimebaseInfo; time = mach_absolute_time(); if ( sTimebaseInfo.denom == 0 ) { (void) mach_timebase_info(&sTimebaseInfo); } elapsedNano = time * sTimebaseInfo.numer / sTimebaseInfo.denom; return caml_copy_double(time / 1000000000.0); #elif defined CLOCK_MONOTONIC_RAW struct timespec t; clock_gettime(CLOCK_MONOTONIC_RAW, &t); return caml_copy_double(TimeSpecToSeconds(t)); #else struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return caml_copy_double(TimeSpecToSeconds(t)); #endif } CAMLprim value sys_getch( value b ) { # ifdef _WIN32 return Val_int( Bool_val(b)?getche():getch() ); # else // took some time to figure out how to do that // without relying on ncurses, which clear the // terminal on initscr() int c; struct termios term, old; tcgetattr(fileno(stdin), &old); term = old; cfmakeraw(&term); tcsetattr(fileno(stdin), 0, &term); c = getchar(); tcsetattr(fileno(stdin), 0, &old); if( Bool_val(b) ) fputc(c,stdout); return Val_int(c); # endif } CAMLprim value sys_filetime( value file ) { # ifdef _WIN32 FILETIME fp; ULARGE_INTEGER ui; HANDLE h = CreateFile(String_val(file),FILE_READ_ATTRIBUTES,FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL); if( h == INVALID_HANDLE_VALUE || !GetFileTime(h,NULL,NULL,&fp) ) { CloseHandle(h); return caml_copy_double(0.); } CloseHandle(h); ui.LowPart = fp.dwLowDateTime; ui.HighPart = fp.dwHighDateTime; return caml_copy_double( ((double)ui.QuadPart) / 10000000.0 - EPOCH_DIFF ); # else struct stat sbuf; if( stat(String_val(file),&sbuf) < 0 ) return caml_copy_double(0.); return caml_copy_double( sbuf.st_mtime ); # endif }