util.cxx - systemtap
Global variables defined
Functions defined
Macros defined
Source code
#include "util.h"
#include "stap-probe.h"
#include <stdexcept>
#include <cerrno>
#include <map>
#include <set>
#include <string>
#include <fstream>
#include <cassert>
#include <ext/stdio_filebuf.h>
extern "C" {
#include <elf.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <regex.h>
#include <stdarg.h>
#ifndef SINGLE_THREADED
#include <pthread.h>
#endif
}
using namespace std;
using namespace __gnu_cxx;
const char *
get_home_directory(void)
{
const char *p = getenv("HOME");
if (p)
return p;
struct passwd *pwd = getpwuid(getuid());
if (pwd)
return pwd->pw_dir;
cerr << _("Unable to determine home directory") << endl;
return "/";
}
size_t
get_file_size(const string &path)
{
struct stat file_info;
if (stat(path.c_str(), &file_info) == 0)
return file_info.st_size;
else
return 0;
}
size_t
get_file_size(int fd)
{
struct stat file_info;
if (fstat(fd, &file_info) == 0)
return file_info.st_size;
else
return 0;
}
bool
file_exists (const string &path)
{
struct stat file_info;
if (stat(path.c_str(), &file_info) == 0)
return true;
return false;
}
bool
copy_file(const string& src, const string& dest, bool verbose)
{
int fd1, fd2;
char buf[10240];
int n;
string tmp;
char *tmp_name;
mode_t mask;
if (verbose)
clog << _F("Copying %s to %s", src.c_str(), dest.c_str()) << endl;
fd1 = open(src.c_str(), O_RDONLY);
if (fd1 == -1)
goto error;
tmp = dest + string(".XXXXXX");
tmp_name = (char *)tmp.c_str();
fd2 = mkstemp(tmp_name);
if (fd2 == -1)
{
close(fd1);
goto error;
}
while ((n = read(fd1, buf, sizeof(buf))) > 0)
{
if (write(fd2, buf, n) != n)
{
close(fd2);
close(fd1);
unlink(tmp_name);
goto error;
}
}
close(fd1);
mask = umask(0);
fchmod(fd2, 0666 & ~mask);
umask(mask);
if (close(fd2) == -1)
{
unlink(tmp_name);
goto error;
}
unlink(dest.c_str());
if (rename(tmp_name, dest.c_str()) == -1)
{
unlink(tmp_name);
goto error;
}
return true;
error:
cerr << _F("Copy failed (\"%s\" to \"%s\"): %s", src.c_str(),
dest.c_str(), strerror(errno)) << endl;
return false;
}
int
create_dir(const char *dir, int mode)
{
struct stat st;
if (stat(dir, &st) == 0)
{
if (S_ISDIR(st.st_mode))
return 0;
errno = ENOTDIR;
return 1;
}
vector<string> components;
tokenize (dir, components, "/");
string path;
if (*dir == '/')
{
path = "/";
}
unsigned limit = components.size ();
assert (limit != 0);
for (unsigned ix = 0; ix < limit; ++ix)
{
path += components[ix] + '/';
if (mkdir(path.c_str (), mode) != 0 && errno != EEXIST)
return 1;
}
return 0;
}
int
remove_file_or_dir (const char *name)
{
int rc;
struct stat st;
if ((rc = stat(name, &st)) != 0)
{
if (errno == ENOENT)
return 0;
return 1;
}
if (remove (name) != 0)
return 1;
return 0;
}
gid_t get_gid (const char *group_name)
{
struct group *stgr;
stgr = getgrnam(group_name);
if (stgr == NULL)
return (gid_t)-1;
return stgr->gr_gid;
}
bool
in_group_id (gid_t target_gid)
{
if (target_gid == getegid())
return true;
int ngids = getgroups(0, 0); if (ngids > 0) {
gid_t gidlist[ngids];
ngids = getgroups(ngids, gidlist);
for (int i = 0; i < ngids; i++) {
if (gidlist[i] == target_gid)
return true;
}
}
if (ngids < 0) {
cerr << _("Unable to retrieve group list") << endl;
return false;
}
return false;
}
string
getmemusage ()
{
static long sz = sysconf(_SC_PAGESIZE);
long pages;
ostringstream oss;
ifstream statm("/proc/self/statm");
statm >> pages;
long kb1 = pages * sz / 1024; statm >> pages;
long kb2 = pages * sz / 1024; statm >> pages;
long kb3 = pages * sz / 1024; statm >> pages;
long kb4 = pages * sz / 1024; statm >> pages;
(void) kb4;
long kb5 = pages * sz / 1024; statm >> pages;
(void) kb5;
long kb6 = pages * sz / 1024; statm >> pages;
long kb7 = pages * sz / 1024; (void) kb7;
oss << _F("using %ldvirt/%ldres/%ldshr/%lddata kb, ", kb1, kb2, kb3, kb6);
return oss.str();
}
void
tokenize(const string& str, vector<string>& tokens,
const string& delimiters)
{
string::size_type lastPos = str.find_first_not_of(delimiters, 0);
string::size_type pos = str.find_first_of(delimiters, lastPos);
while (pos != string::npos || lastPos != string::npos)
{
tokens.push_back(str.substr(lastPos, pos - lastPos));
lastPos = str.find_first_not_of(delimiters, pos);
pos = str.find_first_of(delimiters, lastPos);
}
}
void
tokenize_full(const string& str, vector<string>& tokens,
const string& delimiters)
{
if (str.size() <= 1)
return;
string::size_type lastPos = 0;
string::size_type pos = str.find_first_of(delimiters, lastPos);
if (pos == string::npos)
return;
if (pos == lastPos)
++lastPos;
assert (lastPos < str.size());
do
{
pos = str.find_first_of(delimiters, lastPos);
if (pos == string::npos)
break; tokens.push_back(str.substr (lastPos, pos - lastPos));
lastPos = pos + 1;
}
while (lastPos < str.size());
if (lastPos < str.size())
{
assert (pos == string::npos);
tokens.push_back(str.substr (lastPos));
}
}
void
tokenize_cxx(const string& str, vector<string>& tokens)
{
int angle_count = 0;
string::size_type pos = 0;
string::size_type colon_pos = str.find("::");
string::size_type angle_pos = str.find_first_of("<>");
while (colon_pos != string::npos &&
(angle_count == 0 || angle_pos != string::npos))
{
if (angle_count > 0 || angle_pos < colon_pos)
{
angle_count += str.at(angle_pos) == '<' ? 1 : -1;
colon_pos = str.find("::", angle_pos + 1);
angle_pos = str.find_first_of("<>", angle_pos + 1);
}
else
{
tokens.push_back(str.substr(pos, colon_pos - pos));
pos = colon_pos + 2;
colon_pos = str.find("::", pos);
angle_pos = str.find_first_of("<>", pos);
}
}
tokens.push_back(str.substr(pos));
}
vector<pair<const char*,int> >
split_lines(const char *buf, size_t n)
{
vector<pair<const char*,int> > lines;
const char *eol, *line;
line = eol = buf;
while ((size_t)(eol-buf) < n)
{
if (*eol == '\n' || *eol == '\0')
{
lines.push_back(make_pair(line, eol-line+1));
line = ++eol;
}
else
eol++;
}
if (eol > line)
lines.push_back(make_pair(line, eol-line));
return lines;
}
string find_executable(const string& name)
{
const map<string, string> sysenv;
return find_executable(name, "", sysenv);
}
string find_executable(const string& name, const string& sysroot,
const map<string, string>& sysenv,
const string& env_path)
{
string retpath;
if (name.size() == 0)
return name;
struct stat st;
if (name.find('/') != string::npos) {
retpath = sysroot + name;
}
else {
const char *path;
if (sysenv.count(env_path) != 0)
path = sysenv.find(env_path)->second.c_str();
else
path = getenv(env_path.c_str());
if (path)
{
vector<string> dirs;
tokenize(string(path), dirs, string(":"));
for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); i++)
{
string fname = sysroot + *i + "/" + name;
const char *f = fname.c_str();
if (access(f, X_OK) == 0
&& stat(f, &st) == 0
&& S_ISREG(st.st_mode))
{
retpath = fname;
break;
}
}
}
}
if (retpath == "")
retpath = sysroot + name;
string scf = resolve_path(retpath);
if (!startswith(scf, sysroot))
throw runtime_error(_F("find_executable(): file %s not in sysroot %s",
scf.c_str(), sysroot.c_str()));
return scf;
}
bool is_fully_resolved(const string& path, const string& sysroot,
const map<string, string>& sysenv,
const string& env_path)
{
return !path.empty()
&& !contains_glob_chars(path)
&& path.find('/') != string::npos
&& path == find_executable(path, sysroot, sysenv, env_path);
}
const string cmdstr_quoted(const string& cmd)
{
string quoted_cmd;
string quote("'");
string replace("'\"'\"'");
string::size_type pos = 0;
quoted_cmd += quote;
for (string::size_type quote_pos = cmd.find(quote, pos);
quote_pos != string::npos;
quote_pos = cmd.find(quote, pos)) {
quoted_cmd += cmd.substr(pos, quote_pos - pos);
quoted_cmd += replace;
pos = quote_pos + 1;
}
quoted_cmd += cmd.substr(pos, cmd.length() - pos);
quoted_cmd += quote;
return quoted_cmd;
}
const string
cmdstr_join(const vector<string>& cmds)
{
if (cmds.empty())
throw runtime_error(_("cmdstr_join called with an empty command!"));
stringstream cmd;
cmd << cmdstr_quoted(cmds[0]);
for (size_t i = 1; i < cmds.size(); ++i)
cmd << " " << cmdstr_quoted(cmds[i]);
return cmd.str();
}
class spawned_pids_t {
private:
set<pid_t> pids;
#ifndef SINGLE_THREADED
pthread_mutex_t mux_pids;
#endif
public:
bool contains (pid_t p)
{
stap_sigmasker masked;
#ifndef SINGLE_THREADED
pthread_mutex_lock(&mux_pids);
#endif
bool ret = (pids.count(p)==0) ? true : false;
#ifndef SINGLE_THREADED
pthread_mutex_unlock(&mux_pids);
#endif
return ret;
}
bool insert (pid_t p)
{
stap_sigmasker masked;
#ifndef SINGLE_THREADED
pthread_mutex_lock(&mux_pids);
#endif
bool ret = (p > 0) ? pids.insert(p).second : false;
#ifndef SINGLE_THREADED
pthread_mutex_unlock(&mux_pids);
#endif
return ret;
}
void erase (pid_t p)
{
stap_sigmasker masked;
#ifndef SINGLE_THREADED
pthread_mutex_lock(&mux_pids);
#endif
pids.erase(p);
#ifndef SINGLE_THREADED
pthread_mutex_unlock(&mux_pids);
#endif
}
int killall (int sig)
{
int ret = 0;
stap_sigmasker masked;
#ifndef SINGLE_THREADED
pthread_mutex_lock(&mux_pids);
#endif
for (set<pid_t>::const_iterator it = pids.begin();
it != pids.end(); ++it)
ret = kill(*it, sig) ?: ret;
#ifndef SINGLE_THREADED
pthread_mutex_unlock(&mux_pids);
#endif
return ret;
}
spawned_pids_t()
{
#ifndef SINGLE_THREADED
pthread_mutex_init(&mux_pids, NULL);
#endif
}
~spawned_pids_t()
{
#ifndef SINGLE_THREADED
pthread_mutex_destroy (&mux_pids);
#endif
}
};
static spawned_pids_t spawned_pids;
int
stap_waitpid(int verbose, pid_t pid)
{
int ret, status;
if (verbose > 1 && spawned_pids.contains(pid))
clog << _F("Spawn waitpid call on unmanaged pid %d", pid) << endl;
ret = waitpid(pid, &status, 0);
if (ret == pid)
{
spawned_pids.erase(pid);
ret = WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status);
if (verbose > 1)
clog << _F("Spawn waitpid result (0x%x): %d", status, ret) << endl;
}
else
{
if (verbose > 1)
clog << _F("Spawn waitpid error (%d): %s", ret, strerror(errno)) << endl;
ret = -1;
}
PROBE2(stap, stap_system__complete, ret, pid);
return ret;
}
static int
pipe_child_fd(posix_spawn_file_actions_t* fa, int pipefd[2], int childfd)
{
if (pipe(pipefd))
return -1;
int dir = childfd ? 1 : 0;
if (!fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) &&
!fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) &&
!posix_spawn_file_actions_adddup2(fa, pipefd[dir], childfd))
return 0;
close(pipefd[0]);
close(pipefd[1]);
return -1;
}
static int
null_child_fd(posix_spawn_file_actions_t* fa, int childfd)
{
int flags = childfd ? O_WRONLY : O_RDONLY;
return posix_spawn_file_actions_addopen(fa, childfd, "/dev/null", flags, 0);
}
pid_t
stap_spawn(int verbose, const vector<string>& args,
posix_spawn_file_actions_t* fa, const vector<string>& envVec)
{
string::const_iterator it;
it = args[0].begin();
string command;
if(*it == '/' && (access(args[0].c_str(), X_OK)==-1)) XXX clog << _F("WARNING: %s is not executable (%s)", args[0].c_str(), strerror(errno)) << endl;
for (size_t i = 0; i < args.size(); ++i)
command += " " + args[i];
PROBE1(stap, stap_system__start, command.c_str());
if (verbose > 1)
clog << _("Running") << command << endl;
char const * argv[args.size() + 1];
for (size_t i = 0; i < args.size(); ++i)
argv[i] = args[i].c_str();
argv[args.size()] = NULL;
char** env;
bool allocated;
if(envVec.empty() && environ != NULL)
{
env = environ;
allocated = false;
}
else
{
allocated = true;
env = new char*[envVec.size() + 1];
for (size_t i = 0; i < envVec.size(); ++i)
env[i] = (char*)envVec[i].c_str();
env[envVec.size()] = NULL;
}
pid_t pid = 0;
int ret = posix_spawnp(&pid, argv[0], fa, NULL,
const_cast<char * const *>(argv), env);
if (allocated)
delete[] env;
PROBE2(stap, stap_system__spawn, ret, pid);
if (ret != 0)
{
if (verbose > 1)
clog << _F("Spawn error (%d): %s", ret, strerror(ret)) << endl;
pid = -1;
}
else
spawned_pids.insert(pid);
return pid;
}
pid_t
stap_spawn(int verbose, const vector<string>& args)
{
return stap_spawn(verbose, args, NULL);
}
pid_t
stap_spawn_piped(int verbose, const vector<string>& args,
int *child_in, int *child_out, int* child_err)
{
pid_t pid = -1;
int infd[2], outfd[2], errfd[2];
posix_spawn_file_actions_t fa;
if (posix_spawn_file_actions_init(&fa) != 0)
return -1;
if (child_in && pipe_child_fd(&fa, infd, 0) != 0)
goto cleanup_fa;
if (child_out && pipe_child_fd(&fa, outfd, 1) != 0)
goto cleanup_in;
if (child_err && pipe_child_fd(&fa, errfd, 2) != 0)
goto cleanup_out;
pid = stap_spawn(verbose, args, &fa);
if (child_err)
{
if (pid > 0)
*child_err = errfd[0];
else
close(errfd[0]);
close(errfd[1]);
}
cleanup_out:
if (child_out)
{
if (pid > 0)
*child_out = outfd[0];
else
close(outfd[0]);
close(outfd[1]);
}
cleanup_in:
if (child_in)
{
if (pid > 0)
*child_in = infd[1];
else
close(infd[1]);
close(infd[0]);
}
cleanup_fa:
posix_spawn_file_actions_destroy(&fa);
return pid;
}
const set<string>&
localization_variables()
{
static set<string> localeVars;
if (localeVars.empty())
{
localeVars.insert("LANG");
localeVars.insert("LC_ALL");
localeVars.insert("LC_CTYPE");
localeVars.insert("LC_COLLATE");
localeVars.insert("LC_MESSAGES");
localeVars.insert("LC_TIME");
localeVars.insert("LC_MONETARY");
localeVars.insert("LC_NUMERIC");
}
return localeVars;
}
int
stap_system(int verbose, const string& description,
const vector<string>& args,
bool null_out, bool null_err)
{
int ret = 0;
posix_spawn_file_actions_t fa;
if (posix_spawn_file_actions_init(&fa) != 0)
return -1;
if ((null_out && null_child_fd(&fa, 1) != 0) ||
(null_err && null_child_fd(&fa, 2) != 0))
ret = -1;
else
{
pid_t pid = stap_spawn(verbose, args, &fa);
ret = pid;
if (pid > 0){
ret = stap_waitpid(verbose, pid);
XXX if (ret > 128)
clog << _F("WARNING: %s exited with signal: %d (%s)",
description.c_str(), ret - 128, strsignal(ret - 128)) << endl;
else if (ret > 0)
clog << _F("WARNING: %s exited with status: %d",
description.c_str(), ret) << endl;
}
}
posix_spawn_file_actions_destroy(&fa);
return ret;
}
int
stap_system_read(int verbose, const vector<string>& args, ostream& out)
{
int child_fd = -1;
pid_t child = stap_spawn_piped(verbose, args, NULL, &child_fd);
if (child > 0)
{
stdio_filebuf<char> in(child_fd, ios_base::in);
out << ∈
return stap_waitpid(verbose, child);
}
return -1;
}
int
kill_stap_spawn(int sig)
{
return spawned_pids.killall(sig);
}
void assert_regexp_match (const string& name, const string& value, const string& re)
{
typedef map<string,regex_t*> cache;
static cache compiled;
cache::iterator it = compiled.find (re);
regex_t* r = 0;
if (it == compiled.end())
{
r = new regex_t;
int rc = regcomp (r, re.c_str(), REG_ICASE|REG_NOSUB|REG_EXTENDED);
assert (rc == 0);
compiled[re] = r;
}
else
r = it->second;
int rc = regexec (r, value.c_str(), 0, 0, 0);
if (rc)
throw runtime_error
(_F("ERROR: Safety pattern mismatch for %s ('%s' vs. '%s') rc=%d",
name.c_str(), value.c_str(), re.c_str(), rc));
}
int regexp_match (const string& value, const string& re, vector<string>& matches)
{
typedef map<string,regex_t*> cache; static cache compiled;
cache::iterator it = compiled.find (re);
regex_t* r = 0;
if (it == compiled.end())
{
r = new regex_t;
int rc = regcomp (r, re.c_str(), REG_EXTENDED); assert (rc == 0);
compiled[re] = r;
}
else
r = it->second;
#define maxmatches 10
regmatch_t rm[maxmatches];
int rc = regexec (r, value.c_str(), maxmatches, rm, 0);
if (rc) return rc;
matches.erase(matches.begin(), matches.end());
for (unsigned i=0; i<maxmatches; i++) XXX {
if (rm[i].rm_so >= 0)
matches.push_back(value.substr (rm[i].rm_so, rm[i].rm_eo-rm[i].rm_so));
else
matches.push_back("");
}
return 0;
}
bool contains_glob_chars (const string& str)
{
for (unsigned i=0; i<str.size(); i++)
{
char this_char = str[i];
if (this_char == '\\' && (str.size() > i+1))
{
i++;
continue;
}
if (this_char == '*' || this_char == '?' || this_char == '[')
return true;
}
return false;
}
string escape_glob_chars (const string& str)
{
string op;
for (unsigned i=0; i<str.size(); i++)
{
char this_char = str[i];
if (this_char == '*' || this_char == '?' || this_char == '[')
op += '\\';
op += this_char;
}
return op;
}
string unescape_glob_chars (const string& str)
{
string op;
for (unsigned i=0; i<str.size(); i++)
{
char this_char = str[i];
if (this_char == '\\' && (str.size() > i+1) )
{
op += str[i+1];
i++;
continue;
}
op += this_char;
}
return op;
}
string
normalize_machine(const string& machine)
{
if (machine == "i486") return "i386";
else if (machine == "i586") return "i386";
else if (machine == "i686") return "i386";
else if (machine == "sun4u") return "sparc64";
else if (machine.substr(0,3) == "arm") return "arm";
else if (machine == "sa110") return "arm";
else if (machine == "s390x") return "s390";
else if (machine == "aarch64") return "arm64";
else if (machine.substr(0,3) == "ppc") return "powerpc";
else if (machine.substr(0,4) == "mips") return "mips";
else if (machine.substr(0,3) == "sh2") return "sh";
else if (machine.substr(0,3) == "sh3") return "sh";
else if (machine.substr(0,3) == "sh4") return "sh";
return machine;
}
int
elf_class_from_normalized_machine (const string &machine)
{
if (machine == "i386"
|| machine == "arm") return ELFCLASS32;
else if (machine == "s390" || machine == "powerpc" || machine == "x86_64"
|| machine == "ia64"
|| machine == "arm64")
return ELFCLASS64;
cerr << _F("Unknown kernel machine architecture '%s', don't know elf class",
machine.c_str()) << endl;
return -1;
}
string
kernel_release_from_build_tree (const string &kernel_build_tree, int verbose)
{
string version_file_name = kernel_build_tree + "/include/config/kernel.release";
ifstream version_file (version_file_name.c_str());
if (version_file.fail ())
{
if (verbose > 1)
cerr << _F("Missing %s", version_file_name.c_str()) << endl;
return "";
}
string kernel_release;
char c;
while (version_file.get(c) && c != '\n')
kernel_release.push_back(c);
return kernel_release;
}
string autosprintf(const char* format, ...)
{
va_list args;
char *str;
va_start (args, format);
int rc = vasprintf (&str, format, args);
if (rc < 0)
{
va_end(args);
return _F("autosprintf/vasprintf error %d", rc);
}
string s = str;
va_end (args);
free (str);
return s; }
string
get_self_path()
{
char buf[1024]; const char *file = "/proc/self/exe";
ssize_t len = readlink(file, buf, sizeof(buf) - 1);
if (len > 0)
{
buf[len] = '\0';
file = buf;
}
return string(file);
}
bool
is_valid_pid (pid_t pid, string& err_msg)
{
err_msg = "";
if (pid <= 0)
{
err_msg = _F("cannot probe pid %d: Invalid pid", pid);
return false;
}
else if (kill(pid, 0) == -1)
{
err_msg = _F("cannot probe pid %d: %s", pid, strerror(errno));
return false;
}
return true;
}
TODOunsigned
levenshtein(const string& a, const string& b)
{
Array2D<unsigned> d(a.size()+1, b.size()+1);
for (unsigned i = 0; i < d.width; i++)
d(i, 0) = i;
for (unsigned j = 0; j < d.height; j++)
d(0, j) = j;
for (unsigned i = 1; i < d.width; i++) {
for (unsigned j = 1; j < d.height; j++) {
if (a[i-1] == b[j-1]) d(i,j) = d(i-1, j-1);
else {
unsigned subpen = 2; if (tolower(a[i-1]) == tolower(b[j-1]))
subpen = 1; d(i,j) = min(min(
d(i-1,j-1) + subpen, d(i-1,j) + 2), d(i,j-1) + 2); }
}
}
return d(d.width-1, d.height-1);
}
string
levenshtein_suggest(const string& target, const set<string>& elems, unsigned max, unsigned threshold) {
multimap<unsigned, string> scores;
for (set<string>::const_iterator it = elems.begin();
it != elems.end(); ++it)
{
if (it->empty()) continue;
unsigned min_score = labs(target.size() - it->size());
if (min_score > threshold) continue;
unsigned maxth_score = std::numeric_limits<unsigned>::max();
if (scores.size() >= max) {
multimap<unsigned, string>::iterator itt = scores.begin();
for (unsigned i = 0; i < max-1; i++) itt++; maxth_score = itt->first;
}
if (min_score > maxth_score) continue;
unsigned score = levenshtein(target, *it);
if (score > maxth_score) continue;
if (score > threshold) continue;
scores.insert(make_pair(score, *it));
}
string suggestions;
multimap<unsigned, string>::iterator it = scores.begin();
for (unsigned i = 0; it != scores.end() && i < max; ++it, i++)
suggestions += it->second + ", ";
if (!suggestions.empty())
suggestions.erase(suggestions.size()-2);
return suggestions;
}
#ifndef HAVE_PPOLL
int
ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts,
const sigset_t *sigmask)
{
sigset_t origmask;
int timeout = (timeout_ts == NULL) ? 1000 : (timeout_ts->tv_sec * 1000 + timeout_ts->tv_nsec / 1000000);
sigprocmask(SIG_SETMASK, sigmask, &origmask);
int rc = poll(fds, nfds, timeout);
sigprocmask(SIG_SETMASK, &origmask, NULL);
return rc;
}
#endif