]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #12074 from poettering/io-acct
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 25 Apr 2019 09:59:37 +0000 (11:59 +0200)
committerGitHub <noreply@github.com>
Thu, 25 Apr 2019 09:59:37 +0000 (11:59 +0200)
expose IO stats on the bus and in "systemctl status" and "systemd-run --wait"

1  2 
src/core/cgroup.c
src/core/cgroup.h
src/run/run.c
src/systemctl/systemctl.c

diff --combined src/core/cgroup.c
index 946fab24c2c15c7313fe62b351c89a34615d19d3,2bfa8df0cb4403a2927dadbe7ae55a1045b4a366..e4e0965bfbf386512baaf307ff97ddec4bac2a08
@@@ -202,7 -202,6 +202,7 @@@ void cgroup_context_done(CGroupContext 
  }
  
  void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
 +        _cleanup_free_ char *disable_controllers_str = NULL;
          CGroupIODeviceLimit *il;
          CGroupIODeviceWeight *iw;
          CGroupIODeviceLatency *l;
  
          prefix = strempty(prefix);
  
 +        (void) cg_mask_to_string(c->disable_controllers, &disable_controllers_str);
 +
          fprintf(f,
                  "%sCPUAccounting=%s\n"
                  "%sIOAccounting=%s\n"
                  "%sStartupIOWeight=%" PRIu64 "\n"
                  "%sBlockIOWeight=%" PRIu64 "\n"
                  "%sStartupBlockIOWeight=%" PRIu64 "\n"
 +                "%sDefaultMemoryMin=%" PRIu64 "\n"
 +                "%sDefaultMemoryLow=%" PRIu64 "\n"
                  "%sMemoryMin=%" PRIu64 "\n"
                  "%sMemoryLow=%" PRIu64 "\n"
                  "%sMemoryHigh=%" PRIu64 "\n"
                  "%sMemoryLimit=%" PRIu64 "\n"
                  "%sTasksMax=%" PRIu64 "\n"
                  "%sDevicePolicy=%s\n"
 +                "%sDisableControllers=%s\n"
                  "%sDelegate=%s\n",
                  prefix, yes_no(c->cpu_accounting),
                  prefix, yes_no(c->io_accounting),
                  prefix, c->startup_io_weight,
                  prefix, c->blockio_weight,
                  prefix, c->startup_blockio_weight,
 +                prefix, c->default_memory_min,
 +                prefix, c->default_memory_low,
                  prefix, c->memory_min,
                  prefix, c->memory_low,
                  prefix, c->memory_high,
                  prefix, c->memory_limit,
                  prefix, c->tasks_max,
                  prefix, cgroup_device_policy_to_string(c->device_policy),
 +                prefix, strnull(disable_controllers_str),
                  prefix, yes_no(c->delegate));
  
          if (c->delegate) {
@@@ -391,38 -382,6 +391,38 @@@ int cgroup_add_device_allow(CGroupConte
          return 0;
  }
  
 +#define UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP(entry)                       \
 +        uint64_t unit_get_ancestor_##entry(Unit *u) {                   \
 +                CGroupContext *c;                                       \
 +                                                                        \
 +                /* 1. Is entry set in this unit? If so, use that.       \
 +                 * 2. Is the default for this entry set in any          \
 +                 *    ancestor? If so, use that.                        \
 +                 * 3. Otherwise, return CGROUP_LIMIT_MIN. */            \
 +                                                                        \
 +                assert(u);                                              \
 +                                                                        \
 +                c = unit_get_cgroup_context(u);                         \
 +                                                                        \
 +                if (c->entry##_set)                                     \
 +                        return c->entry;                                \
 +                                                                        \
 +                while (UNIT_ISSET(u->slice)) {                          \
 +                        u = UNIT_DEREF(u->slice);                       \
 +                        c = unit_get_cgroup_context(u);                 \
 +                                                                        \
 +                        if (c->default_##entry##_set)                   \
 +                                return c->default_##entry;              \
 +                }                                                       \
 +                                                                        \
 +                /* We've reached the root, but nobody had default for   \
 +                 * this entry set, so set it to the kernel default. */  \
 +                return CGROUP_LIMIT_MIN;                                \
 +}
 +
 +UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP(memory_low);
 +UNIT_DEFINE_ANCESTOR_MEMORY_LOOKUP(memory_min);
 +
  static void cgroup_xattr_apply(Unit *u) {
          char ids[SD_ID128_STRING_MAX];
          int r;
@@@ -918,17 -877,8 +918,17 @@@ static void cgroup_apply_blkio_device_l
          (void) set_attribute_and_warn(u, "blkio", "blkio.throttle.write_bps_device", buf);
  }
  
 -static bool cgroup_context_has_unified_memory_config(CGroupContext *c) {
 -        return c->memory_min > 0 || c->memory_low > 0 || c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX || c->memory_swap_max != CGROUP_LIMIT_MAX;
 +static bool unit_has_unified_memory_config(Unit *u) {
 +        CGroupContext *c;
 +
 +        assert(u);
 +
 +        c = unit_get_cgroup_context(u);
 +        assert(c);
 +
 +        return c->memory_min > 0 || unit_get_ancestor_memory_low(u) > 0 ||
 +               c->memory_high != CGROUP_LIMIT_MAX || c->memory_max != CGROUP_LIMIT_MAX ||
 +               c->memory_swap_max != CGROUP_LIMIT_MAX;
  }
  
  static void cgroup_apply_unified_memory_limit(Unit *u, const char *file, uint64_t v) {
@@@ -1177,7 -1127,7 +1177,7 @@@ static void cgroup_context_apply
                  if (cg_all_unified() > 0) {
                          uint64_t max, swap_max = CGROUP_LIMIT_MAX;
  
 -                        if (cgroup_context_has_unified_memory_config(c)) {
 +                        if (unit_has_unified_memory_config(u)) {
                                  max = c->memory_max;
                                  swap_max = c->memory_swap_max;
                          } else {
                          }
  
                          cgroup_apply_unified_memory_limit(u, "memory.min", c->memory_min);
 -                        cgroup_apply_unified_memory_limit(u, "memory.low", c->memory_low);
 +                        cgroup_apply_unified_memory_limit(u, "memory.low", unit_get_ancestor_memory_low(u));
                          cgroup_apply_unified_memory_limit(u, "memory.high", c->memory_high);
                          cgroup_apply_unified_memory_limit(u, "memory.max", max);
                          cgroup_apply_unified_memory_limit(u, "memory.swap.max", swap_max);
                          char buf[DECIMAL_STR_MAX(uint64_t) + 1];
                          uint64_t val;
  
 -                        if (cgroup_context_has_unified_memory_config(c)) {
 +                        if (unit_has_unified_memory_config(u)) {
                                  val = c->memory_max;
                                  log_cgroup_compat(u, "Applying MemoryMax=%" PRIi64 " as MemoryLimit=", val);
                          } else
@@@ -1373,13 -1323,8 +1373,13 @@@ static bool unit_get_needs_bpf_firewall
          return false;
  }
  
 -static CGroupMask cgroup_context_get_mask(CGroupContext *c) {
 +static CGroupMask unit_get_cgroup_mask(Unit *u) {
          CGroupMask mask = 0;
 +        CGroupContext *c;
 +
 +        assert(u);
 +
 +        c = unit_get_cgroup_context(u);
  
          /* Figure out which controllers we need, based on the cgroup context object */
  
  
          if (c->memory_accounting ||
              c->memory_limit != CGROUP_LIMIT_MAX ||
 -            cgroup_context_has_unified_memory_config(c))
 +            unit_has_unified_memory_config(u))
                  mask |= CGROUP_MASK_MEMORY;
  
          if (c->device_allow ||
@@@ -1435,7 -1380,7 +1435,7 @@@ CGroupMask unit_get_own_mask(Unit *u) 
          if (!c)
                  return 0;
  
 -        return (cgroup_context_get_mask(c) | unit_get_bpf_mask(u) | unit_get_delegate_mask(u)) & ~unit_get_ancestor_disable_mask(u);
 +        return (unit_get_cgroup_mask(u) | unit_get_bpf_mask(u) | unit_get_delegate_mask(u)) & ~unit_get_ancestor_disable_mask(u);
  }
  
  CGroupMask unit_get_delegate_mask(Unit *u) {
@@@ -3233,21 -3178,140 +3233,140 @@@ int unit_get_ip_accounting
          return r;
  }
  
+ static int unit_get_io_accounting_raw(Unit *u, uint64_t ret[static _CGROUP_IO_ACCOUNTING_METRIC_MAX]) {
+         static const char *const field_names[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = {
+                 [CGROUP_IO_READ_BYTES]       = "rbytes=",
+                 [CGROUP_IO_WRITE_BYTES]      = "wbytes=",
+                 [CGROUP_IO_READ_OPERATIONS]  = "rios=",
+                 [CGROUP_IO_WRITE_OPERATIONS] = "wios=",
+         };
+         uint64_t acc[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = {};
+         _cleanup_free_ char *path = NULL;
+         _cleanup_fclose_ FILE *f = NULL;
+         int r;
+         assert(u);
+         if (!u->cgroup_path)
+                 return -ENODATA;
+         if (unit_has_host_root_cgroup(u))
+                 return -ENODATA; /* TODO: return useful data for the top-level cgroup */
+         r = cg_all_unified();
+         if (r < 0)
+                 return r;
+         if (r == 0) /* TODO: support cgroupv1 */
+                 return -ENODATA;
+         if (!FLAGS_SET(u->cgroup_realized_mask, CGROUP_MASK_IO))
+                 return -ENODATA;
+         r = cg_get_path("io", u->cgroup_path, "io.stat", &path);
+         if (r < 0)
+                 return r;
+         f = fopen(path, "re");
+         if (!f)
+                 return -errno;
+         for (;;) {
+                 _cleanup_free_ char *line = NULL;
+                 const char *p;
+                 r = read_line(f, LONG_LINE_MAX, &line);
+                 if (r < 0)
+                         return r;
+                 if (r == 0)
+                         break;
+                 p = line;
+                 p += strcspn(p, WHITESPACE); /* Skip over device major/minor */
+                 p += strspn(p, WHITESPACE);  /* Skip over following whitespace */
+                 for (;;) {
+                         _cleanup_free_ char *word = NULL;
+                         r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE);
+                         if (r < 0)
+                                 return r;
+                         if (r == 0)
+                                 break;
+                         for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++) {
+                                 const char *x;
+                                 x = startswith(word, field_names[i]);
+                                 if (x) {
+                                         uint64_t w;
+                                         r = safe_atou64(x, &w);
+                                         if (r < 0)
+                                                 return r;
+                                         /* Sum up the stats of all devices */
+                                         acc[i] += w;
+                                         break;
+                                 }
+                         }
+                 }
+         }
+         memcpy(ret, acc, sizeof(acc));
+         return 0;
+ }
+ int unit_get_io_accounting(
+                 Unit *u,
+                 CGroupIOAccountingMetric metric,
+                 bool allow_cache,
+                 uint64_t *ret) {
+         uint64_t raw[_CGROUP_IO_ACCOUNTING_METRIC_MAX];
+         int r;
+         /* Retrieve an IO account parameter. This will subtract the counter when the unit was started. */
+         if (!UNIT_CGROUP_BOOL(u, io_accounting))
+                 return -ENODATA;
+         if (allow_cache && u->io_accounting_last[metric] != UINT64_MAX)
+                 goto done;
+         r = unit_get_io_accounting_raw(u, raw);
+         if (r == -ENODATA && u->io_accounting_last[metric] != UINT64_MAX)
+                 goto done;
+         if (r < 0)
+                 return r;
+         for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++) {
+                 /* Saturated subtraction */
+                 if (raw[i] > u->io_accounting_base[i])
+                         u->io_accounting_last[i] = raw[i] - u->io_accounting_base[i];
+                 else
+                         u->io_accounting_last[i] = 0;
+         }
+ done:
+         if (ret)
+                 *ret = u->io_accounting_last[metric];
+         return 0;
+ }
  int unit_reset_cpu_accounting(Unit *u) {
-         nsec_t ns;
          int r;
  
          assert(u);
  
          u->cpu_usage_last = NSEC_INFINITY;
  
-         r = unit_get_cpu_usage_raw(u, &ns);
+         r = unit_get_cpu_usage_raw(u, &u->cpu_usage_base);
          if (r < 0) {
                  u->cpu_usage_base = 0;
                  return r;
          }
  
-         u->cpu_usage_base = ns;
          return 0;
  }
  
@@@ -3267,6 -3331,35 +3386,35 @@@ int unit_reset_ip_accounting(Unit *u) 
          return r < 0 ? r : q;
  }
  
+ int unit_reset_io_accounting(Unit *u) {
+         int r;
+         assert(u);
+         for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++)
+                 u->io_accounting_last[i] = UINT64_MAX;
+         r = unit_get_io_accounting_raw(u, u->io_accounting_base);
+         if (r < 0) {
+                 zero(u->io_accounting_base);
+                 return r;
+         }
+         return 0;
+ }
+ int unit_reset_accounting(Unit *u) {
+         int r, q, v;
+         assert(u);
+         r = unit_reset_cpu_accounting(u);
+         q = unit_reset_io_accounting(u);
+         v = unit_reset_ip_accounting(u);
+         return r < 0 ? r : q < 0 ? q : v;
+ }
  void unit_invalidate_cgroup(Unit *u, CGroupMask m) {
          assert(u);
  
diff --combined src/core/cgroup.h
index 0cac8ce76b9f4131e4583080d204aee30f716f50,ad04be30e91a6d4ebc6a6f847c8696d730412d50..8fb5481fc0d3e738415e7bf27026210b84b9694e
@@@ -98,19 -98,12 +98,19 @@@ struct CGroupContext 
          LIST_HEAD(CGroupIODeviceLimit, io_device_limits);
          LIST_HEAD(CGroupIODeviceLatency, io_device_latencies);
  
 +        uint64_t default_memory_min;
 +        uint64_t default_memory_low;
          uint64_t memory_min;
          uint64_t memory_low;
          uint64_t memory_high;
          uint64_t memory_max;
          uint64_t memory_swap_max;
  
 +        bool default_memory_min_set;
 +        bool default_memory_low_set;
 +        bool memory_min_set;
 +        bool memory_low_set;
 +
          LIST_HEAD(IPAddressAccessItem, ip_address_allow);
          LIST_HEAD(IPAddressAccessItem, ip_address_deny);
  
@@@ -142,6 -135,16 +142,16 @@@ typedef enum CGroupIPAccountingMetric 
          _CGROUP_IP_ACCOUNTING_METRIC_INVALID = -1,
  } CGroupIPAccountingMetric;
  
+ /* Used when querying IO accounting data */
+ typedef enum CGroupIOAccountingMetric {
+         CGROUP_IO_READ_BYTES,
+         CGROUP_IO_WRITE_BYTES,
+         CGROUP_IO_READ_OPERATIONS,
+         CGROUP_IO_WRITE_OPERATIONS,
+         _CGROUP_IO_ACCOUNTING_METRIC_MAX,
+         _CGROUP_IO_ACCOUNTING_METRIC_INVALID = -1,
+ } CGroupIOAccountingMetric;
  typedef struct Unit Unit;
  typedef struct Manager Manager;
  
@@@ -199,9 -202,6 +209,9 @@@ Unit *manager_get_unit_by_cgroup(Manage
  Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid);
  Unit* manager_get_unit_by_pid(Manager *m, pid_t pid);
  
 +uint64_t unit_get_ancestor_memory_min(Unit *u);
 +uint64_t unit_get_ancestor_memory_low(Unit *u);
 +
  int unit_search_main_pid(Unit *u, pid_t *ret);
  int unit_watch_all_pids(Unit *u);
  
@@@ -210,10 -210,13 +220,13 @@@ int unit_synthesize_cgroup_empty_event(
  int unit_get_memory_current(Unit *u, uint64_t *ret);
  int unit_get_tasks_current(Unit *u, uint64_t *ret);
  int unit_get_cpu_usage(Unit *u, nsec_t *ret);
+ int unit_get_io_accounting(Unit *u, CGroupIOAccountingMetric metric, bool allow_cache, uint64_t *ret);
  int unit_get_ip_accounting(Unit *u, CGroupIPAccountingMetric metric, uint64_t *ret);
  
  int unit_reset_cpu_accounting(Unit *u);
  int unit_reset_ip_accounting(Unit *u);
+ int unit_reset_io_accounting(Unit *u);
+ int unit_reset_accounting(Unit *u);
  
  #define UNIT_CGROUP_BOOL(u, name)                       \
          ({                                              \
diff --combined src/run/run.c
index 8a981775758af3fc2663579e91477c001dd3d80a,18b46f2a40a4c762ad055bb3b5df8d6a888413c8..6a0b0d78b981a513ab41aded7a2454e189e880d9
@@@ -380,31 -380,13 +380,31 @@@ static int parse_argv(int argc, char *a
                          arg_with_timer = true;
                          break;
  
 -                case ARG_ON_CALENDAR:
 +                case ARG_ON_CALENDAR: {
 +                        _cleanup_(calendar_spec_freep) CalendarSpec *cs = NULL;
 +                        usec_t next, curr;
 +
 +                        /* Let's make sure the given calendar event is not in the past */
 +                        curr = now(CLOCK_REALTIME);
 +                        r = calendar_spec_from_string(optarg, &cs);
 +                        if (r < 0)
 +                                return log_error_errno(r, "Failed to parse calendar event specification");
 +                        r = calendar_spec_next_usec(cs, curr, &next);
 +                        if (r < 0) {
 +                                /* The calendar event is in the past - in such case
 +                                 * don't add an OnCalendar property and execute
 +                                 * the command immediately instead */
 +                                log_warning("Specified calendar event is in the past, executing immediately");
 +                                break;
 +                        }
 +
                          r = add_timer_property("OnCalendar", optarg);
                          if (r < 0)
                                  return r;
  
                          arg_with_timer = true;
                          break;
 +                }
  
                  case ARG_ON_TIMEZONE_CHANGE:
                          r = add_timer_property("OnTimezoneChange", "yes");
@@@ -948,6 -930,8 +948,8 @@@ typedef struct RunContext 
          uint64_t cpu_usage_nsec;
          uint64_t ip_ingress_bytes;
          uint64_t ip_egress_bytes;
+         uint64_t io_read_bytes;
+         uint64_t io_write_bytes;
          uint32_t exit_code;
          uint32_t exit_status;
  } RunContext;
@@@ -993,6 -977,8 +995,8 @@@ static int run_context_update(RunContex
                  { "CPUUsageNSec",                     "t", NULL, offsetof(RunContext, cpu_usage_nsec)      },
                  { "IPIngressBytes",                   "t", NULL, offsetof(RunContext, ip_ingress_bytes)    },
                  { "IPEgressBytes",                    "t", NULL, offsetof(RunContext, ip_egress_bytes)     },
+                 { "IOReadBytes",                      "t", NULL, offsetof(RunContext, io_read_bytes)       },
+                 { "IOWriteBytes",                     "t", NULL, offsetof(RunContext, io_write_bytes)      },
                  {}
          };
  
@@@ -1181,6 -1167,8 +1185,8 @@@ static int start_transient_service
                          .cpu_usage_nsec = NSEC_INFINITY,
                          .ip_ingress_bytes = UINT64_MAX,
                          .ip_egress_bytes = UINT64_MAX,
+                         .io_read_bytes = UINT64_MAX,
+                         .io_write_bytes = UINT64_MAX,
                          .inactive_exit_usec = USEC_INFINITY,
                          .inactive_enter_usec = USEC_INFINITY,
                  };
                                  char bytes[FORMAT_BYTES_MAX];
                                  log_info("IP traffic sent: %s", format_bytes(bytes, sizeof(bytes), c.ip_egress_bytes));
                          }
+                         if (c.io_read_bytes != UINT64_MAX) {
+                                 char bytes[FORMAT_BYTES_MAX];
+                                 log_info("IO bytes read: %s", format_bytes(bytes, sizeof(bytes), c.io_read_bytes));
+                         }
+                         if (c.io_write_bytes != UINT64_MAX) {
+                                 char bytes[FORMAT_BYTES_MAX];
+                                 log_info("IO bytes written: %s", format_bytes(bytes, sizeof(bytes), c.io_write_bytes));
+                         }
                  }
  
                  /* Try to propagate the service's return value */
index 25621745b18427a052e7f065d56ad0af1a931639,cfcc4f6874a32407fd3c4a21794388cd35606b31..dd485338995bc3f81f7ee90de978c6a175270338
@@@ -4125,13 -4125,11 +4125,14 @@@ typedef struct UnitStatusInfo 
          uint64_t cpu_usage_nsec;
          uint64_t tasks_current;
          uint64_t tasks_max;
          uint64_t ip_ingress_bytes;
          uint64_t ip_egress_bytes;
+         uint64_t io_read_bytes;
+         uint64_t io_write_bytes;
  
 +        uint64_t default_memory_min;
 +        uint64_t default_memory_low;
 +
          LIST_HEAD(ExecStatusInfo, exec);
  } UnitStatusInfo;
  
@@@ -4485,6 -4483,14 +4486,14 @@@ static void print_status_info
                          format_bytes(buf_out, sizeof(buf_out), i->ip_egress_bytes));
          }
  
+         if (i->io_read_bytes != UINT64_MAX && i->io_write_bytes != UINT64_MAX) {
+                 char buf_in[FORMAT_BYTES_MAX], buf_out[FORMAT_BYTES_MAX];
+                 printf("       IO: %s read, %s written\n",
+                         format_bytes(buf_in, sizeof(buf_in), i->io_read_bytes),
+                         format_bytes(buf_out, sizeof(buf_out), i->io_write_bytes));
+         }
          if (i->tasks_current != (uint64_t) -1) {
                  printf("    Tasks: %" PRIu64, i->tasks_current);
  
@@@ -5482,8 -5488,6 +5491,8 @@@ static int show_one
                  { "Where",                          "s",              NULL,           offsetof(UnitStatusInfo, where)                             },
                  { "What",                           "s",              NULL,           offsetof(UnitStatusInfo, what)                              },
                  { "MemoryCurrent",                  "t",              NULL,           offsetof(UnitStatusInfo, memory_current)                    },
 +                { "DefaultMemoryMin",               "t",              NULL,           offsetof(UnitStatusInfo, default_memory_min)                },
 +                { "DefaultMemoryLow",               "t",              NULL,           offsetof(UnitStatusInfo, default_memory_low)                },
                  { "MemoryMin",                      "t",              NULL,           offsetof(UnitStatusInfo, memory_min)                        },
                  { "MemoryLow",                      "t",              NULL,           offsetof(UnitStatusInfo, memory_low)                        },
                  { "MemoryHigh",                     "t",              NULL,           offsetof(UnitStatusInfo, memory_high)                       },
                  { "TasksMax",                       "t",              NULL,           offsetof(UnitStatusInfo, tasks_max)                         },
                  { "IPIngressBytes",                 "t",              NULL,           offsetof(UnitStatusInfo, ip_ingress_bytes)                  },
                  { "IPEgressBytes",                  "t",              NULL,           offsetof(UnitStatusInfo, ip_egress_bytes)                   },
+                 { "IOReadBytes",                    "t",              NULL,           offsetof(UnitStatusInfo, io_read_bytes)                     },
+                 { "IOWriteBytes",                   "t",              NULL,           offsetof(UnitStatusInfo, io_write_bytes)                    },
                  { "ExecStartPre",                   "a(sasbttttuii)", map_exec,       0                                                           },
                  { "ExecStart",                      "a(sasbttttuii)", map_exec,       0                                                           },
                  { "ExecStartPost",                  "a(sasbttttuii)", map_exec,       0                                                           },
                  .tasks_max = (uint64_t) -1,
                  .ip_ingress_bytes = (uint64_t) -1,
                  .ip_egress_bytes = (uint64_t) -1,
+                 .io_read_bytes = UINT64_MAX,
+                 .io_write_bytes = UINT64_MAX,
          };
          char **pp;
          int r;