#if defined(__sun) #define _POSIX_PTHREAD_SEMANTICS #endif #include #include #include #include #include #include "prolog.h" #include "query.h" #ifndef _WIN32 #include #include #endif #if !defined(_WIN32) && !defined(__wasi__) #define USE_SYSLOG 1 #include #endif #if !defined(_WIN32) && !defined(__wasi__) #define USE_POSIX_FILES 1 #include #include #endif #ifdef _WIN32 #define ctime_r(p1,p2) ctime(p1) #define gmtime_r(p1,p2) gmtime(p1) #define localtime_r(p1,p2) localtime(p1) #endif static bool bif_posix_strftime_3(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p3,var); GET_NEXT_ARG(p2,compound); if ((p2->val_off != g_tm_s) || (p2->arity != 9)) { return false; } const char *format = C_STR(q, p1); size_t length = C_STRLEN(q, p1); // XXX: Is this check reasonable? May strftime() return non-empty // result for empty format? if (length == 0) { cell tmp; make_atom(&tmp, g_empty_s); return unify(q, p3, p3_ctx, &tmp, q->st.cur_ctx); } struct tm tm = {0}; cell *arg; arg = deref(q, p2+1, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_sec = get_smallint(arg); arg = deref(q, p2+2, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_min = get_smallint(arg); arg = deref(q, p2+3, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_hour = get_smallint(arg); arg = deref(q, p2+4, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_mday = get_smallint(arg); arg = deref(q, p2+5, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_mon = get_smallint(arg); arg = deref(q, p2+6, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_year = get_smallint(arg); arg = deref(q, p2+7, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_wday = get_smallint(arg); arg = deref(q, p2+8, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_yday = get_smallint(arg); arg = deref(q, p2+9, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_isdst = get_smallint(arg); char *buffer = NULL; int tries = 0; const int max_tries = 5; while (++tries <= max_tries) { // make enough space for some long formats, e.g. `%c' length = 128 + length * 2; buffer = TPL_realloc(buffer, length); // FIXME: `0' returned by strftime() does not always indicate // an error, seems there is no easy way to check that. if (strftime(buffer, length, format, &tm) > 0) { cell tmp; make_string(&tmp, buffer); TPL_free(buffer); bool ok = unify(q, p3, p3_ctx, &tmp, q->st.cur_ctx); unshare_cell(&tmp); return ok; } } TPL_free(buffer); return false; } #if !defined(_WIN32) && !defined(__riscos__) static bool bif_posix_strptime_3(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,string); GET_NEXT_ARG(p3,var); struct tm tm = {0}; if (strptime(C_STR(q, p2), C_STR(q, p1), &tm) == NULL) return false; cell *tmp = alloc_heap(q, 10); make_instr(tmp, g_tm_s, NULL, 9, 0); pl_idx num_cells = 1; make_int(tmp+num_cells++, tm.tm_sec); make_int(tmp+num_cells++, tm.tm_min); make_int(tmp+num_cells++, tm.tm_hour); make_int(tmp+num_cells++, tm.tm_mday); make_int(tmp+num_cells++, tm.tm_mon); make_int(tmp+num_cells++, tm.tm_year); make_int(tmp+num_cells++, tm.tm_wday); make_int(tmp+num_cells++, tm.tm_yday); make_int(tmp+num_cells++, tm.tm_isdst); return unify(q, p3, p3_ctx, tmp, q->st.cur_ctx); } #endif static bool bif_posix_mktime_2(query *q) { GET_FIRST_ARG(p1,compound); GET_NEXT_ARG(p2,var); if ((p1->val_off != g_tm_s) || (p1->arity != 9)) { return false; } struct tm tm = {0}; cell *arg; arg = deref(q, p1+1, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_sec = get_smallint(arg); arg = deref(q, p1+2, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_min = get_smallint(arg); arg = deref(q, p1+3, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_hour = get_smallint(arg); arg = deref(q, p1+4, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_mday = get_smallint(arg); arg = deref(q, p1+5, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_mon = get_smallint(arg); arg = deref(q, p1+6, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_year = get_smallint(arg); arg = deref(q, p1+7, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_wday = get_smallint(arg); arg = deref(q, p1+8, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_yday = get_smallint(arg); arg = deref(q, p1+9, p2_ctx); if (!is_smallint(arg)) return throw_error(q, arg, p2_ctx, "type_error", "integer"); tm.tm_isdst = get_smallint(arg); time_t now = mktime(&tm); cell tmp; make_int(&tmp, now); return unify(q, p2, p2_ctx, &tmp, q->st.cur_ctx); } static bool bif_posix_gmtime_2(query *q) { GET_FIRST_ARG(p1,integer); GET_NEXT_ARG(p2,var); time_t t = get_smallint(p1); struct tm tm = {0}; if (gmtime_r(&t, &tm) == NULL) return 0; cell *tmp = alloc_heap(q, 10); make_instr(tmp, g_tm_s, NULL, 9, 0); pl_idx num_cells = 1; make_int(tmp+num_cells++, tm.tm_sec); make_int(tmp+num_cells++, tm.tm_min); make_int(tmp+num_cells++, tm.tm_hour); make_int(tmp+num_cells++, tm.tm_mday); make_int(tmp+num_cells++, tm.tm_mon); make_int(tmp+num_cells++, tm.tm_year); make_int(tmp+num_cells++, tm.tm_wday); make_int(tmp+num_cells++, tm.tm_yday); make_int(tmp+num_cells++, tm.tm_isdst); return unify(q, p2, p2_ctx, tmp, q->st.cur_ctx); } static bool bif_posix_localtime_2(query *q) { GET_FIRST_ARG(p1,integer); GET_NEXT_ARG(p2,var); time_t t = get_smallint(p1); struct tm tm = {0}; if (localtime_r(&t, &tm) == NULL) return 0; cell *tmp = alloc_heap(q, 10); make_instr(tmp, g_tm_s, NULL, 9, 0); pl_idx num_cells = 1; make_int(tmp+num_cells++, tm.tm_sec); make_int(tmp+num_cells++, tm.tm_min); make_int(tmp+num_cells++, tm.tm_hour); make_int(tmp+num_cells++, tm.tm_mday); make_int(tmp+num_cells++, tm.tm_mon); make_int(tmp+num_cells++, tm.tm_year); make_int(tmp+num_cells++, tm.tm_wday); make_int(tmp+num_cells++, tm.tm_yday); make_int(tmp+num_cells++, tm.tm_isdst); return unify(q, p2, p2_ctx, tmp, q->st.cur_ctx); } static bool bif_posix_ctime_2(query *q) { GET_FIRST_ARG(p1,integer); GET_NEXT_ARG(p2,var); time_t when = get_smallint(p1); char tmpbuf[256]; cell tmp; make_cstring(&tmp, ctime_r(&when, tmpbuf)); bool ok = unify(q, p2, p2_ctx, &tmp, q->st.cur_ctx); unshare_cell(&tmp); return ok; } static bool bif_posix_time_1(query *q) { GET_FIRST_ARG(p1,var); cell tmp; make_int(&tmp, time(NULL)); return unify(q, p1, p1_ctx, &tmp, q->st.cur_ctx); } static bool bif_posix_getpid_1(query *q) { GET_FIRST_ARG(p1,var); cell tmp; #ifndef __wasi__ make_int(&tmp, getpid()); #else make_int(&tmp, -1); #endif return unify(q, p1, p1_ctx, &tmp, q->st.cur_ctx); } static bool bif_posix_getppid_1(query *q) { GET_FIRST_ARG(p1,var); cell tmp; #if !defined(_WIN32) && !defined(__wasi__) make_int(&tmp, getppid()); #else make_int(&tmp, -1); #endif return unify(q, p1, p1_ctx, &tmp, q->st.cur_ctx); } static bool bif_posix_fork_1(query *q) { GET_FIRST_ARG(p1,var); cell tmp; #if !defined(_WIN32) && !defined(__wasi__) signal(SIGCHLD, SIG_IGN); int pid = fork(); make_int(&tmp, pid); #else make_int(&tmp, -1); #endif return unify(q, p1, p1_ctx, &tmp, q->st.cur_ctx); } // --- syslog ------------------------------------------------------------ // // The LOG_* values are platform-specific, so the symbolic names SWI's // library(syslog) uses are mapped here rather than in Prolog. Anything // the platform does not define is simply absent from the table and is // reported as a domain_error. #if USE_SYSLOG typedef struct { const char *name; int val; } syslog_name; static const syslog_name s_syslog_facilities[] = { {"auth", LOG_AUTH}, #ifdef LOG_AUTHPRIV {"authpriv", LOG_AUTHPRIV}, #endif {"cron", LOG_CRON}, {"daemon", LOG_DAEMON}, #ifdef LOG_FTP {"ftp", LOG_FTP}, #endif {"kern", LOG_KERN}, {"local0", LOG_LOCAL0}, {"local1", LOG_LOCAL1}, {"local2", LOG_LOCAL2}, {"local3", LOG_LOCAL3}, {"local4", LOG_LOCAL4}, {"local5", LOG_LOCAL5}, {"local6", LOG_LOCAL6}, {"local7", LOG_LOCAL7}, {"lpr", LOG_LPR}, {"mail", LOG_MAIL}, {"news", LOG_NEWS}, {"syslog", LOG_SYSLOG}, {"user", LOG_USER}, {"uucp", LOG_UUCP}, {NULL, 0} }; static const syslog_name s_syslog_priorities[] = { {"emerg", LOG_EMERG}, {"alert", LOG_ALERT}, {"crit", LOG_CRIT}, {"err", LOG_ERR}, {"warning", LOG_WARNING}, {"notice", LOG_NOTICE}, {"info", LOG_INFO}, {"debug", LOG_DEBUG}, {NULL, 0} }; static const syslog_name s_syslog_options[] = { {"cons", LOG_CONS}, {"ndelay", LOG_NDELAY}, {"nowait", LOG_NOWAIT}, {"odelay", LOG_ODELAY}, #ifdef LOG_PERROR {"perror", LOG_PERROR}, #endif {"pid", LOG_PID}, {NULL, 0} }; static bool syslog_lookup(const syslog_name *tbl, const char *name, int *val) { for (const syslog_name *p = tbl; p->name; p++) { if (!strcmp(p->name, name)) { *val = p->val; return true; } } return false; } // openlog(3) stores the pointer it is given, it does not copy the // string, so the ident has to outlive the call. Keeping our own copy is // not optional: the Prolog atom's storage can move or be reclaimed, and // syslog would then read freed memory on every message. static char *g_syslog_ident = NULL; #endif static bool bif_sys_openlog_3(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,list_or_nil); GET_NEXT_ARG(p3,atom); #if USE_SYSLOG int facility = 0; if (!syslog_lookup(s_syslog_facilities, C_STR(q, p3), &facility)) return throw_error(q, p3, p3_ctx, "domain_error", "syslog_facility"); int mask = 0; PROLOG_LIST_HANDLER(p2); while (is_list(p2)) { cell *h = PROLOG_LIST_HEAD(p2); cell *c = deref(q, h, p2_ctx); pl_ctx c_ctx = q->latest_ctx; if (!is_atom(c)) return throw_error(q, c, c_ctx, "type_error", "atom"); int opt = 0; if (!syslog_lookup(s_syslog_options, C_STR(q, c), &opt)) return throw_error(q, c, c_ctx, "domain_error", "syslog_option"); mask |= opt; p2 = PROLOG_LIST_TAIL(p2); p2 = deref(q, p2, p2_ctx); p2_ctx = q->latest_ctx; } if (!is_nil(p2)) return throw_error(q, p2, p2_ctx, "type_error", "list"); char *ident = strdup(C_STR(q, p1)); CHECKED(ident); openlog(ident, mask, facility); // Freed only after the new one is in place, since the old pointer is // live until openlog() replaces it. free(g_syslog_ident); g_syslog_ident = ident; return true; #else return throw_error(q, p1, p1_ctx, "resource_error", "syslog_unavailable"); #endif } static bool bif_sys_syslog_2(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,any); #if USE_SYSLOG int pri = 0; if (!syslog_lookup(s_syslog_priorities, C_STR(q, p1), &pri)) return throw_error(q, p1, p1_ctx, "domain_error", "syslog_priority"); const char *src; char *tofree = NULL; if (is_atom(p2) || is_string(p2)) { src = C_STR(q, p2); } else if (is_nil(p2)) { src = ""; } else if (is_iso_list(p2)) { if (!scan_is_chars_list(q, p2, p2_ctx, true)) return throw_error(q, p2, p2_ctx, "type_error", "text"); tofree = chars_list_to_string(q, p2, p2_ctx); CHECKED(tofree); src = tofree; } else return throw_error(q, p2, p2_ctx, "type_error", "text"); // "%s" rather than passing the message as the format itself - a // message carrying %s or %n would otherwise read arbitrary memory. syslog(pri, "%s", src); if (tofree) free(tofree); return true; #else return throw_error(q, p1, p1_ctx, "resource_error", "syslog_unavailable"); #endif } static bool bif_sys_closelog_0(query *q) { #if USE_SYSLOG closelog(); free(g_syslog_ident); g_syslog_ident = NULL; return true; #else return throw_error(q, q->st.instr, q->st.cur_ctx, "resource_error", "syslog_unavailable"); #endif } // --- filesystem --------------------------------------------------------- // // Thin wrappers over the POSIX calls that library(filesex) needs and // Trealla did not already have. Anything that is only path arithmetic - // directory_file_path/3, relative_file_name/3 - is done in Prolog and is // deliberately absent here. // // POSIX only. Windows and WASI get resource_error rather than a silent // no-op, so a caller finds out instead of believing the link was made. #if USE_POSIX_FILES static bool posix_file_error(query *q, cell *c, pl_ctx c_ctx, const char *op) { // The errno name is carried through so a caller can tell a missing // path from a permission problem, as with the socket layer. const char *name; switch (errno) { case EACCES: name = "eacces"; break; case EEXIST: name = "eexist"; break; case EINVAL: name = "einval"; break; case EISDIR: name = "eisdir"; break; case ELOOP: name = "eloop"; break; case EMLINK: name = "emlink"; break; case ENAMETOOLONG: name = "enametoolong"; break; case ENOENT: name = "enoent"; break; case ENOSPC: name = "enospc"; break; case ENOTDIR: name = "enotdir"; break; case ENOTEMPTY: name = "enotempty"; break; case EPERM: name = "eperm"; break; case EROFS: name = "erofs"; break; case EXDEV: name = "exdev"; break; default: name = "unknown"; break; } cell tmp; make_atom(&tmp, new_atom(q->pl, name)); (void) op; return throw_error2(q, c, c_ctx, "file_error", name, &tmp); } #endif #define POSIX_FILES_GUARD(q, c, c_ctx) \ return throw_error(q, c, c_ctx, "resource_error", "posix_files_unavailable") static bool bif_posix_rmdir_1(query *q) { GET_FIRST_ARG(p1,atom); #if USE_POSIX_FILES if (rmdir(C_STR(q, p1))) return posix_file_error(q, p1, p1_ctx, "rmdir"); return true; #else POSIX_FILES_GUARD(q, p1, p1_ctx); #endif } // delete_file/1 checks the file exists first, which a *dangling* // symlink does not - and a recursive delete creates dangling links as it // goes, by removing a target before the link to it. unlink(2) removes // the link itself and does not care what it points at. static bool bif_posix_unlink_1(query *q) { GET_FIRST_ARG(p1,atom); #if USE_POSIX_FILES if (unlink(C_STR(q, p1))) return posix_file_error(q, p1, p1_ctx, "unlink"); return true; #else POSIX_FILES_GUARD(q, p1, p1_ctx); #endif } static bool bif_posix_link_2(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,atom); #if USE_POSIX_FILES if (link(C_STR(q, p1), C_STR(q, p2))) return posix_file_error(q, p2, p2_ctx, "link"); return true; #else POSIX_FILES_GUARD(q, p1, p1_ctx); #endif } static bool bif_posix_symlink_2(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,atom); #if USE_POSIX_FILES if (symlink(C_STR(q, p1), C_STR(q, p2))) return posix_file_error(q, p2, p2_ctx, "symlink"); return true; #else POSIX_FILES_GUARD(q, p1, p1_ctx); #endif } static bool bif_posix_readlink_2(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,var); #if USE_POSIX_FILES char buf[4096]; ssize_t n = readlink(C_STR(q, p1), buf, sizeof(buf) - 1); if (n < 0) return posix_file_error(q, p1, p1_ctx, "readlink"); buf[n] = '\0'; cell tmp; make_cstring(&tmp, buf); return unify(q, p2, p2_ctx, &tmp, q->st.cur_ctx); #else POSIX_FILES_GUARD(q, p1, p1_ctx); #endif } // The mode is the permission bits only, as an integer - the symbolic // specs chmod/2 accepts are parsed in Prolog. // Resolves symlinks, so a recursive walk can tell it has already been // somewhere and not spin on a link that points back up its own tree. static bool bif_posix_realpath_2(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,var); #if USE_POSIX_FILES char *real = realpath(C_STR(q, p1), NULL); if (!real) return posix_file_error(q, p1, p1_ctx, "realpath"); cell tmp; make_cstring(&tmp, real); free(real); return unify(q, p2, p2_ctx, &tmp, q->st.cur_ctx); #else POSIX_FILES_GUARD(q, p1, p1_ctx); #endif } static bool bif_posix_chmod_2(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,integer); #if USE_POSIX_FILES if (chmod(C_STR(q, p1), (mode_t)(get_smallint(p2) & 07777))) return posix_file_error(q, p1, p1_ctx, "chmod"); return true; #else POSIX_FILES_GUARD(q, p1, p1_ctx); #endif } static bool bif_posix_file_mode_2(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,var); #if USE_POSIX_FILES struct stat st; if (stat(C_STR(q, p1), &st)) return posix_file_error(q, p1, p1_ctx, "stat"); cell tmp; make_int(&tmp, st.st_mode & 07777); return unify(q, p2, p2_ctx, &tmp, q->st.cur_ctx); #else POSIX_FILES_GUARD(q, p1, p1_ctx); #endif } // lstat, not stat: a symlink must report as a symlink or a recursive // delete would follow it out of the tree it was asked to remove. static bool bif_posix_file_type_2(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,var); #if USE_POSIX_FILES struct stat st; if (lstat(C_STR(q, p1), &st)) return posix_file_error(q, p1, p1_ctx, "lstat"); const char *type; switch (st.st_mode & S_IFMT) { case S_IFREG: type = "regular"; break; case S_IFDIR: type = "directory"; break; case S_IFLNK: type = "symlink"; break; case S_IFIFO: type = "fifo"; break; case S_IFSOCK: type = "socket"; break; case S_IFCHR: type = "char_device"; break; case S_IFBLK: type = "block_device"; break; default: type = "unknown"; break; } cell tmp; make_atom(&tmp, new_atom(q->pl, type)); return unify(q, p2, p2_ctx, &tmp, q->st.cur_ctx); #else POSIX_FILES_GUARD(q, p1, p1_ctx); #endif } // Access, modified and changed times as floating-point seconds, which is // how set_time_file/3 reports them. static bool bif_posix_file_times_4(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,var); GET_NEXT_ARG(p3,var); GET_NEXT_ARG(p4,var); #if USE_POSIX_FILES struct stat st; if (stat(C_STR(q, p1), &st)) return posix_file_error(q, p1, p1_ctx, "stat"); cell tmp; make_float(&tmp, (double)st.st_atime); if (!unify(q, p2, p2_ctx, &tmp, q->st.cur_ctx)) return false; make_float(&tmp, (double)st.st_mtime); if (!unify(q, p3, p3_ctx, &tmp, q->st.cur_ctx)) return false; make_float(&tmp, (double)st.st_ctime); return unify(q, p4, p4_ctx, &tmp, q->st.cur_ctx); #else POSIX_FILES_GUARD(q, p1, p1_ctx); #endif } // There is no portable way to set the changed time - it is maintained by // the kernel - so set_time_file/3 rejects changed() rather than pretend. static bool bif_posix_set_file_times_3(query *q) { GET_FIRST_ARG(p1,atom); GET_NEXT_ARG(p2,number); GET_NEXT_ARG(p3,number); #if USE_POSIX_FILES double atime = is_float(p2) ? get_float(p2) : (double)get_smallint(p2); double mtime = is_float(p3) ? get_float(p3) : (double)get_smallint(p3); struct timeval tv[2]; tv[0].tv_sec = (time_t)atime; tv[0].tv_usec = (suseconds_t)((atime - (double)(time_t)atime) * 1000000); tv[1].tv_sec = (time_t)mtime; tv[1].tv_usec = (suseconds_t)((mtime - (double)(time_t)mtime) * 1000000); if (utimes(C_STR(q, p1), tv)) return posix_file_error(q, p1, p1_ctx, "utimes"); return true; #else POSIX_FILES_GUARD(q, p1, p1_ctx); #endif } builtins g_posix_bifs[] = { {"posix_strftime", 3, bif_posix_strftime_3, "+atom,-atom,+compound", false, false, BLAH}, #if !defined(_WIN32) && !defined(__riscos__) {"posix_strptime", 3, bif_posix_strptime_3, "+atom,+atom,-compound", false, false, BLAH}, #endif {"posix_gmtime", 2, bif_posix_gmtime_2, "+integer,-compound", false, false, BLAH}, {"posix_localtime", 2, bif_posix_localtime_2, "+integer,-compound", false, false, BLAH}, {"posix_mktime", 2, bif_posix_mktime_2, "+compound,-integer", false, false, BLAH}, {"posix_ctime", 2, bif_posix_ctime_2, "+integer,-atom", false, false, BLAH}, {"posix_time", 1, bif_posix_time_1, "-integer", false, false, BLAH}, {"posix_getppid", 1, bif_posix_getppid_1, "-integer", false, false, BLAH}, {"posix_getpid", 1, bif_posix_getpid_1, "-integer", false, false, BLAH}, {"posix_fork", 1, bif_posix_fork_1, "-integer", false, false, BLAH}, {"posix_rmdir", 1, bif_posix_rmdir_1, "+atom", false, false, BLAH}, {"posix_unlink", 1, bif_posix_unlink_1, "+atom", false, false, BLAH}, {"posix_link", 2, bif_posix_link_2, "+atom,+atom", false, false, BLAH}, {"posix_symlink", 2, bif_posix_symlink_2, "+atom,+atom", false, false, BLAH}, {"posix_readlink", 2, bif_posix_readlink_2, "+atom,-atom", false, false, BLAH}, {"posix_realpath", 2, bif_posix_realpath_2, "+atom,-atom", false, false, BLAH}, {"posix_chmod", 2, bif_posix_chmod_2, "+atom,+integer", false, false, BLAH}, {"posix_file_mode", 2, bif_posix_file_mode_2, "+atom,-integer", false, false, BLAH}, {"posix_file_type", 2, bif_posix_file_type_2, "+atom,-atom", false, false, BLAH}, {"posix_file_times", 4, bif_posix_file_times_4, "+atom,-float,-float,-float", false, false, BLAH}, {"posix_set_file_times", 3, bif_posix_set_file_times_3, "+atom,+number,+number", false, false, BLAH}, {"$openlog", 3, bif_sys_openlog_3, "+atom,+list,+atom", false, false, BLAH}, {"$syslog", 2, bif_sys_syslog_2, "+atom,+term", false, false, BLAH}, {"$closelog", 0, bif_sys_closelog_0, NULL, false, false, BLAH}, // For Logtalk... {"pid", 1, bif_posix_getpid_1, "-integer", false, false, BLAH}, {0} };