Kernel - Implimenting PTYs (untested, unintegrated)
[tpg/acess2.git] / KernelLand / Kernel / drv / pty.c
1 /*
2  * Acess2 Kernel
3  * - By John Hodge (thePowersGang)
4  *
5  * drv/pty.c
6  * - Pseudo Terminals
7  */
8 #include <acess.h>
9 #include <vfs.h>
10 #include <fs_devfs.h>
11 #include <drv_pty.h>
12 #include <modules.h>
13 #include <rwlock.h>
14 #include <mutex.h>
15
16 // === CONSTANTS ===
17 #define OUTPUT_RINGBUFFER_LEN   1024    // Number of bytes in output queue before client blocks
18 #define INPUT_RINGBUFFER_LEN    256     // Number of bytes in input queue before being dropped
19 #define INPUT_LINE_LEN  256
20
21 // === TYPES ===
22 struct sPTY
23 {
24         tPTY    *Next;
25         
26         char    *Name;
27          int    NumericName;
28         void    *OutputHandle;
29         tPTY_OutputFcn  OutputFcn;
30
31         struct ptymode  Mode;
32         struct ptydims  Dims;
33
34          int    HasHitEOF;      
35         tMutex  InputMutex;
36          int    InputWritePos;
37          int    InputReadPos;
38         char    InputData[INPUT_RINGBUFFER_LEN];
39         
40          int    LineLength;
41         char    LineData[INPUT_LINE_LEN];
42
43         tMutex  OutputMutex;    
44          int    OutputWritePos;
45          int    OutputReadPos;
46         char    OutputData[OUTPUT_RINGBUFFER_LEN];
47         
48         tVFS_Node       ClientNode;
49         tVFS_Node       ServerNode;
50         tVFS_ACL        OwnerRW;
51         
52         // TODO: Maintain list of client PIDs
53 };
54
55 // === PROTOTYPES ===
56  int    PTY_Install(char **Arguments);
57  int    PTY_ReadDir(tVFS_Node *Node, int Pos, char Name[FILENAME_MAX]);
58 tVFS_Node       *PTY_FindDir(tVFS_Node *Node, const char *Name, Uint Flags);
59
60 size_t  _rb_write(void *buf, size_t buflen, int *rd, int *wr, const void *data, size_t len);
61 size_t  _rb_read(void *buf, size_t buflen, int *rd, int *wr, void *data, size_t len);
62 size_t  PTY_int_WriteInput(tPTY *PTY, const char *Input, size_t Length);
63 size_t  PTY_int_SendInput(tPTY *PTY, const char *Input, size_t Length);
64 // PTY_SendInput
65 size_t  PTY_ReadClient(tVFS_Node *Node, off_t Offset, size_t Length, void *Buffer, Uint Flags);
66 size_t  PTY_WriteClient(tVFS_Node *Node, off_t Offset, size_t Length, const void *Buffer, Uint Flags);
67  int    PTY_IOCtlClient(tVFS_Node *Node, int ID, void *Arg);
68 void    PTY_ReferenceClient(tVFS_Node *Node);
69 void    PTY_CloseClient(tVFS_Node *Node);
70 size_t  PTY_ReadServer(tVFS_Node *Node, off_t Offset, size_t Length, void *Buffer, Uint Flags);
71 size_t  PTY_WriteServer(tVFS_Node *Node, off_t Offset, size_t Length, const void *Buffer, Uint Flags);
72  int    PTY_IOCtlServer(tVFS_Node *Node, int ID, void *Arg);
73 void    PTY_CloseServer(tVFS_Node *Node);
74
75 // === GLOBALS ===
76 MODULE_DEFINE(0, 0x100, PTY, PTY_Install, NULL, NULL);
77 tVFS_NodeType   gPTY_NodeType_Root = {
78         .TypeName = "PTY-Root",
79         .ReadDir = PTY_ReadDir,
80         .FindDir = PTY_FindDir
81 };
82 tVFS_NodeType   gPTY_NodeType_Client = {
83         .TypeName = "PTY-Client",
84         .Read = PTY_ReadClient,
85         .Write = PTY_WriteClient,
86         .IOCtl = PTY_IOCtlClient,
87         .Reference = PTY_ReferenceClient,
88         .Close = PTY_CloseClient
89 };
90 tVFS_NodeType   gPTY_NodeType_Server = {
91         .TypeName = "PTY-Server",
92         .Read = PTY_ReadServer,
93         .Write = PTY_WriteServer,
94         .IOCtl = PTY_IOCtlServer,
95         .Close = PTY_CloseServer
96 };
97  int    giPTY_NumCount;
98 tRWLock glPTY_NumPTYs;
99 tPTY    *gpPTY_FirstNumPTY;
100  int    giPTY_NamedCount;
101 tRWLock glPTY_NamedPTYs;
102 tPTY    *gpPTY_FirstNamedPTY;
103
104 // === CODE ===
105 int PTY_Install(char **Arguments)
106 {
107         return MODULE_ERR_OK;
108 }
109
110 // --- Management ---
111 tPTY *PTY_Create(const char *Name, void *Handle, tPTY_OutputFcn Output)
112 {
113         tPTY    **prev_np = NULL;
114         size_t  namelen;
115          int    idx = 1;
116         if( Name && Name[0] )
117         {
118                 prev_np = &gpPTY_FirstNamedPTY;
119                 
120                 // Check the name isn't decimal
121                 char *end;
122                 if( strtol(Name, &end, 10) != 0 && *end == '\0' ) {
123                         errno = EINVAL;
124                         return NULL;
125                 }
126                 
127                 RWLock_AcquireWrite(&glPTY_NamedPTYs);
128                 // Detect duplicates
129                 for( tPTY *pty = gpPTY_FirstNamedPTY; pty; prev_np = &pty->Next, pty = pty->Next )
130                 {
131                          int    cmp = strcmp(pty->Name, Name);
132                         if( cmp < 0 )
133                                 continue;
134                         if( cmp == 0 ) {
135                                 RWLock_Release(&glPTY_NamedPTYs);
136                                 errno = EEXIST;
137                                 return NULL;
138                         }
139                         break;
140                 }
141                 namelen = strlen(Name);
142                 idx = -1;
143         }
144         else
145         {
146                 RWLock_AcquireWrite(&glPTY_NumPTYs);
147                 // Get a pty ID if Name==NULL
148                 prev_np = &gpPTY_FirstNumPTY;
149                 for( tPTY *pty = gpPTY_FirstNumPTY; pty; prev_np = &pty->Next, pty = pty->Next )
150                 {
151                         if( pty->NumericName > idx )
152                                 break;
153                         idx ++;
154                 }
155                 namelen = 0;
156         }
157         
158         tPTY *ret = calloc(sizeof(tPTY) + namelen + 1, 1);
159         
160         // - List maintainance
161         ret->Next = *prev_np;
162         *prev_np = ret;
163         // - PTY Name (Used by VT)
164         ret->Name = (char*)(ret + 1);
165         if(Name)
166                 strcpy(ret->Name, Name);
167         else
168                 ret->Name[0] = 0;
169         ret->NumericName = idx;
170         // - Output function and handle (same again)
171         ret->OutputHandle = Handle;
172         ret->OutputFcn = Output;
173         // - Server node
174         ret->ServerNode.ImplPtr = ret;
175         ret->ServerNode.Type = &gPTY_NodeType_Server;
176         ret->ServerNode.UID = Threads_GetUID();
177         ret->ServerNode.GID = Threads_GetGID();
178         ret->ServerNode.NumACLs = 1;
179         ret->ServerNode.ACLs = &ret->OwnerRW;
180         ret->ServerNode.ReferenceCount = (Output ? 1 : 0);      // Prevents a userland open/close killing a kernel pty
181         // - Client node
182         ret->ClientNode.ImplPtr = ret;
183         ret->ClientNode.Type = &gPTY_NodeType_Client;
184         ret->ClientNode.UID = Threads_GetUID();
185         ret->ClientNode.GID = Threads_GetGID();
186         ret->ClientNode.NumACLs = 1;
187         ret->ClientNode.ACLs = &ret->OwnerRW;
188         // - Owner Read-Write ACL
189         ret->OwnerRW.Ent.ID = Threads_GetUID();
190         ret->OwnerRW.Perm.Perms = -1;
191
192         if( Name && Name[0] ) {
193                 giPTY_NamedCount ++;
194                 RWLock_Release(&glPTY_NamedPTYs);
195         }
196         else {
197                 giPTY_NumCount ++;
198                 RWLock_Release(&glPTY_NumPTYs); 
199         }
200
201         return ret;
202 }
203
204 void PTY_SetAttrib(tPTY *PTY, const struct ptydims *Dims, const struct ptymode *Mode, int WasClient)
205 {
206         if( Mode ) {
207                 PTY->Mode = *Mode;
208                 if( !WasClient && !PTY->OutputFcn )
209                 {
210                         Log_Warning("PTY", "TODO: Need to stop client output until modeset has been ACKed");
211                         // Block write until acked
212                         // ACK by server doing GETMODE
213                 }
214         }
215         if( Dims ) {
216                 PTY->Dims = *Dims;
217                 if( WasClient ) {
218                         // Poke the server?
219                 }
220                 else {
221                         // SIGWINSZ to client
222                 }
223         }
224 }
225
226 void PTY_Close(tPTY *PTY)
227 {
228         
229 }
230
231 size_t _rb_write(void *buf, size_t buflen, int *rd, int *wr, const void *data, size_t len)
232 {
233         size_t space = (*rd - *wr + buflen) % buflen - 1;
234         len = MIN(space, len);
235         if(*wr + len >= buflen) {
236                 size_t prelen = buflen - *wr;
237                 memcpy((char*)buf + *wr, data, prelen);
238                 memcpy(buf, (char*)data + prelen, len - prelen);
239                 *wr = len - prelen;
240         }
241         else {
242                 memcpy((char*)buf + *wr, data, len);
243                 *wr += len;
244         }
245         return len;
246 }
247 size_t _rb_read(void *buf, size_t buflen, int *rd, int *wr, void *data, size_t len)
248 {
249         size_t space = (*wr - *rd + buflen) % buflen;
250         len = MIN(space, len);
251         if(*rd + len >= buflen) {
252                 size_t prelen = buflen - *rd;
253                 memcpy(data, (char*)buf + *rd, prelen);
254                 memcpy((char*)data + prelen, buf, len - prelen);
255                 *rd = len - prelen;
256         }
257         else {
258                 memcpy(data, (char*)buf + *rd, len);
259                 *rd += len;
260         }
261         return len;
262 }
263
264 size_t PTY_int_WriteInput(tPTY *PTY, const char *Input, size_t Length)
265 {
266         size_t  ret;
267
268         Mutex_Acquire(&PTY->InputMutex);        
269
270         ret = _rb_write(PTY->InputData, INPUT_RINGBUFFER_LEN, &PTY->InputReadPos, &PTY->InputWritePos,
271                 Input, Length);
272         
273         Mutex_Release(&PTY->InputMutex);
274
275         VFS_MarkAvaliable(&PTY->ClientNode, 1);
276         if(ret < Length)
277                 VFS_MarkFull(&PTY->ServerNode, 1);      
278
279         return ret;
280 }
281
282 size_t PTY_int_SendInput(tPTY *PTY, const char *Input, size_t Length)
283 {
284         size_t  ret = 1, print = 1;
285         
286         // Only counts for text input modes
287         if( (PTY->Mode.OutputMode & PTYOMODE_BUFFMT) == PTYBUFFMT_TEXT )
288                 return PTY_int_WriteInput(PTY, Input, Length);
289         // If in raw mode, flush directlr
290         if( (PTY->Mode.InputMode & PTYIMODE_RAW) )
291                 return PTY_int_WriteInput(PTY, Input, Length);
292         
293         if( PTY->Mode.InputMode & PTYIMODE_CANON )
294         {
295                 const char      char_bs = '\b';
296                 switch(Input[0])
297                 {
298                 case 3: // INTR - ^C
299                         // TODO: Send SIGINT
300                         print = 0;
301                         break;
302                 case 4: // EOF - ^D
303                         PTY_int_WriteInput(PTY, PTY->LineData, PTY->LineLength);
304                         PTY->HasHitEOF = (PTY->LineLength == 0);
305                         PTY->LineLength = 0;
306                         print = 0;
307                         break;
308                 case 8: // Backspace
309                         if(PTY->LineLength != 0)
310                                 PTY->LineLength --;
311                         break;
312                 case 'w'-'a':   // Word erase
313                         while(PTY->LineLength != 0 && isalnum(PTY->LineData[--PTY->LineLength]))
314                                 PTY_WriteClient(&PTY->ClientNode, 0, 1, &char_bs, 0);
315                         print = 0;
316                         break;
317                 case 'u'-'a':   // Kill
318                         while(PTY->LineLength > 0)
319                                 PTY_WriteClient(&PTY->ClientNode, 0, 1, &char_bs, 0);
320                         print = 0;
321                         break;
322                 case 'v'-'a':
323                         Input ++;
324                         Length --;
325                         ret ++;
326                         goto _default;
327                 case '\0':
328                 case '\n':
329                         if(PTY->LineLength == INPUT_LINE_LEN) {
330                                 PTY_int_WriteInput(PTY, PTY->LineData, PTY->LineLength);
331                                 PTY->LineLength = 0;
332                         }
333                         PTY->LineData[PTY->LineLength++] = '\n';
334                         PTY_int_WriteInput(PTY, PTY->LineData, PTY->LineLength);
335                         PTY->LineLength = 0;
336                         break;
337                 // TODO: Handle ^[[D and ^[[C for in-line editing, also ^[[1~/^[[4~ (home/end)
338                 //case 0x1B:
339                 //      break;
340                 default:
341                 _default:
342                         if(PTY->LineLength == INPUT_LINE_LEN) {
343                                 PTY_int_WriteInput(PTY, PTY->LineData, PTY->LineLength);
344                                 PTY->LineLength = 0;
345                         }
346                         PTY->LineData[PTY->LineLength++] = Input[0];
347                         break;
348                 }
349         }
350         else
351         {
352                 ret = PTY_int_WriteInput(PTY, Input, Length);
353         }
354         
355         // Echo if requested
356         if( PTY->Mode.InputMode & PTYIMODE_ECHO )
357         {
358                 PTY_WriteClient(&PTY->ClientNode, 0, print, Input, 0);
359         }
360         
361         return ret;
362 }
363
364 size_t PTY_SendInput(tPTY *PTY, const char *Input, size_t Length)
365 {
366         size_t ret = 0;
367         while( ret < Length && !PTY->ClientNode.BufferFull )
368         {
369                 // TODO: Detect blocking?
370                 ret += PTY_int_SendInput(PTY, Input + ret, Length - ret);
371         }
372         return ret;
373 }
374
375 // --- VFS ---
376 int PTY_ReadDir(tVFS_Node *Node, int Pos, char Name[FILENAME_MAX])
377 {
378         tPTY    *pty = NULL;
379         if( Pos < giPTY_NumCount * 2 )
380         {
381                 RWLock_AcquireRead(&glPTY_NumPTYs);
382                 for( pty = gpPTY_FirstNumPTY; pty; pty = pty->Next )
383                 {
384                         if( Pos < 2 )
385                                 break;
386                         Pos -= 2;
387                 }
388                 RWLock_Release(&glPTY_NumPTYs);
389         }
390         else if( Pos < (giPTY_NumCount + giPTY_NamedCount) * 2 )
391         {
392                 RWLock_AcquireRead(&glPTY_NamedPTYs);
393                 for( pty = gpPTY_FirstNamedPTY; pty; pty = pty->Next )
394                 {
395                         if( Pos < 2 )
396                                 break;
397                         Pos -= 2;
398                 }
399                 RWLock_Release(&glPTY_NamedPTYs);
400         }
401         
402
403         if( !pty )
404                 return -1;
405         
406         if( pty->Name[0] )
407                 snprintf(Name, 255, "%s%c", pty->Name, (Pos == 0 ? 'c' : 's'));
408         else
409                 snprintf(Name, 255, "%i%c", pty->NumericName, (Pos == 0 ? 'c' : 's'));
410         return 0;
411 }
412
413 tVFS_Node *PTY_FindDir(tVFS_Node *Node, const char *Name, Uint Flags)
414 {
415         char    *end;
416          int    num = strtol(Name, &end, 10);
417         
418         if( Name[0] == '\0' || Name[1] == '\0' )
419                 return NULL;
420         
421         size_t  len = strlen(Name);
422         if( Name[len-1] != 'c' && Name[len-1] != 's' )
423                 return NULL;
424          int    isServer = (Name[len-1] != 'c');
425         
426         tPTY    *ret = NULL;
427         if( num && (end[0] == 'c' || end[0] == 's') && end[1] == '\0' )
428         {
429                 // Numeric name
430                 RWLock_AcquireRead(&glPTY_NumPTYs);
431                 for( tPTY *pty = gpPTY_FirstNumPTY; pty; pty = pty->Next )
432                 {
433                         if( pty->NumericName > num )
434                                 break;
435                         if( pty->NumericName == num ) {
436                                 ret = pty;
437                                 break;
438                         }
439                 }
440                 RWLock_Release(&glPTY_NumPTYs);
441         }
442         else
443         {
444                 // String name
445                 RWLock_AcquireRead(&glPTY_NamedPTYs);
446                 for( tPTY *pty = gpPTY_FirstNamedPTY; pty; pty = pty->Next )
447                 {
448                         int cmp = strncmp(pty->Name, Name, len-1);
449                         if(cmp > 0)
450                                 break;
451                         if(cmp == 0 && pty->Name[len-1] == '\0' ) {
452                                 ret = pty;
453                                 break;
454                         }
455                 }
456                 RWLock_Release(&glPTY_NamedPTYs);
457         }
458         if( ret )
459                 return (isServer ? &ret->ServerNode : &ret->ClientNode);
460         else
461                 return NULL;
462 }
463
464 //\! Read from the client's input
465 size_t PTY_ReadClient(tVFS_Node *Node, off_t Offset, size_t Length, void *Buffer, Uint Flags)
466 {
467         tPTY *pty = Node->ImplPtr;
468         
469         // Read from flushed queue
470         tTime   timeout_z = 0, *timeout = (Flags & VFS_IOFLAG_NOBLOCK) ? &timeout_z : NULL;
471          int    rv;
472 _select:
473         rv = VFS_SelectNode(Node, VFS_SELECT_READ, timeout, "PTY_ReadClient");
474         if(!rv) {
475                 errno = (timeout ? EWOULDBLOCK : EINTR);
476                 return -1;
477         }
478
479         Mutex_Acquire(&pty->InputMutex);
480         Length = _rb_read(pty->InputData, INPUT_RINGBUFFER_LEN, &pty->InputReadPos, &pty->InputWritePos,
481                 Buffer, Length);
482         Mutex_Release(&pty->InputMutex);
483
484         if(Length == 0 && !pty->HasHitEOF) {
485                 goto _select;
486         }
487         pty->HasHitEOF = 0;
488
489         return Length;
490 }
491
492 //\! Write to the client's output
493 size_t PTY_WriteClient(tVFS_Node *Node, off_t Offset, size_t Length, const void *Buffer, Uint Flags)
494 {
495         tPTY *pty = Node->ImplPtr;
496         
497         // Write to either FIFO or directly to output function
498         if( pty->OutputFcn )
499         {
500                 pty->OutputFcn(pty->OutputHandle, Buffer, Length, &pty->Dims);
501         }
502         else
503         {
504                 // Write to output ringbuffer
505                 Length = _rb_write(pty->OutputData, OUTPUT_RINGBUFFER_LEN,
506                         &pty->OutputReadPos, &pty->OutputWritePos,
507                         Buffer, Length);
508                 VFS_MarkAvaliable(&pty->ServerNode, 1);
509         }
510         
511         return Length;
512 }
513
514 int PTY_IOCtlClient(tVFS_Node *Node, int ID, void *Data)
515 {
516         tPTY    *pty = Node->ImplPtr;
517         struct ptymode  *mode = Data;
518         struct ptydims  *dims = Data;
519         switch(ID)
520         {
521         case DRV_IOCTL_TYPE:    return DRV_TYPE_TERMINAL;
522         case DRV_IOCTL_IDENT:   memcpy(Data, "PTY\0", 4);       return 0;
523         case DRV_IOCTL_VER:     return 0x100;
524         case DRV_IOCTL_LOOKUP:  return 0;
525         
526         case PTY_IOCTL_GETMODE:
527                 if( !CheckMem(Data, sizeof(*mode)) ) { errno = EINVAL; return -1; }
528                 *mode = pty->Mode;
529                 return 0;
530         case PTY_IOCTL_SETMODE:
531                 if( !CheckMem(Data, sizeof(*mode)) ) { errno = EINVAL; return -1; }
532                 PTY_SetAttrib(pty, NULL, mode, 1);
533                 return 0;
534         case PTY_IOCTL_GETDIMS:
535                 if( !CheckMem(Data, sizeof(*dims)) ) { errno = EINVAL; return -1; }
536                 *dims = pty->Dims;
537                 return 0;
538         case PTY_IOCTL_SETDIMS:
539                 if( !CheckMem(Data, sizeof(*dims)) ) { errno = EINVAL; return -1; }
540                 PTY_SetAttrib(pty, dims, NULL, 1);
541                 // Inform the server?
542                 return 0;
543         }
544         errno = ENOSYS;
545         return -1;
546 }
547
548 void PTY_ReferenceClient(tVFS_Node *Node)
549 {
550         Node->ReferenceCount ++;
551         // TODO: Add PID to list of client PIDs
552         Log_Notice("PTY", "TODO: List of client PIDs");
553 }
554
555 void PTY_CloseClient(tVFS_Node *Node)
556 {
557         tPTY    *pty = Node->ImplPtr;
558         Node->ReferenceCount --;
559
560         // Remove PID from list
561
562         // Free structure if this was the last open handle
563         if( Node->ReferenceCount == 0 && pty->ServerNode.ReferenceCount == 0 )
564         {
565                 // Free the structure! (Should be off the PTY list now)
566                 free(pty);
567         }
568 }
569
570 //\! Read from the client's output
571 size_t PTY_ReadServer(tVFS_Node *Node, off_t Offset, size_t Length, void *Buffer, Uint Flags)
572 {
573         tPTY *pty = Node->ImplPtr;
574
575         // TODO: Prevent two servers fighting over client's output      
576         if( pty->OutputFcn )
577         {
578                 // Kernel-land PTYs can't be read from userland
579                 return 0;
580         }
581         
582         // Read back from fifo
583         tTime   timeout_z = 0, *timeout = (Flags & VFS_IOFLAG_NOBLOCK) ? &timeout_z : NULL;
584         int rv = VFS_SelectNode(Node, VFS_SELECT_READ, timeout, "PTY_ReadServer");
585         if(!rv) {
586                 errno = (timeout ? EWOULDBLOCK : EINTR);
587                 return -1;
588         }
589         
590         Length = _rb_read(pty->OutputData, OUTPUT_RINGBUFFER_LEN,
591                 &pty->OutputReadPos, &pty->OutputWritePos,
592                 Buffer, Length);
593         if( pty->OutputReadPos == pty->OutputWritePos )
594                 VFS_MarkAvaliable(Node, 0);
595         VFS_MarkFull(&pty->ClientNode, 0);
596         
597         return Length;
598 }
599
600 //\! Write to the client's input
601 size_t PTY_WriteServer(tVFS_Node *Node, off_t Offset, size_t Length, const void *Buffer, Uint Flags)
602 {
603         // Write to current line buffer, flushing on unknown character or newline
604         // - or line wrap?
605         // Echo back if instructed
606         PTY_SendInput(Node->ImplPtr, Buffer, Length);
607         return -1;
608 }
609
610 int PTY_IOCtlServer(tVFS_Node *Node, int ID, void *Data)
611 {
612         tPTY    *pty = Node->ImplPtr;
613         struct ptymode  *mode = Data;
614         struct ptydims  *dims = Data;
615         switch(ID)
616         {
617         case DRV_IOCTL_TYPE:    return DRV_TYPE_TERMINAL;
618         case DRV_IOCTL_IDENT:   memcpy(Data, "PTY\0", 4);       return 0;
619         case DRV_IOCTL_VER:     return 0x100;
620         case DRV_IOCTL_LOOKUP:  return 0;
621         
622         case PTY_IOCTL_GETMODE:
623                 if( !CheckMem(Data, sizeof(*mode)) ) { errno = EINVAL; return -1; }
624                 *mode = pty->Mode;
625                 // ACK client's SETMODE
626                 return 0;
627         case PTY_IOCTL_SETMODE:
628                 if( !CheckMem(Data, sizeof(*mode)) ) { errno = EINVAL; return -1; }
629                 PTY_SetAttrib(pty, NULL, mode, 0);
630                 return 0;
631         case PTY_IOCTL_GETDIMS:
632                 if( !CheckMem(Data, sizeof(*dims)) ) { errno = EINVAL; return -1; }
633                 *dims = pty->Dims;
634                 return 0;
635         case PTY_IOCTL_SETDIMS:
636                 if( !CheckMem(Data, sizeof(*dims)) ) { errno = EINVAL; return -1; }
637                 PTY_SetAttrib(pty, dims, NULL, 0);
638                 break;
639         }
640         errno = ENOSYS;
641         return -1;
642 }
643
644 void PTY_CloseServer(tVFS_Node *Node)
645 {
646         // Dereference node
647         Node->ReferenceCount --;
648         // If reference count == 0, remove from main list and SIGPIPE all clients when they write
649 }
650

UCC git Repository :: git.ucc.asn.au