1/*
2 * The majority of this code is from Android's
3 * external/libselinux/src/android.c and upstream
4 * selinux/policycoreutils/setfiles/restore.c
5 *
6 * See selinux_restorecon(3) for details.
7 */
8
9#include <unistd.h>
10#include <string.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <stdbool.h>
14#include <ctype.h>
15#include <errno.h>
16#include <fcntl.h>
17#include <include/fts.h>
18#include <inttypes.h>
19#include <limits.h>
20#include <stdint.h>
21#include <sys/types.h>
22#include <sys/stat.h>
23#include <sys/xattr.h>
24#include <sys/vfs.h>
25#include <sys/statvfs.h>
26#include <sys/utsname.h>
27#include <linux/magic.h>
28#include <libgen.h>
29#include <syslog.h>
30#include <assert.h>
31
32#include <selinux/selinux.h>
33#include <selinux/context.h>
34#include <selinux/label.h>
35#include <selinux/restorecon.h>
36#include <selinux/skip_elx_constants.h>
37
38#include "ignore_path.h"
39#include "callbacks.h"
40#include "selinux_internal.h"
41#include "label_file.h"
42#include "sha1.h"
43
44#define STAR_COUNT 1024
45
46static struct selabel_handle *fc_sehandle = NULL;
47static bool selabel_no_digest;
48static char *rootpath = NULL;
49static size_t rootpathlen;
50
51/* Information on excluded fs and directories. */
52struct edir {
53	char *directory;
54	size_t size;
55	/* True if excluded by selinux_restorecon_set_exclude_list(3). */
56	bool caller_excluded;
57};
58#define CALLER_EXCLUDED true
59static bool ignore_mounts;
60static uint64_t exclude_non_seclabel_mounts(void);
61static int exclude_count = 0;
62static struct edir *exclude_lst = NULL;
63static uint64_t fc_count = 0;	/* Number of files processed so far */
64static uint64_t efile_count;	/* Estimated total number of files */
65static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER;
66
67/* Store information on directories with xattr's. */
68static struct dir_xattr *dir_xattr_list;
69static struct dir_xattr *dir_xattr_last;
70
71/* Number of errors ignored during the file tree walk. */
72static long unsigned skipped_errors;
73
74/* restorecon_flags for passing to restorecon_sb() */
75struct rest_flags {
76	bool nochange;
77	bool verbose;
78	bool progress;
79	bool mass_relabel;
80	bool set_specctx;
81	bool add_assoc;
82	bool recurse;
83	bool userealpath;
84	bool set_xdev;
85	bool abort_on_error;
86	bool syslog_changes;
87	bool log_matches;
88	bool ignore_noent;
89	bool warnonnomatch;
90	bool conflicterror;
91	bool count_errors;
92	bool skipelx;
93};
94
95static void restorecon_init(void)
96{
97	struct selabel_handle *sehandle = NULL;
98
99	if (!fc_sehandle) {
100		sehandle = selinux_restorecon_default_handle();
101		selinux_restorecon_set_sehandle(sehandle);
102	}
103
104	efile_count = 0;
105	if (!ignore_mounts)
106		efile_count = exclude_non_seclabel_mounts();
107}
108
109static pthread_once_t fc_once = PTHREAD_ONCE_INIT;
110
111/*
112 * Manage excluded directories:
113 *  remove_exclude() - This removes any conflicting entries as there could be
114 *                     a case where a non-seclabel fs is mounted on /foo and
115 *                     then a seclabel fs is mounted on top of it.
116 *                     However if an entry has been added via
117 *                     selinux_restorecon_set_exclude_list(3) do not remove.
118 *
119 *  add_exclude()    - Add a directory/fs to be excluded from labeling. If it
120 *                     has already been added, then ignore.
121 *
122 *  check_excluded() - Check if directory/fs is to be excluded when relabeling.
123 *
124 *  file_system_count() - Calculates the number of files to be processed.
125 *                        The count is only used if SELINUX_RESTORECON_PROGRESS
126 *                        is set and a mass relabel is requested.
127 *
128 *  exclude_non_seclabel_mounts() - Reads /proc/mounts to determine what
129 *                                  non-seclabel mounts to exclude from
130 *                                  relabeling. restorecon_init() will not
131 *                                  call this function if the
132 *                                  SELINUX_RESTORECON_IGNORE_MOUNTS
133 *                                  flag is set.
134 *                                  Setting SELINUX_RESTORECON_IGNORE_MOUNTS
135 *                                  is useful where there is a non-seclabel fs
136 *                                  mounted on /foo and then a seclabel fs is
137 *                                  mounted on a directory below this.
138 */
139static void remove_exclude(const char *directory)
140{
141	int i;
142
143	for (i = 0; i < exclude_count; i++) {
144		if (strcmp(directory, exclude_lst[i].directory) == 0 &&
145					!exclude_lst[i].caller_excluded) {
146			free(exclude_lst[i].directory);
147			if (i != exclude_count - 1)
148				exclude_lst[i] = exclude_lst[exclude_count - 1];
149			exclude_count--;
150			return;
151		}
152	}
153}
154
155static int add_exclude(const char *directory, bool who)
156{
157	struct edir *tmp_list, *current;
158	size_t len = 0;
159	int i;
160
161	/* Check if already present. */
162	for (i = 0; i < exclude_count; i++) {
163		if (strcmp(directory, exclude_lst[i].directory) == 0)
164			return 0;
165	}
166
167	if (directory == NULL || directory[0] != '/') {
168		selinux_log(SELINUX_ERROR,
169			    "Full path required for exclude: %s.\n",
170			    directory);
171		errno = EINVAL;
172		return -1;
173	}
174
175	if (exclude_count >= INT_MAX - 1) {
176		selinux_log(SELINUX_ERROR, "Too many directory excludes: %d.\n", exclude_count);
177		errno = EOVERFLOW;
178		return -1;
179	}
180
181	tmp_list = realloc(exclude_lst,
182			   sizeof(struct edir) * (exclude_count + 1));
183	if (!tmp_list)
184		goto oom;
185
186	exclude_lst = tmp_list;
187
188	len = strlen(directory);
189	while (len > 1 && directory[len - 1] == '/')
190		len--;
191
192	current = (exclude_lst + exclude_count);
193
194	current->directory = strndup(directory, len);
195	if (!current->directory)
196		goto oom;
197
198	current->size = len;
199	current->caller_excluded = who;
200	exclude_count++;
201	return 0;
202
203oom:
204	selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __func__);
205	return -1;
206}
207
208static int check_excluded(const char *file)
209{
210	int i;
211
212	for (i = 0; i < exclude_count; i++) {
213		if (strncmp(file, exclude_lst[i].directory,
214		    exclude_lst[i].size) == 0) {
215			if (file[exclude_lst[i].size] == 0 ||
216					 file[exclude_lst[i].size] == '/')
217				return 1;
218		}
219	}
220	return 0;
221}
222
223static uint64_t file_system_count(const char *name)
224{
225	struct statvfs statvfs_buf;
226	uint64_t nfile = 0;
227
228	memset(&statvfs_buf, 0, sizeof(statvfs_buf));
229	if (!statvfs(name, &statvfs_buf))
230		nfile = statvfs_buf.f_files - statvfs_buf.f_ffree;
231
232	return nfile;
233}
234
235/*
236 * This is called once when selinux_restorecon() is first called.
237 * Searches /proc/mounts for all file systems that do not support extended
238 * attributes and adds them to the exclude directory table.  File systems
239 * that support security labels have the seclabel option, return
240 * approximate total file count.
241 */
242static uint64_t exclude_non_seclabel_mounts(void)
243{
244	struct utsname uts;
245	FILE *fp;
246	size_t len;
247	int index = 0, found = 0;
248	uint64_t nfile = 0;
249	char *mount_info[4];
250	char *buf = NULL, *item;
251
252	/* Check to see if the kernel supports seclabel */
253	if (uname(&uts) == 0 && strverscmp(uts.release, "2.6.30") < 0)
254		return 0;
255	if (is_selinux_enabled() <= 0)
256		return 0;
257
258	fp = fopen("/proc/mounts", "re");
259	if (!fp)
260		return 0;
261
262	while (getline(&buf, &len, fp) != -1) {
263		found = 0;
264		index = 0;
265		item = strtok(buf, " ");
266		while (item != NULL) {
267			mount_info[index] = item;
268			index++;
269			if (index == 4)
270				break;
271			item = strtok(NULL, " ");
272		}
273		if (index < 4) {
274			selinux_log(SELINUX_ERROR,
275				    "/proc/mounts record \"%s\" has incorrect format.\n",
276				    buf);
277			continue;
278		}
279
280		/* Remove pre-existing entry */
281		remove_exclude(mount_info[1]);
282
283		item = strtok(mount_info[3], ",");
284		while (item != NULL) {
285			if (strcmp(item, "seclabel") == 0) {
286				found = 1;
287				nfile += file_system_count(mount_info[1]);
288				break;
289			}
290			item = strtok(NULL, ",");
291		}
292
293		/* Exclude mount points without the seclabel option */
294		if (!found) {
295			if (add_exclude(mount_info[1], !CALLER_EXCLUDED) &&
296			    errno == ENOMEM)
297				assert(0);
298		}
299	}
300
301	free(buf);
302	fclose(fp);
303	/* return estimated #Files + 5% for directories and hard links */
304	return nfile * 1.05;
305}
306
307/* Called by selinux_restorecon_xattr(3) to build a linked list of entries. */
308static int add_xattr_entry(const char *directory, bool delete_nonmatch,
309			   bool delete_all)
310{
311	char *sha1_buf = NULL;
312	size_t i, digest_len = 0;
313	int rc;
314	enum digest_result digest_result;
315	bool match;
316	struct dir_xattr *new_entry;
317	uint8_t *xattr_digest = NULL;
318	uint8_t *calculated_digest = NULL;
319
320	if (!directory) {
321		errno = EINVAL;
322		return -1;
323	}
324
325	match = selabel_get_digests_all_partial_matches(fc_sehandle, directory,
326								&calculated_digest, &xattr_digest,
327								&digest_len);
328
329	if (!xattr_digest || !digest_len) {
330		free(calculated_digest);
331		return 1;
332	}
333
334	/* Convert entry to a hex encoded string. */
335	sha1_buf = malloc(digest_len * 2 + 1);
336	if (!sha1_buf) {
337		free(xattr_digest);
338		free(calculated_digest);
339		goto oom;
340	}
341
342	for (i = 0; i < digest_len; i++)
343		sprintf((&sha1_buf[i * 2]), "%02x", xattr_digest[i]);
344
345	digest_result = match ? MATCH : NOMATCH;
346
347	if ((delete_nonmatch && !match) || delete_all) {
348		digest_result = match ? DELETED_MATCH : DELETED_NOMATCH;
349		rc = removexattr(directory, RESTORECON_PARTIAL_MATCH_DIGEST);
350		if (rc) {
351			selinux_log(SELINUX_ERROR,
352				  "Error: %m removing xattr \"%s\" from: %s\n",
353				  RESTORECON_PARTIAL_MATCH_DIGEST, directory);
354			digest_result = ERROR;
355		}
356	}
357	free(xattr_digest);
358	free(calculated_digest);
359
360	/* Now add entries to link list. */
361	new_entry = malloc(sizeof(struct dir_xattr));
362	if (!new_entry) {
363		free(sha1_buf);
364		goto oom;
365	}
366	new_entry->next = NULL;
367
368	new_entry->directory = strdup(directory);
369	if (!new_entry->directory) {
370		free(new_entry);
371		free(sha1_buf);
372		goto oom;
373	}
374
375	new_entry->digest = strdup(sha1_buf);
376	if (!new_entry->digest) {
377		free(new_entry->directory);
378		free(new_entry);
379		free(sha1_buf);
380		goto oom;
381	}
382
383	new_entry->result = digest_result;
384
385	if (!dir_xattr_list) {
386		dir_xattr_list = new_entry;
387		dir_xattr_last = new_entry;
388	} else {
389		dir_xattr_last->next = new_entry;
390		dir_xattr_last = new_entry;
391	}
392
393	free(sha1_buf);
394	return 0;
395
396oom:
397	selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __func__);
398	return -1;
399}
400
401/*
402 * Support filespec services filespec_add(), filespec_eval() and
403 * filespec_destroy().
404 *
405 * selinux_restorecon(3) uses filespec services when the
406 * SELINUX_RESTORECON_ADD_ASSOC flag is set for adding associations between
407 * an inode and a specification.
408 */
409
410/*
411 * The hash table of associations, hashed by inode number. Chaining is used
412 * for collisions, with elements ordered by inode number in each bucket.
413 * Each hash bucket has a dummy header.
414 */
415#define HASH_BITS 16
416#define HASH_BUCKETS (1 << HASH_BITS)
417#define HASH_MASK (HASH_BUCKETS-1)
418
419/*
420 * An association between an inode and a context.
421 */
422typedef struct file_spec {
423	ino_t ino;		/* inode number */
424	char *con;		/* matched context */
425	char *file;		/* full pathname */
426	struct file_spec *next;	/* next association in hash bucket chain */
427} file_spec_t;
428
429static file_spec_t *fl_head;
430static pthread_mutex_t fl_mutex = PTHREAD_MUTEX_INITIALIZER;
431
432/*
433 * Try to add an association between an inode and a context. If there is a
434 * different context that matched the inode, then use the first context
435 * that matched.
436 */
437static int filespec_add(ino_t ino, const char *con, const char *file,
438			const struct rest_flags *flags)
439{
440	file_spec_t *prevfl, *fl;
441	uint32_t h;
442	int ret;
443	struct stat64 sb;
444
445	__pthread_mutex_lock(&fl_mutex);
446
447	if (!fl_head) {
448		fl_head = calloc(HASH_BUCKETS, sizeof(file_spec_t));
449		if (!fl_head)
450			goto oom;
451	}
452
453	h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
454	for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
455	     prevfl = fl, fl = fl->next) {
456		if (ino == fl->ino) {
457			ret = lstat64(fl->file, &sb);
458			if (ret < 0 || sb.st_ino != ino) {
459				freecon(fl->con);
460				free(fl->file);
461				fl->file = strdup(file);
462				if (!fl->file)
463					goto oom;
464				fl->con = strdup(con);
465				if (!fl->con)
466					goto oom;
467				goto unlock_1;
468			}
469
470			if (strcmp(fl->con, con) == 0)
471				goto unlock_1;
472
473			selinux_log(SELINUX_ERROR,
474				"conflicting specifications for %s and %s, using %s.\n",
475				file, fl->file, fl->con);
476			free(fl->file);
477			fl->file = strdup(file);
478			if (!fl->file)
479				goto oom;
480
481			__pthread_mutex_unlock(&fl_mutex);
482
483			if (flags->conflicterror) {
484				selinux_log(SELINUX_ERROR,
485				"treating conflicting specifications as an error.\n");
486				return -1;
487			}
488			return 1;
489		}
490
491		if (ino > fl->ino)
492			break;
493	}
494
495	fl = malloc(sizeof(file_spec_t));
496	if (!fl)
497		goto oom;
498	fl->ino = ino;
499	fl->con = strdup(con);
500	if (!fl->con)
501		goto oom_freefl;
502	fl->file = strdup(file);
503	if (!fl->file)
504		goto oom_freeflcon;
505	fl->next = prevfl->next;
506	prevfl->next = fl;
507
508	__pthread_mutex_unlock(&fl_mutex);
509	return 0;
510
511oom_freeflcon:
512	free(fl->con);
513oom_freefl:
514	free(fl);
515oom:
516	__pthread_mutex_unlock(&fl_mutex);
517	selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __func__);
518	return -1;
519unlock_1:
520	__pthread_mutex_unlock(&fl_mutex);
521	return 1;
522}
523
524/*
525 * Evaluate the association hash table distribution.
526 */
527#ifdef DEBUG
528static void filespec_eval(void)
529{
530	file_spec_t *fl;
531	uint32_t h;
532	size_t used, nel, len, longest;
533
534	if (!fl_head)
535		return;
536
537	used = 0;
538	longest = 0;
539	nel = 0;
540	for (h = 0; h < HASH_BUCKETS; h++) {
541		len = 0;
542		for (fl = fl_head[h].next; fl; fl = fl->next)
543			len++;
544		if (len)
545			used++;
546		if (len > longest)
547			longest = len;
548		nel += len;
549	}
550
551	selinux_log(SELINUX_INFO,
552		     "filespec hash table stats: %zu elements, %zu/%zu buckets used, longest chain length %zu\n",
553		     nel, used, HASH_BUCKETS, longest);
554}
555#else
556static void filespec_eval(void)
557{
558}
559#endif
560
561/*
562 * Destroy the association hash table.
563 */
564static void filespec_destroy(void)
565{
566	file_spec_t *fl, *tmp;
567	uint32_t h;
568
569	if (!fl_head)
570		return;
571
572	for (h = 0; h < HASH_BUCKETS; h++) {
573		fl = fl_head[h].next;
574		while (fl) {
575			tmp = fl;
576			fl = fl->next;
577			freecon(tmp->con);
578			free(tmp->file);
579			free(tmp);
580		}
581		fl_head[h].next = NULL;
582	}
583	free(fl_head);
584	fl_head = NULL;
585}
586
587/*
588 * Called if SELINUX_RESTORECON_SET_SPECFILE_CTX is not set to check if
589 * the type components differ, updating newtypecon if so.
590 */
591static int compare_types(const char *curcon, const char *newcon, char **newtypecon)
592{
593	int types_differ = 0;
594	context_t cona;
595	context_t conb;
596	int rc = 0;
597
598	cona = context_new(curcon);
599	if (!cona) {
600		rc = -1;
601		goto out;
602	}
603	conb = context_new(newcon);
604	if (!conb) {
605		context_free(cona);
606		rc = -1;
607		goto out;
608	}
609
610	types_differ = strcmp(context_type_get(cona), context_type_get(conb));
611	if (types_differ) {
612		rc |= context_user_set(conb, context_user_get(cona));
613		rc |= context_role_set(conb, context_role_get(cona));
614		rc |= context_range_set(conb, context_range_get(cona));
615		if (!rc) {
616			*newtypecon = strdup(context_str(conb));
617			if (!*newtypecon) {
618				rc = -1;
619				goto err;
620			}
621		}
622	}
623
624err:
625	context_free(cona);
626	context_free(conb);
627out:
628	return rc;
629}
630
631#define DATA_APP_EL1 "/data/app/el1/"
632#define DATA_APP_EL2 "/data/app/el2/"
633#define DATA_APP_EL3 "/data/app/el3/"
634#define DATA_APP_EL4 "/data/app/el4/"
635#define DATA_ACCOUNTS_ACCOUNT_0 "/data/accounts/account_0/"
636#define HNP_ROOT_PATH "/data/app/el1/bundle/"
637#define HNP_PUBLIC_DIR "/hnppublic"
638#define HNP_ROOT_PATH_LEN 21
639#define HNP_PUBLIC_DIR_LEN 10
640
641// Allow the hnp process to refresh the labels of files in the HNP_ROOT_PATH directory
642static bool is_hnp_path(const char *path)
643{
644	size_t pathLen = strlen(path);
645	if ((pathLen < HNP_ROOT_PATH_LEN + 1 + HNP_PUBLIC_DIR_LEN + 1) ||
646		(strstr(path, HNP_PUBLIC_DIR) == NULL)) {
647		return false;
648	}
649
650	if (strncmp(path, HNP_ROOT_PATH, HNP_ROOT_PATH_LEN) != 0) {
651		return false;
652	}
653	return true;
654}
655
656static bool check_path_allow_restorecon(const char *pathname)
657{
658	if ((!strncmp(pathname, DATA_APP_EL1, sizeof(DATA_APP_EL1) - 1) && (!is_hnp_path(pathname))) ||
659		!strncmp(pathname, DATA_APP_EL2, sizeof(DATA_APP_EL2) - 1) ||
660		!strncmp(pathname, DATA_APP_EL3, sizeof(DATA_APP_EL3) - 1) ||
661		!strncmp(pathname, DATA_APP_EL4, sizeof(DATA_APP_EL4) - 1) ||
662		!strncmp(pathname, DATA_ACCOUNTS_ACCOUNT_0, sizeof(DATA_ACCOUNTS_ACCOUNT_0) - 1)) {
663		return false;
664	}
665	return true;
666}
667
668
669static int restorecon_sb(const char *pathname, const struct stat *sb,
670			    const struct rest_flags *flags, bool first)
671{
672	char *newcon = NULL;
673	char *curcon = NULL;
674	char *newtypecon = NULL;
675	int rc;
676	const char *lookup_path = pathname;
677
678	if (!check_path_allow_restorecon(pathname)) {
679		goto out;
680	}
681
682	if (rootpath) {
683		if (strncmp(rootpath, lookup_path, rootpathlen) != 0) {
684			selinux_log(SELINUX_ERROR,
685				    "%s is not located in alt_rootpath %s\n",
686				    lookup_path, rootpath);
687			return -1;
688		}
689		lookup_path += rootpathlen;
690	}
691
692	if (rootpath != NULL && lookup_path[0] == '\0')
693		/* this is actually the root dir of the alt root. */
694		rc = selabel_lookup_raw(fc_sehandle, &newcon, "/",
695						    sb->st_mode & S_IFMT);
696	else
697		rc = selabel_lookup_raw(fc_sehandle, &newcon, lookup_path,
698						    sb->st_mode & S_IFMT);
699
700	if (rc < 0) {
701		if (errno == ENOENT) {
702			if (flags->warnonnomatch && first)
703				selinux_log(SELINUX_INFO,
704					    "Warning no default label for %s\n",
705					    lookup_path);
706
707			return 0; /* no match, but not an error */
708		}
709
710		return -1;
711	}
712
713	if (flags->progress) {
714		__pthread_mutex_lock(&progress_mutex);
715		fc_count++;
716		if (fc_count % STAR_COUNT == 0) {
717			if (flags->mass_relabel && efile_count > 0) {
718				float pc = (fc_count < efile_count) ? (100.0 *
719					     fc_count / efile_count) : 100;
720				fprintf(stdout, "\r%-.1f%%", (double)pc);
721			} else {
722				fprintf(stdout, "\r%" PRIu64 "k", fc_count / STAR_COUNT);
723			}
724			fflush(stdout);
725		}
726		__pthread_mutex_unlock(&progress_mutex);
727	}
728
729	if (flags->add_assoc) {
730		rc = filespec_add(sb->st_ino, newcon, pathname, flags);
731
732		if (rc < 0) {
733			selinux_log(SELINUX_ERROR,
734				    "filespec_add error: %s\n", pathname);
735			freecon(newcon);
736			return -1;
737		}
738
739		if (rc > 0) {
740			/* Already an association and it took precedence. */
741			freecon(newcon);
742			return 0;
743		}
744	}
745
746	if (flags->log_matches)
747		selinux_log(SELINUX_INFO, "%s matched by %s\n",
748			    pathname, newcon);
749
750	if (lgetfilecon_raw(pathname, &curcon) < 0) {
751		if (errno != ENODATA)
752			goto err;
753
754		curcon = NULL;
755	}
756
757	if (curcon == NULL || strcmp(curcon, newcon) != 0) {
758		bool updated = false;
759
760		if (!flags->set_specctx && curcon &&
761				    (is_context_customizable(curcon) > 0)) {
762			if (flags->verbose) {
763				selinux_log(SELINUX_INFO,
764				 "%s not reset as customized by admin to %s\n",
765							    pathname, curcon);
766			}
767			goto out;
768		}
769
770		if (!flags->set_specctx && curcon) {
771			/* If types different then update newcon. */
772			rc = compare_types(curcon, newcon, &newtypecon);
773			if (rc)
774				goto err;
775
776			if (newtypecon) {
777				freecon(newcon);
778				newcon = newtypecon;
779			} else {
780				goto out;
781			}
782		}
783
784		if (!flags->nochange) {
785			if (lsetfilecon(pathname, newcon) < 0)
786				goto err;
787			updated = true;
788		}
789
790		if (flags->verbose)
791			selinux_log(SELINUX_INFO,
792				    "%s %s from %s to %s\n",
793				    updated ? "Relabeled" : "Would relabel",
794				    pathname,
795				    curcon ? curcon : "<no context>",
796				    newcon);
797
798		if (flags->syslog_changes && !flags->nochange) {
799			if (curcon)
800				syslog(LOG_INFO,
801					    "relabeling %s from %s to %s\n",
802					    pathname, curcon, newcon);
803			else
804				syslog(LOG_INFO, "labeling %s to %s\n",
805					    pathname, newcon);
806		}
807	}
808
809out:
810	rc = 0;
811out1:
812	freecon(curcon);
813	freecon(newcon);
814	return rc;
815err:
816	selinux_log(SELINUX_ERROR,
817		    "Could not set context for %s:  %m\n",
818		    pathname);
819	rc = -1;
820	goto out1;
821}
822
823struct dir_hash_node {
824	char *path;
825	uint8_t digest[SHA1_HASH_SIZE];
826	struct dir_hash_node *next;
827};
828/*
829 * Returns true if the digest of all partial matched contexts is the same as
830 * the one saved by setxattr. Otherwise returns false and constructs a
831 * dir_hash_node with the newly calculated digest.
832 */
833static bool check_context_match_for_dir(const char *pathname,
834					struct dir_hash_node **new_node,
835					int error)
836{
837	bool status;
838	size_t digest_len = 0;
839	uint8_t *read_digest = NULL;
840	uint8_t *calculated_digest = NULL;
841
842	if (!new_node)
843		return false;
844
845	*new_node = NULL;
846
847	/* status = true if digests match, false otherwise. */
848	status = selabel_get_digests_all_partial_matches(fc_sehandle, pathname,
849							 &calculated_digest,
850							 &read_digest,
851							 &digest_len);
852
853	if (status)
854		goto free;
855
856	/* Save digest of all matched contexts for the current directory. */
857	if (!error && calculated_digest) {
858		*new_node = calloc(1, sizeof(struct dir_hash_node));
859
860		if (!*new_node)
861			goto oom;
862
863		(*new_node)->path = strdup(pathname);
864
865		if (!(*new_node)->path) {
866			free(*new_node);
867			*new_node = NULL;
868			goto oom;
869		}
870		memcpy((*new_node)->digest, calculated_digest, digest_len);
871		(*new_node)->next = NULL;
872	}
873
874free:
875	free(calculated_digest);
876	free(read_digest);
877	return status;
878
879oom:
880	selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __func__);
881	goto free;
882}
883
884static bool is_in_skip_elx(const char *path) {
885	if (sizeof(skip_elx_path) == 0) {
886		return false;
887	}
888	size_t len = sizeof(skip_elx_path) / sizeof(skip_elx_path[0]);
889	for (size_t i = 0; i < len; i++) {
890		if (strncmp(path, skip_elx_path[i], strlen(skip_elx_path[i])) == 0) {
891			return true;
892		}
893	}
894	return false;
895}
896
897struct rest_state {
898	struct rest_flags flags;
899	dev_t dev_num;
900	struct statfs sfsb;
901	bool ignore_digest;
902	bool setrestorecondigest;
903	bool parallel;
904
905	FTS *fts;
906	FTSENT *ftsent_first;
907	struct dir_hash_node *head, *current;
908	bool abort;
909	int error;
910	long unsigned skipped_errors;
911	int saved_errno;
912	pthread_mutex_t mutex;
913};
914
915static void *selinux_restorecon_thread(void *arg)
916{
917	struct rest_state *state = arg;
918	FTS *fts = state->fts;
919	FTSENT *ftsent;
920	int error;
921	char ent_path[PATH_MAX];
922	struct stat ent_st;
923	bool first = false;
924
925	if (state->parallel)
926		pthread_mutex_lock(&state->mutex);
927
928	if (state->ftsent_first) {
929		ftsent = state->ftsent_first;
930		state->ftsent_first = NULL;
931		first = true;
932		goto loop_body;
933	}
934
935	while (((void)(errno = 0), ftsent = fts_read(fts)) != NULL) {
936loop_body:
937		/* If the FTS_XDEV flag is set and the device is different */
938		if (state->flags.set_xdev &&
939		    ftsent->fts_statp->st_dev != state->dev_num)
940			continue;
941
942		switch (ftsent->fts_info) {
943		case FTS_DC:
944			selinux_log(SELINUX_ERROR,
945				    "Directory cycle on %s.\n",
946				    ftsent->fts_path);
947			errno = ELOOP;
948			state->error = -1;
949			state->abort = true;
950			goto finish;
951		case FTS_DP:
952			continue;
953		case FTS_DNR:
954			error = errno;
955			errno = ftsent->fts_errno;
956			selinux_log(SELINUX_ERROR,
957				    "Could not read %s: %m.\n",
958				    ftsent->fts_path);
959			errno = error;
960			fts_set(fts, ftsent, FTS_SKIP);
961			continue;
962		case FTS_NS:
963			error = errno;
964			errno = ftsent->fts_errno;
965			selinux_log(SELINUX_ERROR,
966				    "Could not stat %s: %m.\n",
967				    ftsent->fts_path);
968			errno = error;
969			fts_set(fts, ftsent, FTS_SKIP);
970			continue;
971		case FTS_ERR:
972			error = errno;
973			errno = ftsent->fts_errno;
974			selinux_log(SELINUX_ERROR,
975				    "Error on %s: %m.\n",
976				    ftsent->fts_path);
977			errno = error;
978			fts_set(fts, ftsent, FTS_SKIP);
979			continue;
980		case FTS_D:
981			if (state->sfsb.f_type == SYSFS_MAGIC &&
982			    !selabel_partial_match(fc_sehandle,
983			    ftsent->fts_path)) {
984				fts_set(fts, ftsent, FTS_SKIP);
985				continue;
986			}
987
988			if (check_excluded(ftsent->fts_path)) {
989				fts_set(fts, ftsent, FTS_SKIP);
990				continue;
991			}
992
993			if (state->setrestorecondigest) {
994				struct dir_hash_node *new_node = NULL;
995
996				if (check_context_match_for_dir(ftsent->fts_path,
997								&new_node,
998								state->error) &&
999								!state->ignore_digest) {
1000					selinux_log(SELINUX_INFO,
1001						"Skipping restorecon on directory(%s)\n",
1002						    ftsent->fts_path);
1003					fts_set(fts, ftsent, FTS_SKIP);
1004					continue;
1005				}
1006
1007				if (new_node && !state->error) {
1008					if (!state->current) {
1009						state->current = new_node;
1010						state->head = state->current;
1011					} else {
1012						state->current->next = new_node;
1013						state->current = new_node;
1014					}
1015				}
1016			}
1017
1018			if (state->flags.skipelx && is_in_skip_elx(ftsent->fts_path)) {
1019				fts_set(fts, ftsent, FTS_SKIP);
1020				continue;
1021			}
1022
1023			enum skip_type skip_ignore_flag = skip_ignore_relabel(ftsent->fts_path);
1024			selinux_log(SELINUX_INFO,
1025						"ignore cfg parsing result %d \n",
1026						skip_ignore_flag);
1027			switch (skip_ignore_flag) {
1028			case SKIP_SELF_SUB_DIR:
1029				selinux_log(SELINUX_INFO,
1030							"Skipping restorecon on directory(%s), cause ignroe_cfg\n",
1031							ftsent->fts_path);
1032				fts_set(fts, ftsent, FTS_SKIP);
1033				continue;
1034			case SKIP_SUB_DIR:
1035				selinux_log(SELINUX_INFO,
1036							"Skipping restorecon on directory(%s) sub directory, cause ignroe_cfg\n",
1037							ftsent->fts_path);
1038				fts_set(fts, ftsent, FTS_SKIP);
1039			default:
1040				break;
1041			}
1042
1043			/* fall through */
1044		default:
1045			if (strlcpy(ent_path, ftsent->fts_path, sizeof(ent_path)) >= sizeof(ent_path)) {
1046				selinux_log(SELINUX_ERROR,
1047					    "Path name too long on %s.\n",
1048					    ftsent->fts_path);
1049				errno = ENAMETOOLONG;
1050				state->error = -1;
1051				state->abort = true;
1052				goto finish;
1053			}
1054
1055			ent_st = *ftsent->fts_statp;
1056			if (state->parallel)
1057				pthread_mutex_unlock(&state->mutex);
1058
1059			error = restorecon_sb(ent_path, &ent_st, &state->flags,
1060					      first);
1061
1062			if (state->parallel) {
1063				pthread_mutex_lock(&state->mutex);
1064				if (state->abort)
1065					goto unlock;
1066			}
1067
1068			first = false;
1069			if (error) {
1070				if (state->flags.abort_on_error) {
1071					state->error = error;
1072					state->abort = true;
1073					goto finish;
1074				}
1075				if (state->flags.count_errors)
1076					state->skipped_errors++;
1077				else
1078					state->error = error;
1079			}
1080			break;
1081		}
1082	}
1083
1084finish:
1085	if (!state->saved_errno)
1086		state->saved_errno = errno;
1087unlock:
1088	if (state->parallel)
1089		pthread_mutex_unlock(&state->mutex);
1090	return NULL;
1091}
1092
1093static int selinux_restorecon_common(const char *pathname_orig,
1094				     unsigned int restorecon_flags,
1095				     size_t nthreads)
1096{
1097	struct rest_state state;
1098
1099	state.flags.nochange = (restorecon_flags &
1100		    SELINUX_RESTORECON_NOCHANGE) ? true : false;
1101	state.flags.verbose = (restorecon_flags &
1102		    SELINUX_RESTORECON_VERBOSE) ? true : false;
1103	state.flags.progress = (restorecon_flags &
1104		    SELINUX_RESTORECON_PROGRESS) ? true : false;
1105	state.flags.mass_relabel = (restorecon_flags &
1106		    SELINUX_RESTORECON_MASS_RELABEL) ? true : false;
1107	state.flags.recurse = (restorecon_flags &
1108		    SELINUX_RESTORECON_RECURSE) ? true : false;
1109	state.flags.set_specctx = (restorecon_flags &
1110		    SELINUX_RESTORECON_SET_SPECFILE_CTX) ? true : false;
1111	state.flags.userealpath = (restorecon_flags &
1112		   SELINUX_RESTORECON_REALPATH) ? true : false;
1113	state.flags.set_xdev = (restorecon_flags &
1114		   SELINUX_RESTORECON_XDEV) ? true : false;
1115	state.flags.add_assoc = (restorecon_flags &
1116		   SELINUX_RESTORECON_ADD_ASSOC) ? true : false;
1117	state.flags.abort_on_error = (restorecon_flags &
1118		   SELINUX_RESTORECON_ABORT_ON_ERROR) ? true : false;
1119	state.flags.syslog_changes = (restorecon_flags &
1120		   SELINUX_RESTORECON_SYSLOG_CHANGES) ? true : false;
1121	state.flags.log_matches = (restorecon_flags &
1122		   SELINUX_RESTORECON_LOG_MATCHES) ? true : false;
1123	state.flags.ignore_noent = (restorecon_flags &
1124		   SELINUX_RESTORECON_IGNORE_NOENTRY) ? true : false;
1125	state.flags.warnonnomatch = true;
1126	state.flags.conflicterror = (restorecon_flags &
1127		   SELINUX_RESTORECON_CONFLICT_ERROR) ? true : false;
1128	ignore_mounts = (restorecon_flags &
1129		   SELINUX_RESTORECON_IGNORE_MOUNTS) ? true : false;
1130	state.ignore_digest = (restorecon_flags &
1131		    SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false;
1132	state.flags.count_errors = (restorecon_flags &
1133		    SELINUX_RESTORECON_COUNT_ERRORS) ? true : false;
1134	state.flags.skipelx = (restorecon_flags &
1135		    SELINUX_RESTORECON_SKIPELX) ? true : false;
1136	state.setrestorecondigest = true;
1137
1138	state.head = NULL;
1139	state.current = NULL;
1140	state.abort = false;
1141	state.error = 0;
1142	state.skipped_errors = 0;
1143	state.saved_errno = 0;
1144
1145	struct stat sb;
1146	char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname;
1147	char *paths[2] = { NULL, NULL };
1148	int fts_flags, error;
1149	struct dir_hash_node *current = NULL;
1150
1151	if (state.flags.verbose && state.flags.progress)
1152		state.flags.verbose = false;
1153
1154	__selinux_once(fc_once, restorecon_init);
1155
1156	if (!fc_sehandle)
1157		return -1;
1158
1159	/*
1160	 * If selabel_no_digest = true then no digest has been requested by
1161	 * an external selabel_open(3) call.
1162	 */
1163	if (selabel_no_digest ||
1164	    (restorecon_flags & SELINUX_RESTORECON_SKIP_DIGEST))
1165		state.setrestorecondigest = false;
1166
1167	if (!__pthread_supported) {
1168		if (nthreads != 1) {
1169			nthreads = 1;
1170			selinux_log(SELINUX_WARNING,
1171				"Threading functionality not available, falling back to 1 thread.");
1172		}
1173	} else if (nthreads == 0) {
1174		long nproc = sysconf(_SC_NPROCESSORS_ONLN);
1175
1176		if (nproc > 0) {
1177			nthreads = nproc;
1178		} else {
1179			nthreads = 1;
1180			selinux_log(SELINUX_WARNING,
1181				"Unable to detect CPU count, falling back to 1 thread.");
1182		}
1183	}
1184
1185	/*
1186	 * Convert passed-in pathname to canonical pathname by resolving
1187	 * realpath of containing dir, then appending last component name.
1188	 */
1189	if (state.flags.userealpath) {
1190		char *basename_cpy = strdup(pathname_orig);
1191		if (!basename_cpy)
1192			goto realpatherr;
1193		pathbname = basename(basename_cpy);
1194		if (!strcmp(pathbname, "/") || !strcmp(pathbname, ".") ||
1195					    !strcmp(pathbname, "..")) {
1196			pathname = realpath(pathname_orig, NULL);
1197			if (!pathname) {
1198				free(basename_cpy);
1199				/* missing parent directory */
1200				if (state.flags.ignore_noent && errno == ENOENT) {
1201					return 0;
1202				}
1203				goto realpatherr;
1204			}
1205		} else {
1206			char *dirname_cpy = strdup(pathname_orig);
1207			if (!dirname_cpy) {
1208				free(basename_cpy);
1209				goto realpatherr;
1210			}
1211			pathdname = dirname(dirname_cpy);
1212			pathdnamer = realpath(pathdname, NULL);
1213			free(dirname_cpy);
1214			if (!pathdnamer) {
1215				free(basename_cpy);
1216				if (state.flags.ignore_noent && errno == ENOENT) {
1217					return 0;
1218				}
1219				goto realpatherr;
1220			}
1221			if (!strcmp(pathdnamer, "/"))
1222				error = asprintf(&pathname, "/%s", pathbname);
1223			else
1224				error = asprintf(&pathname, "%s/%s",
1225						    pathdnamer, pathbname);
1226			if (error < 0) {
1227				free(basename_cpy);
1228				goto oom;
1229			}
1230		}
1231		free(basename_cpy);
1232	} else {
1233		pathname = strdup(pathname_orig);
1234		if (!pathname)
1235			goto oom;
1236	}
1237
1238	paths[0] = pathname;
1239
1240	if (lstat(pathname, &sb) < 0) {
1241		if (state.flags.ignore_noent && errno == ENOENT) {
1242			free(pathdnamer);
1243			free(pathname);
1244			return 0;
1245		} else {
1246			selinux_log(SELINUX_ERROR,
1247				    "lstat(%s) failed: %m\n",
1248				    pathname);
1249			error = -1;
1250			goto cleanup;
1251		}
1252	}
1253
1254	/* Skip digest if not a directory */
1255	if (!S_ISDIR(sb.st_mode))
1256		state.setrestorecondigest = false;
1257
1258	if (!state.flags.recurse) {
1259		if (check_excluded(pathname)) {
1260			error = 0;
1261			goto cleanup;
1262		}
1263
1264		error = restorecon_sb(pathname, &sb, &state.flags, true);
1265		goto cleanup;
1266	}
1267
1268	/* Obtain fs type */
1269	memset(&state.sfsb, 0, sizeof(state.sfsb));
1270	if (!S_ISLNK(sb.st_mode) && statfs(pathname, &state.sfsb) < 0) {
1271		selinux_log(SELINUX_ERROR,
1272			    "statfs(%s) failed: %m\n",
1273			    pathname);
1274		error = -1;
1275		goto cleanup;
1276	}
1277
1278	/* Skip digest on in-memory filesystems and /sys */
1279	if (state.sfsb.f_type == RAMFS_MAGIC || state.sfsb.f_type == TMPFS_MAGIC ||
1280	    state.sfsb.f_type == SYSFS_MAGIC)
1281		state.setrestorecondigest = false;
1282
1283	if (state.flags.set_xdev)
1284		fts_flags = FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV;
1285	else
1286		fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1287
1288	state.fts = fts_open(paths, fts_flags, NULL);
1289	if (!state.fts)
1290		goto fts_err;
1291
1292	state.ftsent_first = fts_read(state.fts);
1293	if (!state.ftsent_first)
1294		goto fts_err;
1295
1296	/*
1297	 * Keep the inode of the first device. This is because the FTS_XDEV
1298	 * flag tells fts not to descend into directories with different
1299	 * device numbers, but fts will still give back the actual directory.
1300	 * By saving the device number of the directory that was passed to
1301	 * selinux_restorecon() and then skipping all actions on any
1302	 * directories with a different device number when the FTS_XDEV flag
1303	 * is set (from http://marc.info/?l=selinux&m=124688830500777&w=2).
1304	 */
1305	state.dev_num = state.ftsent_first->fts_statp->st_dev;
1306
1307	bool load_ignore_path = load_ignore_cfg();
1308	if (!load_ignore_path) {
1309		selinux_log(SELINUX_ERROR, "Failed to load ignore cfg!\n");
1310	}
1311
1312	if (nthreads == 1) {
1313		state.parallel = false;
1314		selinux_restorecon_thread(&state);
1315	} else {
1316		size_t i;
1317		pthread_t self = pthread_self();
1318		pthread_t *threads = NULL;
1319
1320		pthread_mutex_init(&state.mutex, NULL);
1321
1322		threads = calloc(nthreads - 1, sizeof(*threads));
1323		if (!threads)
1324			goto oom;
1325
1326		state.parallel = true;
1327		/*
1328		 * Start (nthreads - 1) threads - the main thread is going to
1329		 * take part, too.
1330		 */
1331		for (i = 0; i < nthreads - 1; i++) {
1332			if (pthread_create(&threads[i], NULL,
1333					   selinux_restorecon_thread, &state)) {
1334				/*
1335				 * If any thread fails to be created, just mark
1336				 * it as such and let the successfully created
1337				 * threads do the job. In the worst case the
1338				 * main thread will do everything, but that's
1339				 * still better than to give up.
1340				 */
1341				threads[i] = self;
1342			}
1343		}
1344
1345		/* Let's join in on the fun! */
1346		selinux_restorecon_thread(&state);
1347
1348		/* Now wait for all threads to finish. */
1349		for (i = 0; i < nthreads - 1; i++) {
1350			/* Skip threads that failed to be created. */
1351			if (pthread_equal(threads[i], self))
1352				continue;
1353			pthread_join(threads[i], NULL);
1354		}
1355		free(threads);
1356
1357		pthread_mutex_destroy(&state.mutex);
1358	}
1359
1360	error = state.error;
1361	if (state.saved_errno)
1362		goto out;
1363
1364	/*
1365	 * Labeling successful. Write partial match digests for subdirectories.
1366	 * TODO: Write digest upon FTS_DP if no error occurs in its descents.
1367	 * Note: we can't ignore errors here that we've masked due to
1368	 * SELINUX_RESTORECON_COUNT_ERRORS.
1369	 */
1370	if (state.setrestorecondigest && !state.flags.nochange && !error &&
1371	    state.skipped_errors == 0) {
1372		current = state.head;
1373		while (current != NULL) {
1374			if (setxattr(current->path,
1375			    RESTORECON_PARTIAL_MATCH_DIGEST,
1376			    current->digest,
1377			    SHA1_HASH_SIZE, 0) < 0) {
1378				selinux_log(SELINUX_ERROR,
1379					    "setxattr failed: %s: %m\n",
1380					    current->path);
1381			}
1382			current = current->next;
1383		}
1384	}
1385
1386	skipped_errors = state.skipped_errors;
1387
1388out:
1389	if (state.flags.progress && state.flags.mass_relabel)
1390		fprintf(stdout, "\r%s 100.0%%\n", pathname);
1391
1392	(void) fts_close(state.fts);
1393	errno = state.saved_errno;
1394cleanup:
1395	if (state.flags.add_assoc) {
1396		if (state.flags.verbose)
1397			filespec_eval();
1398		filespec_destroy();
1399	}
1400	free(pathdnamer);
1401	free(pathname);
1402
1403	current = state.head;
1404	while (current != NULL) {
1405		struct dir_hash_node *next = current->next;
1406
1407		free(current->path);
1408		free(current);
1409		current = next;
1410	}
1411	return error;
1412
1413oom:
1414	selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __func__);
1415	error = -1;
1416	goto cleanup;
1417
1418realpatherr:
1419	selinux_log(SELINUX_ERROR,
1420		    "SELinux: Could not get canonical path for %s restorecon: %m.\n",
1421		    pathname_orig);
1422	error = -1;
1423	goto cleanup;
1424
1425fts_err:
1426	selinux_log(SELINUX_ERROR,
1427		    "fts error while labeling %s: %m\n",
1428		    paths[0]);
1429	error = -1;
1430	goto cleanup;
1431}
1432
1433
1434/*
1435 * Public API
1436 */
1437
1438/* selinux_restorecon(3) - Main function that is responsible for labeling */
1439int selinux_restorecon(const char *pathname_orig,
1440		       unsigned int restorecon_flags)
1441{
1442	return selinux_restorecon_common(pathname_orig, restorecon_flags, 1);
1443}
1444
1445/* selinux_restorecon_parallel(3) - Parallel version of selinux_restorecon(3) */
1446int selinux_restorecon_parallel(const char *pathname_orig,
1447				unsigned int restorecon_flags,
1448				size_t nthreads)
1449{
1450	return selinux_restorecon_common(pathname_orig, restorecon_flags, nthreads);
1451}
1452
1453/* selinux_restorecon_set_sehandle(3) is called to set the global fc handle */
1454void selinux_restorecon_set_sehandle(struct selabel_handle *hndl)
1455{
1456	char **specfiles;
1457	unsigned char *fc_digest;
1458	size_t num_specfiles, fc_digest_len;
1459
1460	fc_sehandle = hndl;
1461	if (!fc_sehandle)
1462		return;
1463
1464	/* Check if digest requested in selabel_open(3), if so use it. */
1465	if (selabel_digest(fc_sehandle, &fc_digest, &fc_digest_len,
1466				   &specfiles, &num_specfiles) < 0)
1467		selabel_no_digest = true;
1468	else
1469		selabel_no_digest = false;
1470}
1471
1472
1473/*
1474 * selinux_restorecon_default_handle(3) is called to set the global restorecon
1475 * handle by a process if the default params are required.
1476 */
1477struct selabel_handle *selinux_restorecon_default_handle(void)
1478{
1479	struct selabel_handle *sehandle;
1480
1481	struct selinux_opt fc_opts[] = {
1482		{ SELABEL_OPT_DIGEST, (char *)1 }
1483	};
1484
1485	sehandle = selabel_open(SELABEL_CTX_FILE, fc_opts, 1);
1486
1487	if (!sehandle) {
1488		selinux_log(SELINUX_ERROR,
1489			    "Error obtaining file context handle: %m\n");
1490		return NULL;
1491	}
1492
1493	selabel_no_digest = false;
1494	return sehandle;
1495}
1496
1497/*
1498 * selinux_restorecon_set_exclude_list(3) is called to add additional entries
1499 * to be excluded from labeling checks.
1500 */
1501void selinux_restorecon_set_exclude_list(const char **exclude_list)
1502{
1503	int i;
1504	struct stat sb;
1505
1506	for (i = 0; exclude_list[i]; i++) {
1507		if (lstat(exclude_list[i], &sb) < 0 && errno != EACCES) {
1508			selinux_log(SELINUX_ERROR,
1509				    "lstat error on exclude path \"%s\", %m - ignoring.\n",
1510				    exclude_list[i]);
1511			break;
1512		}
1513		if (add_exclude(exclude_list[i], CALLER_EXCLUDED) &&
1514		    errno == ENOMEM)
1515			assert(0);
1516	}
1517}
1518
1519/* selinux_restorecon_set_alt_rootpath(3) sets an alternate rootpath. */
1520int selinux_restorecon_set_alt_rootpath(const char *alt_rootpath)
1521{
1522	size_t len;
1523
1524	/* This should be NULL on first use */
1525	if (rootpath)
1526		free(rootpath);
1527
1528	rootpath = strdup(alt_rootpath);
1529	if (!rootpath) {
1530		selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __func__);
1531		return -1;
1532	}
1533
1534	/* trim trailing /, if present */
1535	len = strlen(rootpath);
1536	while (len && (rootpath[len - 1] == '/'))
1537		rootpath[--len] = '\0';
1538	rootpathlen = len;
1539
1540	return 0;
1541}
1542
1543/* selinux_restorecon_xattr(3)
1544 * Find RESTORECON_PARTIAL_MATCH_DIGEST entries.
1545 */
1546int selinux_restorecon_xattr(const char *pathname, unsigned int xattr_flags,
1547			     struct dir_xattr ***xattr_list)
1548{
1549	bool recurse = (xattr_flags &
1550	    SELINUX_RESTORECON_XATTR_RECURSE) ? true : false;
1551	bool delete_nonmatch = (xattr_flags &
1552	    SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS) ? true : false;
1553	bool delete_all = (xattr_flags &
1554	    SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS) ? true : false;
1555	ignore_mounts = (xattr_flags &
1556	   SELINUX_RESTORECON_XATTR_IGNORE_MOUNTS) ? true : false;
1557
1558	int rc, fts_flags;
1559	struct stat sb;
1560	struct statfs sfsb;
1561	struct dir_xattr *current, *next;
1562	FTS *fts;
1563	FTSENT *ftsent;
1564	char *paths[2] = { NULL, NULL };
1565
1566	__selinux_once(fc_once, restorecon_init);
1567
1568	if (!fc_sehandle)
1569		return -1;
1570
1571	if (lstat(pathname, &sb) < 0) {
1572		if (errno == ENOENT)
1573			return 0;
1574
1575		selinux_log(SELINUX_ERROR,
1576			    "lstat(%s) failed: %m\n",
1577			    pathname);
1578		return -1;
1579	}
1580
1581	if (!recurse) {
1582		if (statfs(pathname, &sfsb) == 0) {
1583			if (sfsb.f_type == RAMFS_MAGIC ||
1584			    sfsb.f_type == TMPFS_MAGIC)
1585				return 0;
1586		}
1587
1588		if (check_excluded(pathname))
1589			return 0;
1590
1591		rc = add_xattr_entry(pathname, delete_nonmatch, delete_all);
1592
1593		if (!rc && dir_xattr_list)
1594			*xattr_list = &dir_xattr_list;
1595		else if (rc == -1)
1596			return rc;
1597
1598		return 0;
1599	}
1600
1601	paths[0] = (char *)pathname;
1602	fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
1603
1604	fts = fts_open(paths, fts_flags, NULL);
1605	if (!fts) {
1606		selinux_log(SELINUX_ERROR,
1607			    "fts error on %s: %m\n",
1608			    paths[0]);
1609		return -1;
1610	}
1611
1612	while ((ftsent = fts_read(fts)) != NULL) {
1613		switch (ftsent->fts_info) {
1614		case FTS_DP:
1615			continue;
1616		case FTS_D:
1617			if (statfs(ftsent->fts_path, &sfsb) == 0) {
1618				if (sfsb.f_type == RAMFS_MAGIC ||
1619				    sfsb.f_type == TMPFS_MAGIC)
1620					continue;
1621			}
1622			if (check_excluded(ftsent->fts_path)) {
1623				fts_set(fts, ftsent, FTS_SKIP);
1624				continue;
1625			}
1626
1627			rc = add_xattr_entry(ftsent->fts_path,
1628					     delete_nonmatch, delete_all);
1629			if (rc == 1)
1630				continue;
1631			else if (rc == -1)
1632				goto cleanup;
1633			break;
1634		default:
1635			break;
1636		}
1637	}
1638
1639	if (dir_xattr_list)
1640		*xattr_list = &dir_xattr_list;
1641
1642	(void) fts_close(fts);
1643	return 0;
1644
1645cleanup:
1646	rc = errno;
1647	(void) fts_close(fts);
1648	errno = rc;
1649
1650	if (dir_xattr_list) {
1651		/* Free any used memory */
1652		current = dir_xattr_list;
1653		while (current) {
1654			next = current->next;
1655			free(current->directory);
1656			free(current->digest);
1657			free(current);
1658			current = next;
1659		}
1660	}
1661	return -1;
1662}
1663
1664long unsigned selinux_restorecon_get_skipped_errors(void)
1665{
1666	return skipped_errors;
1667}
1668