Import from old repository commit 61ef2b42a9c4ba8e1600f15bb0236765edc2ad45.
[cascardo/ovs.git] / extras / ezio / tty.c
1 /* Copyright (c) 2008, 2009 Nicira Networks, Inc.
2  *
3  * This program is free software: you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation, either version 3 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
15  *
16  * In addition, as a special exception, Nicira Networks gives permission
17  * to link the code of its release of vswitchd with the OpenSSL project's
18  * "OpenSSL" library (or with modified versions of it that use the same
19  * license as the "OpenSSL" library), and distribute the linked
20  * executables.  You must obey the GNU General Public License in all
21  * respects for all of the code used other than "OpenSSL".  If you modify
22  * this file, you may extend this exception to your version of the file,
23  * but you are not obligated to do so.  If you do not wish to do so,
24  * delete this exception statement from your version.
25  *
26  */
27
28 #include <config.h>
29 #include "extras/ezio/tty.h"
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <pwd.h>
33 #include <signal.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <stropts.h>
37 #include <sys/ioctl.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include "fatal-signal.h"
41 #include "socket-util.h"
42 #include "util.h"
43
44 #define THIS_MODULE VLM_tty
45 #include "vlog.h"
46
47 /* Get major() and minor() macros. */
48 #if MAJOR_IN_MKDEV
49 #  include <sys/mkdev.h>
50 #elif MAJOR_IN_SYSMACROS
51 #  include <sys/sysmacros.h>
52 #else
53 #  include <sys/types.h>
54 #  ifndef major
55 #    define major(dev) (((dev) >> 8) & 0xff)
56 #    define minor(dev) ((dev) & 0xff)
57 #  endif
58 #endif
59
60 static int
61 fcntl_lock(int fd)
62 {
63     struct flock l;
64     memset(&l, 0, sizeof l);
65     l.l_type = F_WRLCK;
66     l.l_whence = SEEK_SET;
67     l.l_start = 0;
68     l.l_len = 0;
69     return fcntl(fd, F_SETLK, &l) == -1 ? errno : 0;
70 }
71
72 static int
73 remove_lockfile(const char *name)
74 {
75     char buffer[BUFSIZ];
76     ssize_t n;
77     pid_t pid;
78     int fd;
79
80     /* Remove existing lockfile. */
81     fd = open(name, O_RDWR);
82     if (fd < 0) {
83         if (errno == ENOENT) {
84             return 0;
85         } else {
86             VLOG_ERR("%s: open: %s", name, strerror(errno));
87             return errno;
88         }
89     }
90
91     /* Read lockfile. */
92     n = read(fd, buffer, sizeof buffer - 1);
93     if (n < 0) {
94         int error = errno;
95         VLOG_ERR("%s: read: %s", name, strerror(error));
96         close(fd);
97         return error;
98     }
99     buffer[n] = '\0';
100     if (n == 4 && memchr(buffer, '\0', n)) {
101         int32_t x;
102         memcpy(&x, buffer, sizeof x);
103         pid = x;
104     } else if (n >= 0) {
105         pid = strtol(buffer, NULL, 10);
106     }
107     if (pid <= 0) {
108         close(fd);
109         VLOG_WARN("%s: format not recognized, treating as locked.", name);
110         return EACCES;
111     }
112
113     /* Is lockfile fresh? */
114     if (strstr(buffer, "fcntl")) {
115         int retval = fcntl_lock(fd);
116         if (retval) {
117             close(fd);
118             VLOG_ERR("%s: device is locked (via fcntl): %s",
119                      name, strerror(retval));
120             return retval;
121         } else {
122             VLOG_WARN("%s: removing stale lockfile (checked via fcntl)", name);
123         }
124     } else {
125         if (!(kill(pid, 0) < 0 && errno == ESRCH)) {
126             close(fd);
127             VLOG_ERR("%s: device is locked (without fcntl)", name);
128             return EACCES;
129         } else {
130             VLOG_WARN("%s: removing stale lockfile (without fcntl)", name);
131         }
132     }
133     close(fd);
134
135     /* Remove stale lockfile. */
136     if (unlink(name)) {
137         VLOG_ERR("%s: unlink: %s", name, strerror(errno));
138         return errno;
139     }
140     return 0;
141 }
142
143 static int
144 create_lockfile(const char *name)
145 {
146     const char *username;
147     char buffer[BUFSIZ];
148     struct passwd *pwd;
149     mode_t old_umask;
150     uid_t uid;
151     int fd;
152
153     /* Create file. */
154     old_umask = umask(022);
155     fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
156     if (fd < 0) {
157         int error = errno;
158         VLOG_ERR("%s: create: %s", name, strerror(error));
159         umask(old_umask);
160         return error;
161     }
162     umask(old_umask);
163
164     /* Lock file. */
165     if (fcntl_lock(fd)) {
166         int error = errno;
167         close(fd);
168         VLOG_ERR("%s: cannot lock: %s", name, strerror(error));
169         return error;
170     }
171
172     /* Write to file. */
173     uid = getuid();
174     pwd = getpwuid(uid);
175     username = pwd ? pwd->pw_name : "unknown";
176     snprintf(buffer, sizeof buffer, "%10ld %s %.20s fcntl\n",
177              (long int) getpid(), program_name, username);
178     if (write(fd, buffer, strlen(buffer)) != strlen(buffer)) {
179         int error = errno;
180         VLOG_ERR("%s: write: %s", name, strerror(error));
181         close(fd);
182         unlink(name);
183         return error;
184     }
185
186     /* We intentionally do not close 'fd', to avoid releasing the fcntl lock.
187      * The asssumption here is that we never unlock a tty. */
188     fatal_signal_add_file_to_unlink(name);
189
190     return 0;
191 }
192
193 static int
194 do_lock(char *name)
195 {
196     int retval = remove_lockfile(name);
197     if (!retval) {
198         retval = create_lockfile(name);
199     }
200     free(name);
201     return retval;
202 }
203
204 int
205 tty_lock(const char *dev_name)
206 {
207     struct stat s;
208     char *name;
209     int retval;
210
211     /* Check that the lockfile directory exists. */
212     if (stat(TTY_LOCK_DIR, &s)) {
213         VLOG_ERR("%s: stat: %s", TTY_LOCK_DIR, strerror(errno));
214         return errno;
215     }
216
217     /* First lock by device number. */
218     if (stat(dev_name, &s)) {
219         VLOG_ERR("%s: stat: %s", dev_name, strerror(errno));
220         return errno;
221     }
222     retval = do_lock(xasprintf("%s/LK.%03d.%03d.%03d", TTY_LOCK_DIR,
223                                major(s.st_dev),
224                                major(s.st_rdev), minor(s.st_rdev)));
225     if (retval) {
226         return retval;
227     }
228
229     /* Then lock by device name. */
230     if (!strncmp(dev_name, "/dev/", 5)) {
231         char *cp;
232
233         name = xasprintf("%s/%s", TTY_LOCK_DIR, dev_name + 5);
234         for (cp = name + strlen(dev_name) + 1; *cp; cp++) {
235             if (*cp == '/') {
236                 *cp = '_';
237             }
238         }
239     } else {
240         char *slash = strrchr(dev_name, '/');
241         name = xasprintf ("%s/%s", TTY_LOCK_DIR, slash ? slash + 1 : dev_name);
242     }
243     return do_lock(name);
244 }
245
246 struct saved_termios {
247     int fd;
248     struct termios tios;
249 };
250
251 static void
252 restore_termios(void *s_)
253 {
254     struct saved_termios *s = s_;
255     tcsetattr(s->fd, TCSAFLUSH, &s->tios);
256 }
257
258 int
259 tty_set_raw_mode(int fd, speed_t speed)
260 {
261     if (isatty(fd)) {
262         struct termios tios;
263         struct saved_termios *s;
264
265         if (tcgetattr(fd, &tios) < 0) {
266             return errno;
267         }
268
269         s = xmalloc(sizeof *s);
270         s->fd = dup(fd);
271         if (s->fd < 0) {
272             int error = errno;
273             VLOG_WARN("dup failed: %s", strerror(error));
274             free(s);
275             return errno;
276         }
277         s->tios = tios;
278         fatal_signal_add_hook(restore_termios, s, true);
279
280         tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
281                           | INLCR | IGNCR | ICRNL | IXON);
282         tios.c_oflag &= ~OPOST;
283         tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
284         tios.c_cflag &= ~(CSIZE | PARENB);
285         tios.c_cflag |= CS8;
286         if (speed != B0) {
287             cfsetispeed(&tios, speed);
288             cfsetospeed(&tios, speed);
289         }
290         if (tcsetattr(fd, TCSAFLUSH, &tios) < 0) {
291             return errno;
292         }
293     }
294     return set_nonblocking(fd);
295 }
296
297 int
298 tty_open_master_pty(void)
299 {
300     int retval;
301     int fd;
302
303     fd = posix_openpt(O_RDWR | O_NOCTTY);
304     if (fd < 0) {
305         int error = errno;
306         VLOG_WARN("posix_openpt failed: %s", strerror(error));
307         close(fd);
308         return -error;
309     }
310
311     if (grantpt(fd) < 0) {
312         int error = errno;
313         VLOG_WARN("grantpt failed: %s", strerror(error));
314         close(fd);
315         return -error;
316     }
317
318     if (unlockpt(fd) < 0) {
319         int error = errno;
320         VLOG_WARN("unlockpt failed: %s", strerror(error));
321         close(fd);
322         return -error;
323     }
324
325     retval = set_nonblocking(fd);
326     if (retval) {
327         VLOG_WARN("set_nonblocking failed: %s", strerror(retval));
328         close(fd);
329         return retval;
330     }
331
332     return fd;
333 }
334
335 int
336 tty_fork_child(int master_fd, char *argv[])
337 {
338     int retval = fork();
339     if (!retval) {
340         char *slave_name;
341         int slave_fd;
342         int fd;
343
344         /* Running in child process. */
345         fatal_signal_fork();
346
347         /* Open pty slave as controlling terminal. */
348         setsid();
349         slave_name = ptsname(master_fd);
350         if (slave_name == NULL) {
351             ovs_fatal(errno, "ptsname");
352         }
353         slave_fd = open(slave_name, O_RDWR);
354         if (isastream(slave_fd)
355             && (ioctl(slave_fd, I_PUSH, "ptem") < 0
356                 || ioctl(slave_fd, I_PUSH, "ldterm") < 0)) {
357             ovs_fatal(errno, "STREAMS ioctl");
358         }
359
360         /* Make pty slave stdin, stdout. */
361         if (dup2(slave_fd, STDIN_FILENO) < 0
362             || dup2(slave_fd, STDOUT_FILENO) < 0
363             || dup2(slave_fd, STDERR_FILENO) < 0) {
364             ovs_fatal(errno, "dup2");
365         }
366
367         /* Close other file descriptors. */
368         for (fd = 3; fd < 20; fd++) {
369             close(fd);
370         }
371
372         /* Set terminal type. */
373         setenv("TERM", "ezio3", true);
374
375         /* Invoke subprocess. */
376         execvp(argv[0], argv);
377         ovs_fatal(errno, "execvp");
378     } else if (retval > 0) {
379         /* Running in parent process. */
380         return 0;
381     } else {
382         /* Fork failed. */
383         VLOG_WARN("fork failed: %s", strerror(errno));
384         return errno;
385     }
386 }
387
388 int
389 tty_set_window_size(int fd UNUSED, int rows UNUSED, int columns UNUSED)
390 {
391 #ifdef TIOCGWINSZ
392     struct winsize win;
393     win.ws_row = rows;
394     win.ws_col = columns;
395     win.ws_xpixel = 0;
396     win.ws_ypixel = 0;
397     if (ioctl(fd, TIOCSWINSZ, &win) == -1) {
398         return errno;
399     }
400 #else
401 #error
402 #endif
403     return 0;
404 }