[PATCH ethtool] ethtool.c: fix memory leaks

Subsystems: the rest

STALE3734d

5 messages, 3 authors, 2016-06-27 · open the first message on its own page

[PATCH ethtool] ethtool.c: fix memory leaks

From: Ivan Vecera <ivecera@redhat.com>
Date: 2016-03-18 12:24:17

Memory allocated at several places is not appropriately freed.

Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 ethtool.c | 60 +++++++++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 45 insertions(+), 15 deletions(-)
diff --git a/ethtool.c b/ethtool.c
index 0cd0d4f..ca0bf28 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -2065,10 +2065,14 @@ static int do_gfeatures(struct cmd_context *ctx)
 	features = get_features(ctx, defs);
 	if (!features) {
 		fprintf(stdout, "no feature info available\n");
+		free(defs);
 		return 1;
 	}
 
 	dump_features(defs, features, NULL);
+
+	free(features);
+	free(defs);
 	return 0;
 }
 
@@ -2078,11 +2082,11 @@ static int do_sfeatures(struct cmd_context *ctx)
 	int any_changed = 0, any_mismatch = 0;
 	u32 off_flags_wanted = 0;
 	u32 off_flags_mask = 0;
-	struct ethtool_sfeatures *efeatures;
+	struct ethtool_sfeatures *efeatures = NULL;
 	struct cmdline_info *cmdline_features;
-	struct feature_state *old_state, *new_state;
+	struct feature_state *old_state = NULL, *new_state = NULL;
 	struct ethtool_value eval;
-	int err;
+	int err, retval = 1;
 	int i, j;
 
 	defs = get_feature_defs(ctx);
@@ -2096,7 +2100,7 @@ static int do_sfeatures(struct cmd_context *ctx)
 				   sizeof(efeatures->features[0]));
 		if (!efeatures) {
 			perror("Cannot parse arguments");
-			return 1;
+			goto finish;
 		}
 		efeatures->cmd = ETHTOOL_SFEATURES;
 		efeatures->size = FEATURE_BITS_TO_BLOCKS(defs->n_features);
