Many drivers in DPDK read single values, and each one
open codes the same sequence: build a path with snprintf, open the
file, read a line, and convert it. Several drivers grew their own
private copy of that helper, and most of them parse with fscanf()
which cannot tell a failed conversion from a legitimate zero and
silently accepts trailing garbage.

Add a single set of routines in EAL that build the path from a
printf style format and do the conversion with strtoul()/strtol():

  rte_sysfs_parse_uint()    unsigned value
  rte_sysfs_parse_int()     signed value
  rte_sysfs_parse_string()  string value, newline stripped
  rte_sysfs_write_string()  write a string

rte_sysfs_vparse_uint() takes a va_list, so that a wrapper such
as the one in the power library can forward its arguments without
formatting the path a second time.

Folding the path construction into the routine removes the separate
snprintf() and its truncation check from every caller.

The signed variant exists because some attributes are genuinely
signed: numa_node is -1 when the device is not tied to a node.

These replace eal_parse_sysfs_value(), which was declared in the
internal eal_filesystem.h yet exported as a stable symbol, and was
reached by drivers through that private header.

Convert all of the existing callers.

Signed-off-by: Stephen Hemminger <[email protected]>
---
 app/test/test_eal_fs.c                        | 135 ++++++++++---
 drivers/bus/auxiliary/linux/auxiliary.c       |  13 +-
 drivers/bus/cdx/cdx.c                         |   7 +-
 drivers/bus/pci/linux/pci.c                   |  44 ++---
 drivers/bus/platform/platform.c               |  10 +-
 drivers/bus/vmbus/linux/vmbus_bus.c           |  25 +--
 drivers/common/cnxk/roc_model.c               |  40 +---
 drivers/common/cnxk/roc_platform.h            |   3 +-
 .../common/mlx5/linux/mlx5_common_auxiliary.c |  13 +-
 drivers/net/af_xdp/rte_eth_af_xdp.c           |  13 +-
 lib/eal/common/eal_filesystem.h               |   4 +-
 lib/eal/include/meson.build                   |   1 +
 lib/eal/include/rte_sysfs.h                   | 121 ++++++++++++
 lib/eal/linux/eal_hugepage_info.c             |  32 +--
 lib/eal/linux/eal_lcore.c                     |  18 +-
 lib/eal/unix/eal_filesystem.c                 |  31 ---
 lib/eal/unix/eal_unix_sysfs.c                 | 187 ++++++++++++++++++
 lib/eal/unix/meson.build                      |   1 +
 18 files changed, 493 insertions(+), 205 deletions(-)
 create mode 100644 lib/eal/include/rte_sysfs.h
 create mode 100644 lib/eal/unix/eal_unix_sysfs.c

diff --git a/app/test/test_eal_fs.c b/app/test/test_eal_fs.c
index 62eea98677..00c676c4d6 100644
--- a/app/test/test_eal_fs.c
+++ b/app/test/test_eal_fs.c
@@ -7,8 +7,10 @@
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <limits.h>
+#include <unistd.h>
 
-#include "eal_filesystem.h"
+#include <rte_sysfs.h>
 
 #ifdef RTE_EXEC_ENV_WINDOWS
 static int
@@ -30,13 +32,15 @@ test_parse_sysfs_value(void)
        FILE *fd = NULL;
        unsigned valid_number;
        unsigned long retval = 0;
+       char strval[64];
+       long sretval = 0;
 
 #ifdef RTE_EXEC_ENV_FREEBSD
        /* BSD doesn't have /proc/pid/fd */
        return 0;
 #endif
 
-       printf("Testing function eal_parse_sysfs_value()\n");
+       printf("Testing sysfs value functions\n");
 
        /* get a temporary filename to use for all tests - create temp file 
handle and then
         * use /proc to get the actual file that we can open */
@@ -54,8 +58,8 @@ test_parse_sysfs_value(void)
 
        /* test we get an error value if we use file before it's created */
        printf("Test reading a missing file ...\n");
