xref: /reactos/base/shell/cmd/ctty.c (revision 3f976713)
1 /*
2  *  CTTY.C - ctty (Change TTY) command.
3  *
4  *  This command redirects the first three standard handles
5  *  stdin, stdout, stderr to another terminal.
6  *
7  *
8  *  History:
9  *
10  * 14 Aug 1998 (John P Price)
11  * - Created dummy command.
12  *
13  * 2000/01/14 ska
14  * + Added to change the first three handles to the given device name
15  * + Supports only redirection of stdin and stdout, e.g.:
16  *    C:\> CTTY COM1 >file
17  *  -or-
18  *    C:\> echo Hallo | CTTY COM1 | echo du
19  *  The CTTY command effects the commands on the _next_ line.
20  *
21  * 20 Oct 2016 (Hermes Belusca-Maito)
22  *     Port it to NT.
23  */
24 
25 #include "precomp.h"
26 
27 #if defined(INCLUDE_CMD_CTTY) && defined(FEATURE_REDIRECTION)
28 
29 static WORD
30 CheckTerminalDeviceType(IN LPCTSTR pszName)
31 {
32     /* Console reserved "file" names */
33     static const LPCWSTR DosLPTDevice = L"LPT";
34     static const LPCWSTR DosCOMDevice = L"COM";
35     static const LPCWSTR DosPRNDevice = L"PRN";
36     static const LPCWSTR DosAUXDevice = L"AUX";
37     static const LPCWSTR DosCONDevice = L"CON";
38     static const LPCWSTR DosNULDevice = L"NUL";
39 
40     LPCWSTR DeviceName;
41     ULONG DeviceNameInfo;
42     WORD DeviceType = 0; // 0: Unknown; 1: CON; 2: COM etc...
43 
44 #ifndef _UNICODE // UNICODE means that TCHAR == WCHAR == UTF-16
45     /* Convert from the current process/thread's codepage to UTF-16 */
46     DWORD len = strlen(pszName) + 1;
47     WCHAR *buffer = cmd_alloc(len * sizeof(WCHAR));
48     if (!buffer)
49     {
50         // SetLastError(ERROR_NOT_ENOUGH_MEMORY);
51         return FALSE;
52     }
53     len = (DWORD)MultiByteToWideChar(CP_THREAD_ACP, // CP_ACP, CP_OEMCP
54                                      0, pszName, (INT)len, buffer, (INT)len);
55     DeviceName = buffer;
56 #else
57     DeviceName = pszName;
58 #endif
59 
60     /*
61      * Check whether we deal with a DOS device, and if so,
62      * strip the path till the file name.
63      * Therefore, things like \\.\CON or C:\some_path\COM1
64      * are transformed into CON or COM1, for example.
65      */
66     DeviceNameInfo = RtlIsDosDeviceName_U(DeviceName);
67     if (DeviceNameInfo != 0)
68     {
69         DeviceName = (LPCWSTR)((ULONG_PTR)DeviceName + ((DeviceNameInfo >> 16) & 0xFFFF));
70 
71         if (_wcsnicmp(DeviceName, DosCONDevice, 3) == 0)
72         {
73             DeviceType = 1;
74         }
75         else
76         if ( _wcsnicmp(DeviceName, DosLPTDevice, 3) == 0 ||
77              _wcsnicmp(DeviceName, DosCOMDevice, 3) == 0 ||
78              _wcsnicmp(DeviceName, DosPRNDevice, 3) == 0 ||
79              _wcsnicmp(DeviceName, DosAUXDevice, 3) == 0 ||
80              _wcsnicmp(DeviceName, DosNULDevice, 3) == 0 )
81         {
82             DeviceType = 2;
83         }
84         // else DeviceType = 0;
85     }
86 
87 #ifndef _UNICODE
88     cmd_free(buffer);
89 #endif
90 
91     return DeviceType;
92 }
93 
94 /*
95  * See also redir.c!PerformRedirection().
96  *
97  * The CTTY command allows only the usage of CON, COM, AUX, LPT, PRN and NUL
98  * DOS devices as valid terminal devices. Everything else is forbidden.
99  *
100  * CTTY does not set ERRORLEVEL on error.
101  */
102 INT cmd_ctty(LPTSTR param)
103 {
104     static SECURITY_ATTRIBUTES SecAttr = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
105 
106     BOOL Success;
107     WORD DeviceType;
108     HANDLE hDevice, hStdHandles[3]; // hStdIn, hStdOut, hStdErr;
109 
110     /* The user asked for help */
111     if (_tcsncmp(param, _T("/?"), 2) == 0)
112     {
113         ConOutResPaging(TRUE, STRING_CTTY_HELP);
114         return 0;
115     }
116 
117     if (!*param)
118     {
119         error_req_param_missing();
120         return 1;
121     }
122 
123     /* Check whether this is a valid terminal device name */
124     DeviceType = CheckTerminalDeviceType(param);
125     if (DeviceType == 1)
126     {
127         /*
128          * Special case for CON device.
129          *
130          * We do not open CON with GENERIC_READ or GENERIC_WRITE as is,
131          * but instead we separately open CONIN$ and CONOUT$ with both
132          * GENERIC_READ | GENERIC_WRITE access.
133          * We do so because otherwise, opening in particular CON with GENERIC_WRITE
134          * only would open CONOUT$ with an handle not passing the IsConsoleHandle()
135          * test, meaning that we could not use the full console functionalities.
136          */
137         BOOL bRetry = FALSE;
138 
139 RetryOpenConsole:
140         /*
141          * If we previously failed in opening handles to the console,
142          * this means the existing console is almost destroyed.
143          * Close the existing console and allocate and open a new one.
144          */
145         if (bRetry)
146         {
147             FreeConsole();
148             if (!AllocConsole())
149                 return 1;
150         }
151 
152         /* Attempt to retrieve a handle for standard input */
153         hStdHandles[0] = CreateFile(_T("CONIN$"),
154                                     GENERIC_READ | GENERIC_WRITE,
155                                     FILE_SHARE_READ | FILE_SHARE_WRITE,
156                                     &SecAttr,
157                                     OPEN_EXISTING,
158                                     0,
159                                     NULL);
160         if (hStdHandles[0] == INVALID_HANDLE_VALUE)
161         {
162             // TODO: Error
163             // error_no_rw_device(param);
164 
165             if (bRetry)
166                 return 1;
167             bRetry = TRUE;
168             goto RetryOpenConsole;
169         }
170 
171         /* Attempt to retrieve a handle for standard output.
172          * Note that GENERIC_READ is needed for IsConsoleHandle() to succeed afterwards. */
173         hStdHandles[1] = CreateFile(_T("CONOUT$"),
174                                     GENERIC_READ | GENERIC_WRITE,
175                                     FILE_SHARE_READ | FILE_SHARE_WRITE,
176                                     &SecAttr,
177                                     OPEN_EXISTING,
178                                     0,
179                                     NULL);
180         if (hStdHandles[1] == INVALID_HANDLE_VALUE)
181         {
182             // TODO: Error
183             // error_no_rw_device(param);
184 
185             CloseHandle(hStdHandles[0]);
186 
187             if (bRetry)
188                 return 1;
189             bRetry = TRUE;
190             goto RetryOpenConsole;
191         }
192 
193         /* Duplicate a handle for standard error */
194         Success = DuplicateHandle(GetCurrentProcess(),
195                                   hStdHandles[1],
196                                   GetCurrentProcess(),
197                                   &hStdHandles[2],
198                                   0, // GENERIC_WRITE,
199                                   TRUE,
200                                   DUPLICATE_SAME_ACCESS /* 0 */);
201         if (!Success)
202         {
203             // TODO: Error
204             // error_no_rw_device(param);
205             CloseHandle(hStdHandles[1]);
206             CloseHandle(hStdHandles[0]);
207             return 1;
208         }
209     }
210     else if (DeviceType == 2)
211     {
212         /*
213          * COM and the other devices can only be opened once.
214          * Since we need different handles, we need to duplicate them.
215          */
216 
217         /* Attempt to retrieve a handle to the device for read/write access */
218         hDevice = CreateFile(param,
219                              GENERIC_READ | GENERIC_WRITE,
220                              FILE_SHARE_READ | FILE_SHARE_WRITE,
221                              &SecAttr,
222                              OPEN_EXISTING,
223                              0, // FILE_FLAG_OVERLAPPED, // 0,
224                              NULL);
225         if (hDevice == INVALID_HANDLE_VALUE)
226         {
227             // TODO: Error
228             // error_no_rw_device(param);
229             return 1;
230         }
231 
232         /* Duplicate a handle for standard input */
233         Success = DuplicateHandle(GetCurrentProcess(),
234                                   hDevice,
235                                   GetCurrentProcess(),
236                                   &hStdHandles[0],
237                                   GENERIC_READ,
238                                   TRUE,
239                                   0);
240         if (!Success)
241         {
242             // TODO: Error
243             // error_no_rw_device(param);
244             CloseHandle(hDevice);
245             return 1;
246         }
247 
248         /* Duplicate a handle for standard output */
249         Success = DuplicateHandle(GetCurrentProcess(),
250                                   hDevice,
251                                   GetCurrentProcess(),
252                                   &hStdHandles[1],
253                                   GENERIC_WRITE,
254                                   TRUE,
255                                   0);
256         if (!Success)
257         {
258             // TODO: Error
259             // error_no_rw_device(param);
260             CloseHandle(hStdHandles[0]);
261             CloseHandle(hDevice);
262             return 1;
263         }
264 
265         /* Duplicate a handle for standard error */
266         Success = DuplicateHandle(GetCurrentProcess(),
267                                   hDevice,
268                                   GetCurrentProcess(),
269                                   &hStdHandles[2],
270                                   GENERIC_WRITE,
271                                   TRUE,
272                                   0);
273         if (!Success)
274         {
275             // TODO: Error
276             // error_no_rw_device(param);
277             CloseHandle(hStdHandles[1]);
278             CloseHandle(hStdHandles[0]);
279             CloseHandle(hDevice);
280             return 1;
281         }
282 
283         /* Now get rid of the main device handle */
284         CloseHandle(hDevice);
285     }
286     else
287     {
288         // FIXME: Localize!
289         ConOutPrintf(L"Invalid device '%s'\n", param);
290         return 1;
291     }
292 
293 #if 0
294   /* Now change the file descriptors:
295     0 := rdonly
296     1,2 := wronly
297 
298     if CTTY is called within a pipe or its I/O is redirected,
299     oldinfd or oldoutfd is not equal to -1. In such case the
300     old*fd is modified in order to effect the file descriptor
301     after the redirections are restored. Otherwise a pipe or
302     redirection would left CTTY in a half-made status.
303   */
304   // int failed;
305   failed = dup2(f, 2);   /* no redirection support */
306   if(oldinfd != -1)
307   	dos_close(oldinfd);
308   oldinfd = f;
309   if(oldoutfd != -1)
310   	dos_close(oldoutfd);
311   if((oldoutfd = dup(f)) == -1)
312   	failed = 1;
313 
314   if(failed)
315     error_ctty_dup(param);
316 
317   return failed;
318 #endif
319 
320     /* Now set the standard handles */
321 
322     hDevice = GetHandle(0);
323     if (hDevice != INVALID_HANDLE_VALUE)
324         CloseHandle(hDevice);
325     SetHandle(0, hStdHandles[0]);
326 
327     hDevice = GetHandle(1);
328     if (hDevice != INVALID_HANDLE_VALUE)
329         CloseHandle(hDevice);
330     SetHandle(1, hStdHandles[1]);
331 
332     hDevice = GetHandle(2);
333     if (hDevice != INVALID_HANDLE_VALUE)
334         CloseHandle(hDevice);
335     SetHandle(2, hStdHandles[2]);
336 
337     return 0;
338 }
339 
340 #endif /* INCLUDE_CMD_CTTY && FEATURE_REDIRECTION */
341 
342 /* EOF */
343