]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
New program: chcon
authorJim Meyering <jim@meyering.net>
Thu, 4 Jan 2007 15:33:43 +0000 (16:33 +0100)
committerJim Meyering <jim@meyering.net>
Thu, 29 Mar 2007 19:37:05 +0000 (21:37 +0200)
* gl/modules/selinux-at: New module.  Check for libselinux and set
LIB_SELINUX here, unconditionally, rather than depending on
the configure-time --enable-selinux option.
* gl/modules/selinux-h: New module.
* bootstrap.conf (gnulib_modules): Add selinux-at.
* gl/lib/selinux-at.c, gl/lib/selinux-at.h: New files.
* gl/lib/se-selinux_.h: New file.
* gl/lib/se-context_.h: New file.
* gl/m4/selinux-selinux-h.m4: New file.
* gl/m4/selinux-context-h.m4: New file.
* src/Makefile.am (bin_PROGRAMS): Add chcon.
(chcon_LDADD): Define.
* README: Add chcon to the list of programs.
* src/chcon.c: Rewrite the original (Red Hat) chcon to use fts.

13 files changed:
ChangeLog
README
bootstrap.conf
gl/lib/se-context_.h [new file with mode: 0644]
gl/lib/se-selinux_.h [new file with mode: 0644]
gl/lib/selinux-at.c [new file with mode: 0644]
gl/lib/selinux-at.h [new file with mode: 0644]
gl/m4/selinux-context-h.m4 [new file with mode: 0644]
gl/m4/selinux-selinux-h.m4 [new file with mode: 0644]
gl/modules/selinux-at [new file with mode: 0644]
gl/modules/selinux-h [new file with mode: 0644]
src/Makefile.am
src/chcon.c [new file with mode: 0644]

index 1396ed803e5c4fded0c276c8ccdc22e10135653c..8d4692dedb97ccd31e692d60b962dd8322295da3 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
 
 2007-01-04  Jim Meyering  <jim@meyering.net>
 
+       New program: chcon
+       * gl/modules/selinux-at: New module.  Check for libselinux and set
+       LIB_SELINUX here, unconditionally, rather than depending on
+       the configure-time --enable-selinux option.
+       * gl/modules/selinux-h: New module.
+       * bootstrap.conf (gnulib_modules): Add selinux-at.
+       * gl/lib/selinux-at.c, gl/lib/selinux-at.h: New files.
+       * gl/lib/se-selinux_.h: New file.
+       * gl/lib/se-context_.h: New file.
+       * gl/m4/selinux-selinux-h.m4: New file.
+       * gl/m4/selinux-context-h.m4: New file.
+       * src/Makefile.am (bin_PROGRAMS): Add chcon.
+       (chcon_LDADD): Define.
+       * README: Add chcon to the list of programs.
+       * src/chcon.c: Rewrite the original (Red Hat) chcon to use fts.
+
        * Makefile.cfg (local-checks-to-skip): Skip strftime-check, in
        case you don't have convenient access to glibc info documentation.
 
diff --git a/README b/README
index 218bad00a60fa4263ff49810b46cf80912f6cb1d..13ef2dc72beb7cf61fa1d16f8387be2c1e2e3577 100644 (file)
--- a/README
+++ b/README
@@ -7,14 +7,14 @@ arbitrary limits.
 
 The programs that can be built with this package are:
 
