compare_acls(acl_t acl, struct archive_test_acl_t *myacls, int n)
#endif
{
	int *marker;
	int matched;
	int i;
#if HAVE_SUN_ACL
	int e;
	aclent_t *acl_entry;
#else
	int entry_id = ACL_FIRST_ENTRY;
	acl_entry_t acl_entry;
#endif

	/* Count ACL entries in myacls array and allocate an indirect array. */
	marker = malloc(sizeof(marker[0]) * n);
	if (marker == NULL)
		return;
	for (i = 0; i < n; i++)
		marker[i] = i;

	/*
	 * Iterate over acls in system acl object, try to match each
	 * one with an item in the myacls array.
	 */
#if HAVE_SUN_ACL
	for(e = 0; e < acl->acl_cnt; e++) {
		acl_entry = &((aclent_t *)acl->acl_aclp)[e];
#else
	while (1 == acl_get_entry(acl, entry_id, &acl_entry)) {
		/* After the first time... */
		entry_id = ACL_NEXT_ENTRY;
#endif

		/* Search for a matching entry (tag and qualifier) */
		for (i = 0, matched = 0; i < n && !matched; i++) {
			if (acl_match(acl_entry, &myacls[marker[i]])) {
				/* We found a match; remove it. */
				marker[i] = marker[n - 1];
				n--;
				matched = 1;
			}
		}

		/* TODO: Print out more details in this case. */
		failure("ACL entry on file that shouldn't be there");
		assert(matched == 1);
	}

	/* Dump entries in the myacls array that weren't in the system acl. */
	for (i = 0; i < n; ++i) {
		failure(" ACL entry missing from file: "
		    "type=%#010x,permset=%#010x,tag=%d,qual=%d,name=``%s''\n",
		    myacls[marker[i]].type, myacls[marker[i]].permset,
		    myacls[marker[i]].tag, myacls[marker[i]].qual,
		    myacls[marker[i]].name);
		assert(0); /* Record this as a failure. */
	}
	free(marker);
}

#endif


/*
 * Verify ACL restore-to-disk.  This test is Platform-specific.
 */

DEFINE_TEST(test_acl_platform_posix1e_restore)
{
#if !HAVE_SUN_ACL && !HAVE_POSIX_ACL
	skipping("POSIX.1e ACLs are not supported on this platform");
#else	/* HAVE_SUN_ACL || HAVE_POSIX_ACL */
	struct stat st;
	struct archive *a;
	struct archive_entry *ae;
	int n, fd;
	char *func;
#if HAVE_SUN_ACL
	acl_t *acl, *acl2;
#else
	acl_t acl;
#endif

	/*
	 * First, do a quick manual set/read of ACL data to
	 * verify that the local filesystem does support ACLs.
	 * If it doesn't, we'll simply skip the remaining tests.
	 */
#if HAVE_SUN_ACL
	n = acl_fromtext("user::rwx,user:1:rw-,group::rwx,group:15:r-x,other:rwx,mask:rwx", &acl);
	failure("acl_fromtext(): errno = %d (%s)", errno, strerror(errno));
	assertEqualInt(0, n);
#else
	acl = acl_from_text("u::rwx,u:1:rw,g::rwx,g:15:rx,o::rwx,m::rwx");
	failure("acl_from_text(): errno = %d (%s)", errno, strerror(errno));
	assert((void *)acl != NULL);
#endif

	/* Create a test file and try ACL on it. */
	fd = open("pretest", O_WRONLY | O_CREAT | O_EXCL, 0777);
	failure("Could not create test file?!");
	if (!assert(fd >= 0)) {
		acl_free(acl);
		return;
	}

#if HAVE_SUN_ACL
	n = facl_get(fd, 0, &acl2);
	if (n != 0) {
		close(fd);
		acl_free(acl);
	}
	if (errno == ENOSYS) {
		skipping("POSIX.1e ACLs are not supported on this filesystem");
		return;
	}
	failure("facl_get(): errno = %d (%s)", errno, strerror(errno));
	assertEqualInt(0, n);

	if (acl2->acl_type != ACLENT_T) {
		acl_free(acl2);
		skipping("POSIX.1e ACLs are not supported on this filesystem");
		return;
	}
	acl_free(acl2);

	func = "facl_set()";
	n = facl_set(fd, acl);
#else
	func = "acl_set_fd()";
	n = acl_set_fd(fd, acl);
#endif
	acl_free(acl);
	if (n != 0) {
#if HAVE_SUN_ACL
		if (errno == ENOSYS)
#else
		if (errno == EOPNOTSUPP || errno == EINVAL)
#endif
		{
			close(fd);
			skipping("POSIX.1e ACLs are not supported on this filesystem");
			return;
		}
	}
	failure("%s: errno = %d (%s)", func, errno, strerror(errno));
	assertEqualInt(0, n);

#if HAVE_SUN_ACL

#endif
	close(fd);

	/* Create a write-to-disk object. */
	assert(NULL != (a = archive_write_disk_new()));
	archive_write_disk_set_options(a,
	    ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL);

	/* Populate an archive entry with some metadata, including ACL info */
	ae = archive_entry_new();
	assert(ae != NULL);
	archive_entry_set_pathname(ae, "test0");
	archive_entry_set_mtime(ae, 123456, 7890);
	archive_entry_set_size(ae, 0);
	assertEntrySetAcls(ae, acls2, sizeof(acls2)/sizeof(acls2[0]));
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	archive_entry_free(ae);

	/* Close the archive. */
	assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
	assertEqualInt(ARCHIVE_OK, archive_write_free(a));

	/* Verify the data on disk. */
	assertEqualInt(0, stat("test0", &st));
	assertEqualInt(st.st_mtime, 123456);
#if HAVE_SUN_ACL
	n = acl_get("test0", 0, &acl);
	failure("acl_get(): errno = %d (%s)", errno, strerror(errno));
	assertEqualInt(0, n);
#else
	acl = acl_get_file("test0", ACL_TYPE_ACCESS);
	failure("acl_get_file(): errno = %d (%s)", errno, strerror(errno));
	assert(acl != (acl_t)NULL);
#endif
	compare_acls(acl, acls2, sizeof(acls2)/sizeof(acls2[0]));
	acl_free(acl);
#endif	/* HAVE_SUN_ACL || HAVE_POSIX_ACL */
}

/*
 * Verify ACL read-from-disk.  This test is Platform-specific.
 */
DEFINE_TEST(test_acl_platform_posix1e_read)
{
#if !HAVE_SUN_ACL && !HAVE_POSIX_ACL
	skipping("POSIX.1e ACLs are not supported on this platform");
#else
	struct archive *a;
	struct archive_entry *ae;
	int n, fd, flags, dflags;
	char *func, *acl_text;
	const char *acl1_text, *acl2_text, *acl3_text;
#if HAVE_SUN_ACL
	acl_t *acl, *acl1, *acl2, *acl3;
#else
	acl_t acl1, acl2, acl3;
#endif

	/*
	 * Manually construct a directory and two files with
	 * different ACLs.  This also serves to verify that ACLs
	 * are supported on the local filesystem.
	 */

	/* Create a test file f1 with acl1 */
#if HAVE_SUN_ACL
	acl1_text = "user::rwx,"
	    "group::rwx,"
	    "other:rwx,"
	    "user:1:rw-,"
	    "group:15:r-x,"
	    "mask:rwx";
	n = acl_fromtext(acl1_text, &acl1);
	failure("acl_fromtext(): errno = %d (%s)", errno, strerror(errno));
	assertEqualInt(0, n);
#else
	acl1_text = "user::rwx\n"
	    "group::rwx\n"
	    "other::rwx\n"
	    "user:1:rw-\n"
	    "group:15:r-x\n"
	    "mask::rwx";
	acl1 = acl_from_text(acl1_text);
	failure("acl_from_text(): errno = %d (%s)", errno, strerror(errno));
	assert((void *)acl1 != NULL);
#endif
	fd = open("f1", O_WRONLY | O_CREAT | O_EXCL, 0777);
	failure("Could not create test file?!");
	if (!assert(fd >= 0)) {
		acl_free(acl1);
		return;
	}
#if HAVE_SUN_ACL
	/* Check if Solaris filesystem supports POSIX.1e ACLs */
	n = facl_get(fd, 0, &acl);
	if (n != 0)
		close(fd);
	if (n != 0 && errno == ENOSYS) {
		acl_free(acl1);
		skipping("POSIX.1e ACLs are not supported on this filesystem");
		return;
	}
	failure("facl_get(): errno = %d (%s)", errno, strerror(errno));
	assertEqualInt(0, n);

	if (acl->acl_type != ACLENT_T) {
		acl_free(acl);
		acl_free(acl1);
		close(fd);
		skipping("POSIX.1e ACLs are not supported on this filesystem");
		return;
	}

	func = "facl_set()";
	n = facl_set(fd, acl1);
#else
	func = "acl_set_fd()";
	n = acl_set_fd(fd, acl1);
#endif
	acl_free(acl1);

	if (n != 0) {
#if HAVE_SUN_ACL
		if (errno == ENOSYS)
#else
		if (errno == EOPNOTSUPP || errno == EINVAL)
#endif
		{
			close(fd);
			skipping("POSIX.1e ACLs are not supported on this filesystem");
			return;
		}
	}
	failure("%s: errno = %d (%s)", func, errno, strerror(errno));
	assertEqualInt(0, n);

	close(fd);

	assertMakeDir("d", 0700);

	/*
	 * Create file d/f1 with acl2
	 *
	 * This differs from acl1 in the u:1: and g:15: permissions.
	 *
	 * This file deliberately has the same name but a different ACL.
	 * Github Issue #777 explains how libarchive's directory traversal
	 * did not always correctly enter directories before attempting
	 * to read ACLs, resulting in reading the ACL from a like-named
	 * file in the wrong directory.
	 */
#if HAVE_SUN_ACL
	acl2_text = "user::rwx,"
	    "group::rwx,"
	    "other:---,"
	    "user:1:r--,"
	    "group:15:r--,"
	    "mask:rwx";
	n = acl_fromtext(acl2_text, &acl2);
	failure("acl_fromtext(): errno = %d (%s)", errno, strerror(errno));
	assertEqualInt(0, n);
#else
	acl2_text = "user::rwx\n"
	    "group::rwx\n"
	    "other::---\n"
	    "user:1:r--\n"
	    "group:15:r--\n"
	    "mask::rwx";
	acl2 = acl_from_text(acl2_text);
	failure("acl_from_text(): errno = %d (%s)", errno, strerror(errno));
	assert((void *)acl2 != NULL);
#endif
	fd = open("d/f1", O_WRONLY | O_CREAT | O_EXCL, 0777);
	failure("Could not create test file?!");
	if (!assert(fd >= 0)) {
		acl_free(acl2);
		return;
	}
#if HAVE_SUN_ACL
	func = "facl_set()";
	n = facl_set(fd, acl2);
#else
	func = "acl_set_fd()";
	n = acl_set_fd(fd, acl2);
#endif
	acl_free(acl2);
	if (n != 0)
		close(fd);
	failure("%s: errno = %d (%s)", func, errno, strerror(errno));
	assertEqualInt(0, n);
	close(fd);

	/* Create nested directory d2 with default ACLs */
	assertMakeDir("d/d2", 0755);

#if HAVE_SUN_ACL
	acl3_text = "user::rwx,"
	    "group::r-x,"
	    "other:r-x,"
	    "user:2:r--,"
	    "group:16:-w-,"
	    "mask:rwx,"
	    "default:user::rwx,"
	    "default:user:1:r--,"
	    "default:group::r-x,"
	    "default:group:15:r--,"
	    "default:mask:rwx,"
	    "default:other:r-x";
	n = acl_fromtext(acl3_text, &acl3);
	failure("acl_fromtext(): errno = %d (%s)", errno, strerror(errno));
	assertEqualInt(0, n);
#else
	acl3_text = "user::rwx\n"
	    "user:1:r--\n"
	    "group::r-x\n"
	    "group:15:r--\n"
	    "mask::rwx\n"
	    "other::r-x";
	acl3 = acl_from_text(acl3_text);
	failure("acl_from_text(): errno = %d (%s)", errno, strerror(errno));
	assert((void *)acl3 != NULL);
#endif

#if HAVE_SUN_ACL
	func = "acl_set()";
	n = acl_set("d/d2", acl3);
#else
	func = "acl_set_file()";
	n = acl_set_file("d/d2", ACL_TYPE_DEFAULT, acl3);
#endif
	acl_free(acl3);

	failure("%s: errno = %d (%s)", func, errno, strerror(errno));
	assertEqualInt(0, n);

	/* Create a read-from-disk object. */
	assert(NULL != (a = archive_read_disk_new()));
	assertEqualIntA(a, ARCHIVE_OK, archive_read_disk_open(a, "."));
	assert(NULL != (ae = archive_entry_new()));

#if HAVE_SUN_ACL
	flags = ARCHIVE_ENTRY_ACL_TYPE_POSIX1E
	    | ARCHIVE_ENTRY_ACL_STYLE_SEPARATOR_COMMA
	    | ARCHIVE_ENTRY_ACL_STYLE_SOLARIS;
	dflags = flags;
#else
	flags = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
	dflags = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT;
#endif

	/* Walk the dir until we see both of the files */
	while (ARCHIVE_OK == archive_read_next_header2(a, ae)) {
		archive_read_disk_descend(a);
		if (strcmp(archive_entry_pathname(ae), "./f1") == 0) {
			acl_text = archive_entry_acl_to_text(ae, NULL, flags);
			assertEqualString(acl_text, acl1_text);
			free(acl_text);
		} else if (strcmp(archive_entry_pathname(ae), "./d/f1") == 0) {
			acl_text = archive_entry_acl_to_text(ae, NULL, flags);
			assertEqualString(acl_text, acl2_text);
			free(acl_text);
		} else if (strcmp(archive_entry_pathname(ae), "./d/d2") == 0) {
			acl_text = archive_entry_acl_to_text(ae, NULL, dflags);
			assertEqualString(acl_text, acl3_text);
			free(acl_text);
		}
	}

	archive_entry_free(ae);
	assertEqualInt(ARCHIVE_OK, archive_free(a));
#endif
}
Esempio n. 2
0
static int
qcopy_acl (const char *src_name, int source_desc, const char *dst_name,
           int dest_desc, mode_t mode)
{
#if USE_ACL && HAVE_ACL_GET_FILE
  /* POSIX 1003.1e (draft 17 -- abandoned) specific version.  */
  /* Linux, FreeBSD, MacOS X, IRIX, Tru64 */
# if MODE_INSIDE_ACL
  /* Linux, FreeBSD, IRIX, Tru64 */

  acl_t acl;
  int ret;

  if (HAVE_ACL_GET_FD && source_desc != -1)
    acl = acl_get_fd (source_desc);
  else
    acl = acl_get_file (src_name, ACL_TYPE_ACCESS);
  if (acl == NULL)
    {
      if (ACL_NOT_WELL_SUPPORTED (errno))
        return qset_acl (dst_name, dest_desc, mode);
      else
        return -2;
    }

  if (HAVE_ACL_SET_FD && dest_desc != -1)
    ret = acl_set_fd (dest_desc, acl);
  else
    ret = acl_set_file (dst_name, ACL_TYPE_ACCESS, acl);
  if (ret != 0)
    {
      int saved_errno = errno;

      if (ACL_NOT_WELL_SUPPORTED (errno) && !acl_access_nontrivial (acl))
        {
          acl_free (acl);
          return chmod_or_fchmod (dst_name, dest_desc, mode);
        }
      else
        {
          acl_free (acl);
          chmod_or_fchmod (dst_name, dest_desc, mode);
          errno = saved_errno;
          return -1;
        }
    }
  else
    acl_free (acl);

  if (mode & (S_ISUID | S_ISGID | S_ISVTX))
    {
      /* We did not call chmod so far, and either the mode and the ACL are
         separate or special bits are to be set which don't fit into ACLs.  */

      if (chmod_or_fchmod (dst_name, dest_desc, mode) != 0)
        return -1;
    }

  if (S_ISDIR (mode))
    {
      acl = acl_get_file (src_name, ACL_TYPE_DEFAULT);
      if (acl == NULL)
        return -2;

      if (acl_set_file (dst_name, ACL_TYPE_DEFAULT, acl))
        {
          int saved_errno = errno;

          acl_free (acl);
          errno = saved_errno;
          return -1;
        }
      else
        acl_free (acl);
    }
  return 0;

# else /* !MODE_INSIDE_ACL */
  /* MacOS X */

#  if !HAVE_ACL_TYPE_EXTENDED
#   error Must have ACL_TYPE_EXTENDED
#  endif

  /* On MacOS X,  acl_get_file (name, ACL_TYPE_ACCESS)
     and          acl_get_file (name, ACL_TYPE_DEFAULT)
     always return NULL / EINVAL.  You have to use
                  acl_get_file (name, ACL_TYPE_EXTENDED)
     or           acl_get_fd (open (name, ...))
     to retrieve an ACL.
     On the other hand,
                  acl_set_file (name, ACL_TYPE_ACCESS, acl)
     and          acl_set_file (name, ACL_TYPE_DEFAULT, acl)
     have the same effect as
                  acl_set_file (name, ACL_TYPE_EXTENDED, acl):
     Each of these calls sets the file's ACL.  */

  acl_t acl;
  int ret;

  if (HAVE_ACL_GET_FD && source_desc != -1)
    acl = acl_get_fd (source_desc);
  else
    acl = acl_get_file (src_name, ACL_TYPE_EXTENDED);
  if (acl == NULL)
    {
      if (ACL_NOT_WELL_SUPPORTED (errno))
        return qset_acl (dst_name, dest_desc, mode);
      else
        return -2;
    }

  if (HAVE_ACL_SET_FD && dest_desc != -1)
    ret = acl_set_fd (dest_desc, acl);
  else
    ret = acl_set_file (dst_name, ACL_TYPE_EXTENDED, acl);
  if (ret != 0)
    {
      int saved_errno = errno;

      if (ACL_NOT_WELL_SUPPORTED (errno) && !acl_extended_nontrivial (acl))
        {
          acl_free (acl);
          return chmod_or_fchmod (dst_name, dest_desc, mode);
        }
      else
        {
          acl_free (acl);
          chmod_or_fchmod (dst_name, dest_desc, mode);
          errno = saved_errno;
          return -1;
        }
    }
  else
    acl_free (acl);

  /* Since !MODE_INSIDE_ACL, we have to call chmod explicitly.  */
  return chmod_or_fchmod (dst_name, dest_desc, mode);

# endif

#elif USE_ACL && defined GETACL /* Solaris, Cygwin, not HP-UX */

# if defined ACL_NO_TRIVIAL
  /* Solaris 10 (newer version), which has additional API declared in
     <sys/acl.h> (acl_t) and implemented in libsec (acl_set, acl_trivial,
     acl_fromtext, ...).  */

  int ret;
  acl_t *aclp = NULL;
  ret = (source_desc < 0
         ? acl_get (src_name, ACL_NO_TRIVIAL, &aclp)
         : facl_get (source_desc, ACL_NO_TRIVIAL, &aclp));
  if (ret != 0 && errno != ENOSYS)
    return -2;

  ret = qset_acl (dst_name, dest_desc, mode);
  if (ret != 0)
    return -1;

  if (aclp)
    {
      ret = (dest_desc < 0
             ? acl_set (dst_name, aclp)
             : facl_set (dest_desc, aclp));
      if (ret != 0)
        {
          int saved_errno = errno;

          acl_free (aclp);
          errno = saved_errno;
          return -1;
        }
      acl_free (aclp);
    }

  return 0;

# else /* Solaris, Cygwin, general case */

  /* Solaris 2.5 through Solaris 10, Cygwin, and contemporaneous versions
     of Unixware.  The acl() call returns the access and default ACL both
     at once.  */
#  ifdef ACE_GETACL
  int ace_count;
  ace_t *ace_entries;
#  endif
  int count;
  aclent_t *entries;
  int did_chmod;
  int saved_errno;
  int ret;

#  ifdef ACE_GETACL
  /* Solaris also has a different variant of ACLs, used in ZFS and NFSv4
     file systems (whereas the other ones are used in UFS file systems).
     There is an API
       pathconf (name, _PC_ACL_ENABLED)
       fpathconf (desc, _PC_ACL_ENABLED)
     that allows to determine which of the two kinds of ACLs is supported
     for the given file.  But some file systems may implement this call
     incorrectly, so better not use it.
     When fetching the source ACL, we simply fetch both ACL types.
     When setting the destination ACL, we try either ACL types, assuming
     that the kernel will translate the ACL from one form to the other.
     (See in <http://docs.sun.com/app/docs/doc/819-2241/6n4huc7ia?l=en&a=view>
     the description of ENOTSUP.)  */
  for (;;)
    {
      ace_count = (source_desc != -1
                   ? facl (source_desc, ACE_GETACLCNT, 0, NULL)
                   : acl (src_name, ACE_GETACLCNT, 0, NULL));

      if (ace_count < 0)
        {
          if (errno == ENOSYS || errno == EINVAL)
            {
              ace_count = 0;
              ace_entries = NULL;
              break;
            }
          else
            return -2;
        }

      if (ace_count == 0)
        {
          ace_entries = NULL;
          break;
        }

      ace_entries = (ace_t *) malloc (ace_count * sizeof (ace_t));
      if (ace_entries == NULL)
        {
          errno = ENOMEM;
          return -2;
        }

      if ((source_desc != -1
           ? facl (source_desc, ACE_GETACL, ace_count, ace_entries)
           : acl (src_name, ACE_GETACL, ace_count, ace_entries))
          == ace_count)
        break;
      /* Huh? The number of ACL entries changed since the last call.
         Repeat.  */
    }
#  endif

  for (;;)
    {
      count = (source_desc != -1
               ? facl (source_desc, GETACLCNT, 0, NULL)
               : acl (src_name, GETACLCNT, 0, NULL));

      if (count < 0)
        {
          if (errno == ENOSYS || errno == ENOTSUP || errno == EOPNOTSUPP)
            {
              count = 0;
              entries = NULL;
              break;
            }
          else
            return -2;
        }

      if (count == 0)
        {
          entries = NULL;
          break;
        }

      entries = (aclent_t *) malloc (count * sizeof (aclent_t));
      if (entries == NULL)
        {
          errno = ENOMEM;
          return -2;
        }

      if ((source_desc != -1
           ? facl (source_desc, GETACL, count, entries)
           : acl (src_name, GETACL, count, entries))
          == count)
        break;
      /* Huh? The number of ACL entries changed since the last call.
         Repeat.  */
    }

  /* Is there an ACL of either kind?  */
#  ifdef ACE_GETACL
  if (ace_count == 0)
#  endif
    if (count == 0)
      return qset_acl (dst_name, dest_desc, mode);

  did_chmod = 0; /* set to 1 once the mode bits in 0777 have been set */
  saved_errno = 0; /* the first non-ignorable error code */

  if (!MODE_INSIDE_ACL)
    {
      /* On Cygwin, it is necessary to call chmod before acl, because
         chmod can change the contents of the ACL (in ways that don't
         change the allowed accesses, but still visible).  */
      if (chmod_or_fchmod (dst_name, dest_desc, mode) != 0)
        saved_errno = errno;
      did_chmod = 1;
    }

  /* If both ace_entries and entries are available, try SETACL before
     ACE_SETACL, because SETACL cannot fail with ENOTSUP whereas ACE_SETACL
     can.  */

  if (count > 0)
    {
      ret = (dest_desc != -1
             ? facl (dest_desc, SETACL, count, entries)
             : acl (dst_name, SETACL, count, entries));
      if (ret < 0 && saved_errno == 0)
        {
          saved_errno = errno;
          if ((errno == ENOSYS || errno == EOPNOTSUPP)
              && !acl_nontrivial (count, entries))
            saved_errno = 0;
        }
      else
        did_chmod = 1;
    }
  free (entries);

#  ifdef ACE_GETACL
  if (ace_count > 0)
    {
      ret = (dest_desc != -1
             ? facl (dest_desc, ACE_SETACL, ace_count, ace_entries)
             : acl (dst_name, ACE_SETACL, ace_count, ace_entries));
      if (ret < 0 && saved_errno == 0)
        {
          saved_errno = errno;
          if ((errno == ENOSYS || errno == EINVAL || errno == ENOTSUP)
              && !acl_ace_nontrivial (ace_count, ace_entries))
            saved_errno = 0;
        }
    }
  free (ace_entries);
#  endif

  if (MODE_INSIDE_ACL
      && did_chmod <= ((mode & (S_ISUID | S_ISGID | S_ISVTX)) ? 1 : 0))
    {
      /* We did not call chmod so far, and either the mode and the ACL are
         separate or special bits are to be set which don't fit into ACLs.  */

      if (chmod_or_fchmod (dst_name, dest_desc, mode) != 0)
        {
          if (saved_errno == 0)
            saved_errno = errno;
        }
    }

  if (saved_errno)
    {
      errno = saved_errno;
      return -1;
    }
  return 0;

# endif

#elif USE_ACL && HAVE_GETACL /* HP-UX */

  int count;
  struct acl_entry entries[NACLENTRIES];
  int ret;

  for (;;)
    {
      count = (source_desc != -1
               ? fgetacl (source_desc, 0, NULL)
               : getacl (src_name, 0, NULL));

      if (count < 0)
        {
          if (errno == ENOSYS || errno == EOPNOTSUPP)
            {
              count = 0;
              break;
            }
          else
            return -2;
        }

      if (count == 0)
        break;

      if (count > NACLENTRIES)
        /* If NACLENTRIES cannot be trusted, use dynamic memory allocation.  */
        abort ();

      if ((source_desc != -1
           ? fgetacl (source_desc, count, entries)
           : getacl (src_name, count, entries))
          == count)
        break;
      /* Huh? The number of ACL entries changed since the last call.
         Repeat.  */
    }

  if (count == 0)
    return qset_acl (dst_name, dest_desc, mode);

  ret = (dest_desc != -1
         ? fsetacl (dest_desc, count, entries)
         : setacl (dst_name, count, entries));
  if (ret < 0)
    {
      int saved_errno = errno;

      if (errno == ENOSYS || errno == EOPNOTSUPP)
        {
          struct stat source_statbuf;

          if ((source_desc != -1
               ? fstat (source_desc, &source_statbuf)
               : stat (src_name, &source_statbuf)) == 0)
            {
              if (!acl_nontrivial (count, entries, &source_statbuf))
                return chmod_or_fchmod (dst_name, dest_desc, mode);
            }
          else
            saved_errno = errno;
        }

      chmod_or_fchmod (dst_name, dest_desc, mode);
      errno = saved_errno;
      return -1;
    }

  if (mode & (S_ISUID | S_ISGID | S_ISVTX))
    {
      /* We did not call chmod so far, and either the mode and the ACL are
         separate or special bits are to be set which don't fit into ACLs.  */

      return chmod_or_fchmod (dst_name, dest_desc, mode);
    }
  return 0;

#elif USE_ACL && HAVE_ACLX_GET && 0 /* AIX */

  /* TODO */

#elif USE_ACL && HAVE_STATACL /* older AIX */

  union { struct acl a; char room[4096]; } u;
  int ret;

  if ((source_desc != -1
       ? fstatacl (source_desc, STX_NORMAL, &u.a, sizeof (u))
       : statacl (src_name, STX_NORMAL, &u.a, sizeof (u)))
      < 0)
    return -2;

  ret = (dest_desc != -1
         ? fchacl (dest_desc, &u.a, u.a.acl_len)
         : chacl (dst_name, &u.a, u.a.acl_len));
  if (ret < 0)
    {
      int saved_errno = errno;

      chmod_or_fchmod (dst_name, dest_desc, mode);
      errno = saved_errno;
      return -1;
    }

  /* No need to call chmod_or_fchmod at this point, since the mode bits
     S_ISUID, S_ISGID, S_ISVTX are also stored in the ACL.  */

  return 0;

#else

  return qset_acl (dst_name, dest_desc, mode);

#endif
}
Esempio n. 3
0
int
copy_acl (const char *src_name, int source_desc, const char *dst_name,
	  int dest_desc, mode_t mode)
{
  int ret;

#if USE_ACL && HAVE_ACL_GET_FILE && HAVE_ACL_SET_FILE && HAVE_ACL_FREE
  /* POSIX 1003.1e (draft 17 -- abandoned) specific version.  */
  /* Linux, FreeBSD, MacOS X, IRIX, Tru64 */

  acl_t acl;
  if (HAVE_ACL_GET_FD && source_desc != -1)
    acl = acl_get_fd (source_desc);
  else
    acl = acl_get_file (src_name, ACL_TYPE_ACCESS);
  if (acl == NULL)
    {
      if (ACL_NOT_WELL_SUPPORTED (errno))
	return set_acl (dst_name, dest_desc, mode);
      else
        {
	  error (0, errno, "%s", quote (src_name));
	  return -1;
	}
    }

  if (HAVE_ACL_SET_FD && dest_desc != -1)
    ret = acl_set_fd (dest_desc, acl);
  else
    ret = acl_set_file (dst_name, ACL_TYPE_ACCESS, acl);
  if (ret != 0)
    {
      int saved_errno = errno;

      if (ACL_NOT_WELL_SUPPORTED (errno))
        {
	  int n = acl_entries (acl);

	  acl_free (acl);
	  /* On most hosts with MODE_INSIDE_ACL an ACL is trivial if n == 3,
	     and it cannot be less than 3.  On IRIX 6.5 it is also trivial if
	     n == -1.
	     For simplicity and safety, assume the ACL is trivial if n <= 3.
	     Also see file-has-acl.c for some of the other possibilities;
	     it's not clear whether that complexity is needed here.  */
	  if (n <= 3 * MODE_INSIDE_ACL)
	    {
	      if (chmod_or_fchmod (dst_name, dest_desc, mode) != 0)
		saved_errno = errno;
	      else
		return 0;
	    }
	  else
	    chmod_or_fchmod (dst_name, dest_desc, mode);
	}
      else
	{
	  acl_free (acl);
	  chmod_or_fchmod (dst_name, dest_desc, mode);
	}
      error (0, saved_errno, _("preserving permissions for %s"),
	     quote (dst_name));
      return -1;
    }
  else
    acl_free (acl);

  if (!MODE_INSIDE_ACL || (mode & (S_ISUID | S_ISGID | S_ISVTX)))
    {
      /* We did not call chmod so far, and either the mode and the ACL are
	 separate or special bits are to be set which don't fit into ACLs.  */

      if (chmod_or_fchmod (dst_name, dest_desc, mode) != 0)
	{
	  error (0, errno, _("preserving permissions for %s"),
		 quote (dst_name));
	  return -1;
	}
    }

  if (S_ISDIR (mode))
    {
      acl = acl_get_file (src_name, ACL_TYPE_DEFAULT);
      if (acl == NULL)
	{
	  error (0, errno, "%s", quote (src_name));
	  return -1;
	}

      if (acl_set_file (dst_name, ACL_TYPE_DEFAULT, acl))
	{
	  error (0, errno, _("preserving permissions for %s"),
		 quote (dst_name));
	  acl_free (acl);
	  return -1;
	}
      else
        acl_free (acl);
    }
  return 0;

#else

# if USE_ACL && defined ACL_NO_TRIVIAL
  /* Solaris 10 NFSv4 ACLs.  */
  acl_t *aclp = NULL;
  ret = (source_desc < 0
	 ? acl_get (src_name, ACL_NO_TRIVIAL, &aclp)
	 : facl_get (source_desc, ACL_NO_TRIVIAL, &aclp));
  if (ret != 0 && errno != ENOSYS)
    {
      error (0, errno, "%s", quote (src_name));
      return ret;
    }
# endif

  ret = qset_acl (dst_name, dest_desc, mode);
  if (ret != 0)
    error (0, errno, _("preserving permissions for %s"), quote (dst_name));

# if USE_ACL && defined ACL_NO_TRIVIAL
  if (ret == 0 && aclp)
    {
      ret = (dest_desc < 0
	     ? acl_set (dst_name, aclp)
	     : facl_set (dest_desc, aclp));
      if (ret != 0)
	error (0, errno, _("preserving permissions for %s"), quote (dst_name));
      acl_free (aclp);
    }
# endif

  return ret;
#endif
}