fix
[oweals/gnunet.git] / src / util / os_load.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2003, 2005, 2006 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file util/os_load.c
23  * @brief calls to determine current CPU load
24  * @author Tzvetan Horozov
25  * @author Christian Grothoff
26  * @author Igor Wronsky
27  * @author Alex Harper (OS X portion)
28  */
29
30 #include "platform.h"
31 #include "gnunet_common.h"
32 #include "gnunet_os_lib.h"
33 #include "gnunet_strings_lib.h"
34
35 #if SOLARIS
36 #if HAVE_KSTAT_H
37 #include <kstat.h>
38 #endif
39 #if HAVE_SYS_SYSINFO_H
40 #include <sys/sysinfo.h>
41 #endif
42 #if HAVE_KVM_H
43 #include <kvm.h>
44 #endif
45 #endif
46 #if SOMEBSD
47 #if HAVE_KVM_H
48 #include <kvm.h>
49 #endif
50 #endif
51
52 #ifdef DARWIN
53 #include <mach/mach.h>
54
55 static processor_cpu_load_info_t prev_cpu_load;
56 #endif
57
58 #define DEBUG_STATUSCALLS GNUNET_NO
59
60 #ifdef LINUX
61 /**
62  * File descriptor for reading /proc/stat (or NULL)
63  */
64 static FILE *proc_stat;
65
66 /**
67  * Is this the first time we're trying to open /proc/stat?  If
68  * not, we don't try again if we failed...
69  */
70 static int first_time;
71 #endif
72
73 /**
74  * Current CPU load, as percentage of CPU cycles not idle or
75  * blocked on IO.
76  */
77 static int currentCPULoad;
78
79 static double agedCPULoad = -1;
80
81 /**
82  * Current IO load, as percentage of CPU cycles blocked on IO.
83  */
84 static int currentIOLoad;
85
86 static double agedIOLoad = -1;
87
88 #ifdef DARWIN
89 static int
90 initMachCpuStats ()
91 {
92   unsigned int cpu_count;
93   processor_cpu_load_info_t cpu_load;
94   mach_msg_type_number_t cpu_msg_count;
95   kern_return_t kret;
96   int i, j;
97
98   kret = host_processor_info (mach_host_self (),
99                               PROCESSOR_CPU_LOAD_INFO,
100                               &cpu_count,
101                               (processor_info_array_t *) & cpu_load,
102                               &cpu_msg_count);
103   if (kret != KERN_SUCCESS)
104     {
105       GNUNET_log (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
106                   "host_processor_info failed.");
107       return GNUNET_SYSERR;
108     }
109   prev_cpu_load = GNUNET_malloc (cpu_count * sizeof (*prev_cpu_load));
110   for (i = 0; i < cpu_count; i++)
111     {
112       for (j = 0; j < CPU_STATE_MAX; j++)
113         {
114           prev_cpu_load[i].cpu_ticks[j] = cpu_load[i].cpu_ticks[j];
115         }
116     }
117   vm_deallocate (mach_task_self (),
118                  (vm_address_t) cpu_load,
119                  (vm_size_t) (cpu_msg_count * sizeof (*cpu_load)));
120   return GNUNET_OK;
121 }
122 #endif
123
124
125 /**
126  * Update the currentCPU and currentIO load values.
127  *
128  * Before its first invocation the method initStatusCalls() must be called.
129  * If there is an error the method returns -1.
130  */
131 static int
132 updateUsage ()
133 {
134   currentIOLoad = -1;
135   currentCPULoad = -1;
136 #ifdef LINUX
137   /* under linux, first try %idle/usage using /proc/stat;
138      if that does not work, disable /proc/stat for the future
139      by closing the file and use the next-best method. */
140   if (0 == first_time)
141     {
142       first_time = 1;
143       proc_stat = fopen ("/proc/stat", "r");
144       if (NULL == proc_stat)
145         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "fopen", "/proc/stat");
146     }
147   if (proc_stat != NULL)
148     {
149       static unsigned long long last_cpu_results[5] = { 0, 0, 0, 0, 0 };
150       static int have_last_cpu = GNUNET_NO;
151       int ret;
152       char line[256];
153       unsigned long long user_read, system_read, nice_read, idle_read,
154         iowait_read;
155       unsigned long long user, system, nice, idle, iowait;
156       unsigned long long usage_time = 0, total_time = 1;
157
158       /* Get the first line with the data */
159       memset (line, 0, sizeof (line));
160       rewind (proc_stat);
161       fflush (proc_stat);
162       if (NULL == fgets (line, sizeof (line), proc_stat))
163         {
164           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR |
165                                     GNUNET_ERROR_TYPE_BULK,
166                                     "fgets", "/proc/stat");
167           fclose (proc_stat);
168           proc_stat = NULL;     /* don't try again */
169         }
170       else
171         {
172           iowait_read = 0;
173           ret = sscanf (line, "%*s %llu %llu %llu %llu %llu",
174                         &user_read,
175                         &system_read, &nice_read, &idle_read, &iowait_read);
176           if (ret < 4)
177             {
178               GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR |
179                                         GNUNET_ERROR_TYPE_BULK,
180                                         "fgets-sscanf", "/proc/stat");
181               fclose (proc_stat);
182               proc_stat = NULL; /* don't try again */
183               have_last_cpu = GNUNET_NO;
184             }
185           else
186             {
187               /* Store the current usage */
188               user = user_read - last_cpu_results[0];
189               system = system_read - last_cpu_results[1];
190               nice = nice_read - last_cpu_results[2];
191               idle = idle_read - last_cpu_results[3];
192               iowait = iowait_read - last_cpu_results[4];
193               /* Calculate the % usage */
194               usage_time = user + system + nice;
195               total_time = usage_time + idle + iowait;
196               if ((total_time > 0) && (have_last_cpu == GNUNET_YES))
197                 {
198                   currentCPULoad = (int) (100L * usage_time / total_time);
199                   if (ret > 4)
200                     currentIOLoad = (int) (100L * iowait / total_time);
201                   else
202                     currentIOLoad = -1; /* 2.4 kernel */
203                 }
204               /* Store the values for the next calculation */
205               last_cpu_results[0] = user_read;
206               last_cpu_results[1] = system_read;
207               last_cpu_results[2] = nice_read;
208               last_cpu_results[3] = idle_read;
209               last_cpu_results[4] = iowait_read;
210               have_last_cpu = GNUNET_YES;
211               return GNUNET_OK;
212             }
213         }
214     }
215 #endif
216
217 #ifdef DARWIN
218   {
219     unsigned int cpu_count;
220     processor_cpu_load_info_t cpu_load;
221     mach_msg_type_number_t cpu_msg_count;
222     unsigned long long t_sys, t_user, t_nice, t_idle, t_total;
223     unsigned long long t_idle_all, t_total_all;
224     kern_return_t kret;
225     int i, j;
226
227     t_idle_all = t_total_all = 0;
228     kret = host_processor_info (mach_host_self (), PROCESSOR_CPU_LOAD_INFO,
229                                 &cpu_count,
230                                 (processor_info_array_t *) & cpu_load,
231                                 &cpu_msg_count);
232     if (kret == KERN_SUCCESS)
233       {
234         for (i = 0; i < cpu_count; i++)
235           {
236             if (cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM] >=
237                 prev_cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM])
238               {
239                 t_sys = cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM] -
240                   prev_cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM];
241               }
242             else
243               {
244                 t_sys = cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM] +
245                   (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM] +
246                    1);
247               }
248
249             if (cpu_load[i].cpu_ticks[CPU_STATE_USER] >=
250                 prev_cpu_load[i].cpu_ticks[CPU_STATE_USER])
251               {
252                 t_user = cpu_load[i].cpu_ticks[CPU_STATE_USER] -
253                   prev_cpu_load[i].cpu_ticks[CPU_STATE_USER];
254               }
255             else
256               {
257                 t_user = cpu_load[i].cpu_ticks[CPU_STATE_USER] +
258                   (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_USER] +
259                    1);
260               }
261
262             if (cpu_load[i].cpu_ticks[CPU_STATE_NICE] >=
263                 prev_cpu_load[i].cpu_ticks[CPU_STATE_NICE])
264               {
265                 t_nice = cpu_load[i].cpu_ticks[CPU_STATE_NICE] -
266                   prev_cpu_load[i].cpu_ticks[CPU_STATE_NICE];
267               }
268             else
269               {
270                 t_nice = cpu_load[i].cpu_ticks[CPU_STATE_NICE] +
271                   (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_NICE] +
272                    1);
273               }
274
275             if (cpu_load[i].cpu_ticks[CPU_STATE_IDLE] >=
276                 prev_cpu_load[i].cpu_ticks[CPU_STATE_IDLE])
277               {
278                 t_idle = cpu_load[i].cpu_ticks[CPU_STATE_IDLE] -
279                   prev_cpu_load[i].cpu_ticks[CPU_STATE_IDLE];
280               }
281             else
282               {
283                 t_idle = cpu_load[i].cpu_ticks[CPU_STATE_IDLE] +
284                   (ULONG_MAX - prev_cpu_load[i].cpu_ticks[CPU_STATE_IDLE] +
285                    1);
286               }
287             t_total = t_sys + t_user + t_nice + t_idle;
288             t_idle_all += t_idle;
289             t_total_all += t_total;
290           }
291         for (i = 0; i < cpu_count; i++)
292           {
293             for (j = 0; j < CPU_STATE_MAX; j++)
294               {
295                 prev_cpu_load[i].cpu_ticks[j] = cpu_load[i].cpu_ticks[j];
296               }
297           }
298         if (t_total_all > 0)
299           currentCPULoad = 100 - (100 * t_idle_all) / t_total_all;
300         else
301           currentCPULoad = -1;
302         vm_deallocate (mach_task_self (),
303                        (vm_address_t) cpu_load,
304                        (vm_size_t) (cpu_msg_count * sizeof (*cpu_load)));
305         currentIOLoad = -1;     /* There's no IO load meter on darwin */
306         return GNUNET_OK;
307       }
308     else
309       {
310         GNUNET_log (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
311                     "host_processor_info failed.");
312         return GNUNET_SYSERR;
313       }
314   }
315 #endif
316   /* try kstat (Solaris only) */
317 #if SOLARIS && HAVE_KSTAT_H && HAVE_SYS_SYSINFO_H
318   {
319     static long long last_idlecount;
320     static long long last_totalcount;
321     static int kstat_once;      /* if open fails, don't keep
322                                    trying */
323     kstat_ctl_t *kc;
324     kstat_t *khelper;
325     long long idlecount;
326     long long totalcount;
327     long long deltaidle;
328     long long deltatotal;
329
330     if (kstat_once == 1)
331       goto ABORT_KSTAT;
332     kc = kstat_open ();
333     if (kc == NULL)
334       {
335         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
336                              "kstat_open");
337         goto ABORT_KSTAT;
338       }
339
340     idlecount = 0;
341     totalcount = 0;
342     for (khelper = kc->kc_chain; khelper != NULL; khelper = khelper->ks_next)
343       {
344         cpu_stat_t stats;
345
346         if (0 != strncmp (khelper->ks_name, "cpu_stat", strlen ("cpu_stat")))
347           continue;
348         if (khelper->ks_data_size > sizeof (cpu_stat_t))
349           continue;             /* better save then sorry! */
350         if (-1 != kstat_read (kc, khelper, &stats))
351           {
352             idlecount += stats.cpu_sysinfo.cpu[CPU_IDLE];
353             totalcount
354               += stats.cpu_sysinfo.cpu[CPU_IDLE] +
355               stats.cpu_sysinfo.cpu[CPU_USER] +
356               stats.cpu_sysinfo.cpu[CPU_KERNEL] +
357               stats.cpu_sysinfo.cpu[CPU_WAIT];
358           }
359       }
360     if (0 != kstat_close (kc))
361       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
362                            "kstat_close");
363     if ((idlecount == 0) && (totalcount == 0))
364       goto ABORT_KSTAT;         /* no stats found => abort */
365     deltaidle = idlecount - last_idlecount;
366     deltatotal = totalcount - last_totalcount;
367     if ((deltatotal > 0) && (last_totalcount > 0))
368       {
369         currentCPULoad = (unsigned int) (100.0 * deltaidle / deltatotal);
370         if (currentCPULoad > 100)
371           currentCPULoad = 100; /* odd */
372         if (currentCPULoad < 0)
373           currentCPULoad = 0;   /* odd */
374         currentCPULoad = 100 - currentCPULoad;  /* computed idle-load before! */
375       }
376     else
377       currentCPULoad = -1;
378     currentIOLoad = -1;         /* FIXME-SOLARIS! */
379     last_idlecount = idlecount;
380     last_totalcount = totalcount;
381     return GNUNET_OK;
382   ABORT_KSTAT:
383     kstat_once = 1;             /* failed, don't try again */
384     return GNUNET_SYSERR;
385   }
386 #endif
387
388   /* insert methods better than getloadavg for
389      other platforms HERE! */
390
391   /* ok, maybe we have getloadavg on this platform */
392 #if HAVE_GETLOADAVG
393   {
394     static int warnOnce = 0;
395     double loadavg;
396     if (1 != getloadavg (&loadavg, 1))
397       {
398         /* only warn once, if there is a problem with
399            getloadavg, we're going to hit it frequently... */
400         if (warnOnce == 0)
401           {
402             warnOnce = 1;
403             GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "getloadavg");
404           }
405         return GNUNET_SYSERR;
406       }
407     else
408       {
409         /* success with getloadavg */
410         currentCPULoad = (int) (100 * loadavg);
411         currentIOLoad = -1;     /* FIXME */
412         return GNUNET_OK;
413       }
414   }
415 #endif
416
417 #if MINGW
418   /* Win NT? */
419   if (GNNtQuerySystemInformation)
420     {
421       static double dLastKernel;
422       static double dLastIdle;
423       static double dLastUser;
424       double dKernel;
425       double dIdle;
426       double dUser;
427       double dDiffKernel;
428       double dDiffIdle;
429       double dDiffUser;
430       SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION theInfo;
431
432       if (GNNtQuerySystemInformation (SystemProcessorPerformanceInformation,
433                                       &theInfo,
434                                       sizeof (theInfo), NULL) == NO_ERROR)
435         {
436           /* PORT-ME MINGW: Multi-processor? */
437           dKernel = Li2Double (theInfo.KernelTime);
438           dIdle = Li2Double (theInfo.IdleTime);
439           dUser = Li2Double (theInfo.UserTime);
440           dDiffKernel = dKernel - dLastKernel;
441           dDiffIdle = dIdle - dLastIdle;
442           dDiffUser = dUser - dLastUser;
443
444           if (((dDiffKernel + dDiffUser) > 0) &&
445               (dLastIdle + dLastKernel + dLastUser > 0))
446             currentCPULoad =
447               100.0 - (dDiffIdle / (dDiffKernel + dDiffUser)) * 100.0;
448           else
449             currentCPULoad = -1;        /* don't know (yet) */
450
451           dLastKernel = dKernel;
452           dLastIdle = dIdle;
453           dLastUser = dUser;
454
455           currentIOLoad = -1;   /* FIXME-MINGW */
456           return GNUNET_OK;
457         }
458       else
459         {
460           /* only warn once, if there is a problem with
461              NtQuery..., we're going to hit it frequently... */
462           static int once;
463           if (once == 0)
464             {
465               once = 1;
466               GNUNET_log (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
467                           _("Cannot query the CPU usage (Windows NT).\n"));
468             }
469           return GNUNET_SYSERR;
470         }
471     }
472   else
473     {                           /* Win 9x */
474       HKEY hKey;
475       DWORD dwDataSize, dwType, dwDummy;
476
477       /* Start query */
478       if (RegOpenKeyEx (HKEY_DYN_DATA,
479                         "PerfStats\\StartSrv",
480                         0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
481         {
482           /* only warn once */
483           static int once = 0;
484           if (once == 0)
485             {
486               once = 1;
487               GNUNET_log (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
488                           _("Cannot query the CPU usage (Win 9x)\n"));
489             }
490         }
491
492       RegOpenKeyEx (HKEY_DYN_DATA,
493                     "PerfStats\\StartStat", 0, KEY_ALL_ACCESS, &hKey);
494       dwDataSize = sizeof (dwDummy);
495       RegQueryValueEx (hKey,
496                        "KERNEL\\CPUUsage",
497                        NULL, &dwType, (LPBYTE) & dwDummy, &dwDataSize);
498       RegCloseKey (hKey);
499
500       /* Get CPU usage */
501       RegOpenKeyEx (HKEY_DYN_DATA,
502                     "PerfStats\\StatData", 0, KEY_ALL_ACCESS, &hKey);
503       dwDataSize = sizeof (currentCPULoad);
504       RegQueryValueEx (hKey,
505                        "KERNEL\\CPUUsage",
506                        NULL, &dwType, (LPBYTE) & currentCPULoad, &dwDataSize);
507       RegCloseKey (hKey);
508       currentIOLoad = -1;       /* FIXME-MINGW! */
509
510       /* Stop query */
511       RegOpenKeyEx (HKEY_DYN_DATA,
512                     "PerfStats\\StopStat", 0, KEY_ALL_ACCESS, &hKey);
513       RegOpenKeyEx (HKEY_DYN_DATA,
514                     "PerfStats\\StopSrv", 0, KEY_ALL_ACCESS, &hKey);
515       dwDataSize = sizeof (dwDummy);
516       RegQueryValueEx (hKey,
517                        "KERNEL\\CPUUsage",
518                        NULL, &dwType, (LPBYTE) & dwDummy, &dwDataSize);
519       RegCloseKey (hKey);
520
521       return GNUNET_OK;
522     }
523 #endif
524
525   /* loadaverage not defined and no platform
526      specific alternative defined
527      => default: error
528    */
529   return GNUNET_SYSERR;
530 }
531
532 /**
533  * Update load values (if enough time has expired),
534  * including computation of averages.  Code assumes
535  * that lock has already been obtained.
536  */
537 static void
538 updateAgedLoad (const struct GNUNET_CONFIGURATION_Handle *cfg)
539 {
540   static struct GNUNET_TIME_Absolute lastCall;
541
542   if ((agedCPULoad == -1)
543       || (GNUNET_TIME_absolute_get_duration (lastCall).value > 500))
544     {
545       /* use smoothing, but do NOT update lastRet at frequencies higher
546          than 500ms; this makes the smoothing (mostly) independent from
547          the frequency at which getCPULoad is called (and we don't spend
548          more time measuring CPU than actually computing something). */
549       lastCall = GNUNET_TIME_absolute_get ();
550       updateUsage ();
551       if (currentCPULoad == -1)
552         {
553           agedCPULoad = -1;
554         }
555       else
556         {
557           if (agedCPULoad == -1)
558             {
559               agedCPULoad = currentCPULoad;
560             }
561           else
562             {
563               /* for CPU, we don't do the 'fast increase' since CPU is much
564                  more jitterish to begin with */
565               agedCPULoad = (agedCPULoad * 31 + currentCPULoad) / 32;
566             }
567         }
568       if (currentIOLoad == -1)
569         {
570           agedIOLoad = -1;
571         }
572       else
573         {
574           if (agedIOLoad == -1)
575             {
576               agedIOLoad = currentIOLoad;
577             }
578           else
579             {
580               /* for IO, we don't do the 'fast increase' since IO is much
581                  more jitterish to begin with */
582               agedIOLoad = (agedIOLoad * 31 + currentIOLoad) / 32;
583             }
584         }
585     }
586 }
587
588 /**
589  * Get the load of the CPU relative to what is allowed.
590  * @return the CPU load as a percentage of allowed
591  *        (100 is equivalent to full load)
592  */
593 int
594 GNUNET_OS_load_cpu_get (const struct GNUNET_CONFIGURATION_Handle *cfg)
595 {
596   unsigned long long maxCPULoad;
597   int ret;
598
599   updateAgedLoad (cfg);
600   ret = agedCPULoad;
601   if (ret == -1)
602     return -1;
603   if (GNUNET_OK !=
604       GNUNET_CONFIGURATION_get_value_number (cfg, "LOAD", "MAXCPULOAD",
605                                              &maxCPULoad))
606     return GNUNET_SYSERR;
607   if (maxCPULoad == 0)
608     return 100;
609   return (100 * ret) / maxCPULoad;
610 }
611
612
613 /**
614  * Get the load of the CPU relative to what is allowed.
615  * @return the CPU load as a percentage of allowed
616  *        (100 is equivalent to full load)
617  */
618 int
619 GNUNET_OS_load_disk_get (const struct GNUNET_CONFIGURATION_Handle *cfg)
620 {
621   unsigned long long maxIOLoad;
622   int ret;
623
624   updateAgedLoad (cfg);
625   ret = agedIOLoad;
626   if (ret == -1)
627     return -1;
628   if (-1 ==
629       GNUNET_CONFIGURATION_get_value_number (cfg, "LOAD", "MAXIOLOAD",
630                                              &maxIOLoad))
631     return GNUNET_SYSERR;
632   if (maxIOLoad == 0)
633     return 100;
634   return (100 * ret) / maxIOLoad;
635 }
636
637 /**
638  * The following method is called in order to initialize the status calls
639  * routines.  After that it is safe to call each of the status calls separately
640  * @return GNUNET_OK on success and GNUNET_SYSERR on error (or calls errexit).
641  */
642 void __attribute__ ((constructor)) GNUNET_cpustats_ltdl_init ()
643 {
644 #ifdef LINUX
645   updateUsage ();               /* initialize */
646   /* Most GNUnet processes don't really need this, so close the FD, but allow 
647      re-opening it (unless we failed) */
648   if (proc_stat != NULL)
649     {
650       GNUNET_break (0 == fclose (proc_stat));
651       proc_stat = NULL;
652       first_time = 0;
653     }
654 #elif defined(DARWIN)
655   initMachCpuStats ();
656   updateUsage ();               /* initialize */
657 #elif MINGW
658   InitWinEnv (NULL);
659   updateUsage ();               /* initialize */
660 #else
661   updateUsage ();               /* initialize */
662 #endif
663 }
664
665 /**
666  * Shutdown the status calls module.
667  */
668 void __attribute__ ((destructor)) GNUNET_cpustats_ltdl_fini ()
669 {
670 #ifdef LINUX
671   if (proc_stat != NULL)
672     {
673       fclose (proc_stat);
674       proc_stat = NULL;
675     }
676 #elif defined(DARWIN)
677   GNUNET_free_non_null (prev_cpu_load);
678 #elif MINGW
679   ShutdownWinEnv ();
680 #endif
681 }
682
683
684 /* end of os_load.c */