Bug 23209 - Mono use of /proc not suitable for other Unix / BSD
Summary: Mono use of /proc not suitable for other Unix / BSD
Alias: None
Product: Class Libraries
Classification: Mono
Component: System (show other bugs)
Version: master
Hardware: PC Other
: --- normal
Target Milestone: Untriaged
Assignee: Bugzilla
Depends on:
Reported: 2014-09-19 22:25 UTC by Ben Woods
Modified: 2014-11-06 04:40 UTC (History)
2 users (show)

See Also:
Is this bug a regression?: ---
Last known good build:


Description Ben Woods 2014-09-19 22:25:55 UTC
Mono on Linux makes use of the /proc filesystem, which is not compatible with other Unix / BSD operating systems.

I came across this problem when trying to get mediabrowser.tv to work on FreeBSD, and it gave an error /proc/net/route file not found. This reference is only made in "mcs/class/System/System.Net.NetworkInformation/IPInterfaceProperties.cs", but there are other class libraries which reference /proc.

To make mono more portable to other Unix operating systems, suggest minimising usage of /proc.
Comment 1 Ben Woods 2014-09-19 22:28:57 UTC
FreeBSD can mount Linux procfs style filesystems, but the only file in /proc/net is dev. Refer to https://www.freebsd.org/cgi/man.cgi?linprocfs(5)

OpenBSD can mount Linux procfs style filesystems with mount_procfs -o linux /proc /proc. Not sure if this provides a /proc/net/route.

Either way, it's not ideal for other Unix / BSD operating systems to have to write a Linux compatibility layer to use mono, when mono could instead use more POSIX style code.
Comment 2 Ben Woods 2014-09-19 22:31:08 UTC
I performed a search for "proc/" in the mono v3.6 code and found the following:

# grep -R proc\/ .
./man/mono.1:   echo 4096 > /proc/sys/dev/rtc/max-user-freq
./config.guess: case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in
./config.guess: case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
./mono/mini/mini-amd64.c:       optimize_for_xen = access ("/proc/xen", F_OK) == 0;
./mono/mini/mini-x86.c:         optimize_for_xen = access ("/proc/xen", F_OK) == 0;
./mono/mini/mini-ppc.c: FILE* f = fopen ("/proc/self/auxv", "rb");
./mono/mini/ChangeLog:  the info in /proc/self/smaps. Avoid the assert on sigaction during
./mono/mini/ChangeLog:  better info in proc/pid/smaps these days).
./mono/mini/mini.c:     if (access ("/proc/self/maps", F_OK) != 0) {
./mono/tests/ChangeLog: * stress-runner.cs: Add 'exit-stress', add a few new /proc/pid/status entries.
./mono/tests/stress-runner.pl:  open (PROC, "</proc/$pid/status") || return undef; # might be dead already
./mono/metadata/assembly.c:     s = readlink ("/proc/self/exe", buf, sizeof (buf)-1);
./mono/metadata/assembly.c:     str = g_strdup_printf ("/proc/%d/path/a.out", getpid ());
./mono/io-layer/processes.c:    gchar *dir = g_strdup_printf ("/proc/%d", pid);
./mono/io-layer/processes.c:            "/proc/%d/maps",        /* GNU/Linux */
./mono/io-layer/processes.c:            "/proc/%d/map",         /* FreeBSD */
./mono/io-layer/processes.c:     * for now.)  Get the info from /proc/<pid>/maps on linux,
./mono/io-layer/processes.c:     * /proc/<pid>/map on FreeBSD, other systems will have to
./mono/io-layer/processes.c:            /* No /proc/<pid>/maps so just return the main module
./mono/io-layer/processes.c:             * /proc/<pid>/maps isn't the executable, and we need
./mono/io-layer/processes.c:    filename = g_strdup_printf ("/proc/%d/psinfo", pid);
./mono/io-layer/processes.c:    filename = g_strdup_printf ("/proc/%d/exe", pid);
./mono/io-layer/processes.c:    filename = g_strdup_printf ("/proc/%d/cmdline", pid);
./mono/io-layer/processes.c:    filename = g_strdup_printf ("/proc/%d/stat", pid);
./mono/io-layer/processes.c:    /* Look up the address in /proc/<pid>/maps */
./mono/io-layer/processes.c:                    /* No /proc/<pid>/maps, so just return failure
./mono/io-layer/processes.c:                     * case where reading /proc/$pid/maps gives an
./mono/io-layer/processes.c:    /* Look up the address in /proc/<pid>/maps */
./mono/io-layer/processes.c:            /* No /proc/<pid>/maps, so just return failure
./mono/io-layer/handles.c:/* Scan /proc/<pids>/fd/ for open file descriptors to the file in
./mono/io-layer/handles.c:                      /* Look in /proc/<pid>/fd/ but ignore
./mono/io-layer/handles.c:                       * /proc/<our pid>/fd/<fd>, as we have the
./mono/io-layer/handles.c:                      g_snprintf (subdir, _POSIX_PATH_MAX, "/proc/%d/fd",
./mono/io-layer/handles.c:                                          "/proc/%d/fd/%s", pid,
./mono/io-layer/io.c:   fd = open ("/proc/self/mountinfo", O_RDONLY);
./mono/io-layer/io.c:           fd = open ("/proc/mounts", O_RDONLY);
./mono/io-layer/ChangeLog:      not even compile in the code that scans for /proc/fd, and go
./mono/io-layer/ChangeLog:        in /proc/<PID>/maps.
./mono/io-layer/ChangeLog:        the module passed in is NULL, search the /proc/<PID>/maps for a name that
./mono/io-layer/ChangeLog:      /proc/<pid>/maps parsing to avoid using glib functions only
./mono/utils/mono-hwcap-arm.c:   * permissions, so fall back to /proc/cpuinfo. We also
./mono/utils/mono-hwcap-arm.c:  FILE *file = fopen ("/proc/cpuinfo", "r");
./mono/utils/mono-proclib.c:    GDir *dir = g_dir_open ("/proc/", 0, NULL);
./mono/utils/mono-proclib.c:    g_snprintf (buf, sizeof (buf), "/proc/%d/status", pid);
./mono/utils/mono-proclib.c:    sprintf (fname, "/proc/%d/cmdline", GPOINTER_TO_INT (pid));
./mono/utils/mono-proclib.c: * /proc/pid/stat format:
./mono/utils/mono-proclib.c:    g_snprintf (buf, sizeof (buf), "/proc/%d/stat", pid);
./mono/utils/mono-proclib.c:    FILE *f = fopen ("/proc/stat", "r");
./mono/utils/mono-hwcap-x86.c:  mono_hwcap_x86_is_xen = !access ("/proc/xen", F_OK);
./mono/utils/mono-dl.c: binl = readlink ("/proc/self/exe", buf, sizeof (buf)-1);
./mono/utils/ChangeLog: network statistics from "/proc/net/dev" for performance counters.
./mono/utils/mono-counters.c:   FILE *f = fopen ("/proc/loadavg", "r");
./mono/utils/mono-networkinterfaces.c:  f = fopen ("/proc/net/dev", "r");
./mono/utils/mono-networkinterfaces.c:  f = fopen ("/proc/net/dev", "r");
./mono/utils/mono-time.c:       FILE *uptime = fopen ("/proc/uptime", "r");
./mono/profiler/proflog.c:              int l = readlink ("/proc/self/exe", buf, sizeof (buf) - 1);
./mono/profiler/proflog.c:                      fprintf (stderr, "Perf syscall denied, do \"echo 1 > /proc/sys/kernel/perf_event_paranoid\" as root to enable.\n");
./mono/profiler/ChangeLog:      The issue is that while reading /proc/self/maps is can happen that
./mono/profiler/utils.c:        if (!(cpuinfo = fopen ("/proc/cpuinfo", "r")))
./libgc/solaris_threads.c:      sprintf(buf, "/proc/%d", getpid());
./libgc/config.guess:   case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in
./libgc/config.guess:   case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
./libgc/dyn_load.c:      sprintf(buf, "/proc/%d", getpid());
./libgc/doc/README.changes: - Changed Linux dynamic library registration code to look at /proc/self/maps
./libgc/doc/README.changes: - Fixed the /proc/self/maps code to not seek, since that apparently is not
./libgc/doc/README.changes:   /proc/self/maps after checking the __libc symbol, but before guessing.
./libgc/doc/README.changes:   /proc/self/maps so it only exists in one place (all platforms).
./libgc/doc/README.changes:   /proc/self/maps on Linux.  This ceased to be true about 2 years ago.
./libgc/doc/README.environment:GC_PRINT_ADDRESS_MAP - Linux only.  Dump /proc/self/maps, i.e. various address
./libgc/os_dep.c:/* We need to parse /proc/self/maps, either to find dynamic libraries, */
./libgc/os_dep.c: * Apply fn to a buffer containing the contents of /proc/self/maps.
./libgc/os_dep.c: * We currently do nothing to /proc/self/maps other than simply read
./libgc/os_dep.c:    /* Read /proc/self/maps, growing maps_buf as necessary.    */
./libgc/os_dep.c:           f = open("/proc/self/maps", O_RDONLY);
./libgc/os_dep.c://  GC_parse_map_entry parses an entry from /proc/self/maps so we can
./libgc/os_dep.c:#endif /* Need to parse /proc/self/maps. */
./libgc/os_dep.c:                       /* field in /proc/self/stat                     */
./libgc/os_dep.c:    /* Try to read the backing store base from /proc/self/maps.        */
./libgc/os_dep.c:    /* We read the stack base value from /proc/self/stat.  We do this  */
./libgc/os_dep.c:    f = open("/proc/self/stat", O_RDONLY);
./libgc/os_dep.c:       ABORT("Couldn't read /proc/self/stat");
./libgc/os_dep.c:    sprintf(buf, "/proc/%d", getpid());
./libgc/os_dep.c:                 ret_code = readlink("/proc/self/exe", exe_name, EXE_SZ);
./libgc/os_dep.c:/* Dump /proc/self/maps to GC_stderr, to enable looking up names for
./libgc/pthread_support.c:    /* We look for lines "cpu<n>" in /proc/stat.                      */
./libgc/pthread_support.c:      /* entry in /proc/stat.  We identify those as           */
./libgc/pthread_support.c:    f = open("/proc/stat", O_RDONLY);
./libgc/pthread_support.c:      WARN("Couldn't read /proc/stat\n", 0);
./libgc/pthread_support.c:    /* doesn't work because the stack base in /proc/self/stat is the  */
./mcs/class/Mono.Management/Mono.Attach/VirtualMachine.cs:                      return File.OpenText ("/proc/" + pid + "/cmdline").ReadToEnd ().Split ('\0');
./mcs/class/Mono.Management/Mono.Attach/VirtualMachine.cs:                      return UnixPath.ReadLink ("/proc/" + pid + "/cwd");
./mcs/class/Managed.Windows.Forms/System.Windows.Forms/FileDialog.cs:                   // maybe check if the current user can access /proc/mounts
./mcs/class/Managed.Windows.Forms/System.Windows.Forms/FileDialog.cs:                           if (File.Exists ("/proc/mounts"))
./mcs/class/Managed.Windows.Forms/System.Windows.Forms/FileDialog.cs:                           StreamReader sr = new StreamReader ("/proc/mounts");
./mcs/class/Managed.Windows.Forms/System.Windows.Forms/ChangeLog:           this information from /proc/mount. Updated MWFFileView to make
./mcs/class/corlib/Microsoft.Win32/UnixRegistryApi.cs:                  if (!File.Exists ("/proc/stat"))
./mcs/class/corlib/Microsoft.Win32/UnixRegistryApi.cs:                          using (StreamReader stat_file = new StreamReader ("/proc/stat", Encoding.ASCII)) {
./mcs/class/System/System.Net.NetworkInformation/ChangeLog:       and here is non-Windows version, based on /proc/net/snmp(6).
./mcs/class/System/System.Net.NetworkInformation/IPv4InterfaceProperties.cs:                            string iface_path = "/proc/sys/net/ipv4/conf/" + iface.Name + "/forwarding";
./mcs/class/System/System.Net.NetworkInformation/IPInterfaceProperties.cs:                              using (StreamReader reader = new StreamReader ("/proc/net/route")) {
./mcs/class/System/System.Net.NetworkInformation/IPGlobalProperties.cs: // It expects /proc/net/snmp (or /usr/compat/linux/proc/net/snmp),
./mcs/class/System/System.Net.NetworkInformation/IPGlobalProperties.cs:                 // There is no TCP info in /proc/net/snmp,
./mcs/class/System/System.IO/InotifyWatcher.cs:                                         using (StreamReader reader = new StreamReader ("/proc/sys/fs/inotify/max_user_watches")) {
./mcs/class/System/System.IO/InotifyWatcher.cs:                                                         "in /proc/sys/fs/inotify/max_user_watches.", nr_watches);
Comment 3 Rodrigo Kumpera 2014-10-03 08:44:40 UTC
Mono's proc usage is either a compile time switch or done at runtime by probing.

FreeBSD is currently community maintained, could you provide a patch to fix your issue here?
Comment 4 Ben Woods 2014-10-03 20:23:00 UTC
I have submitted the below pull request:

This addresses the issue I was having with /proc/net/route usage in mcs/class/System/System.Net.NetworkInformation/IPInterfaceProperties.cs.

This change obviously needs testing. I am trying to workout why I sometimes get the following exception when I run mono with --trace=N:nothing:
EXCEPTION handling: System.ArgumentNullException: Argument cannot be null.
Comment 5 Ben Woods 2014-10-29 10:35:00 UTC
I have determined that the System.ArgumentNullException I received was unrelated to this patch. It was due to the locale not being set on my system, causing get_posix_locale to return NULL in mono/mono/metadata/locales.c, which was caught in mcs/class/corlib/System.Globalization/CultureInfo.cs.

This patch therefore works fine on FreeBSD - think we can merge this pull request?
Comment 6 Ben Woods 2014-11-06 04:40:55 UTC
I have found another Linuxism which was preventing the mono networking code from working on FreeBSD. This is separate to the bug fixed in my pull request above (both fixes are required).

My new pull request is here: https://github.com/mono/mono/pull/1390

In summary, Mono on FreeBSD will currently use the mono Linux NetworkInterface code, which contains some Linuxisms (such as parsing files in /sys/class/net/). Refer to the problem code here:

This change causes mono on FreeBSD to use the Mac NetworkInterface code instead, which works fine. Refer to the above link which shows class MacOsNetworkInterface : UnixNetworkInterface.

Note You need to log in before you can comment on or make changes to this bug.