Extend label file backend to support label-by-symlink for ueventd.

When ueventd creates a device node, it may also create one or more
symlinks to the device node.  These symlinks may be the only stable
name for the device, e.g. if the partition is dynamically assigned.
Extend the label file backend to support looking up the "best match"
for a device node based on its real path (key) and any links to it
(aliases).  The order of precedence for best match is:
1) An exact match for the real path (key), or
2) An exact match for any of the links (aliases), or
3) The longest fixed prefix match.

Change-Id: Id6c2597eee2b6723a5089dcf7c450f8d0a4128f4
Signed-off-by: Stephen Smalley <sds@tycho.nsa.gov>
diff --git a/include/selinux/label.h b/include/selinux/label.h
index facfa57..a7e91b7 100644
--- a/include/selinux/label.h
+++ b/include/selinux/label.h
@@ -101,6 +101,9 @@
 
 bool selabel_partial_match(struct selabel_handle *handle, const char *key);
 
+int selabel_lookup_best_match(struct selabel_handle *rec, char **con,
+			      const char *key, const char **aliases, int type);
+
 /**
  * selabel_stats - log labeling operation statistics.
  * @handle: specifies backend instance to query
diff --git a/src/label.c b/src/label.c
index 800a8a2..dd51aa3 100644
--- a/src/label.c
+++ b/src/label.c
@@ -131,6 +131,24 @@
 	return rec->func_partial_match(rec, key);
 }
 
+int selabel_lookup_best_match(struct selabel_handle *rec, char **con,
+			      const char *key, const char **aliases, int type)
+{
+	struct selabel_lookup_rec *lr;
+
+	if (!rec->func_lookup_best_match) {
+		errno = ENOTSUP;
+		return -1;
+	}
+
+	lr = rec->func_lookup_best_match(rec, key, aliases, type);
+	if (!lr)
+		return -1;
+
+	*con = strdup(lr->ctx_raw);
+	return *con ? 0 : -1;
+}
+
 void selabel_close(struct selabel_handle *rec)
 {
 	rec->func_close(rec);
diff --git a/src/label_file.c b/src/label_file.c
index 9923e38..279a83d 100644
--- a/src/label_file.c
+++ b/src/label_file.c
@@ -569,16 +569,17 @@
 	free(data);
 }
 
-static struct selabel_lookup_rec *lookup_common(struct selabel_handle *rec,
-						const char *key, int type,
-						bool partial)
+static spec_t *lookup_common(struct selabel_handle *rec,
+			     const char *key,
+			     int type,
+			     bool partial)
 {
 	struct saved_data *data = (struct saved_data *)rec->data;
 	spec_t *spec_arr = data->spec_arr;
 	int i, rc, file_stem;
 	mode_t mode = (mode_t)type;
 	const char *buf;
-	struct selabel_lookup_rec *ret = NULL;
+	spec_t *ret = NULL;
 	char *clean_key = NULL;
 	const char *prev_slash, *next_slash;
 	unsigned int sofar = 0;
@@ -669,7 +670,7 @@
 		goto finish;
 	}
 
-	ret = &spec_arr[i].lr;
+	ret = &spec_arr[i];
 
 finish:
 	free(clean_key);
@@ -679,7 +680,11 @@
 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
 					 const char *key, int type)
 {
-	return lookup_common(rec, key, type, false);
+	spec_t *spec;
+	spec = lookup_common(rec, key, type, false);
+	if (spec)
+		return &spec->lr;
+	return NULL;
 }
 
 static bool partial_match(struct selabel_handle *rec, const char *key)
@@ -687,6 +692,61 @@
 	return lookup_common(rec, key, 0, true) ? true : false;
 }
 
+static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec,
+						    const char *key,
+						    const char **aliases,
+						    int type)
+{
+	size_t n, i;
+	int best = -1;
+	spec_t **specs;
+	size_t prefix_len = 0;
+	struct selabel_lookup_rec *lr = NULL;
+
+	if (!aliases || !aliases[0])
+		return lookup(rec, key, type);
+
+	for (n = 0; aliases[n]; n++)
+		;
+
+	specs = calloc(n+1, sizeof(spec_t *));
+	if (!specs)
+		return NULL;
+	specs[0] = lookup_common(rec, key, type, false);
+	if (specs[0]) {
+		if (!specs[0]->hasMetaChars) {
+			/* exact match on key */
+			lr = &specs[0]->lr;
+			goto out;
+		}
+		best = 0;
+		prefix_len = specs[0]->prefix_len;
+	}
+	for (i = 1; i <= n; i++) {
+		specs[i] = lookup_common(rec, aliases[i-1], type, false);
+		if (specs[i]) {
+			if (!specs[i]->hasMetaChars) {
+				/* exact match on alias */
+				lr = &specs[i]->lr;
+				goto out;
+			}
+			if (specs[i]->prefix_len > prefix_len) {
+				best = i;
+				prefix_len = specs[i]->prefix_len;
+			}
+		}
+	}
+
+	if (best >= 0) {
+		/* longest fixed prefix match on key or alias */
+		lr = &specs[best]->lr;
+	}
+
+out:
+	free(specs);
+	return lr;
+}
+
 static void stats(struct selabel_handle *rec)
 {
 	struct saved_data *data = (struct saved_data *)rec->data;
@@ -726,6 +786,7 @@
 	rec->func_stats = &stats;
 	rec->func_lookup = &lookup;
 	rec->func_partial_match = &partial_match;
+	rec->func_lookup_best_match = &lookup_best_match;
 
 	return init(rec, opts, nopts);
 }
diff --git a/src/label_internal.h b/src/label_internal.h
index e44e3cc..00a9bbf 100644
--- a/src/label_internal.h
+++ b/src/label_internal.h
@@ -55,6 +55,10 @@
 	void (*func_close) (struct selabel_handle *h);
 	void (*func_stats) (struct selabel_handle *h);
 	bool (*func_partial_match) (struct selabel_handle *h, const char *key);
+	struct selabel_lookup_rec *(*func_lookup_best_match) (struct selabel_handle *h,
+							 const char *key,
+							 const char **aliases,
+							 int type);
 
 	/* supports backend-specific state information */
 	void *data;