@@ -2114,7 +2118,7 @@ static int do_sfeatures(struct cmd_context *ctx)
 				  sizeof(cmdline_features[0]));
 	if (!cmdline_features) {
 		perror("Cannot parse arguments");
-		return 1;
+		goto finish;
 	}
 	for (i = 0; i < ARRAY_SIZE(off_flag_def); i++)
 		flag_to_cmdline_info(off_flag_def[i].short_name,
@@ -2133,12 +2137,13 @@ static int do_sfeatures(struct cmd_context *ctx)
 
 	if (!any_changed) {
 		fprintf(stdout, "no features changed\n");
-		return 0;
+		retval = 0;
+		goto finish;
 	}
 
 	old_state = get_features(ctx, defs);
 	if (!old_state)
-		return 1;
+		goto finish;
 
 	if (efeatures) {
 		/* For each offload that the user specified, update any
@@ -2182,7 +2187,7 @@ static int do_sfeatures(struct cmd_context *ctx)
 		err = send_ioctl(ctx, efeatures);
 		if (err < 0) {
 			perror("Cannot set device feature settings");
-			return 1;
+			goto finish;
 		}
 	} else {
 		for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
@@ -2197,7 +2202,7 @@ static int do_sfeatures(struct cmd_context *ctx)
 					fprintf(stderr,
 						"Cannot set device %s settings: %m\n",
 						off_flag_def[i].long_name);
-					return 1;
+					goto finish;
 				}
 			}
 		}
@@ -2211,7 +2216,8 @@ static int do_sfeatures(struct cmd_context *ctx)
 			err = send_ioctl(ctx, &eval);
 			if (err) {
 				perror("Cannot set device flag settings");
-				return 92;
+				retval = 92;
+				goto finish;
 			}
 		}
 	}
@@ -2219,7 +2225,7 @@ static int do_sfeatures(struct cmd_context *ctx)
 	/* Compare new state with requested state */
 	new_state = get_features(ctx, defs);
 	if (!new_state)
-		return 1;
+		goto finish;
 	any_changed = new_state->off_flags != old_state->off_flags;
 	any_mismatch = (new_state->off_flags !=
 			((old_state->off_flags & ~off_flags_mask) |
@@ -2238,13 +2244,19 @@ static int do_sfeatures(struct cmd_context *ctx)
 		if (!any_changed) {
 			fprintf(stderr,
 				"Could not change any device features\n");
-			return 1;
+			goto finish;
 		}
 		printf("Actual changes:\n");
 		dump_features(defs, new_state, old_state);
 	}
 
-	return 0;
+	retval = 0;
+finish:
+	free(new_state);
+	free(old_state);
+	free(efeatures);
+	free(defs);
+	return retval;
 }
 
 static int do_gset(struct cmd_context *ctx)
@@ -2705,8 +2717,18 @@ static int do_gregs(struct cmd_context *ctx)
 			return 75;
 		}
 
-		regs = realloc(regs, sizeof(*regs) + st.st_size);
-		regs->len = st.st_size;
+		if (regs->len != st.st_size) {
+			struct ethtool_regs *new_regs;
+			new_regs = realloc(regs, sizeof(*regs) + st.st_size);
+			if (!new_regs) {
+				perror("Cannot allocate memory for register "
+				       "dump");
+				free(regs);
+				return 73;
+			}
+			regs = new_regs;
+			regs->len = st.st_size;
+		}
 		nread = fread(regs->data, regs->len, 1, f);
 		fclose(f);
 		if (nread != 1) {
@@ -3775,6 +3797,7 @@ static int do_gprivflags(struct cmd_context *ctx)
 	}
 	if (strings->len == 0) {
 		fprintf(stderr, "No private flags defined\n");
+		free(strings);
 		return 1;
 	}
 	if (strings->len > 32) {
@@ -3786,6 +3809,7 @@ static int do_gprivflags(struct cmd_context *ctx)
 	flags.cmd = ETHTOOL_GPFLAGS;
 	if (send_ioctl(ctx, &flags)) {
 		perror("Cannot get private flags");
+		free(strings);
 		return 1;
 	}
 
@@ -3804,6 +3828,7 @@ static int do_gprivflags(struct cmd_context *ctx)
 		       (const char *)strings->data + i * ETH_GSTRING_LEN,
 		       (flags.data & (1U << i)) ? "on" : "off");
 
+	free(strings);
 	return 0;
 }
 
@@ -3825,6 +3850,7 @@ static int do_sprivflags(struct cmd_context *ctx)
 	}
 	if (strings->len == 0) {
 		fprintf(stderr, "No private flags defined\n");
+		free(strings);
 		return 1;
 	}
 	if (strings->len > 32) {
@@ -3836,6 +3862,7 @@ static int do_sprivflags(struct cmd_context *ctx)
 	cmdline = calloc(strings->len, sizeof(*cmdline));
 	if (!cmdline) {
 		perror("Cannot parse arguments");
+		free(strings);
 		return 1;
 	}
 	for (i = 0; i < strings->len; i++) {
@@ -3852,6 +3879,7 @@ static int do_sprivflags(struct cmd_context *ctx)
 	flags.cmd = ETHTOOL_GPFLAGS;
 	if (send_ioctl(ctx, &flags)) {
 		perror("Cannot get private flags");
+		free(strings);
 		return 1;
 	}
 
@@ -3859,9 +3887,11 @@ static int do_sprivflags(struct cmd_context *ctx)
 	flags.data = (flags.data & ~seen_flags) | wanted_flags;
 	if (send_ioctl(ctx, &flags)) {
 		perror("Cannot set private flags");
+		free(strings);
 		return 1;
 	}
 
+	free(strings);
 	return 0;
 }
 
-- 
2.7.3

Re: [PATCH ethtool] ethtool.c: fix memory leaks

From: Ivan Vecera <ivecera@redhat.com>
Date: 2016-04-08 08:06:41

On 18.3.2016 13:24, Ivan Vecera wrote:
Memory allocated at several places is not appropriately freed.

Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Ben, ping.

I.
quoted hunk
---
  ethtool.c | 60 +++++++++++++++++++++++++++++++++++++++++++++---------------
  1 file changed, 45 insertions(+), 15 deletions(-)
diff --git a/ethtool.c b/ethtool.c
index 0cd0d4f..ca0bf28 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -2065,10 +2065,14 @@ static int do_gfeatures(struct cmd_context *ctx)
  	features = get_features(ctx, defs);
  	if (!features) {
  		fprintf(stdout, "no feature info available\n");
+		free(defs);
  		return 1;
  	}

  	dump_features(defs, features, NULL);
+
+	free(features);
+	free(defs);
  	return 0;
  }