-       if (eal_parse_sysfs_value("/dev/not-quite-null", &retval) == 0) {
-               printf("Error with eal_parse_sysfs_value() - returned success 
on reading empty file\n");
+       if (rte_sysfs_parse_uint(&retval, "/dev/not-quite-null") == 0) {
+               printf("Error with rte_sysfs_parse_uint() - returned success on 
reading empty file\n");
                goto error;
        }
        printf("Confirmed return error when reading empty file\n");
@@ -71,12 +75,12 @@ test_parse_sysfs_value(void)
        fprintf(fd,"%u\n", valid_number);
        fclose(fd);
        fd = NULL;
-       if (eal_parse_sysfs_value(filename, &retval) < 0) {
-               printf("eal_parse_sysfs_value() returned error - test 
failed\n");
+       if (rte_sysfs_parse_uint(&retval, "%s", filename) < 0) {
+               printf("rte_sysfs_parse_uint() returned error - test failed\n");
                goto error;
        }
        if (retval != valid_number) {
-               printf("Invalid value read by eal_parse_sysfs_value() - test 
failed\n");
+               printf("Invalid value read by rte_sysfs_parse_uint() - test 
failed\n");
                goto error;
        }
        printf("Read '%u\\n' ok\n", valid_number);
@@ -91,43 +95,93 @@ test_parse_sysfs_value(void)
        fprintf(fd,"0x%x\n", valid_number);
        fclose(fd);
        fd = NULL;
-       if (eal_parse_sysfs_value(filename, &retval) < 0) {
-               printf("eal_parse_sysfs_value() returned error - test 
failed\n");
+       if (rte_sysfs_parse_uint(&retval, "%s", filename) < 0) {
+               printf("rte_sysfs_parse_uint() returned error - test failed\n");
                goto error;
        }
        if (retval != valid_number) {
-               printf("Invalid value read by eal_parse_sysfs_value() - test 
failed\n");
+               printf("Invalid value read by rte_sysfs_parse_uint() - test 
failed\n");
                goto error;
        }
        printf("Read '0x%x\\n' ok\n", valid_number);
 
-       printf("Test reading invalid values ...\n");
+       /* a value without a trailing newline is accepted */
+       valid_number = 3;
+       fd = fopen(filename, "w");
+       if (fd == NULL) {
+               printf("line %d, Error opening %s: %s\n", __LINE__, filename, 
strerror(errno));
+               goto error;
+       }
+       fprintf(fd, "%u", valid_number);
+       fclose(fd);
+       fd = NULL;
+       if (rte_sysfs_parse_uint(&retval, "%s", filename) < 0 || retval != 
valid_number) {
+               printf("rte_sysfs_parse_uint() failed without trailing newline 
- test failed\n");
+               goto error;
+       }
+       printf("Read '%u' (no newline) ok\n", valid_number);
 
-       /* test reading an empty file - expect failure!*/
-       fd = fopen(filename,"w");
+       /* a negative value is rejected by the unsigned variant ... */
+       fd = fopen(filename, "w");
        if (fd == NULL) {
                printf("line %d, Error opening %s: %s\n", __LINE__, filename, 
strerror(errno));
                goto error;
        }
+       fprintf(fd, "-1\n");
        fclose(fd);
        fd = NULL;
-       if (eal_parse_sysfs_value(filename, &retval) == 0) {
-               printf("eal_parse_sysfs_value() read invalid value  - test 
failed\n");
+       if (rte_sysfs_parse_uint(&retval, "%s", filename) == 0) {
+               printf("rte_sysfs_parse_uint() accepted a negative value - test 
failed\n");
                goto error;
        }
 
-       /* test reading a valid number value *without* "\n" on the end - expect 
failure!*/
-       valid_number = 3;
+       /* ... and read correctly by the signed one, as for numa_node */
+       if (rte_sysfs_parse_int(&sretval, "%s", filename) < 0 || sretval != -1) 
{
+               printf("rte_sysfs_parse_int() failed to read -1 - test 
failed\n");
+               goto error;
+       }
+       printf("Read '-1' as signed ok\n");
+
+       /* string read strips the trailing newline */
+       fd = fopen(filename, "w");
+       if (fd == NULL) {
+               printf("line %d, Error opening %s: %s\n", __LINE__, filename, 
strerror(errno));
+               goto error;
+       }
+       fprintf(fd, "performance\n");
+       fclose(fd);
+       fd = NULL;
+       if (rte_sysfs_parse_string(strval, sizeof(strval), "%s", filename) < 0 
||
+                       strcmp(strval, "performance") != 0) {
+               printf("rte_sysfs_parse_string() returned '%s' - test 
failed\n", strval);
+               goto error;
+       }
+       printf("Read 'performance' ok\n");
+
+       /* write it back and read it again */
+       if (rte_sysfs_write_string("powersave", "%s", filename) < 0) {
+               printf("rte_sysfs_write_string() returned error - test 
failed\n");
+               goto error;
+       }
+       if (rte_sysfs_parse_string(strval, sizeof(strval), "%s", filename) < 0 
||
+                       strcmp(strval, "powersave") != 0) {
+               printf("read back '%s' after write - test failed\n", strval);
+               goto error;
+       }
+       printf("Wrote and read back 'powersave' ok\n");
+
+       printf("Test reading invalid values ...\n");
+
+       /* test reading an empty file - expect failure!*/
        fd = fopen(filename,"w");
        if (fd == NULL) {
                printf("line %d, Error opening %s: %s\n", __LINE__, filename, 
strerror(errno));
                goto error;
        }
-       fprintf(fd,"%u", valid_number);
        fclose(fd);
        fd = NULL;
-       if (eal_parse_sysfs_value(filename, &retval) == 0) {
-               printf("eal_parse_sysfs_value() read invalid value  - test 
failed\n");
+       if (rte_sysfs_parse_uint(&retval, "%s", filename) == 0) {
+               printf("rte_sysfs_parse_uint() read invalid value  - test 
failed\n");
                goto error;
        }
 
@@ -141,8 +195,8 @@ test_parse_sysfs_value(void)
        fprintf(fd,"%uJ\n", valid_number);
        fclose(fd);
        fd = NULL;
-       if (eal_parse_sysfs_value(filename, &retval) == 0) {
-               printf("eal_parse_sysfs_value() read invalid value  - test 
failed\n");
+       if (rte_sysfs_parse_uint(&retval, "%s", filename) == 0) {
+               printf("rte_sysfs_parse_uint() read invalid value  - test 
failed\n");
                goto error;
        }
 
@@ -155,14 +209,45 @@ test_parse_sysfs_value(void)
        fprintf(fd,"error\n");
        fclose(fd);
        fd = NULL;
-       if (eal_parse_sysfs_value(filename, &retval) == 0) {
-               printf("eal_parse_sysfs_value() read invalid value  - test 
failed\n");
+       if (rte_sysfs_parse_uint(&retval, "%s", filename) == 0) {
+               printf("rte_sysfs_parse_uint() read invalid value  - test 
failed\n");
+               goto error;
+       }
+
+       /* test reading a negative value as unsigned - expect failure! */
+       fd = fopen(filename, "w");
+       if (fd == NULL) {
+               printf("line %d, Error opening %s: %s\n", __LINE__, filename, 
strerror(errno));
+               goto error;
+       }
+       fprintf(fd, "-1\n");
+       fclose(fd);
+       fd = NULL;
+       if (rte_sysfs_parse_uint(&retval, "%s", filename) == 0) {
+               printf("rte_sysfs_parse_uint() read negative value  - test 
failed\n");
+               goto error;
+       }
+
+       /*
+        * Same, but with leading whitespace: strtoul() skips it before it
+        * negates, so the sign has to be looked for past the whitespace.
+        */
+       fd = fopen(filename, "w");
+       if (fd == NULL) {
+               printf("line %d, Error opening %s: %s\n", __LINE__, filename, 
strerror(errno));
+               goto error;
+       }
+       fprintf(fd, " -1\n");
+       fclose(fd);
+       fd = NULL;
+       if (rte_sysfs_parse_uint(&retval, "%s", filename) == 0) {
+               printf("rte_sysfs_parse_uint() read negative value  - test 
failed\n");
                goto error;
        }
 
        close(tmp_file_handle);
        unlink(filename);
-       printf("eal_parse_sysfs_value() - OK\n");
+       printf("sysfs value functions - OK\n");
        return 0;
 
 error:
diff --git a/drivers/bus/auxiliary/linux/auxiliary.c 
b/drivers/bus/auxiliary/linux/auxiliary.c
index 3a2dca2865..c8d289eecb 100644
--- a/drivers/bus/auxiliary/linux/auxiliary.c
+++ b/drivers/bus/auxiliary/linux/auxiliary.c
@@ -9,6 +9,7 @@
 #include <rte_malloc.h>
 #include <rte_devargs.h>
 #include <rte_memcpy.h>
+#include <rte_sysfs.h>
 #include <eal_filesystem.h>
 
 #include "../private.h"
@@ -21,8 +22,7 @@ auxiliary_scan_one(const char *dirname, const char *name)
 {
        struct rte_auxiliary_device *dev;
        struct rte_auxiliary_device *dev2;
-       char filename[PATH_MAX];
-       unsigned long tmp;
+       long num;
        int ret;
 
        dev = malloc(sizeof(*dev));
@@ -36,12 +36,9 @@ auxiliary_scan_one(const char *dirname, const char *name)
        }
        dev->device.name = dev->name;
 
-       /* Get NUMA node, default to 0 if not present */
-       snprintf(filename, sizeof(filename), "%s/%s/numa_node",
-                dirname, name);
-       if (access(filename, F_OK) == 0 &&
-           eal_parse_sysfs_value(filename, &tmp) == 0)
-               dev->device.numa_node = tmp;
+       /* Get NUMA node, default to SOCKET_ID_ANY if not present */
+       if (rte_sysfs_parse_int(&num, "%s/%s/numa_node", dirname, name) == 0)
+               dev->device.numa_node = num;
        else
                dev->device.numa_node = SOCKET_ID_ANY;
 
diff --git a/drivers/bus/cdx/cdx.c b/drivers/bus/cdx/cdx.c
index c0b46a41ad..8cbe2473a9 100644
--- a/drivers/bus/cdx/cdx.c
+++ b/drivers/bus/cdx/cdx.c
@@ -74,6 +74,7 @@
 #include <rte_kvargs.h>
 #include <rte_malloc.h>
 #include <rte_vfio.h>
+#include <rte_sysfs.h>
 
 #include <eal_export.h>
 #include <eal_filesystem.h>
@@ -179,16 +180,14 @@ cdx_scan_one(const char *dirname, const char *dev_name)
        }
 
        /* get vendor id */
-       snprintf(filename, sizeof(filename), "%s/vendor", dirname);
-       if (eal_parse_sysfs_value(filename, &tmp) < 0) {
+       if (rte_sysfs_parse_uint(&tmp, "%s/vendor", dirname) < 0) {
                ret = -1;
                goto err;
        }
        dev->id.vendor_id = (uint16_t)tmp;
 
        /* get device id */
-       snprintf(filename, sizeof(filename), "%s/device", dirname);
-       if (eal_parse_sysfs_value(filename, &tmp) < 0) {
+       if (rte_sysfs_parse_uint(&tmp, "%s/device", dirname) < 0) {
                ret = -1;
                goto err;
        }
diff --git a/drivers/bus/pci/linux/pci.c b/drivers/bus/pci/linux/pci.c
index 9aae0a5d14..d40a09e78a 100644
--- a/drivers/bus/pci/linux/pci.c
+++ b/drivers/bus/pci/linux/pci.c
@@ -12,6 +12,7 @@
 #include <rte_devargs.h>
 #include <rte_memcpy.h>
 #include <rte_vfio.h>
+#include <rte_sysfs.h>
 
 #include <eal_export.h>
 #include "eal_filesystem.h"
@@ -205,6 +206,7 @@ pci_scan_one(const char *dirname, const struct rte_pci_addr 
*addr)
 {
        char filename[PATH_MAX];
        unsigned long tmp;
+       long num;
        struct rte_pci_device_internal *pdev;
        struct rte_pci_device *dev;
        char driver[PATH_MAX];
@@ -221,43 +223,35 @@ pci_scan_one(const char *dirname, const struct 
rte_pci_addr *addr)
        dev->addr = *addr;
 
        /* get vendor id */
-       snprintf(filename, sizeof(filename), "%s/vendor", dirname);
-       if (eal_parse_sysfs_value(filename, &tmp) < 0) {
+       if (rte_sysfs_parse_uint(&tmp, "%s/vendor", dirname) < 0) {
                pci_free(pdev);
                return -1;
        }
        dev->id.vendor_id = (uint16_t)tmp;
 
        /* get device id */
-       snprintf(filename, sizeof(filename), "%s/device", dirname);
-       if (eal_parse_sysfs_value(filename, &tmp) < 0) {
+       if (rte_sysfs_parse_uint(&tmp, "%s/device", dirname) < 0) {
                pci_free(pdev);
                return -1;
        }
        dev->id.device_id = (uint16_t)tmp;
 
        /* get subsystem_vendor id */
-       snprintf(filename, sizeof(filename), "%s/subsystem_vendor",
-                dirname);
-       if (eal_parse_sysfs_value(filename, &tmp) < 0) {
+       if (rte_sysfs_parse_uint(&tmp, "%s/subsystem_vendor", dirname) < 0) {
                pci_free(pdev);
                return -1;
        }
        dev->id.subsystem_vendor_id = (uint16_t)tmp;
 
        /* get subsystem_device id */
-       snprintf(filename, sizeof(filename), "%s/subsystem_device",
-                dirname);
-       if (eal_parse_sysfs_value(filename, &tmp) < 0) {
+       if (rte_sysfs_parse_uint(&tmp, "%s/subsystem_device", dirname) < 0) {
                pci_free(pdev);
                return -1;
        }
        dev->id.subsystem_device_id = (uint16_t)tmp;
 
        /* get class_id */
-       snprintf(filename, sizeof(filename), "%s/class",
-                dirname);
-       if (eal_parse_sysfs_value(filename, &tmp) < 0) {
+       if (rte_sysfs_parse_uint(&tmp, "%s/class", dirname) < 0) {
                pci_free(pdev);
                return -1;
        }
@@ -266,25 +260,15 @@ pci_scan_one(const char *dirname, const struct 
rte_pci_addr *addr)
 
        /* get max_vfs */
        dev->max_vfs = 0;
-       snprintf(filename, sizeof(filename), "%s/max_vfs", dirname);
-       if (!access(filename, F_OK) &&
-           eal_parse_sysfs_value(filename, &tmp) == 0)
+       if (rte_sysfs_parse_uint(&tmp, "%s/max_vfs", dirname) == 0)
+               dev->max_vfs = (uint16_t)tmp;
+       /* for non igb_uio driver, need kernel version >= 3.8 */
+       else if (rte_sysfs_parse_uint(&tmp, "%s/sriov_numvfs", dirname) == 0)
                dev->max_vfs = (uint16_t)tmp;
-       else {
-               /* for non igb_uio driver, need kernel version >= 3.8 */
-               snprintf(filename, sizeof(filename),
-                        "%s/sriov_numvfs", dirname);
-               if (!access(filename, F_OK) &&
-                   eal_parse_sysfs_value(filename, &tmp) == 0)
-                       dev->max_vfs = (uint16_t)tmp;
-       }
-
-       /* get numa node, default to 0 if not present */
-       snprintf(filename, sizeof(filename), "%s/numa_node", dirname);
 
-       if (access(filename, F_OK) == 0 &&
-           eal_parse_sysfs_value(filename, &tmp) == 0)
-               dev->device.numa_node = tmp;
+       /* get numa node, default to SOCKET_ID_ANY if not present */
+       if (rte_sysfs_parse_int(&num, "%s/numa_node", dirname) == 0)
+               dev->device.numa_node = num;
        else
                dev->device.numa_node = SOCKET_ID_ANY;
 
diff --git a/drivers/bus/platform/platform.c b/drivers/bus/platform/platform.c
index 90d865a8df..9585fb79e9 100644
--- a/drivers/bus/platform/platform.c
+++ b/drivers/bus/platform/platform.c
@@ -24,6 +24,7 @@
 #include <rte_memory.h>
 #include <rte_string_fns.h>
 #include <rte_vfio.h>
+#include <rte_sysfs.h>
 
 #include "private.h"
 
@@ -49,8 +50,7 @@ static int
 dev_add(const char *dev_name)
 {
        struct rte_platform_device *pdev, *tmp;
-       char path[PATH_MAX];
-       unsigned long val;
+       long val;
 
        pdev = calloc(1, sizeof(*pdev));
        if (pdev == NULL)
@@ -59,8 +59,10 @@ dev_add(const char *dev_name)
        rte_strscpy(pdev->name, dev_name, sizeof(pdev->name));
        pdev->device.name = pdev->name;
        pdev->device.devargs = rte_bus_find_devargs(&platform_bus, dev_name);
-       snprintf(path, sizeof(path), PLATFORM_BUS_DEVICES_PATH "/%s/numa_node", 
dev_name);
-       pdev->device.numa_node = eal_parse_sysfs_value(path, &val) ? 
rte_socket_id() : val;
+       if (rte_sysfs_parse_int(&val, PLATFORM_BUS_DEVICES_PATH 
"/%s/numa_node", dev_name) == 0)
+               pdev->device.numa_node = val;
+       else
+               pdev->device.numa_node = rte_socket_id();
 
        RTE_BUS_FOREACH_DEV(tmp, &platform_bus) {
                if (!strcmp(tmp->name, pdev->name)) {
diff --git a/drivers/bus/vmbus/linux/vmbus_bus.c 
b/drivers/bus/vmbus/linux/vmbus_bus.c
index 779ea50b92..9ee7983eb6 100644
--- a/drivers/bus/vmbus/linux/vmbus_bus.c
+++ b/drivers/bus/vmbus/linux/vmbus_bus.c
@@ -19,6 +19,7 @@
 #include <rte_malloc.h>
 #include <rte_bus_vmbus.h>
 #include <rte_kvargs.h>
+#include <rte_sysfs.h>
 
 #include <eal_export.h>
 #include "eal_filesystem.h"
@@ -202,11 +203,8 @@ rte_vmbus_map_device(struct rte_vmbus_device *dev)
                        return -1;
                }
 
-               snprintf(filename, sizeof(filename),
-                        "%s/size", dirname);
-               if (eal_parse_sysfs_value(filename, &len) < 0) {
-                       VMBUS_LOG(ERR,
-                               "could not read %s", filename);
+               if (rte_sysfs_parse_uint(&len, "%s/size", dirname) < 0) {
+                       VMBUS_LOG(ERR, "could not read size of %s", dirname);
                        return -1;
                }
                res->len = len;
@@ -280,6 +278,7 @@ vmbus_scan_one(const char *name)
        char filename[PATH_MAX];
        char dirname[PATH_MAX];
        unsigned long tmp;
+       long num;
 
        dev = calloc(1, sizeof(*dev));
        if (dev == NULL)
@@ -314,14 +313,12 @@ vmbus_scan_one(const char *name)
                goto error;
 
        /* get relid */
-       snprintf(filename, sizeof(filename), "%s/id", dirname);
-       if (eal_parse_sysfs_value(filename, &tmp) < 0)
+       if (rte_sysfs_parse_uint(&tmp, "%s/id", dirname) < 0)
                goto error;
        dev->relid = tmp;
 
        /* get monitor id */
-       snprintf(filename, sizeof(filename), "%s/monitor_id", dirname);
-       if (eal_parse_sysfs_value(filename, &tmp) >= 0) {
+       if (rte_sysfs_parse_uint(&tmp, "%s/monitor_id", dirname) >= 0) {
                dev->monitor_id = tmp;
        } else {
                VMBUS_LOG(NOTICE, "monitor disabled on %s", name);
@@ -333,14 +330,8 @@ vmbus_scan_one(const char *name)
        dev->device.numa_node = SOCKET_ID_ANY;
        if (vmbus_use_numa(dev)) {
                /* get numa node (if present) */
-               snprintf(filename, sizeof(filename), "%s/numa_node",
-                        dirname);
-
-               if (access(filename, R_OK) == 0) {
-                       if (eal_parse_sysfs_value(filename, &tmp) < 0)
-                               goto error;
-                       dev->device.numa_node = tmp;
-               }
+               if (rte_sysfs_parse_int(&num, "%s/numa_node", dirname) == 0)
+                       dev->device.numa_node = num;
        }
 
        /* device is valid, add in list (sorted) */
diff --git a/drivers/common/cnxk/roc_model.c b/drivers/common/cnxk/roc_model.c
index f0312a5400..800e1ef013 100644
--- a/drivers/common/cnxk/roc_model.c
+++ b/drivers/common/cnxk/roc_model.c
@@ -105,20 +105,17 @@ is_rvu_device(unsigned long val)
 static int
 rvu_device_lookup(const char *dirname, uint32_t *part, uint32_t *pass)
 {
-       char filename[PATH_MAX];
        unsigned long val;
 
        /* Check if vendor id is cavium */
-       snprintf(filename, sizeof(filename), "%s/vendor", dirname);
-       if (plt_sysfs_value_parse(filename, &val) < 0)
+       if (plt_sysfs_value_parse(&val, "%s/vendor", dirname) < 0)
                goto error;
 
        if (val != PCI_VENDOR_ID_CAVIUM)
                goto error;
 
        /* Get device id  */
-       snprintf(filename, sizeof(filename), "%s/device", dirname);
-       if (plt_sysfs_value_parse(filename, &val) < 0)
+       if (plt_sysfs_value_parse(&val, "%s/device", dirname) < 0)
                goto error;
 
        /* Check if device ID belongs to any RVU device */
@@ -126,15 +123,13 @@ rvu_device_lookup(const char *dirname, uint32_t *part, 
uint32_t *pass)
                goto error;
 
        /* Get subsystem_device id */
-       snprintf(filename, sizeof(filename), "%s/subsystem_device", dirname);
-       if (plt_sysfs_value_parse(filename, &val) < 0)
+       if (plt_sysfs_value_parse(&val, "%s/subsystem_device", dirname) < 0)
                goto error;
 
        *part = val >> MODEL_CN10K_PART_SHIFT;
 
        /* Get revision for pass value*/
-       snprintf(filename, sizeof(filename), "%s/revision", dirname);
-       if (plt_sysfs_value_parse(filename, &val) < 0)
+       if (plt_sysfs_value_parse(&val, "%s/revision", dirname) < 0)
                goto error;
 
        *pass = val & MODEL_CN10K_PASS_MASK;
@@ -230,31 +225,14 @@ populate_model(struct roc_model *model, uint32_t midr)
 static int
 midr_get(unsigned long *val)
 {
-       const char *file =
-               "/sys/devices/system/cpu/cpu0/regs/identification/midr_el1";
-       int rc = UTIL_ERR_FS;
-       char buf[BUFSIZ];
-       char *end = NULL;
-       FILE *f;
-
        if (val == NULL)
-               goto err;
-       f = fopen(file, "r");
-       if (f == NULL)
-               goto err;
-
-       if (fgets(buf, sizeof(buf), f) == NULL)
-               goto fclose;
+               return UTIL_ERR_FS;
 
-       *val = strtoul(buf, &end, 0);
-       if ((buf[0] == '\0') || (end == NULL) || (*end != '\n'))
-               goto fclose;
+       if (plt_sysfs_value_parse(val,
+                       
"/sys/devices/system/cpu/cpu0/regs/identification/midr_el1") < 0)
+               return UTIL_ERR_FS;
 
-       rc = 0;
-fclose:
-       fclose(f);
-err:
-       return rc;
+       return 0;
 }
 
 static void
diff --git a/drivers/common/cnxk/roc_platform.h 
b/drivers/common/cnxk/roc_platform.h
index ac4f76473f..71f9d9a256 100644
--- a/drivers/common/cnxk/roc_platform.h
+++ b/drivers/common/cnxk/roc_platform.h
@@ -23,6 +23,7 @@
 #include <rte_seqcount.h>
 #include <rte_spinlock.h>
 #include <rte_string_fns.h>
+#include <rte_sysfs.h>
 #include <rte_tailq.h>
 #include <rte_telemetry.h>
 
@@ -109,7 +110,7 @@
 #define plt_pci_device             rte_pci_device
 #define plt_pci_read_config        rte_pci_read_config
 #define plt_pci_find_ext_capability rte_pci_find_ext_capability
-#define plt_sysfs_value_parse      eal_parse_sysfs_value
+#define plt_sysfs_value_parse      rte_sysfs_parse_uint
 
 #define plt_log2_u32    rte_log2_u32
 #define plt_cpu_to_be_16 rte_cpu_to_be_16
diff --git a/drivers/common/mlx5/linux/mlx5_common_auxiliary.c 
b/drivers/common/mlx5/linux/mlx5_common_auxiliary.c
index 3ee2f4638a..07bb899f9e 100644
--- a/drivers/common/mlx5/linux/mlx5_common_auxiliary.c
+++ b/drivers/common/mlx5/linux/mlx5_common_auxiliary.c
@@ -10,6 +10,7 @@
 #include <rte_errno.h>
 #include <bus_auxiliary_driver.h>
 #include <rte_common.h>
+#include <rte_sysfs.h>
 #include <eal_export.h>
 #include "eal_filesystem.h"
 
@@ -93,16 +94,12 @@ mlx5_auxiliary_get_pci_str(const struct 
rte_auxiliary_device *dev,
 static int
 mlx5_auxiliary_get_numa(const struct rte_auxiliary_device *dev)
 {
-       unsigned long numa;
-       char numa_path[PATH_MAX];
+       char pci_path[PATH_MAX];
+       long numa;
 
-       if (mlx5_auxiliary_get_pci_path(dev, numa_path, sizeof(numa_path)) != 0)
+       if (mlx5_auxiliary_get_pci_path(dev, pci_path, sizeof(pci_path)) != 0)
                return SOCKET_ID_ANY;
-       if (strcat(numa_path, "/numa_node") == NULL) {
-               rte_errno = ENAMETOOLONG;
-               return SOCKET_ID_ANY;
-       }
-       if (eal_parse_sysfs_value(numa_path, &numa) != 0) {
+       if (rte_sysfs_parse_int(&numa, "%s/numa_node", pci_path) != 0) {
                rte_errno = EINVAL;
                return SOCKET_ID_ANY;
        }
diff --git a/drivers/net/af_xdp/rte_eth_af_xdp.c 
b/drivers/net/af_xdp/rte_eth_af_xdp.c
index 2cdb533276..9fc48e113b 100644
--- a/drivers/net/af_xdp/rte_eth_af_xdp.c
+++ b/drivers/net/af_xdp/rte_eth_af_xdp.c
@@ -38,6 +38,7 @@
 #include <rte_ring.h>
 #include <rte_spinlock.h>
 #include <rte_power_intrinsics.h>
+#include <rte_sysfs.h>
 
 #include "compat.h"
 #include "eal_filesystem.h"
@@ -2568,15 +2569,13 @@ rte_pmd_af_xdp_probe(struct rte_vdev_device *dev)
 
        /* get numa node id from net sysfs */
        if (dev->device.numa_node == SOCKET_ID_ANY) {
-               unsigned long numa = 0;
-               char numa_path[PATH_MAX];
+               long numa;
 
-               snprintf(numa_path, sizeof(numa_path), 
"/sys/class/net/%s/device/numa_node",
-                        if_name);
-               if (access(numa_path, R_OK) != 0 || 
eal_parse_sysfs_value(numa_path, &numa) != 0)
-                       dev->device.numa_node = rte_socket_id();
-               else
+               if (rte_sysfs_parse_int(&numa, 
"/sys/class/net/%s/device/numa_node",
+                                       if_name) == 0)
                        dev->device.numa_node = numa;
+               else
+                       dev->device.numa_node = rte_socket_id();
        }
 
        busy_budget = busy_budget == -1 ? ETH_AF_XDP_DFLT_BUSY_BUDGET :
diff --git a/lib/eal/common/eal_filesystem.h b/lib/eal/common/eal_filesystem.h
index 912f446f64..9859fe7241 100644
--- a/lib/eal/common/eal_filesystem.h
+++ b/lib/eal/common/eal_filesystem.h
@@ -128,8 +128,6 @@ eal_get_hugefile_list_seg_path(char *buffer, size_t buflen,
 /** define the default filename prefix for the %s values above */
 #define HUGEFILE_PREFIX_DEFAULT "rte"
 
-/** Function to read a single numeric value from a file on the filesystem.
- * Used to read information from files on /sys */
-int eal_parse_sysfs_value(const char *filename, unsigned long *val);
+/* Reading and writing of sysfs values is in <rte_sysfs.h> */
 
 #endif /* EAL_FILESYSTEM_H */
diff --git a/lib/eal/include/meson.build b/lib/eal/include/meson.build
index aef5824e5f..8c0a59f3d7 100644
--- a/lib/eal/include/meson.build
+++ b/lib/eal/include/meson.build
@@ -61,6 +61,7 @@ headers += files(
 driver_sdk_headers = files(
         'bus_driver.h',
         'dev_driver.h',
+        'rte_sysfs.h',
 )
 
 # special case install the generic headers, since they go in a subdir
diff --git a/lib/eal/include/rte_sysfs.h b/lib/eal/include/rte_sysfs.h
new file mode 100644
index 0000000000..d9b59f58c3
--- /dev/null
+++ b/lib/eal/include/rte_sysfs.h
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+#ifndef RTE_SYSFS_H
+#define RTE_SYSFS_H
+
+/**
+ * @file
+ * @internal
+ *
+ * Helpers to read and write single values in sysfs, used across DPDK.
+ *
+ * All of these build the path from a printf-style format, so that
+ * callers do not have to construct it separately.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+
+#include <rte_common.h>
+#include <rte_compat.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Read an unsigned numeric value from a file, typically under /sys.
+ *
+ * The value is parsed with strtoul() using base 0, so decimal, octal
+ * and 0x-prefixed hexadecimal are all accepted. A negative value is
+ * rejected rather than wrapping; use rte_sysfs_parse_int() for the
+ * attributes that are signed.
+ *
+ * @param val
+ *   Where to store the parsed value, unmodified on failure.
+ * @param format
+ *   printf-style format describing the path to read.
+ * @return
+ *   0 on success, -1 on error.
+ */
+__rte_internal
+__rte_format_printf(2, 3)
+int rte_sysfs_parse_uint(unsigned long *val, const char *format, ...);
+
+/**
+ * Read an unsigned numeric value from a file, typically under /sys.
+ *
+ * As rte_sysfs_parse_uint(), but takes a va_list, so that a wrapper
+ * can forward its arguments without formatting the path itself.
+ *
+ * @param val
+ *   Where to store the parsed value, unmodified on failure.
+ * @param format
+ *   printf-style format describing the path to read.
+ * @param ap
+ *   Arguments for the format.
+ * @return
+ *   0 on success, -1 on error.
+ */
+__rte_internal
+__rte_format_printf(2, 0)
+int rte_sysfs_vparse_uint(unsigned long *val, const char *format, va_list ap);
+
+/**
+ * Read a signed numeric value from a file, typically under /sys.
+ *
+ * The value is parsed with strtol() using base 0. Some sysfs
+ * attributes are signed, most notably "numa_node" which is -1 when
+ * the device is not associated with any NUMA node.
+ *
+ * @param val
+ *   Where to store the parsed value, unmodified on failure.
+ * @param format
+ *   printf-style format describing the path to read.
+ * @return
+ *   0 on success, -1 on error.
+ */
+__rte_internal
+__rte_format_printf(2, 3)
+int rte_sysfs_parse_int(long *val, const char *format, ...);
+
+/**
+ * Read a string value from a file, typically under /sys.
+ *
+ * The trailing newline, if any, is stripped.
+ *
+ * @param buf
+ *   Where to store the NUL-terminated value. The contents are
+ *   indeterminate on failure.
+ * @param buflen
+ *   Size of buf, the value is truncated if it does not fit.
+ * @param format
+ *   printf-style format describing the path to read.
+ * @return
+ *   0 on success, -1 on error.
+ */
+__rte_internal
+__rte_format_printf(3, 4)
+int rte_sysfs_parse_string(char *buf, size_t buflen, const char *format, ...);
+
+/**
+ * Write a string value to a file, typically under /sys.
+ *
+ * @param str
+ *   The NUL-terminated value to write.
+ * @param format
+ *   printf-style format describing the path to write.
+ * @return
+ *   0 on success, -1 on error.
+ */
+__rte_internal
+__rte_format_printf(2, 3)
+int rte_sysfs_write_string(const char *str, const char *format, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RTE_SYSFS_H */
diff --git a/lib/eal/linux/eal_hugepage_info.c 
b/lib/eal/linux/eal_hugepage_info.c
index 05c5b3f613..a8da0d5455 100644
--- a/lib/eal/linux/eal_hugepage_info.c
+++ b/lib/eal/linux/eal_hugepage_info.c
@@ -25,6 +25,7 @@
 #include <rte_debug.h>
 #include <rte_log.h>
 #include <rte_common.h>
+#include <rte_sysfs.h>
 #include "rte_string_fns.h"
 
 #include "eal_private.h"
@@ -68,18 +69,6 @@ create_shared_memory(const char *filename, const size_t 
mem_size)
        return map_shared_memory(filename, mem_size, O_RDWR | O_CREAT);
 }
 
-static int get_hp_sysfs_value(const char *subdir, const char *file, unsigned 
long *val)
-{
-       char *path = NULL;
-       int ret;
-
-       if (asprintf(&path, "%s/%s/%s", sys_dir_path, subdir, file) < 0)
-               return -1;
-       ret = eal_parse_sysfs_value(path, val);
-       free(path);
-       return ret;
-}
-
 /* this function is only called from eal_hugepage_info_init which itself
  * is only called from a primary process */
 static uint32_t
@@ -92,16 +81,16 @@ get_num_hugepages(const char *subdir, size_t sz, unsigned 
int reusable_pages)
        const char *nr_splus_file = "surplus_hugepages";
 
        /* first, check how many reserved pages kernel reports */
-       if (get_hp_sysfs_value(subdir, nr_rsvd_file, &resv_pages) < 0)
+       if (rte_sysfs_parse_uint(&resv_pages, "%s/%s/%s", sys_dir_path, subdir, 
nr_rsvd_file) < 0)
                return 0;
 
-       if (get_hp_sysfs_value(subdir, nr_hp_file, &num_pages) < 0)
+       if (rte_sysfs_parse_uint(&num_pages, "%s/%s/%s", sys_dir_path, subdir, 
nr_hp_file) < 0)
                return 0;
 
-       if (get_hp_sysfs_value(subdir, nr_over_file, &over_pages) < 0)
+       if (rte_sysfs_parse_uint(&over_pages, "%s/%s/%s", sys_dir_path, subdir, 
nr_over_file) < 0)
                over_pages = 0;
 
-       if (get_hp_sysfs_value(subdir, nr_splus_file, &surplus_pages) < 0)
+       if (rte_sysfs_parse_uint(&surplus_pages, "%s/%s/%s", sys_dir_path, 
subdir, nr_splus_file) < 0)
                surplus_pages = 0;
 
        /* adjust num_pages */
@@ -138,7 +127,7 @@ get_num_hugepages(const char *subdir, size_t sz, unsigned 
int reusable_pages)
 static uint32_t
 get_num_hugepages_on_node(const char *subdir, unsigned int socket, size_t sz)
 {
-       char *path = NULL, *socketpath = NULL;
+       char *socketpath = NULL;
        DIR *socketdir;
        unsigned long num_pages = 0;
        const char *nr_hp_file = "free_hugepages";
@@ -158,13 +147,7 @@ get_num_hugepages_on_node(const char *subdir, unsigned int 
socket, size_t sz)
                goto nopages;
        }
 
-       if (asprintf(&path, "%s/%s/%s", socketpath, subdir, nr_hp_file) < 0) {
-               EAL_LOG(ERR, "Can not format free hugepages path");
-               path = NULL;
-               goto nopages;
-       }
-
-       if (eal_parse_sysfs_value(path, &num_pages) < 0)
+       if (rte_sysfs_parse_uint(&num_pages, "%s/%s/%s", socketpath, subdir, 
nr_hp_file) < 0)
                goto nopages;
 
        if (num_pages == 0)
@@ -179,7 +162,6 @@ get_num_hugepages_on_node(const char *subdir, unsigned int 
socket, size_t sz)
                num_pages = UINT32_MAX;
 
 nopages:
-       free(path);
        free(socketpath);
 
        return num_pages;
diff --git a/lib/eal/linux/eal_lcore.c b/lib/eal/linux/eal_lcore.c
index 29b36dd610..ada1c408f4 100644
--- a/lib/eal/linux/eal_lcore.c
+++ b/lib/eal/linux/eal_lcore.c
@@ -6,6 +6,7 @@
 #include <limits.h>
 
 #include <rte_log.h>
+#include <rte_sysfs.h>
 
 #include "eal_private.h"
 #include "eal_filesystem.h"
@@ -57,18 +58,13 @@ eal_cpu_socket_id(unsigned lcore_id)
 unsigned
 eal_cpu_core_id(unsigned lcore_id)
 {
-       char path[PATH_MAX];
        unsigned long id;
 
-       int len = snprintf(path, sizeof(path), SYS_CPU_DIR "/%s", lcore_id, 
CORE_ID_FILE);
-       if (len <= 0 || (unsigned)len >= sizeof(path))
-               goto err;
-       if (eal_parse_sysfs_value(path, &id) != 0)
-               goto err;
-       return (unsigned)id;
+       if (rte_sysfs_parse_uint(&id, SYS_CPU_DIR "/%s", lcore_id, 
CORE_ID_FILE) != 0) {
+               EAL_LOG(ERR, "Error reading core id value from %s "
+                               "for lcore %u - assuming core 0", SYS_CPU_DIR, 
lcore_id);
+               return 0;
+       }
 
-err:
-       EAL_LOG(ERR, "Error reading core id value from %s "
-                       "for lcore %u - assuming core 0", SYS_CPU_DIR, 
lcore_id);
-       return 0;
+       return (unsigned int)id;
 }
diff --git a/lib/eal/unix/eal_filesystem.c b/lib/eal/unix/eal_filesystem.c
index 6b8451cd3e..c6e827f805 100644
--- a/lib/eal/unix/eal_filesystem.c
+++ b/lib/eal/unix/eal_filesystem.c
@@ -76,34 +76,3 @@ int eal_create_runtime_dir(void)
 
        return 0;
 }
-
-/* parse a sysfs (or other) file containing one integer value */
-RTE_EXPORT_SYMBOL(eal_parse_sysfs_value)
-int eal_parse_sysfs_value(const char *filename, unsigned long *val)
-{
-       FILE *f;
-       char buf[BUFSIZ];
-       char *end = NULL;
-
-       if ((f = fopen(filename, "r")) == NULL) {
-               EAL_LOG(ERR, "%s(): cannot open sysfs value %s",
-                       __func__, filename);
-               return -1;
-       }
-
-       if (fgets(buf, sizeof(buf), f) == NULL) {
-               EAL_LOG(ERR, "%s(): cannot read sysfs value %s",
-                       __func__, filename);
-               fclose(f);
-               return -1;
-       }
-       *val = strtoul(buf, &end, 0);
-       if ((buf[0] == '\0') || (end == NULL) || (*end != '\n')) {
-               EAL_LOG(ERR, "%s(): cannot parse sysfs value %s",
-                               __func__, filename);
-               fclose(f);
-               return -1;
-       }
-       fclose(f);
-       return 0;
-}
diff --git a/lib/eal/unix/eal_unix_sysfs.c b/lib/eal/unix/eal_unix_sysfs.c
new file mode 100644
index 0000000000..444155b793
--- /dev/null
+++ b/lib/eal/unix/eal_unix_sysfs.c
@@ -0,0 +1,187 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_log.h>
+#include <rte_sysfs.h>
+
+#include <eal_export.h>
+#include "eal_private.h"
+
+/* build the path from the format, then read the first line of that file */
+static int
+sysfs_read_line(char *buf, size_t buflen, const char *format, va_list ap)
+{
+       char path[PATH_MAX];
+       FILE *f;
+       int len;
+
+       len = vsnprintf(path, sizeof(path), format, ap);
+       if (len < 0 || len >= (int)sizeof(path)) {
+               EAL_LOG(ERR, "sysfs path too long");
+               return -1;
+       }
+
+       f = fopen(path, "r");
+       if (f == NULL) {
+               /*
+                * A missing attribute is normal: callers probe for optional
+                * ones such as max_vfs or numa_node. Anything else, such as
+                * a permission problem, is worth reporting.
+                */
+               if (errno == ENOENT)
+                       EAL_LOG(DEBUG, "cannot open %s: %s", path, 
strerror(errno));
+               else
+                       EAL_LOG(ERR, "cannot open %s: %s", path, 
strerror(errno));
+               return -1;
+       }
+
+       if (fgets(buf, buflen, f) == NULL) {
+               EAL_LOG(ERR, "cannot read %s", path);
+               fclose(f);
+               return -1;
+       }
+       fclose(f);
+
+       /* sysfs values are newline terminated, strip it */
+       *strchrnul(buf, '\n') = '\0';
+
+       return 0;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(rte_sysfs_vparse_uint)
+int
+rte_sysfs_vparse_uint(unsigned long *val, const char *format, va_list ap)
+{
+       const char *start;
+       char buf[BUFSIZ];
+       unsigned long tmp;
+       char *end;
+
+       if (sysfs_read_line(buf, sizeof(buf), format, ap) < 0)
+               return -1;
+
+       /*
+        * strtoul() skips leading whitespace and then silently negates a
+        * leading '-', so " -1" would come back as ULONG_MAX. Look for the
+        * sign past any whitespace: attributes that are really signed, such
+        * as numa_node, must use rte_sysfs_parse_int() instead.
+        */
+       start = buf;
+       while (isspace((unsigned char)*start))
+               ++start;
+
+       errno = 0;
+       tmp = strtoul(start, &end, 0);
+       if (end == start || *end != '\0' || errno != 0 || *start == '-') {
+               EAL_LOG(ERR, "cannot parse sysfs value '%s'", buf);
+               return -1;
+       }
+
+       *val = tmp;
+       return 0;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(rte_sysfs_parse_uint)
+int
+rte_sysfs_parse_uint(unsigned long *val, const char *format, ...)
+{
+       va_list ap;
+       int ret;
+
+       va_start(ap, format);
+       ret = rte_sysfs_vparse_uint(val, format, ap);
+       va_end(ap);
+
+       return ret;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(rte_sysfs_parse_int)
+int
+rte_sysfs_parse_int(long *val, const char *format, ...)
+{
+       char buf[BUFSIZ];
+       va_list ap;
+       char *end;
+       long tmp;
+       int ret;
+
+       va_start(ap, format);
+       ret = sysfs_read_line(buf, sizeof(buf), format, ap);
+       va_end(ap);
+       if (ret < 0)
+               return -1;
+
+       errno = 0;
+       tmp = strtol(buf, &end, 0);
+       if (end == buf || *end != '\0' || errno != 0) {
+               EAL_LOG(ERR, "cannot parse sysfs value '%s'", buf);
+               return -1;
+       }
+
+       *val = tmp;
+       return 0;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(rte_sysfs_parse_string)
+int
+rte_sysfs_parse_string(char *buf, size_t buflen, const char *format, ...)
+{
+       va_list ap;
+       int ret;
+
+       va_start(ap, format);
+       ret = sysfs_read_line(buf, buflen, format, ap);
+       va_end(ap);
+
+       return ret;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(rte_sysfs_write_string)
+int
+rte_sysfs_write_string(const char *str, const char *format, ...)
+{
+       char path[PATH_MAX];
+       va_list ap;
+       FILE *f;
+       int len;
+
+       va_start(ap, format);
+       len = vsnprintf(path, sizeof(path), format, ap);
+       va_end(ap);
+       if (len < 0 || len >= (int)sizeof(path)) {
+               EAL_LOG(ERR, "sysfs path too long");
+               return -1;
+       }
+
+       f = fopen(path, "w");
+       if (f == NULL) {
+               if (errno == ENOENT)
+                       EAL_LOG(DEBUG, "cannot open %s: %s", path, 
strerror(errno));
+               else
+                       EAL_LOG(ERR, "cannot open %s: %s", path, 
strerror(errno));
+               return -1;
+       }
+
+       if (fputs(str, f) < 0) {
+               EAL_LOG(ERR, "cannot write '%s' to %s", str, path);
+               fclose(f);
+               return -1;
+       }
+
+       /* errors on a sysfs write are reported at close time */
+       if (fclose(f) != 0) {
+               EAL_LOG(ERR, "cannot write '%s' to %s: %s", str, path, 
strerror(errno));
+               return -1;
+       }
+
+       return 0;
+}
diff --git a/lib/eal/unix/meson.build b/lib/eal/unix/meson.build
index 70af352dab..2498e9266e 100644
--- a/lib/eal/unix/meson.build
+++ b/lib/eal/unix/meson.build
@@ -7,6 +7,7 @@ sources += files(
         'eal_filesystem.c',
         'eal_firmware.c',
         'eal_unix_memory.c',
+        'eal_unix_sysfs.c',
         'eal_unix_thread.c',
         'eal_unix_timer.c',
         'rte_basename.c',
-- 
2.53.0

Reply via email to