🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

WaitForMultipleObjects on Linux?

Started by
8 comments, last by Bregma 13 years, 4 months ago
How would I go about using eventfd and epoll to do the equivalent on waiting for multiple events?
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)
Advertisement
Have you considered looking at the epoll man page (available online e.g. here)? The documentation is quite extensive and contains a code example of suggested usage. If you've already done this and things remain unclear, perhaps you could ask a more concrete question, because your post just sounds really vague.
Widelands - laid back, free software strategy

How would I go about using eventfd and epoll to do the equivalent on waiting for multiple events?

The basic use is as follows.
  1. Create an epoll fd using [font="Courier New"]epoll_create()[/font].
  2. Create an event fd using [font="Courier New"]eventfd().[/font]
  3. add a watch on the fd using [font="Courier New"]epoll_ctl().[/font]
  4. use [font="Courier New"]epoll_wait()[/font] in a thread to wait for events, or use [font="Courier New"]select()[/font] or [font="Courier New"]poll()[/font] on your epoll fd, however your event loop is structured.
  5. [font="Courier New"]write()[/font] to your event fd to signal events from your worker thread or signal context.
  6. [font="Courier New"]read()[/font] from your event fd to handle the event.
  7. [font="Courier New"]close()[/font] the file descriptors when done.
The man pages for eventfd(2) and epoll(7) give excellent documentation and examples.

Stephen M. Webb
Professional Free Software Developer

Thanks for this.
I need to in the same wait, not only wait for an event fd but also on a thread, and on a process as well (and abnormal termination also needs to count as a signal, not just a normal exit, so I can't simply write() to an event fd from a global object dtor). What is the best way to do this? With WaitForMultipleObjects it's trivial since events, threads, and processes are all represented by HANDLEs that can be placed in the wait-for array.
Is there maybe way to associate a PID with an fd? I don't know about using signals because the signal could interrupt the processing of another event which is unacceptable, and I don't know how one could delay a signal handling until the event processing code is running from some section.
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)

Thanks for this.
I need to in the same wait, not only wait for an event fd but also on a thread, and on a process as well (and abnormal termination also needs to count as a signal, not just a normal exit, so I can't simply write() to an event fd from a global object dtor). What is the best way to do this? With WaitForMultipleObjects it's trivial since events, threads, and processes are all represented by HANDLEs that can be placed in the wait-for array.
Is there maybe way to associate a PID with an fd? I don't know about using signals because the signal could interrupt the processing of another event which is unacceptable, and I don't know how one could delay a signal handling until the event processing code is running from some section.

Using [font="Courier New"]select()[/font], [font="Courier New"]poll()[/font], or [font="Courier New"]epoll_wait()[/font] is simple because everything in Unix is a file. Files have descriptors. Those wait-for-multiple-file-descriptor-event calls wait for... wait for it... events on multiple file descriptors. The man pages for each go into great details with plenty of example code.

If you need to wait for a subprocess you need to set a signal handler for [font="Courier New"]SIGCHLD[/font]. That signal will be delivered whenever a child process terminates. What you will want to do is create an eventfd in your main thread and bind it to your signal handler function in the [font="Courier New"]sigaction()[/font] call. When the signal is raised, all you need to do is send an event on your event fd using the [font="Courier New"]write()[/font] call. Your [font="Courier New"]epoll_wait()[/font] in the main thread will return, you check and see that an event was delivered on your eventfd, so you call a function that does a [font="Courier New"]waitpid()[/font] to receive the termination information from your child process.

It's up to you to create associations between PIDs and event fds, but enough information is delivered to you signal handler that you can do what you need to do. If you have a table mapping PIDs and fds, the signal handler receives the PID in its [font="Courier New"]siginfo_t[/font] (second) argument, and a pointer to your table in its third argument, so you have all the information your need.

You need to play with and understand signals. They've been around and in heavy use for 43 years in Unix, chances are slim they've been designed wrong. They key is to do as little processing as possible in signal context, just enough to figure out whic fd to write to and then write to it. Everything else is done synchronously in your main loop when you're ready for it.

Stephen M. Webb
Professional Free Software Developer

So if a process is created with execve() does it also count as a subprocess, sending a SIGCHLD to the parent?

On a different note, I also would like to know how to create the new process to run as a different user, since I want to restrict that process to be able to do only three things and nothing else: access files in a specific directory, access opengl and openal, and access limited IPC with the privileged process.
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)

So if a process is created with execve() does it also count as a subprocess, sending a SIGCHLD to the parent?

