Tried to be a little smarter with the heartbeats, using a simple priority mecanism...
[divverent/darkplaces.git] / net_main.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // net_main.c
21
22 #include "quakedef.h"
23 #include "net_master.h"
24
25 qsocket_t *net_activeSockets = NULL;
26 mempool_t *net_mempool;
27
28 qboolean        ipxAvailable = false;
29 qboolean        tcpipAvailable = false;
30
31 int                     net_hostport;
32 int                     DEFAULTnet_hostport = 26000;
33
34 char            my_ipx_address[NET_NAMELEN];
35 char            my_tcpip_address[NET_NAMELEN];
36
37 static qboolean listening = false;
38
39 qboolean        slistInProgress = false;
40 qboolean        slistSilent = false;
41 qboolean        slistLocal = true;
42 static double   slistStartTime;
43 static int              slistLastShown;
44
45 static void Slist_Send(void);
46 static void Slist_Poll(void);
47 PollProcedure   slistSendProcedure = {NULL, 0.0, Slist_Send};
48 PollProcedure   slistPollProcedure = {NULL, 0.0, Slist_Poll};
49
50 static void InetSlist_Send(void);
51 static void InetSlist_Poll(void);
52 PollProcedure   inetSlistSendProcedure = {NULL, 0.0, InetSlist_Send};
53 PollProcedure   inetSlistPollProcedure = {NULL, 0.0, InetSlist_Poll};
54
55
56 sizebuf_t               net_message;
57 int                             net_activeconnections = 0;
58
59 int messagesSent = 0;
60 int messagesReceived = 0;
61 int unreliableMessagesSent = 0;
62 int unreliableMessagesReceived = 0;
63
64 cvar_t  net_messagetimeout = {0, "net_messagetimeout","300"};
65 cvar_t  hostname = {CVAR_SAVE, "hostname", "UNNAMED"};
66
67 qboolean        configRestored = false;
68
69 // these two macros are to make the code more readable
70 #define sfunc   net_drivers[sock->driver]
71 #define dfunc   net_drivers[net_driverlevel]
72
73 int     net_driverlevel;
74
75
76 double                  net_time;
77
78 double SetNetTime(void)
79 {
80         net_time = Sys_DoubleTime();
81         return net_time;
82 }
83
84
85 /*
86 ===================
87 NET_NewQSocket
88
89 Called by drivers when a new communications endpoint is required
90 The sequence and buffer fields will be filled in properly
91 ===================
92 */
93 qsocket_t *NET_NewQSocket (void)
94 {
95         qsocket_t       *sock;
96
97         if (net_activeconnections >= svs.maxclients)
98                 return NULL;
99
100         sock = Mem_Alloc(net_mempool, sizeof(qsocket_t));
101
102         // add it to active list
103         sock->next = net_activeSockets;
104         net_activeSockets = sock;
105
106         sock->disconnected = false;
107         sock->connecttime = net_time;
108         strcpy (sock->address,"UNSET ADDRESS");
109         sock->driver = net_driverlevel;
110         sock->socket = 0;
111         sock->driverdata = NULL;
112         sock->canSend = true;
113         sock->sendNext = false;
114         sock->lastMessageTime = net_time;
115         sock->ackSequence = 0;
116         sock->sendSequence = 0;
117         sock->unreliableSendSequence = 0;
118         sock->sendMessageLength = 0;
119         sock->receiveSequence = 0;
120         sock->unreliableReceiveSequence = 0;
121         sock->receiveMessageLength = 0;
122
123         return sock;
124 }
125
126
127 void NET_FreeQSocket(qsocket_t *sock)
128 {
129         qsocket_t       *s;
130
131         // remove it from active list
132         if (sock == net_activeSockets)
133                 net_activeSockets = net_activeSockets->next;
134         else
135         {
136                 for (s = net_activeSockets; s; s = s->next)
137                         if (s->next == sock)
138                         {
139                                 s->next = sock->next;
140                                 break;
141                         }
142                 if (!s)
143                         Sys_Error ("NET_FreeQSocket: not active\n");
144         }
145
146         Mem_Free(sock);
147 }
148
149
150 static void NET_Listen_f (void)
151 {
152         if (Cmd_Argc () != 2)
153         {
154                 Con_Printf ("\"listen\" is \"%u\"\n", listening ? 1 : 0);
155                 return;
156         }
157
158         listening = atoi(Cmd_Argv(1)) ? true : false;
159
160         for (net_driverlevel=0 ; net_driverlevel<net_numdrivers; net_driverlevel++)
161         {
162                 if (net_drivers[net_driverlevel].initialized == false)
163                         continue;
164                 dfunc.Listen (listening);
165         }
166 }
167
168
169 static void MaxPlayers_f (void)
170 {
171         int n;
172
173         if (Cmd_Argc () != 2)
174         {
175                 Con_Printf ("\"maxplayers\" is \"%u\"\n", svs.maxclients);
176                 return;
177         }
178
179         if (sv.active)
180         {
181                 Con_Printf ("maxplayers can not be changed while a server is running.\n");
182                 return;
183         }
184
185         n = atoi(Cmd_Argv(1));
186         n = bound(1, n, MAX_SCOREBOARD);
187         if (svs.maxclients != n)
188                 Con_Printf ("\"maxplayers\" set to \"%u\"\n", n);
189
190         if ((n == 1) && listening)
191                 Cbuf_AddText ("listen 0\n");
192
193         if ((n > 1) && (!listening))
194                 Cbuf_AddText ("listen 1\n");
195
196         SV_SetMaxClients(n);
197 }
198
199
200 static void NET_Port_f (void)
201 {
202         int     n;
203
204         if (Cmd_Argc () != 2)
205         {
206                 Con_Printf ("\"port\" is \"%u\"\n", net_hostport);
207                 return;
208         }
209
210         n = atoi(Cmd_Argv(1));
211         if (n < 1 || n > 65534)
212         {
213                 Con_Printf ("Bad value, must be between 1 and 65534\n");
214                 return;
215         }
216
217         DEFAULTnet_hostport = n;
218         net_hostport = n;
219
220         if (listening)
221         {
222                 // force a change to the new port
223                 Cbuf_AddText ("listen 0\n");
224                 Cbuf_AddText ("listen 1\n");
225         }
226 }
227
228
229 static void NET_Heartbeat_f (void)
230 {
231         NET_Heartbeat (2);
232 }
233
234
235 static void PrintSlistHeader(void)
236 {
237         Con_Printf("Server          Map             Users\n");
238         Con_Printf("--------------- --------------- -----\n");
239         slistLastShown = 0;
240 }
241
242
243 static void PrintSlist(void)
244 {
245         int n;
246
247         for (n = slistLastShown; n < hostCacheCount; n++)
248         {
249                 if (hostcache[n].maxusers)
250                         Con_Printf("%-15.15s %-15.15s %2u/%2u\n", hostcache[n].name, hostcache[n].map, hostcache[n].users, hostcache[n].maxusers);
251                 else
252                         Con_Printf("%-15.15s %-15.15s\n", hostcache[n].name, hostcache[n].map);
253         }
254         slistLastShown = n;
255 }
256
257
258 static void PrintSlistTrailer(void)
259 {
260         if (hostCacheCount)
261                 Con_Printf("== end list ==\n\n");
262         else
263                 Con_Printf("No Quake servers found.\n\n");
264 }
265
266
267 void NET_Slist_f (void)
268 {
269         if (slistInProgress)
270                 return;
271
272         if (! slistSilent)
273         {
274                 Con_Printf("Looking for Quake servers...\n");
275                 PrintSlistHeader();
276         }
277
278         slistInProgress = true;
279         slistStartTime = Sys_DoubleTime();
280
281         SchedulePollProcedure(&slistSendProcedure, 0.0);
282         SchedulePollProcedure(&slistPollProcedure, 0.1);
283
284         hostCacheCount = 0;
285 }
286
287
288 void NET_InetSlist_f (void)
289 {
290         if (slistInProgress)
291                 return;
292
293         if (! slistSilent)
294         {
295                 Con_Printf("Looking for Quake servers...\n");
296                 PrintSlistHeader();
297         }
298
299         slistInProgress = true;
300         slistStartTime = Sys_DoubleTime();
301
302         SchedulePollProcedure(&inetSlistSendProcedure, 0.0);
303         SchedulePollProcedure(&inetSlistPollProcedure, 0.1);
304
305         hostCacheCount = 0;
306 }
307
308
309 static void Slist_Send(void)
310 {
311         for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
312         {
313                 if (!slistLocal && net_driverlevel == 0)
314                         continue;
315                 if (net_drivers[net_driverlevel].initialized == false)
316                         continue;
317                 dfunc.SearchForHosts (true);
318         }
319
320         if ((Sys_DoubleTime() - slistStartTime) < 0.5)
321                 SchedulePollProcedure(&slistSendProcedure, 0.75);
322 }
323
324
325 static void Slist_Poll(void)
326 {
327         for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
328         {
329                 if (!slistLocal && net_driverlevel == 0)
330                         continue;
331                 if (net_drivers[net_driverlevel].initialized == false)
332                         continue;
333                 dfunc.SearchForHosts (false);
334         }
335
336         if (! slistSilent)
337                 PrintSlist();
338
339         if ((Sys_DoubleTime() - slistStartTime) < 1.5)
340         {
341                 SchedulePollProcedure(&slistPollProcedure, 0.1);
342                 return;
343         }
344
345         if (! slistSilent)
346                 PrintSlistTrailer();
347         slistInProgress = false;
348         slistSilent = false;
349         slistLocal = true;
350 }
351
352
353 static void InetSlist_Send(void)
354 {
355         const char* host;
356
357         if (!slistInProgress)
358                 return;
359
360         while ((host = Master_BuildGetServers ()) != NULL)
361         {
362                 for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
363                 {
364                         if (!slistLocal && net_driverlevel == 0)
365                                 continue;
366                         if (net_drivers[net_driverlevel].initialized == false)
367                                 continue;
368                         dfunc.SearchForInetHosts (host);
369                 }
370         }
371
372         if ((Sys_DoubleTime() - slistStartTime) < 3.5)
373                 SchedulePollProcedure(&inetSlistSendProcedure, 1.0);
374 }
375
376
377 static void InetSlist_Poll(void)
378 {
379         for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
380         {
381                 if (!slistLocal && net_driverlevel == 0)
382                         continue;
383                 if (net_drivers[net_driverlevel].initialized == false)
384                         continue;
385                 // We stop as soon as we have one answer (FIXME: bad...)
386                 if (dfunc.SearchForInetHosts (NULL))
387                         slistInProgress = false;
388         }
389
390         if (! slistSilent)
391                 PrintSlist();
392
393         if (slistInProgress && (Sys_DoubleTime() - slistStartTime) < 4.0)
394         {
395                 SchedulePollProcedure(&inetSlistPollProcedure, 0.1);
396                 return;
397         }
398
399         if (! slistSilent)
400                 PrintSlistTrailer();
401         slistInProgress = false;
402         slistSilent = false;
403         slistLocal = true;
404 }
405
406
407 /*
408 ===================
409 NET_Connect
410 ===================
411 */
412
413 int hostCacheCount = 0;
414 hostcache_t hostcache[HOSTCACHESIZE];
415
416 qsocket_t *NET_Connect (char *host)
417 {
418         qsocket_t               *ret;
419         int                             n;
420         int                             numdrivers = net_numdrivers;
421
422         SetNetTime();
423
424         if (host && *host == 0)
425                 host = NULL;
426
427         if (host)
428         {
429                 if (Q_strcasecmp (host, "local") == 0)
430                 {
431                         numdrivers = 1;
432                         goto JustDoIt;
433                 }
434
435                 if (hostCacheCount)
436                 {
437                         for (n = 0; n < hostCacheCount; n++)
438                                 if (Q_strcasecmp (host, hostcache[n].name) == 0)
439                                 {
440                                         host = hostcache[n].cname;
441                                         break;
442                                 }
443                         if (n < hostCacheCount)
444                                 goto JustDoIt;
445                 }
446         }
447
448         slistSilent = host ? true : false;
449         NET_Slist_f ();
450
451         while(slistInProgress)
452                 NET_Poll();
453
454         if (host == NULL)
455         {
456                 if (hostCacheCount != 1)
457                         return NULL;
458                 host = hostcache[0].cname;
459                 Con_Printf("Connecting to...\n%s @ %s\n\n", hostcache[0].name, host);
460         }
461
462         if (hostCacheCount)
463                 for (n = 0; n < hostCacheCount; n++)
464                         if (Q_strcasecmp (host, hostcache[n].name) == 0)
465                         {
466                                 host = hostcache[n].cname;
467                                 break;
468                         }
469
470 JustDoIt:
471         for (net_driverlevel=0 ; net_driverlevel<numdrivers; net_driverlevel++)
472         {
473                 if (net_drivers[net_driverlevel].initialized == false)
474                         continue;
475                 ret = dfunc.Connect (host);
476                 if (ret)
477                         return ret;
478         }
479
480         if (host)
481         {
482                 Con_Printf("\n");
483                 PrintSlistHeader();
484                 PrintSlist();
485                 PrintSlistTrailer();
486         }
487         
488         return NULL;
489 }
490
491
492 /*
493 ===================
494 NET_CheckNewConnections
495 ===================
496 */
497
498 qsocket_t *NET_CheckNewConnections (void)
499 {
500         qsocket_t       *ret;
501
502         SetNetTime();
503
504         for (net_driverlevel=0 ; net_driverlevel<net_numdrivers; net_driverlevel++)
505         {
506                 if (net_drivers[net_driverlevel].initialized == false)
507                         continue;
508                 if (net_driverlevel && listening == false)
509                         continue;
510                 ret = dfunc.CheckNewConnections ();
511                 if (ret)
512                         return ret;
513         }
514
515         return NULL;
516 }
517
518 /*
519 ===================
520 NET_Close
521 ===================
522 */
523 void NET_Close (qsocket_t *sock)
524 {
525         if (!sock)
526                 return;
527
528         if (sock->disconnected)
529                 return;
530
531         SetNetTime();
532
533         // call the driver_Close function
534         sfunc.Close (sock);
535
536         NET_FreeQSocket(sock);
537 }
538
539
540 /*
541 =================
542 NET_GetMessage
543
544 If there is a complete message, return it in net_message
545
546 returns 0 if no data is waiting
547 returns 1 if a message was received
548 returns -1 if connection is invalid
549 =================
550 */
551
552 extern void PrintStats(qsocket_t *s);
553
554 int     NET_GetMessage (qsocket_t *sock)
555 {
556         int ret;
557
558         if (!sock)
559                 return -1;
560
561         if (sock->disconnected)
562         {
563                 Con_Printf("NET_GetMessage: disconnected socket\n");
564                 return -1;
565         }
566
567         SetNetTime();
568
569         ret = sfunc.QGetMessage(sock);
570
571         // see if this connection has timed out
572         if (ret == 0 && sock->driver)
573         {
574                 if (net_time - sock->lastMessageTime > net_messagetimeout.value)
575                 {
576                         NET_Close(sock);
577                         return -1;
578                 }
579         }
580
581
582         if (ret > 0)
583         {
584                 if (sock->driver)
585                 {
586                         sock->lastMessageTime = net_time;
587                         if (ret == 1)
588                                 messagesReceived++;
589                         else if (ret == 2)
590                                 unreliableMessagesReceived++;
591                 }
592         }
593
594         return ret;
595 }
596
597
598 /*
599 ==================
600 NET_SendMessage
601
602 Try to send a complete length+message unit over the reliable stream.
603 returns 0 if the message cannot be delivered reliably, but the connection
604                 is still considered valid
605 returns 1 if the message was sent properly
606 returns -1 if the connection died
607 ==================
608 */
609 int NET_SendMessage (qsocket_t *sock, sizebuf_t *data)
610 {
611         int             r;
612         
613         if (!sock)
614                 return -1;
615
616         if (sock->disconnected)
617         {
618                 Con_Printf("NET_SendMessage: disconnected socket\n");
619                 return -1;
620         }
621
622         SetNetTime();
623         r = sfunc.QSendMessage(sock, data);
624         if (r == 1 && sock->driver)
625                 messagesSent++;
626
627         return r;
628 }
629
630
631 int NET_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data)
632 {
633         int             r;
634         
635         if (!sock)
636                 return -1;
637
638         if (sock->disconnected)
639         {
640                 Con_Printf("NET_SendMessage: disconnected socket\n");
641                 return -1;
642         }
643
644         SetNetTime();
645         r = sfunc.SendUnreliableMessage(sock, data);
646         if (r == 1 && sock->driver)
647                 unreliableMessagesSent++;
648
649         return r;
650 }
651
652
653 /*
654 ==================
655 NET_CanSendMessage
656
657 Returns true or false if the given qsocket can currently accept a
658 message to be transmitted.
659 ==================
660 */
661 qboolean NET_CanSendMessage (qsocket_t *sock)
662 {
663         int             r;
664         
665         if (!sock)
666                 return false;
667
668         if (sock->disconnected)
669                 return false;
670
671         SetNetTime();
672
673         r = sfunc.CanSendMessage(sock);
674         
675         return r;
676 }
677
678
679 /*
680 ====================
681 NET_Heartbeat
682
683 Send an heartbeat to the master server(s)
684 ====================
685 */
686 void NET_Heartbeat (int priority)
687 {
688         const char* host;
689
690         if (! Master_AllowHeartbeat (priority))
691                 return;
692
693         while ((host = Master_BuildHeartbeat ()) != NULL)
694         {
695                 for (net_driverlevel=0 ; net_driverlevel<net_numdrivers; net_driverlevel++)
696                 {
697                         if (net_drivers[net_driverlevel].initialized == false)
698                                 continue;
699                         if (net_driverlevel && listening == false)
700                                 continue;
701                         dfunc.Heartbeat (host);
702                 }
703         }
704 }
705
706
707 int NET_SendToAll(sizebuf_t *data, int blocktime)
708 {
709         double          start;
710         int                     i;
711         int                     count = 0;
712         qboolean        state1 [MAX_SCOREBOARD];
713         qboolean        state2 [MAX_SCOREBOARD];
714
715         for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
716         {
717                 if (!host_client->netconnection)
718                         continue;
719                 if (host_client->active)
720                 {
721                         if (host_client->netconnection->driver == 0)
722                         {
723                                 NET_SendMessage(host_client->netconnection, data);
724                                 state1[i] = true;
725                                 state2[i] = true;
726                                 continue;
727                         }
728                         count++;
729                         state1[i] = false;
730                         state2[i] = false;
731                 }
732                 else
733                 {
734                         state1[i] = true;
735                         state2[i] = true;
736                 }
737         }
738
739         start = Sys_DoubleTime();
740         while (count)
741         {
742                 count = 0;
743                 for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
744                 {
745                         if (! state1[i])
746                         {
747                                 if (NET_CanSendMessage (host_client->netconnection))
748                                 {
749                                         state1[i] = true;
750                                         NET_SendMessage(host_client->netconnection, data);
751                                 }
752                                 else
753                                 {
754                                         NET_GetMessage (host_client->netconnection);
755                                 }
756                                 count++;
757                                 continue;
758                         }
759
760                         if (! state2[i])
761                         {
762                                 if (NET_CanSendMessage (host_client->netconnection))
763                                 {
764                                         state2[i] = true;
765                                 }
766                                 else
767                                 {
768                                         NET_GetMessage (host_client->netconnection);
769                                 }
770                                 count++;
771                                 continue;
772                         }
773                 }
774                 if ((Sys_DoubleTime() - start) > blocktime)
775                         break;
776         }
777         return count;
778 }
779
780
781 //=============================================================================
782
783 /*
784 ====================
785 NET_Init
786 ====================
787 */
788
789 void NET_Init (void)
790 {
791         int                     i;
792         int                     controlSocket;
793
794         i = COM_CheckParm ("-port");
795         if (!i)
796                 i = COM_CheckParm ("-udpport");
797         if (!i)
798                 i = COM_CheckParm ("-ipxport");
799
800         if (i)
801         {
802                 if (i < com_argc-1)
803                         DEFAULTnet_hostport = atoi (com_argv[i+1]);
804                 else
805                         Sys_Error ("NET_Init: you must specify a number after -port");
806         }
807         net_hostport = DEFAULTnet_hostport;
808
809         if (COM_CheckParm("-listen") || cls.state == ca_dedicated || gamemode == GAME_TRANSFUSION)
810                 listening = true;
811
812         SetNetTime();
813
814         net_mempool = Mem_AllocPool("qsocket");
815
816         // allocate space for network message buffer
817         SZ_Alloc (&net_message, NET_MAXMESSAGE, "net_message");
818
819         Cvar_RegisterVariable (&net_messagetimeout);
820         Cvar_RegisterVariable (&hostname);
821
822         Cmd_AddCommand ("net_slist", NET_Slist_f);
823         Cmd_AddCommand ("net_inetslist", NET_InetSlist_f);
824         Cmd_AddCommand ("listen", NET_Listen_f);
825         Cmd_AddCommand ("maxplayers", MaxPlayers_f);
826         Cmd_AddCommand ("port", NET_Port_f);
827         Cmd_AddCommand ("heartbeat", NET_Heartbeat_f);
828
829         // initialize all the drivers
830         for (net_driverlevel=0 ; net_driverlevel<net_numdrivers ; net_driverlevel++)
831                 {
832                 controlSocket = net_drivers[net_driverlevel].Init();
833                 if (controlSocket == -1)
834                         continue;
835                 net_drivers[net_driverlevel].initialized = true;
836                 net_drivers[net_driverlevel].controlSock = controlSocket;
837                 if (listening)
838                         net_drivers[net_driverlevel].Listen (true);
839                 }
840
841         if (*my_ipx_address)
842                 Con_DPrintf("IPX address %s\n", my_ipx_address);
843         if (*my_tcpip_address)
844                 Con_DPrintf("TCP/IP address %s\n", my_tcpip_address);
845
846         Master_Init ();
847 }
848
849 /*
850 ====================
851 NET_Shutdown
852 ====================
853 */
854
855 void NET_Shutdown (void)
856 {
857         SetNetTime();
858
859         while (net_activeSockets)
860                 NET_Close(net_activeSockets);
861
862 //
863 // shutdown the drivers
864 //
865         for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++)
866         {
867                 if (net_drivers[net_driverlevel].initialized == true)
868                 {
869                         net_drivers[net_driverlevel].Shutdown ();
870                         net_drivers[net_driverlevel].initialized = false;
871                 }
872         }
873
874         Mem_FreePool(&net_mempool);
875 }
876
877
878 static PollProcedure *pollProcedureList = NULL;
879
880 void NET_Poll(void)
881 {
882         PollProcedure *pp;
883
884         if (!configRestored)
885                 configRestored = true;
886
887         SetNetTime();
888
889         for (pp = pollProcedureList; pp; pp = pp->next)
890         {
891                 if (pp->nextTime > net_time)
892                         break;
893                 pollProcedureList = pp->next;
894                 pp->procedure(pp->arg);
895         }
896 }
897
898
899 void SchedulePollProcedure(PollProcedure *proc, double timeOffset)
900 {
901         PollProcedure *pp, *prev;
902
903         proc->nextTime = Sys_DoubleTime() + timeOffset;
904         for (pp = pollProcedureList, prev = NULL; pp; pp = pp->next)
905         {
906                 if (pp->nextTime >= proc->nextTime)
907                         break;
908                 prev = pp;
909         }
910
911         if (prev == NULL)
912         {
913                 proc->next = pollProcedureList;
914                 pollProcedureList = proc;
915                 return;
916         }
917
918         proc->next = pp;
919         prev->next = proc;
920 }
921