fix include file case
[reactos.git] / rosapps / mc / src / cons.saver.c
1 #if defined(linux) || defined(__linux__)
2
3 /* General purpose Linux console screen save/restore server
4 Copyright (C) 1994 Janne Kukonlehto <jtklehto@stekt.oulu.fi>
5 Original idea from Unix Interactive Tools version 3.2b (tty.c)
6 This code requires root privileges.
7 You may want to make the cons.saver setuid root.
8 The code should be safe even if it is setuid but who knows?
9
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
23
24 #include <config.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/ioctl.h>
28 #include <fcntl.h>
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32 #include <termios.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <ctype.h> /* For isdigit() */
36 typedef struct WINDOW WINDOW;
37 #include "cons.saver.h"
38
39 #define cmd_input 0
40 #define cmd_output 1
41
42 /* Meaning of console_flag:
43 -1 == to be detected,
44 0 == not a console
45 1 == is a console, Linux < 1.1.67 (black & white)
46 2 == is a console, Linux >= 1.1.67 (color)
47 3 == is a console, Linux >= 1.1.92 (color, use /dev/vcsa$num
48 */
49 static signed char console_flag = -1;
50 /*
51 Meaning of console_fd:
52 -1 == not opened,
53 >=0 == opened
54 */
55 static int console_fd = -1;
56 static char *tty_name;
57 static int len;
58 static char *buffer = NULL;
59 static int buffer_size = 0;
60 static int columns, rows;
61 static char vcs_name [40];
62 static int vcs_fd;
63
64 static void dwrite (int fd, char *buffer)
65 {
66 write (fd, buffer, strlen (buffer));
67 }
68
69 static void tty_getsize ()
70 {
71 struct winsize winsz;
72
73 winsz.ws_col = winsz.ws_row = 0;
74 ioctl (console_fd, TIOCGWINSZ, &winsz);
75 if (winsz.ws_col && winsz.ws_row){
76 columns = winsz.ws_col;
77 rows = winsz.ws_row;
78 } else {
79 /* Never happens (I think) */
80 dwrite (2, "TIOCGWINSZ failed\n");
81 columns = 80;
82 rows = 25;
83 console_flag = 0;
84 }
85 }
86
87 inline void tty_cursormove(int y, int x)
88 {
89 char buffer [20];
90
91 /* Standard ANSI escape sequence for cursor positioning */
92 sprintf (buffer,"\33[%d;%dH", y + 1, x + 1);
93 dwrite (console_fd, buffer);
94 }
95
96 int check_file (char *filename, int check_console, char **msg)
97 {
98 int fd;
99 struct stat stat_buf;
100
101 /* Avoiding race conditions: use of fstat makes sure that
102 both 'open' and 'stat' operate on the same file */
103
104 *msg = 0;
105
106 fd = open (filename, O_RDWR);
107 if (fd == -1)
108 return -1;
109
110 if (fstat (fd, &stat_buf) == -1)
111 return -1;
112
113 /* Must be character device */
114 if (!S_ISCHR (stat_buf.st_mode)){
115 *msg = "Not a character device";
116 return -1;
117 }
118
119 #ifdef DEBUG
120 fprintf (stderr, "Device: %x\n", stat_buf.st_rdev);
121 #endif
122 if (check_console){
123 /* Second time: must be console */
124 if ((stat_buf.st_rdev & 0xff00) != 0x0400){
125 *msg = "Not a console";
126 return -1;
127 }
128
129 if ((stat_buf.st_rdev & 0x00ff) > 63){
130 *msg = "Minor device number too big";
131 return -1;
132 }
133
134 /* Must be owned by the user */
135 if (stat_buf.st_uid != getuid ()){
136 *msg = "Not a owner";
137 return -1;
138 }
139 }
140
141 /* Everything seems to be okay */
142 return fd;
143 }
144
145 /* Detect console */
146 /* Because the name of the tty is supplied by the user and this
147 can be a setuid program a lot of checks has to done to avoid
148 creating a security hole */
149 char *detect_console (void)
150 {
151 char *msg;
152 int xlen;
153
154 /* Must be console */
155 /* Handle the case for /dev/tty?? */
156 if (tty_name[len-5] == 't')
157 xlen = len - 1;
158 else
159 xlen = len;
160
161 /* General: /dev/ttyn */
162 if (tty_name[xlen - 5] != '/' ||
163 tty_name[xlen - 4] != 't' ||
164 tty_name[xlen - 3] != 't' ||
165 tty_name[xlen - 2] != 'y' ||
166 !isdigit(tty_name[xlen - 1]) ||
167 !isdigit(tty_name[len - 1]))
168 return "Doesn't look like console";
169
170 sprintf (vcs_name, "/dev/vcsa%s", tty_name + xlen - 1);
171 vcs_fd = check_file (vcs_name, 0, &msg);
172 console_fd = check_file (tty_name, 1, &msg);
173
174 #ifdef DEBUG
175 fprintf (stderr, "vcs_fd = %d console_fd = %d\n", vcs_fd, console_fd);
176 #endif
177
178 if (vcs_fd != -1){
179 console_flag = 3;
180 }
181
182 if (console_fd == -1)
183 return msg;
184
185 return NULL;
186 }
187
188 void save_console (void)
189 {
190 int i;
191
192 if (!console_flag)
193 return;
194 buffer [1] = tty_name [len-1] - '0';
195 if (console_flag >= 2){
196 /* Linux >= 1.1.67 */
197 /* Get screen contents and cursor position */
198 buffer [0] = 8;
199 if (console_flag == 2){
200 if ((i = ioctl (console_fd, TIOCLINUX, buffer)) == -1){
201 /* Oops, this is not Linux 1.1.67 */
202 console_flag = 1;
203 }
204 } else {
205 lseek (vcs_fd, 0, 0);
206 read (vcs_fd, buffer, buffer_size);
207 }
208 }
209 if (console_flag == 1){
210 int index, x, y;
211
212 /* Linux < 1.1.67 */
213 /* Get screen contents */
214 buffer [0] = 0;
215 if (ioctl(console_fd, TIOCLINUX, buffer) == -1){
216 buffer[0] = buffer[1] = 0;
217
218 /* Linux bug: bad ioctl on console 8 */
219 if (ioctl(console_fd, TIOCLINUX, buffer) == -1){
220 /* Oops, this is not a console after all */
221 console_flag = 0;
222 return;
223 }
224 }
225 /* Select the beginning of the bottommost empty line
226 to be the cursor position */
227 index = 2 + rows * columns;
228 for (y = rows - 1; y >= 0; y--)
229 for (x = columns - 1; x >= 0; x--)
230 if (buffer[--index] != ' ')
231 goto non_space_found;
232 non_space_found:
233 buffer[0] = y + 1;
234 buffer[1] = 0;
235 /*tty_cursormove(y + 1, 0);*/
236 }
237 }
238
239 void restore_console (void)
240 {
241 if (!console_flag)
242 return;
243 if (console_flag == 2){
244 /* Linux >= 1.1.67 */
245 /* Restore screen contents and cursor position */
246 buffer [0] = 9;
247 buffer [1] = tty_name [len-1] - '0';
248 ioctl (console_fd, TIOCLINUX, buffer);
249 }
250 if (console_flag == 3){
251 lseek (vcs_fd, 0, 0);
252 write (vcs_fd, buffer, buffer_size);
253 }
254 if (console_flag == 1){
255 /* Clear screen */
256 write(console_fd, "\033[H\033[2J", 7);
257 /* Output saved screen contents */
258 write(console_fd, buffer + 2, rows * columns);
259 /* Move the cursor to the previously selected position */
260 tty_cursormove(buffer[0], buffer[1]);
261 }
262 }
263
264 void send_contents ()
265 {
266 unsigned char begin_line=0, end_line=0;
267 int index, x, y;
268 int lastline;
269 unsigned char message;
270 unsigned short bytes;
271 int bytes_per_char;
272
273 bytes_per_char = console_flag == 1 ? 1 : 2;
274
275 /* Calculate the number of used lines */
276 if (console_flag == 2 || console_flag == 1 || console_flag == 3){
277 index = (2 + rows * columns) * bytes_per_char;
278 for (y = rows - 1; y >= 0; y--)
279 for (x = columns - 1; x >= 0; x--){
280 index -= bytes_per_char;
281 if (buffer[index] != ' ')
282 goto non_space_found;
283 }
284 non_space_found:
285 lastline = y + 1;
286 } else
287 return;
288
289 /* Inform the invoker that we can handle this command */
290 message = CONSOLE_CONTENTS;
291 write (cmd_output, &message, 1);
292
293 /* Read the range of lines wanted */
294 read (cmd_input, &begin_line, 1);
295 read (cmd_input, &end_line, 1);
296 if (begin_line > lastline)
297 begin_line = lastline;
298 if (end_line > lastline)
299 end_line = lastline;
300
301 /* Tell the invoker how many bytes it will be */
302 bytes = (end_line - begin_line) * columns;
303 write (cmd_output, &bytes, 2);
304
305 /* Send the contents */
306 for (index = (2 + begin_line * columns) * bytes_per_char;
307 index < (2 + end_line * columns) * bytes_per_char;
308 index += bytes_per_char)
309 write (cmd_output, buffer + index, 1);
310
311 /* All done */
312 }
313
314 int main (int argc, char **argv)
315 {
316 char *error;
317 unsigned char action = 0;
318
319 if (argc != 2){
320 /* Wrong number of arguments */
321
322 dwrite (2, "Usage: cons.saver <ttyname>\n");
323 console_flag = 0;
324 write (cmd_output, &console_flag, 1);
325 return 3;
326 }
327
328 /* Lose the control terminal */
329 setsid ();
330
331 /* Check that the argument is a legal console */
332 tty_name = argv [1];
333 len = strlen(tty_name);
334 error = detect_console ();
335
336 if (error){
337 /* Not a console -> no need for privileges */
338 setuid (getuid ());
339 dwrite (2, error);
340 console_flag = 0;
341 if (console_fd >= 0)
342 close (console_fd);
343 } else {
344 /* Console was detected */
345 if (console_flag != 3)
346 console_flag = 2; /* Default to Linux >= 1.1.67 */
347 /* Allocate buffer for screen image */
348 tty_getsize ();
349 buffer_size = 4 + 2 * columns * rows;
350 buffer = (char*) malloc (buffer_size);
351 }
352
353 /* If using /dev/vcs*, we don't need anymore the console fd */
354 if (console_flag == 3)
355 close (console_fd);
356
357 /* Inform the invoker about the result of the tests */
358 write (cmd_output, &console_flag, 1);
359
360 /* Read commands from the invoker */
361 while (console_flag && read (cmd_input, &action, 1)){
362 /* Handle command */
363 switch (action){
364 case CONSOLE_DONE:
365 console_flag = 0;
366 continue; /* Break while loop instead of switch clause */
367 case CONSOLE_SAVE:
368 save_console ();
369 break;
370 case CONSOLE_RESTORE:
371 restore_console ();
372 break;
373 case CONSOLE_CONTENTS:
374 send_contents ();
375 break;
376 } /* switch (action) */
377
378 /* Inform the invoker that command is handled */
379 write (cmd_output, &console_flag, 1);
380 } /* while (read ...) */
381
382 if (buffer)
383 free (buffer);
384 return 0;
385 }
386
387 #else
388
389 #error The Linux console screen saver works only on Linux.
390
391 #endif /* #ifdef linux */