[font="Courier New"]execve()[/font] does not create a subprocess. The [font="Courier New"]execve()[/font] call overlays the current process with another executable image. You create a child process on a POSIX system using the [font="Courier New"]fork()[/font] call. Most of the time, you fork and then exec a new image in the child process, the parent process keeps running and eventually does a [font="Courier New"]wait()[/font] when the SIGCHLD is received. Google will get you numerous examples.
On a different note, I also would like to know how to create the new process to run as a different user, since I want to restrict that process to be able to do only three things and nothing else: access files in a specific directory, access opengl and openal, and access limited IPC with the privileged process.[/quote]Running under different user or group IDs on a POSIX system is complicated, especially when additional security mechanisms are thrown in (eg. SELinux). The basic idea is that you can never escalate privileges, you can only drop them, and you can only change uid or gid if you have the privilege to do so. In other words, as an ordinary user you can not switch to running as a different user, you need to be root to do that. think about it: if you could just arbitrarily switch to running as another user, there would be no security at all.

Access restrictions on a plain vanilla POSIX system are usually performed through group membership. You give only owner and group r/w perms to files in a directory and you grant group membership to a user who needs to read or write those files.

It is possible to switch uid or gid by toggling the setui/setgid bit on a file you exec. You are unlikely to get any software that does that accepted by a mainstream distro die to the inherent security problems.

Stephen M. Webb
Professional Free Software Developer

Since I'm writing for what are essentially kiosks, I control the OS configuration and if SELinux is the way to go here, that's fine as well since I have admin access to the systems (I'd not be above writing a kernel module to use the LSM API directly either, but that seems overkill). I just wanted to find out what the simplest things to do are first before I spend too much time delving into complexity.

On Windows things were pretty straightforward, where I use a restricted token with reduced ACLs to limit the process, and further marked it as a low integrity process (vista+), and finally used the job object feature which lets me limit a bunch more things, even the percentage of CPU time used on the system. I'm hoping to have a similar or better variety ways to restrict the process on Linux.

I have no choice to run as a different user since the parent process is a watchdog for which I need root access, as it modifies some things such as allowing some users to boost their thread niceness, other scheduling stuff, access the MSR, etc. I assume on Linux the closest thing to a system service is a daemon, so I must make the forked process run as a different user before execve. On Windows the parent is a system service, and I impersonate the logged on user and duplicate its token, apply security restrictions on the token, and then CreateProcessAsUser, then the process further drops some privileges after initialization of libraries that need it, before loading untrusted dlls. So I'm tyring to accomplish something similar in Linux, and I need a bit of guidance here. Is the setui/setgid the best way to go, or should I use SELinux (but I'm not quite sure how to access it programmatically, I only see instructions for an admin configuring it).
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)
If your process is started from some watchdog parent which runs with root privileges anyway, then of course you can use setuid/setgid, though you might also want to look into existing daemons like cron or the various new init-system (launchd and upstart or whatever they're called) to handle launching the process for you. I believe there are ways to drop specific capabilities, perhaps as part of SELinux, but I've only ever written boring old regular userspace applications.
Widelands - laid back, free software strategy

I have no choice to run as a different user since the parent process is a watchdog for which I need root access, as it modifies some things such as allowing some users to boost their thread niceness, other scheduling stuff, access the MSR, etc. I assume on Linux the closest thing to a system service is a daemon, so I must make the forked process run as a different user before execve. On Windows the parent is a system service, and I impersonate the logged on user and duplicate its token, apply security restrictions on the token, and then CreateProcessAsUser, then the process further drops some privileges after initialization of libraries that need it, before loading untrusted dlls. So I'm tyring to accomplish something similar in Linux, and I need a bit of guidance here. Is the setui/setgid the best way to go, or should I use SELinux (but I'm not quite sure how to access it programmatically, I only see instructions for an admin configuring it).

I would recommend that unless your kiosk is hardened for military use, avoid SELinux. It will bring you much grief for little benefit.

If your daemon is running as root, you can do anything. The classic method of dropping root privs and running as another user is to fork your child process, drop privs using set [font="Courier New"]setuid()[/font] call, then exec the app.

For security, you should in fact run your daemon with a non-root effective userid (man [font="Courier New"]setreuid[/font]). This reduces the ability for a malicious attack to gain escalated privileges, but allows you to perform some actions as the root user as necessary.

You might also consider reading up on Linux capabilities (man capabilities). They provide a finer-grained control over what a uid can and can not do. You may find you can have your daemon do everything you need as a non-root user that has been granted specific capabilities (eg. CAP_SYS_NICE). This is a much safer approach.

Stephen M. Webb
Professional Free Software Developer

This topic is closed to new replies.

Advertisement