@@ -2078,11 +2082,11 @@ static int do_sfeatures(struct cmd_context *ctx)
  	int any_changed = 0, any_mismatch = 0;
  	u32 off_flags_wanted = 0;
  	u32 off_flags_mask = 0;
-	struct ethtool_sfeatures *efeatures;
+	struct ethtool_sfeatures *efeatures = NULL;
  	struct cmdline_info *cmdline_features;
-	struct feature_state *old_state, *new_state;
+	struct feature_state *old_state = NULL, *new_state = NULL;
  	struct ethtool_value eval;
-	int err;
+	int err, retval = 1;
  	int i, j;

  	defs = get_feature_defs(ctx);
@@ -2096,7 +2100,7 @@ static int do_sfeatures(struct cmd_context *ctx)
  				   sizeof(efeatures->features[0]));
  		if (!efeatures) {
  			perror("Cannot parse arguments");
-			return 1;
+			goto finish;
  		}
  		efeatures->cmd = ETHTOOL_SFEATURES;
  		efeatures->size = FEATURE_BITS_TO_BLOCKS(defs->n_features);
@@ -2114,7 +2118,7 @@ static int do_sfeatures(struct cmd_context *ctx)
  				  sizeof(cmdline_features[0]));
  	if (!cmdline_features) {
  		perror("Cannot parse arguments");
-		return 1;
+		goto finish;
  	}
  	for (i = 0; i < ARRAY_SIZE(off_flag_def); i++)
  		flag_to_cmdline_info(off_flag_def[i].short_name,
@@ -2133,12 +2137,13 @@ static int do_sfeatures(struct cmd_context *ctx)

  	if (!any_changed) {
  		fprintf(stdout, "no features changed\n");
-		return 0;
+		retval = 0;
+		goto finish;
  	}

  	old_state = get_features(ctx, defs);
  	if (!old_state)
-		return 1;
+		goto finish;

  	if (efeatures) {
  		/* For each offload that the user specified, update any
@@ -2182,7 +2187,7 @@ static int do_sfeatures(struct cmd_context *ctx)
  		err = send_ioctl(ctx, efeatures);
  		if (err < 0) {
  			perror("Cannot set device feature settings");
-			return 1;
+			goto finish;
  		}
  	} else {
  		for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
@@ -2197,7 +2202,7 @@ static int do_sfeatures(struct cmd_context *ctx)
  					fprintf(stderr,
  						"Cannot set device %s settings: %m\n",
  						off_flag_def[i].long_name);
-					return 1;
+					goto finish;
  				}
  			}
  		}
@@ -2211,7 +2216,8 @@ static int do_sfeatures(struct cmd_context *ctx)
  			err = send_ioctl(ctx, &eval);
  			if (err) {
  				perror("Cannot set device flag settings");
-				return 92;
+				retval = 92;
+				goto finish;
  			}
  		}
  	}
@@ -2219,7 +2225,7 @@ static int do_sfeatures(struct cmd_context *ctx)
  	/* Compare new state with requested state */
  	new_state = get_features(ctx, defs);
  	if (!new_state)
-		return 1;
+		goto finish;
  	any_changed = new_state->off_flags != old_state->off_flags;
  	any_mismatch = (new_state->off_flags !=
  			((old_state->off_flags & ~off_flags_mask) |
@@ -2238,13 +2244,19 @@ static int do_sfeatures(struct cmd_context *ctx)
  		if (!any_changed) {
  			fprintf(stderr,
  				"Could not change any device features\n");
-			return 1;
+			goto finish;
  		}
  		printf("Actual changes:\n");
  		dump_features(defs, new_state, old_state);
  	}

-	return 0;
+	retval = 0;
+finish:
+	free(new_state);
+	free(old_state);
+	free(efeatures);
+	free(defs);
+	return retval;
  }

  static int do_gset(struct cmd_context *ctx)
@@ -2705,8 +2717,18 @@ static int do_gregs(struct cmd_context *ctx)
  			return 75;
  		}

-		regs = realloc(regs, sizeof(*regs) + st.st_size);
-		regs->len = st.st_size;
+		if (regs->len != st.st_size) {
+			struct ethtool_regs *new_regs;
+			new_regs = realloc(regs, sizeof(*regs) + st.st_size);
+			if (!new_regs) {
+				perror("Cannot allocate memory for register "
+				       "dump");
+				free(regs);
+				return 73;
+			}
+			regs = new_regs;
+			regs->len = st.st_size;
+		}
  		nread = fread(regs->data, regs->len, 1, f);
  		fclose(f);
  		if (nread != 1) {
@@ -3775,6 +3797,7 @@ static int do_gprivflags(struct cmd_context *ctx)
  	}
  	if (strings->len == 0) {
  		fprintf(stderr, "No private flags defined\n");
+		free(strings);
  		return 1;
  	}
  	if (strings->len > 32) {
@@ -3786,6 +3809,7 @@ static int do_gprivflags(struct cmd_context *ctx)
  	flags.cmd = ETHTOOL_GPFLAGS;
  	if (send_ioctl(ctx, &flags)) {
  		perror("Cannot get private flags");
+		free(strings);
  		return 1;
  	}
@@ -3804,6 +3828,7 @@ static int do_gprivflags(struct cmd_context *ctx)
  		       (const char *)strings->data + i * ETH_GSTRING_LEN,
  		       (flags.data & (1U << i)) ? "on" : "off");

+	free(strings);
  	return 0;
  }
