You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
6.1 KiB
165 lines
6.1 KiB
// Copyright 2014 The Chromium OS Authors. All rights reserved. |
|
// Use of this source code is governed by a BSD-style license that can be |
|
// found in the LICENSE file. |
|
|
|
#include "brillo/file_utils.h" |
|
|
|
#include <fcntl.h> |
|
#include <unistd.h> |
|
|
|
#include <base/files/file_path.h> |
|
#include <base/files/file_util.h> |
|
#include <base/files/scoped_file.h> |
|
#include <base/logging.h> |
|
#include <base/posix/eintr_wrapper.h> |
|
|
|
namespace brillo { |
|
|
|
namespace { |
|
|
|
enum { |
|
kPermissions600 = S_IRUSR | S_IWUSR, |
|
kPermissions777 = S_IRWXU | S_IRWXG | S_IRWXO |
|
}; |
|
|
|
// Verify that base file permission enums are compatible with S_Ixxx. If these |
|
// asserts ever fail, we'll need to ensure that users of these functions switch |
|
// away from using base permission enums and add a note to the function comments |
|
// indicating that base enums can not be used. |
|
static_assert(base::FILE_PERMISSION_READ_BY_USER == S_IRUSR, |
|
"base file permissions don't match unistd.h permissions"); |
|
static_assert(base::FILE_PERMISSION_WRITE_BY_USER == S_IWUSR, |
|
"base file permissions don't match unistd.h permissions"); |
|
static_assert(base::FILE_PERMISSION_EXECUTE_BY_USER == S_IXUSR, |
|
"base file permissions don't match unistd.h permissions"); |
|
static_assert(base::FILE_PERMISSION_READ_BY_GROUP == S_IRGRP, |
|
"base file permissions don't match unistd.h permissions"); |
|
static_assert(base::FILE_PERMISSION_WRITE_BY_GROUP == S_IWGRP, |
|
"base file permissions don't match unistd.h permissions"); |
|
static_assert(base::FILE_PERMISSION_EXECUTE_BY_GROUP == S_IXGRP, |
|
"base file permissions don't match unistd.h permissions"); |
|
static_assert(base::FILE_PERMISSION_READ_BY_OTHERS == S_IROTH, |
|
"base file permissions don't match unistd.h permissions"); |
|
static_assert(base::FILE_PERMISSION_WRITE_BY_OTHERS == S_IWOTH, |
|
"base file permissions don't match unistd.h permissions"); |
|
static_assert(base::FILE_PERMISSION_EXECUTE_BY_OTHERS == S_IXOTH, |
|
"base file permissions don't match unistd.h permissions"); |
|
|
|
enum RegularFileOrDeleteResult { |
|
kFailure = 0, // Failed to delete whatever was at the path. |
|
kRegularFile = 1, // Regular file existed and was unchanged. |
|
kEmpty = 2 // Anything that was at the path has been deleted. |
|
}; |
|
|
|
// Checks if a regular file owned by |uid| and |gid| exists at |path|, otherwise |
|
// deletes anything that might be at |path|. Returns a RegularFileOrDeleteResult |
|
// enum indicating what is at |path| after the function finishes. |
|
RegularFileOrDeleteResult RegularFileOrDelete(const base::FilePath& path, |
|
uid_t uid, |
|
gid_t gid) { |
|
// Check for symlinks by setting O_NOFOLLOW and checking for ELOOP. This lets |
|
// us use the safer fstat() instead of having to use lstat(). |
|
base::ScopedFD scoped_fd(HANDLE_EINTR(openat( |
|
AT_FDCWD, path.value().c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW))); |
|
bool path_not_empty = (errno == ELOOP || scoped_fd != -1); |
|
|
|
// If there is a file/directory at |path|, see if it matches our criteria. |
|
if (scoped_fd != -1) { |
|
struct stat file_stat; |
|
if (fstat(scoped_fd.get(), &file_stat) != -1 && |
|
S_ISREG(file_stat.st_mode) && file_stat.st_uid == uid && |
|
file_stat.st_gid == gid) { |
|
return kRegularFile; |
|
} |
|
} |
|
|
|
// If we get here and anything was at |path|, try to delete it so we can put |
|
// our file there. |
|
if (path_not_empty) { |
|
if (!base::DeleteFile(path, true)) { |
|
PLOG(WARNING) << "Failed to delete entity at \"" << path.value() << '"'; |
|
return kFailure; |
|
} |
|
} |
|
|
|
return kEmpty; |
|
} |
|
|
|
// Handles common touch functionality but also provides an optional |fd_out| |
|
// so that any further modifications to the file (e.g. permissions) can safely |
|
// use the fd rather than the path. |fd_out| will only be set if a new file |
|
// is created, otherwise it will be unchanged. |
|
// If |fd_out| is null, this function will close the file, otherwise it's |
|
// expected that |fd_out| will close the file when it goes out of scope. |
|
bool TouchFileInternal(const base::FilePath& path, |
|
uid_t uid, |
|
gid_t gid, |
|
base::ScopedFD* fd_out) { |
|
RegularFileOrDeleteResult result = RegularFileOrDelete(path, uid, gid); |
|
switch (result) { |
|
case kFailure: |
|
return false; |
|
case kRegularFile: |
|
return true; |
|
case kEmpty: |
|
break; |
|
} |
|
|
|
// base::CreateDirectory() returns true if the directory already existed. |
|
if (!base::CreateDirectory(path.DirName())) { |
|
PLOG(WARNING) << "Failed to create directory for \"" << path.value() << '"'; |
|
return false; |
|
} |
|
|
|
// Create the file as owner-only initially. |
|
base::ScopedFD scoped_fd( |
|
HANDLE_EINTR(openat(AT_FDCWD, |
|
path.value().c_str(), |
|
O_RDONLY | O_NOFOLLOW | O_CREAT | O_EXCL | O_CLOEXEC, |
|
kPermissions600))); |
|
if (scoped_fd == -1) { |
|
PLOG(WARNING) << "Failed to create file \"" << path.value() << '"'; |
|
return false; |
|
} |
|
|
|
if (fd_out) { |
|
fd_out->swap(scoped_fd); |
|
} |
|
return true; |
|
} |
|
|
|
} // namespace |
|
|
|
bool TouchFile(const base::FilePath& path, |
|
int new_file_permissions, |
|
uid_t uid, |
|
gid_t gid) { |
|
// Make sure |permissions| doesn't have any out-of-range bits. |
|
if (new_file_permissions & ~kPermissions777) { |
|
LOG(WARNING) << "Illegal permissions: " << new_file_permissions; |
|
return false; |
|
} |
|
|
|
base::ScopedFD scoped_fd; |
|
if (!TouchFileInternal(path, uid, gid, &scoped_fd)) { |
|
return false; |
|
} |
|
|
|
// scoped_fd is valid only if a new file was created. |
|
if (scoped_fd != -1 && |
|
HANDLE_EINTR(fchmod(scoped_fd.get(), new_file_permissions)) == -1) { |
|
PLOG(WARNING) << "Failed to set permissions for \"" << path.value() << '"'; |
|
base::DeleteFile(path, false); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool TouchFile(const base::FilePath& path) { |
|
// Use TouchFile() instead of TouchFileInternal() to explicitly set |
|
// permissions to 600 in case umask is set strangely. |
|
return TouchFile(path, kPermissions600, geteuid(), getegid()); |
|
} |
|
|
|
} // namespace brillo
|
|
|