-  [ base64 basename cat chgrp chmod chown chroot cksum comm cp csplit cut date
-  dd df dir dircolors dirname du echo env expand expr factor false fmt fold
-  groups head hostid hostname id install join kill link ln logname ls
-  md5sum mkdir mkfifo mknod mv nice nl nohup od paste pathchk pinky pr
-  printenv printf ptx pwd readlink rm rmdir seq sha1sum sha224sum sha256sum
-  sha384sum sha512sum shred shuf sleep sort split stat stty su sum sync tac
-  tail tee test touch tr true tsort tty uname unexpand uniq unlink uptime
-  users vdir wc who whoami yes
+  [ base64 basename cat chcon chgrp chmod chown chroot cksum comm cp
+  csplit cut date dd df dir dircolors dirname du echo env expand expr
+  factor false fmt fold groups head hostid hostname id install join
+  kill link ln logname ls md5sum mkdir mkfifo mknod mv nice nl nohup od
+  paste pathchk pinky pr printenv printf ptx pwd readlink rm rmdir seq
+  sha1sum sha224sum sha256sum sha384sum sha512sum shred shuf sleep sort
+  split stat stty su sum sync tac tail tee test touch tr true tsort tty
+  uname unexpand uniq unlink uptime users vdir wc who whoami yes
 
 See the file NEWS for a list of major changes in the current release.
 
index 4ec43fae61e17a3b98443ead76c94d569b30d1e1..c391a62dc7a3bc93df5b88514839f42c8b63cf2a 100644 (file)
@@ -60,7 +60,9 @@ gnulib_modules="
        root-dev-ino
        rpmatch
        safe-read same
-       save-cwd savedir savewd settime sha1 sig2str ssize_t stat-macros
+       save-cwd savedir savewd
+       selinux-at
+       settime sha1 sig2str ssize_t stat-macros
        stat-time stdbool stdlib-safer stpcpy strftime
        strpbrk strtoimax strtoumax strverscmp sys_stat timespec tzset
        unicodeio unistd-safer unlink-busy unlinkdir unlocked-io
diff --git a/gl/lib/se-context_.h b/gl/lib/se-context_.h
new file mode 100644 (file)
index 0000000..26e1709
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef SELINUX_CONTEXT_H
+# define SELINUX_CONTEXT_H
+
+# include <errno.h>
+/* Some systems don't have ENOSYS.  */
+# ifndef ENOSYS
+#  ifdef ENOTSUP
+#   define ENOSYS ENOTSUP
+#  else
+/* Some systems don't have ENOTSUP either.  */
+#   define ENOSYS EINVAL
+#  endif
+# endif
+
+typedef int context_t;
+static inline context_t context_new (char const *s)
+  { errno = ENOTSUP; return 0; }
+static inline char *context_str (context_t con)
+  { errno = ENOTSUP; return (void *) 0; }
+static inline void context_free (context_t c) {}
+
+static inline int context_user_set (context_t sc, char const *s)
+  { errno = ENOTSUP; return -1; }
+static inline int context_role_set (context_t sc, char const *s)
+  { errno = ENOTSUP; return -1; }
+static inline int context_range_set (context_t sc, char const *s)
+  { errno = ENOTSUP; return -1; }
+static inline int context_type_set (context_t sc, char const *s)
+  { errno = ENOTSUP; return -1; }
+
+#endif
diff --git a/gl/lib/se-selinux_.h b/gl/lib/se-selinux_.h
new file mode 100644 (file)
index 0000000..b08c7ee
--- /dev/null
@@ -0,0 +1,54 @@
+#ifndef SELINUX_SELINUX_H
+# define SELINUX_SELINUX_H
+
+# include <sys/types.h>
+# include <errno.h>
+/* Some systems don't have ENOSYS.  */
+# ifndef ENOSYS
+#  ifdef ENOTSUP
+#   define ENOSYS ENOTSUP
+#  else
+/* Some systems don't have ENOTSUP either.  */
+#   define ENOSYS EINVAL
+#  endif
+# endif
+
+typedef unsigned short security_class_t;
+# define security_context_t char*
+# define is_selinux_enabled() 0
+
+static inline int getcon (security_context_t *con) { errno = ENOTSUP; return -1; }
+static inline void freecon (security_context_t con) {}
+
+
+static inline int getfscreatecon (security_context_t *con)
+  { errno = ENOTSUP; return -1; }
+static inline int setfscreatecon (security_context_t con)
+  { errno = ENOTSUP; return -1; }
+static inline int matchpathcon (char const *s, mode_t m,
+                               security_context_t *con)
+  { errno = ENOTSUP; return -1; }
+
+static inline int getfilecon (char const *s, security_context_t *con)
+  { errno = ENOTSUP; return -1; }
+static inline int lgetfilecon (char const *s, security_context_t *con)
+  { errno = ENOTSUP; return -1; }
+static inline int setfilecon (char const *s, security_context_t con)
+  { errno = ENOTSUP; return -1; }
+static inline int lsetfilecon (char const *s, security_context_t con)
+  { errno = ENOTSUP; return -1; }
+static inline int fsetfilecon (int fd, security_context_t con)
+  { errno = ENOTSUP; return -1; }
+
+static inline int security_check_context (security_context_t con)
+  { errno = ENOTSUP; return -1; }
+static inline int security_check_context_raw (security_context_t con)
+  { errno = ENOTSUP; return -1; }
+static inline int setexeccon (security_context_t con)
+  { errno = ENOTSUP; return -1; }
+static inline int security_compute_create (security_context_t scon,
+                                          security_context_t tcon,
+                                          security_class_t tclass,
+                                          security_context_t *newcon)
+  { errno = ENOTSUP; return -1; }
+#endif
diff --git a/gl/lib/selinux-at.c b/gl/lib/selinux-at.c
new file mode 100644 (file)
index 0000000..ebc41ee
--- /dev/null
@@ -0,0 +1,94 @@
+/* openat-style fd-relative functions for SE Linux
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+/* written by Jim Meyering */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "selinux-at.h"
+#include "openat.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
+#include "save-cwd.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "openat-priv.h"
+
+#define AT_FUNC_NAME getfileconat
+#define AT_FUNC_F1 getfilecon
+#define AT_FUNC_F2 getfilecon
+#define AT_FUNC_USE_F1_COND 1
+#define AT_FUNC_POST_FILE_PARAM_DECLS , security_context_t *con
+#define AT_FUNC_POST_FILE_ARGS        , con
+#include "at-func.c"
+#undef AT_FUNC_NAME
+#undef AT_FUNC_F1
+#undef AT_FUNC_F2
+#undef AT_FUNC_USE_F1_COND
+#undef AT_FUNC_POST_FILE_PARAM_DECLS
+#undef AT_FUNC_POST_FILE_ARGS
+
+#define AT_FUNC_NAME lgetfileconat
+#define AT_FUNC_F1 lgetfilecon
+#define AT_FUNC_F2 lgetfilecon
+#define AT_FUNC_USE_F1_COND 1
+#define AT_FUNC_POST_FILE_PARAM_DECLS , security_context_t *con
+#define AT_FUNC_POST_FILE_ARGS        , con
+#include "at-func.c"
+#undef AT_FUNC_NAME
+#undef AT_FUNC_F1
+#undef AT_FUNC_F2
+#undef AT_FUNC_USE_F1_COND
+#undef AT_FUNC_POST_FILE_PARAM_DECLS
+#undef AT_FUNC_POST_FILE_ARGS
+
+#define AT_FUNC_NAME setfileconat
+#define AT_FUNC_F1 setfilecon
+#define AT_FUNC_F2 setfilecon
+#define AT_FUNC_USE_F1_COND 1
+#define AT_FUNC_POST_FILE_PARAM_DECLS , security_context_t con
+#define AT_FUNC_POST_FILE_ARGS        , con
+#include "at-func.c"
+#undef AT_FUNC_NAME
+#undef AT_FUNC_F1
+#undef AT_FUNC_F2
+#undef AT_FUNC_USE_F1_COND
+#undef AT_FUNC_POST_FILE_PARAM_DECLS
+#undef AT_FUNC_POST_FILE_ARGS
+
+#define AT_FUNC_NAME lsetfileconat
+#define AT_FUNC_F1 lsetfilecon
+#define AT_FUNC_F2 lsetfilecon
+#define AT_FUNC_USE_F1_COND 1
+#define AT_FUNC_POST_FILE_PARAM_DECLS , security_context_t con
+#define AT_FUNC_POST_FILE_ARGS        , con
+#include "at-func.c"
+#undef AT_FUNC_NAME
+#undef AT_FUNC_F1
+#undef AT_FUNC_F2
+#undef AT_FUNC_USE_F1_COND
+#undef AT_FUNC_POST_FILE_PARAM_DECLS
+#undef AT_FUNC_POST_FILE_ARGS
diff --git a/gl/lib/selinux-at.h b/gl/lib/selinux-at.h
new file mode 100644 (file)
index 0000000..f12022c
--- /dev/null
@@ -0,0 +1,24 @@
+/* Prototypes for openat-style fd-relative SELinux functions
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+#include <selinux/selinux.h>
+#include <selinux/context.h>
+
+int  getfileconat (int fd, char const *file, security_context_t *con);
+int lgetfileconat (int fd, char const *file, security_context_t *con);
+int  setfileconat (int fd, char const *file, security_context_t con);
+int lsetfileconat (int fd, char const *file, security_context_t con);
diff --git a/gl/m4/selinux-context-h.m4 b/gl/m4/selinux-context-h.m4
new file mode 100644 (file)
index 0000000..4011dde
--- /dev/null
@@ -0,0 +1,18 @@
+# serial 1   -*- Autoconf -*-
+# Copyright (C) 2006 Free Software Foundation, Inc.
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# From Jim Meyering
+# Provide <selinux/context.h>, if necessary.
+
+AC_DEFUN([gl_HEADERS_SELINUX_CONTEXT_H],
+[
+  AC_LIBSOURCES([se-context_.h])
+  # Check for <selinux/context.h>,
+  AC_CHECK_HEADERS([selinux/context.h],
+                  [SELINUX_CONTEXT_H=],
+                  [SELINUX_CONTEXT_H=selinux/context.h])
+  AC_SUBST([SELINUX_CONTEXT_H])
+])
diff --git a/gl/m4/selinux-selinux-h.m4 b/gl/m4/selinux-selinux-h.m4
new file mode 100644 (file)
index 0000000..13ce2ac
--- /dev/null
@@ -0,0 +1,18 @@
+# serial 1   -*- Autoconf -*-
+# Copyright (C) 2006 Free Software Foundation, Inc.
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# From Jim Meyering
+# Provide <selinux/selinux.h>, if necessary.
+
+AC_DEFUN([gl_HEADERS_SELINUX_SELINUX_H],
+[
+  AC_LIBSOURCES([se-selinux_.h])
+  # Check for <selinux/selinux.h>,
+  AC_CHECK_HEADERS([selinux/selinux.h],
+                  [SELINUX_SELINUX_H=],
+                  [SELINUX_SELINUX_H=selinux/selinux.h])
+  AC_SUBST([SELINUX_SELINUX_H])
+])
diff --git a/gl/modules/selinux-at b/gl/modules/selinux-at
new file mode 100644 (file)
index 0000000..7599083
--- /dev/null
@@ -0,0 +1,32 @@
+Description:
+openat-style fd-relative functions for SE Linux
+
+Files:
+lib/selinux-at.c
+lib/selinux-at.h
+
+Depends-on:
+selinux-h
+
+configure.ac:
+# FIXME: put this in an .m4 file?
+# For runcon.
+AC_CHECK_HEADERS([selinux/flask.h])
+AC_LIBOBJ([selinux-at])
+ac_save_LIBS="$LIBS"
+  AC_SEARCH_LIBS(setfilecon, selinux,
+                 [test "$ac_cv_search_setfilecon" = "none required" ||
+                  LIB_SELINUX=$ac_cv_search_setfilecon])
+  AC_SUBST(LIB_SELINUX)
+LIBS="$ac_save_LIBS"
+
+Makefile.am:
+
+Include:
+selinux-at.h
+
+License:
+LGPL
+
+Maintainer:
+Jim Meyering
diff --git a/gl/modules/selinux-h b/gl/modules/selinux-h
new file mode 100644 (file)
index 0000000..915b9d2
--- /dev/null
@@ -0,0 +1,54 @@
+Description:
+SELinux-related headers for systems that lack them.
+
+Files:
+lib/se-context_.h
+lib/se-selinux_.h
+m4/selinux-context-h.m4
+m4/selinux-selinux-h.m4
+
+Depends-on:
+
+configure.ac:
+gl_HEADERS_SELINUX_SELINUX_H
+gl_HEADERS_SELINUX_CONTEXT_H
+
+Makefile.am:
+BUILT_SOURCES += $(SELINUX_SELINUX_H)
+selinux/selinux.h: se-selinux_.h
+       mkdir -p selinux
+       cp $(srcdir)/se-selinux_.h $@-t
+       chmod a-x $@-t
+       mv $@-t $@
+MOSTLYCLEANFILES += selinux/selinux.h selinux/selinux.h-t
+
+BUILT_SOURCES += $(SELINUX_CONTEXT_H)
+selinux/context.h: se-context_.h
+       mkdir -p selinux
+       cp $(srcdir)/se-context_.h $@-t
+       chmod a-x $@-t
+       mv $@-t $@
+MOSTLYCLEANFILES += selinux/context.h selinux/context.h-t
+MOSTLYCLEANDIRS += selinux
+
+Include:
+#include <selinux/selinux.h>
+#include <selinux/context.h>
+
+License:
+LGPL
+
+Maintainer:
+Jim Meyering
+
+# lib/selinux-at.c
+#
+#   # For runcon.
+#   AC_CHECK_HEADERS([selinux/flask.h])
+#
+#   ac_save_LIBS="$LIBS"
+#   AC_SEARCH_LIBS(setfilecon, selinux,
+#                  [test "$ac_cv_search_setfilecon" = "none required" ||
+#                   LIB_SELINUX=$ac_cv_search_setfilecon])
+#   AC_SUBST(LIB_SELINUX)
+#   LIBS="$ac_save_LIBS"
index 07682169243e7b5982a1fa55fb596f0189dc7882..6a740b65b1b0973d80d822d6f04d07d4c92157e5 100644 (file)
@@ -19,7 +19,7 @@
 EXTRA_PROGRAMS = chroot df hostid nice pinky stty su uname uptime users who
 
 bin_SCRIPTS = groups
-bin_PROGRAMS = [ chgrp chown chmod cp dd dircolors du \
+bin_PROGRAMS = [ chcon chgrp chown chmod cp dd dircolors du \
   ginstall link ln dir vdir ls mkdir \
   mkfifo mknod mv nohup readlink rm rmdir shred stat sync touch unlink \
   cat cksum comm csplit cut expand fmt fold head join md5sum \
@@ -60,6 +60,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/lib
 LDADD = ../lib/libcoreutils.a $(LIBINTL) ../lib/libcoreutils.a
 
 # for eaccess in lib/euidaccess.c.
+chcon_LDADD = $(LDADD) $(LIB_SELINUX)
 cp_LDADD = $(LDADD) $(LIB_EACCESS)
 ginstall_LDADD = $(LDADD) $(LIB_EACCESS)
 mv_LDADD = $(LDADD) $(LIB_EACCESS)
diff --git a/src/chcon.c b/src/chcon.c
new file mode 100644 (file)
index 0000000..66fda1d
--- /dev/null
@@ -0,0 +1,589 @@
+/* chcon -- change security context of files
+   Copyright (C) 2005-2007 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#include "system.h"
+#include "dev-ino.h"
+#include "dirname.h"
+#include "error.h"
+#include "openat.h"
+#include "quote.h"
+#include "quotearg.h"
+#include "root-dev-ino.h"
+#include "selinux-at.h"
+#include "xfts.h"
+
+/* The official name of this program (e.g., no `g' prefix).  */
+#define PROGRAM_NAME "chcon"
+
+#define AUTHORS "Russell Coker", "Jim Meyering"
+
+enum Change_status
+{
+  CH_NOT_APPLIED,
+  CH_SUCCEEDED,
+  CH_FAILED,
+  CH_NO_CHANGE_REQUESTED
+};
+
+enum Verbosity
+{
+  /* Print a message for each file that is processed.  */
+  V_high,
+
+  /* Print a message for each file whose attributes we change.  */
+  V_changes_only,
+
+  /* Do not be verbose.  This is the default. */
+  V_off
+};
+
+/* The name the program was run with. */
+char *program_name;
+
+/* If nonzero, and the systems has support for it, change the context
+   of symbolic links rather than any files they point to.  */
+static bool affect_symlink_referent;
+
+/* If true, change the modes of directories recursively. */
+static bool recurse;
+
+/* Level of verbosity. */
+static bool verbose;
+
+/* Pointer to the device and inode numbers of `/', when --recursive.
+   Otherwise NULL.  */
+static struct dev_ino *root_dev_ino;
+
+/* The name of the context file is being given. */
+static char const *specified_context;
+
+/* Specific components of the context */
+static char const *specified_user;
+static char const *specified_role;
+static char const *specified_range;
+static char const *specified_type;
+
+/* For long options that have no equivalent short option, use a
+   non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
+enum
+{
+  DEREFERENCE_OPTION = CHAR_MAX + 1,
+  NO_PRESERVE_ROOT,
+  PRESERVE_ROOT,
+  REFERENCE_FILE_OPTION
+};
+
+static struct option const long_options[] =
+{
+  {"recursive", no_argument, NULL, 'R'},
+  {"dereference", no_argument, NULL, DEREFERENCE_OPTION},
+  {"no-dereference", no_argument, NULL, 'h'},
+  {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
+  {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
+  {"reference", required_argument, NULL, REFERENCE_FILE_OPTION},
+  {"user", required_argument, NULL, 'u'},
+  {"role", required_argument, NULL, 'r'},
+  {"type", required_argument, NULL, 't'},
+  {"range", required_argument, NULL, 'l'},
+  {"verbose", no_argument, NULL, 'v'},
+  {GETOPT_HELP_OPTION_DECL},
+  {GETOPT_VERSION_OPTION_DECL},
+  {NULL, 0, NULL, 0}
+};
+
+/* Given a security context, CONTEXT, derive a context_t (*RET),
+   setting any portions selected via the global variables, specified_user,
+   specified_role, etc.  */
+static int
+compute_context_from_mask (security_context_t context, context_t *ret)
+{
+  bool ok = true;
+  context_t new_context = context_new (context);
+  if (!new_context)
+    {
+      error (0, errno, _("failed to create security context: %s"),
+            quotearg_colon (context));
+      return 1;
+    }
+
+#define SET_COMPONENT(C, comp)                                         \
+   do                                                                  \
+     {                                                                 \
+       if (specified_ ## comp                                          \
+          && context_ ## comp ## _set ((C), specified_ ## comp))       \
+         {                                                             \
+           error (0, errno,                                            \
+                  _("failed to set %s security context component to %s"), \
+                  #comp, quote (specified_ ## comp));                  \
+           ok = false;                                                 \
+        }                                                              \
+     }                                                                 \
+   while (0)
+
+  SET_COMPONENT (new_context, user);
+  SET_COMPONENT (new_context, range);
+  SET_COMPONENT (new_context, role);
+  SET_COMPONENT (new_context, type);
+
+  if (!ok)
+    {
+      int saved_errno = errno;
+      context_free (new_context);
+      errno = saved_errno;
+      return 1;
+    }
+
+  *ret = new_context;
+  return 0;
+}
+
+/* Change the context of FILE, using specified components.
+   If it is a directory and -R is given, recurse.
+   Return 0 if successful, 1 if errors occurred. */
+
+static int
+change_file_context (int fd, char const *file)
+{
+  security_context_t file_context = NULL;
+  context_t context;
+  security_context_t context_string;
+  int errors = 0;
+
+  if (specified_context == NULL)
+    {
+      int status = (affect_symlink_referent
+                   ? getfileconat (fd, file, &file_context)
+                   : lgetfileconat (fd, file, &file_context));
+
+      if (status < 0 && errno != ENODATA)
+       {
+         error (0, errno, _("failed to get security context of %s"),
+                quote (file));
+         return 1;
+       }
+
+      /* If the file doesn't have a context, and we're not setting all of
+        the context components, there isn't really an obvious default.
+        Thus, we just give up. */
+      if (file_context == NULL)
+       {
+         error (0, 0, _("can't apply partial context to unlabeled file %s"),
+                quote (file));
+         return 1;
+       }
+
+      if (compute_context_from_mask (file_context, &context))
+       return 1;
+    }
+  else
+    {
+      /* FIXME: this should be done exactly once, in main.  */
+      context = context_new (specified_context);
+      if (!context)
+       abort ();
+    }
+
+  context_string = context_str (context);
+
+  if (file_context == NULL || ! STREQ (context_string, file_context))
+    {
+      int fail = (affect_symlink_referent
+                 ?  setfileconat (fd, file, context_string)
+                 : lsetfileconat (fd, file, context_string));
+
+      if (fail)
+       {
+         errors = 1;
+         error (0, errno, _("failed to change context of %s to %s"),
+                quote_n (0, file), quote_n (1, context_string));
+       }
+    }
+
+  context_free (context);
+  freecon (file_context);
+
+  return errors;
+}
+
+/* Change the context of FILE.
+   Return true if successful.  This function is called
+   once for every file system object that fts encounters.  */
+
+static bool
+process_file (FTS *fts, FTSENT *ent)
+{
+  char const *file_full_name = ent->fts_path;
+  char const *file = ent->fts_accpath;
+  const struct stat *file_stats = ent->fts_statp;
+  bool ok = true;
+
+  switch (ent->fts_info)
+    {
+    case FTS_D:
+      if (recurse)
+       {
+         if (ROOT_DEV_INO_CHECK (root_dev_ino, ent->fts_statp))
+           {
+             /* This happens e.g., with "chcon -R --preserve-root ... /"
+                and with "chcon -RH --preserve-root ... symlink-to-root".  */
+             ROOT_DEV_INO_WARN (file_full_name);
+             /* Tell fts not to traverse into this hierarchy.  */
+             fts_set (fts, ent, FTS_SKIP);
+             /* Ensure that we do not process "/" on the second visit.  */
+             ent = fts_read (fts);
+             return false;
+           }
+         return true;
+       }
+      break;
+
+    case FTS_DP:
+      if (! recurse)
+       return true;
+      break;
+
+    case FTS_NS:
+      /* For a top-level file or directory, this FTS_NS (stat failed)
+        indicator is determined at the time of the initial fts_open call.
+        With programs like chmod, chown, and chgrp, that modify
+        permissions, it is possible that the file in question is
+        accessible when control reaches this point.  So, if this is
+        the first time we've seen the FTS_NS for this file, tell
+        fts_read to stat it "again".  */
+      if (ent->fts_level == 0 && ent->fts_number == 0)
+       {
+         ent->fts_number = 1;
+         fts_set (fts, ent, FTS_AGAIN);
+         return true;
+       }
+      error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
+      ok = false;
+      break;
+
+    case FTS_ERR:
+      error (0, ent->fts_errno, _("%s"), quote (file_full_name));
+      ok = false;
+      break;
+
+    case FTS_DNR:
+      error (0, ent->fts_errno, _("cannot read directory %s"),
+            quote (file_full_name));
+      ok = false;
+      break;
+
+    default:
+      break;
+    }
+
+  if (ent->fts_info == FTS_DP
+      && ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
+    {
+      ROOT_DEV_INO_WARN (file_full_name);
+      ok = false;
+    }
+
+  if (ok)
+    {
+      if (verbose)
+       printf (_("changing security context of %s"),
+               quote (file_full_name));
+
+      if (change_file_context (fts->fts_cwd_fd, file) != 0)
+       ok = false;
+    }
+
+  if ( ! recurse)
+    fts_set (fts, ent, FTS_SKIP);
+
+  return ok;
+}
+
+/* Recursively operate on the specified FILES (the last entry
+   of which is NULL).  BIT_FLAGS controls how fts works.
+   Return true if successful.  */
+
+static bool
+process_files (char **files, int bit_flags)
+{
+  bool ok = true;
+
+  FTS *fts = xfts_open (files, bit_flags, NULL);
+
+  while (1)
+    {
+      FTSENT *ent;
+
+      ent = fts_read (fts);
+      if (ent == NULL)
+       {
+         if (errno != 0)
+           {
+             /* FIXME: try to give a better message  */
+             error (0, errno, _("fts_read failed"));
+             ok = false;
+           }
+         break;
+       }
+
+      ok &= process_file (fts, ent);
+    }
+
+  /* Ignore failure, since the only way it can do so is in failing to
+     return to the original directory, and since we're about to exit,
+     that doesn't matter.  */
+  fts_close (fts);
+
+  return ok;
+}
+
+void
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+            program_name);
+  else
+    {
+      printf (_("\
+Usage: %s [OPTION]... CONTEXT FILE...\n\
+  or:  %s [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE...\n\
+  or:  %s [OPTION]... --reference=RFILE FILE...\n\
+"),
+       program_name, program_name, program_name);
+      fputs (_("\
+Change the security context of each FILE to CONTEXT.\n\
+With --reference, change the security context of each FILE to that of RFILE.\n\
+\n\
+  -c, --changes          like verbose but report only when a change is made\n\
+  -h, --no-dereference   affect symbolic links instead of any referenced file\n\
+                         (available only on systems with lchown system call)\n\
+      --reference=RFILE  use RFILE's security context rather than specifying\n\
+                         a CONTEXT value\n\
+  -R, --recursive        operate on files and directories recursively\n\
+  -v, --verbose          output a diagnostic for every file processed\n\
+"), stdout);
+      fputs (_("\
+  -u, --user=USER        set user USER in the target security context\n\
+  -r, --role=ROLE        set role ROLE in the target security context\n\
+  -t, --type=TYPE        set type TYPE in the target security context\n\
+  -l, --range=RANGE      set range RANGE in the target security context\n\
+\n\
+"), stdout);
+      fputs (_("\
+The following options modify how a hierarchy is traversed when the -R\n\
+option is also specified.  If more than one is specified, only the final\n\
+one takes effect.\n\
+\n\
+  -H                     if a command line argument is a symbolic link\n\
+                         to a directory, traverse it\n\
+  -L                     traverse every symbolic link to a directory\n\
+                         encountered\n\
+  -P                     do not traverse any symbolic links (default)\n\
+\n\
+"), stdout);
+      fputs (HELP_OPTION_DESCRIPTION, stdout);
+      fputs (VERSION_OPTION_DESCRIPTION, stdout);
+    }
+  exit (status);
+}
+
+int
+main (int argc, char **argv)
+{
+  security_context_t ref_context = NULL;
+
+  /* Bit flags that control how fts works.  */
+  int bit_flags = FTS_PHYSICAL;
+
+  /* 1 if --dereference, 0 if --no-dereference, -1 if neither has been
+     specified.  */
+  int dereference = -1;
+
+  bool ok;
+  bool preserve_root = false;
+  bool component_specified = false;
+  char *reference_file = NULL;
+  int optc;
+
+  initialize_main (&argc, &argv);
+  program_name = argv[0];
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+
+  atexit (close_stdout);
+
+  while ((optc = getopt_long (argc, argv, "HLPRchvu:r:t:l:", long_options, NULL))
+        != -1)
+    {
+      switch (optc)
+       {
+       case 'H': /* Traverse command-line symlinks-to-directories.  */
+         bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL;
+         break;
+
+       case 'L': /* Traverse all symlinks-to-directories.  */
+         bit_flags = FTS_LOGICAL;
+         break;
+
+       case 'P': /* Traverse no symlinks-to-directories.  */
+         bit_flags = FTS_PHYSICAL;
+         break;
+
+       case 'h': /* --no-dereference: affect symlinks */
+         dereference = 0;
+         break;
+
+       case DEREFERENCE_OPTION: /* --dereference: affect the referent
+                                   of each symlink */
+         dereference = 1;
+         break;
+
+       case NO_PRESERVE_ROOT:
+         preserve_root = false;
+         break;
+
+       case PRESERVE_ROOT:
+         preserve_root = true;
+         break;
+
+       case REFERENCE_FILE_OPTION:
+         reference_file = optarg;
+         break;
+
+       case 'R':
+         recurse = true;
+         break;
+
+       case 'f':
+         /* ignore */
+         break;
+
+       case 'v':
+         verbose = true;
+         break;
+
+       case 'u':
+         specified_user = optarg;
+         component_specified = true;
+         break;
+
+       case 'r':
+         specified_role = optarg;
+         component_specified = true;
+         break;
+
+       case 't':
+         specified_type = optarg;
+         component_specified = true;
+         break;
+
+       case 'l':
+         specified_range = optarg;
+         component_specified = true;
+         break;
+
+       case_GETOPT_HELP_CHAR;
+       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+       default:
+         usage (EXIT_FAILURE);
+       }
+    }
+
+  if (recurse)
+    {
+      if (bit_flags == FTS_PHYSICAL)
+       {
+         if (dereference == 1)
+           error (EXIT_FAILURE, 0,
+                  _("-R --dereference requires either -H or -L"));
+         affect_symlink_referent = false;
+       }
+      else
+       {
+         if (dereference == 0)
+           error (EXIT_FAILURE, 0, _("-R -h requires -P"));
+         affect_symlink_referent = true;
+       }
+    }
+  else
+    {
+      bit_flags = FTS_PHYSICAL;
+      affect_symlink_referent = (dereference != 0);
+    }
+
+  if (argc - optind < (reference_file || component_specified ? 1 : 2))
+    {
+      if (argc <= optind)
+       error (0, 0, _("missing operand"));
+      else
+       error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
+      usage (EXIT_FAILURE);
+    }
+
+  if (reference_file)
+    {
+      if (getfilecon (reference_file, &ref_context) < 0)
+       error (EXIT_FAILURE, errno, _("failed to get security context of %s"),
+              quote (reference_file));
+
+      specified_context = ref_context;
+    }
+  else if (component_specified)
+    {
+      /* FIXME: it's already null, so this is a no-op. */
+      specified_context = NULL;
+    }
+  else
+    {
+      context_t context;
+      specified_context = argv[optind++];
+      context = context_new (specified_context);
+      if (!context)
+       error (EXIT_FAILURE, 0, _("invalid context: %s"),
+              quotearg_colon (specified_context));
+      context_free (context);
+    }
+
+  if (reference_file && component_specified)
+    {
+      error (0, 0, _("conflicting security context specifiers given"));
+      usage (1);
+    }
+
+  if (recurse & preserve_root)
+    {
+      static struct dev_ino dev_ino_buf;
+      root_dev_ino = get_root_dev_ino (&dev_ino_buf);
+      if (root_dev_ino == NULL)
+       error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
+              quote ("/"));
+    }
+  else
+    {
+      root_dev_ino = NULL;
+    }
+
+  ok = process_files (argv + optind, bit_flags | FTS_NOSTAT);
+
+  exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}