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