@@ -3825,6 +3850,7 @@ static int do_sprivflags(struct cmd_context *ctx)
  	}
  	if (strings->len == 0) {
  		fprintf(stderr, "No private flags defined\n");
+		free(strings);
  		return 1;
  	}
  	if (strings->len > 32) {
@@ -3836,6 +3862,7 @@ static int do_sprivflags(struct cmd_context *ctx)
  	cmdline = calloc(strings->len, sizeof(*cmdline));
  	if (!cmdline) {
  		perror("Cannot parse arguments");
+		free(strings);
  		return 1;
  	}
  	for (i = 0; i < strings->len; i++) {
@@ -3852,6 +3879,7 @@ static int do_sprivflags(struct cmd_context *ctx)
  	flags.cmd = ETHTOOL_GPFLAGS;
  	if (send_ioctl(ctx, &flags)) {
  		perror("Cannot get private flags");
+		free(strings);
  		return 1;
  	}
@@ -3859,9 +3887,11 @@ static int do_sprivflags(struct cmd_context *ctx)
  	flags.data = (flags.data & ~seen_flags) | wanted_flags;
  	if (send_ioctl(ctx, &flags)) {
  		perror("Cannot set private flags");
+		free(strings);
  		return 1;
  	}

+	free(strings);
  	return 0;
  }

Re: [PATCH ethtool] ethtool.c: fix memory leaks

From: Ben Hutchings <hidden>
Date: 2016-06-26 08:59:15

On Fri, 2016-03-18 at 13:24 +0100, Ivan Vecera wrote:
Memory allocated at several places is not appropriately freed.
Given that ethtool is not a library or a long-running application - why
does that matter?

Ben.
quoted hunk
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 ethtool.c | 60 +++++++++++++++++++++++++++++++++++++++++++++------
---------
 1 file changed, 45 insertions(+), 15 deletions(-)
diff --git a/ethtool.c b/ethtool.c
index 0cd0d4f..ca0bf28 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -2065,10 +2065,14 @@ static int do_gfeatures(struct cmd_context
*ctx)
 	features = get_features(ctx, defs);
 	if (!features) {
 		fprintf(stdout, "no feature info available\n");
+		free(defs);
 		return 1;
 	}
 
 	dump_features(defs, features, NULL);
+
+	free(features);
+	free(defs);
 	return 0;
 }
 
@@ -2078,11 +2082,11 @@ static int do_sfeatures(struct cmd_context
*ctx)
 	int any_changed = 0, any_mismatch = 0;
 	u32 off_flags_wanted = 0;
 	u32 off_flags_mask = 0;
-	struct ethtool_sfeatures *efeatures;
+	struct ethtool_sfeatures *efeatures = NULL;
 	struct cmdline_info *cmdline_features;
-	struct feature_state *old_state, *new_state;
+	struct feature_state *old_state = NULL, *new_state = NULL;
 	struct ethtool_value eval;
-	int err;
+	int err, retval = 1;
 	int i, j;
 
 	defs = get_feature_defs(ctx);
@@ -2096,7 +2100,7 @@ static int do_sfeatures(struct cmd_context
*ctx)
 				   sizeof(efeatures->features[0]));
 		if (!efeatures) {
 			perror("Cannot parse arguments");
-			return 1;
+			goto finish;
 		}
 		efeatures->cmd = ETHTOOL_SFEATURES;
 		efeatures->size = FEATURE_BITS_TO_BLOCKS(defs-
quoted
n_features);
@@ -2114,7 +2118,7 @@ static int do_sfeatures(struct cmd_context
*ctx)
 				  sizeof(cmdline_features[0]));
 	if (!cmdline_features) {
 		perror("Cannot parse arguments");
-		return 1;
+		goto finish;
 	}
 	for (i = 0; i < ARRAY_SIZE(off_flag_def); i++)
 		flag_to_cmdline_info(off_flag_def[i].short_name,
@@ -2133,12 +2137,13 @@ static int do_sfeatures(struct cmd_context
*ctx)
 
 	if (!any_changed) {
 		fprintf(stdout, "no features changed\n");
-		return 0;
+		retval = 0;
+		goto finish;
 	}
 
 	old_state = get_features(ctx, defs);
 	if (!old_state)
-		return 1;
+		goto finish;
 
 	if (efeatures) {
 		/* For each offload that the user specified, update
any
@@ -2182,7 +2187,7 @@ static int do_sfeatures(struct cmd_context
*ctx)
 		err = send_ioctl(ctx, efeatures);
 		if (err < 0) {
 			perror("Cannot set device feature
settings");
-			return 1;
+			goto finish;
 		}
 	} else {
 		for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
@@ -2197,7 +2202,7 @@ static int do_sfeatures(struct cmd_context
*ctx)
 					fprintf(stderr,
 						"Cannot set device
%s settings: %m\n",
 						off_flag_def[i].long
_name);
-					return 1;
+					goto finish;
 				}
 			}
 		}
@@ -2211,7 +2216,8 @@ static int do_sfeatures(struct cmd_context
*ctx)
 			err = send_ioctl(ctx, &eval);
 			if (err) {
 				perror("Cannot set device flag
settings");
-				return 92;
+				retval = 92;
+				goto finish;
 			}
 		}
 	}
@@ -2219,7 +2225,7 @@ static int do_sfeatures(struct cmd_context
*ctx)
 	/* Compare new state with requested state */
 	new_state = get_features(ctx, defs);
 	if (!new_state)
-		return 1;
+		goto finish;
 	any_changed = new_state->off_flags != old_state->off_flags;
 	any_mismatch = (new_state->off_flags !=
 			((old_state->off_flags & ~off_flags_mask) |
@@ -2238,13 +2244,19 @@ static int do_sfeatures(struct cmd_context
*ctx)
 		if (!any_changed) {
 			fprintf(stderr,
 				"Could not change any device
features\n");
-			return 1;
+			goto finish;
 		}
 		printf("Actual changes:\n");
 		dump_features(defs, new_state, old_state);
 	}
 
-	return 0;
+	retval = 0;
+finish:
+	free(new_state);
+	free(old_state);
+	free(efeatures);
+	free(defs);
+	return retval;
 }
 
 static int do_gset(struct cmd_context *ctx)
@@ -2705,8 +2717,18 @@ static int do_gregs(struct cmd_context *ctx)
 			return 75;
 		}
 
-		regs = realloc(regs, sizeof(*regs) + st.st_size);
-		regs->len = st.st_size;
+		if (regs->len != st.st_size) {
+			struct ethtool_regs *new_regs;
+			new_regs = realloc(regs, sizeof(*regs) +
st.st_size);
+			if (!new_regs) {
+				perror("Cannot allocate memory for
register "
+				       "dump");
+				free(regs);
+				return 73;
+			}
+			regs = new_regs;
+			regs->len = st.st_size;
+		}
 		nread = fread(regs->data, regs->len, 1, f);
 		fclose(f);
 		if (nread != 1) {
@@ -3775,6 +3797,7 @@ static int do_gprivflags(struct cmd_context
*ctx)
 	}
 	if (strings->len == 0) {
 		fprintf(stderr, "No private flags defined\n");
+		free(strings);
 		return 1;
 	}
 	if (strings->len > 32) {
@@ -3786,6 +3809,7 @@ static int do_gprivflags(struct cmd_context
*ctx)
 	flags.cmd = ETHTOOL_GPFLAGS;
 	if (send_ioctl(ctx, &flags)) {
 		perror("Cannot get private flags");
+		free(strings);
 		return 1;
 	}
 
@@ -3804,6 +3828,7 @@ static int do_gprivflags(struct cmd_context
*ctx)
 		       (const char *)strings->data + i *
ETH_GSTRING_LEN,
 		       (flags.data & (1U << i)) ? "on" : "off");
 
+	free(strings);
 	return 0;
 }
 
@@ -3825,6 +3850,7 @@ static int do_sprivflags(struct cmd_context
*ctx)
 	}
 	if (strings->len == 0) {
 		fprintf(stderr, "No private flags defined\n");
+		free(strings);
 		return 1;
 	}
 	if (strings->len > 32) {
@@ -3836,6 +3862,7 @@ static int do_sprivflags(struct cmd_context
*ctx)
 	cmdline = calloc(strings->len, sizeof(*cmdline));
 	if (!cmdline) {
 		perror("Cannot parse arguments");
+		free(strings);
 		return 1;
 	}
 	for (i = 0; i < strings->len; i++) {
@@ -3852,6 +3879,7 @@ static int do_sprivflags(struct cmd_context
*ctx)
 	flags.cmd = ETHTOOL_GPFLAGS;
 	if (send_ioctl(ctx, &flags)) {
 		perror("Cannot get private flags");
+		free(strings);
 		return 1;
 	}
 
@@ -3859,9 +3887,11 @@ static int do_sprivflags(struct cmd_context
*ctx)
 	flags.data = (flags.data & ~seen_flags) | wanted_flags;
 	if (send_ioctl(ctx, &flags)) {
 		perror("Cannot set private flags");
+		free(strings);
 		return 1;
 	}
 
+	free(strings);
 	return 0;
 }
 
-- 

Ben Hutchings
compatible: Gracefully accepts erroneous data from any source

Re: [PATCH ethtool] ethtool.c: fix memory leaks

From: Ivan Vecera <ivecera@redhat.com>
Date: 2016-06-27 09:41:13

On 26.6.2016 10:59, Ben Hutchings wrote:
On Fri, 2016-03-18 at 13:24 +0100, Ivan Vecera wrote:
quoted
Memory allocated at several places is not appropriately freed.
Given that ethtool is not a library or a long-running application - why
does that matter?

Ben.
Because each decently written program should clean up. Sure the kernel 
will take care about it after exit but we cannot accept the fact that 
short-running applications can behave like a pig.

Ivan

RE: [PATCH ethtool] ethtool.c: fix memory leaks

From: David Laight <hidden>
Date: 2016-06-27 10:01:27

From: Ivan Vecera
Sent: 27 June 2016 10:41
On 26.6.2016 10:59, Ben Hutchings wrote:
quoted
On Fri, 2016-03-18 at 13:24 +0100, Ivan Vecera wrote:
quoted
Memory allocated at several places is not appropriately freed.
Given that ethtool is not a library or a long-running application - why
does that matter?

Ben.
Because each decently written program should clean up. Sure the kernel
will take care about it after exit but we cannot accept the fact that
short-running applications can behave like a pig.
It really doesn't matter except for memory that might be allocated
repeatedly.
The risks of getting it wrong probably outway any minor benefit.

free() is also unlikely to reduce the program size (to do so would require
a system call and TLB flush on top of everything else).

	David
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help