blob: bd8475374be7f0430dd0dbb00214a01220f955f8 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "platform/globals.h"
#if defined(TARGET_OS_LINUX)
#include "bin/directory.h"
#include <dirent.h> // NOLINT
#include <errno.h> // NOLINT
#include <string.h> // NOLINT
#include <sys/param.h> // NOLINT
#include <sys/stat.h> // NOLINT
#include <unistd.h> // NOLINT
#include "bin/file.h"
#include "bin/platform.h"
namespace dart {
namespace bin {
class PathBuffer {
public:
PathBuffer() : length(0) {
data = new char[PATH_MAX + 1];
}
~PathBuffer() {
delete[] data;
}
char* data;
int length;
bool Add(const char* name) {
int written = snprintf(data + length,
PATH_MAX - length,
"%s",
name);
data[PATH_MAX] = '\0';
if (written <= PATH_MAX - length &&
written >= 0 &&
static_cast<size_t>(written) == strnlen(name, PATH_MAX + 1)) {
length += written;
return true;
} else {
errno = ENAMETOOLONG;
return false;
}
}
void Reset(int new_length) {
length = new_length;
data[length] = '\0';
}
};
// A linked list of symbolic links, with their unique file system identifiers.
// These are scanned to detect loops while doing a recursive directory listing.
struct LinkList {
dev_t dev;
ino_t ino;
LinkList* next;
};
// Forward declarations.
static bool ListRecursively(PathBuffer* path,
bool recursive,
bool follow_links,
LinkList* seen,
DirectoryListing* listing);
static bool DeleteRecursively(PathBuffer* path);
static void PostError(DirectoryListing *listing,
const char* dir_name) {
listing->HandleError(dir_name);
}
static bool HandleDir(char* dir_name,
PathBuffer* path,
bool recursive,
bool follow_links,
LinkList* seen,
DirectoryListing *listing) {
if (strcmp(dir_name, ".") == 0) return true;
if (strcmp(dir_name, "..") == 0) return true;
if (!path->Add(dir_name)) {
PostError(listing, path->data);
return false;
}
return listing->HandleDirectory(path->data) &&
(!recursive ||
ListRecursively(path, recursive, follow_links, seen, listing));
}
static bool HandleFile(char* file_name,
PathBuffer* path,
DirectoryListing *listing) {
if (!path->Add(file_name)) {
PostError(listing, path->data);
return false;
}
return listing->HandleFile(path->data);
}
static bool HandleLink(char* link_name,
PathBuffer* path,
DirectoryListing *listing) {
if (!path->Add(link_name)) {
PostError(listing, path->data);
return false;
}
return listing->HandleLink(path->data);
}
static bool ListRecursively(PathBuffer* path,
bool recursive,
bool follow_links,
LinkList* seen,
DirectoryListing *listing) {
if (!path->Add(File::PathSeparator())) {
PostError(listing, path->data);
return false;
}
DIR* dir_pointer;
do {
dir_pointer = opendir(path->data);
} while (dir_pointer == NULL && errno == EINTR);
if (dir_pointer == NULL) {
PostError(listing, path->data);
return false;
}
// Iterate the directory and post the directories and files to the
// ports.
int path_length = path->length;
int status = 0;
bool success = true;
dirent entry;
dirent* result;
while ((status = TEMP_FAILURE_RETRY(readdir_r(dir_pointer,
&entry,
&result))) == 0 &&
result != NULL) {
switch (entry.d_type) {
case DT_DIR:
success = HandleDir(entry.d_name,
path,
recursive,
follow_links,
seen,
listing) && success;
break;
case DT_REG:
success = HandleFile(entry.d_name,
path,
listing) && success;
break;
case DT_LNK:
if (!follow_links) {
success = HandleLink(entry.d_name,
path,
listing) && success;
break;
}
// Else fall through to next case.
// Fall through.
case DT_UNKNOWN: {
// On some file systems the entry type is not determined by
// readdir_r. For those and for links we use stat to determine
// the actual entry type. Notice that stat returns the type of
// the file pointed to.
struct stat entry_info;
if (!path->Add(entry.d_name)) {
success = false;
break;
}
int stat_success;
stat_success = TEMP_FAILURE_RETRY(lstat(path->data, &entry_info));
if (stat_success == -1) {
success = false;
PostError(listing, path->data);
break;
}
if (follow_links && S_ISLNK(entry_info.st_mode)) {
// Check to see if we are in a loop created by a symbolic link.
LinkList current_link = { entry_info.st_dev,
entry_info.st_ino,
seen };
LinkList* previous = seen;
bool looping_link = false;
while (previous != NULL) {
if (previous->dev == current_link.dev &&
previous->ino == current_link.ino) {
// Report the looping link as a link, rather than following it.
path->Reset(path_length);
success = HandleLink(entry.d_name,
path,
listing) && success;
looping_link = true;
break;
}
previous = previous->next;
}
if (looping_link) break;
stat_success = TEMP_FAILURE_RETRY(stat(path->data, &entry_info));
if (stat_success == -1) {
// Report a broken link as a link, even if follow_links is true.
path->Reset(path_length);
success = HandleLink(entry.d_name,
path,
listing) && success;
break;
}
if (S_ISDIR(entry_info.st_mode)) {
// Recurse into the subdirectory with current_link added to the
// linked list of seen file system links.
path->Reset(path_length);
success = HandleDir(entry.d_name,
path,
recursive,
follow_links,
&current_link,
listing) && success;
break;
}
}
path->Reset(path_length);
if (S_ISDIR(entry_info.st_mode)) {
success = HandleDir(entry.d_name,
path,
recursive,
follow_links,
seen,
listing) && success;
} else if (S_ISREG(entry_info.st_mode)) {
success = HandleFile(entry.d_name,
path,
listing) && success;
} else if (S_ISLNK(entry_info.st_mode)) {
success = HandleLink(entry.d_name,
path,
listing) && success;
}
break;
}
default:
break;
}
path->Reset(path_length);
}
if (status != 0) {
errno = status;
success = false;
PostError(listing, path->data);
}
if (closedir(dir_pointer) == -1) {
success = false;
PostError(listing, path->data);
}
return success;
}
static bool DeleteFile(char* file_name,
PathBuffer* path) {
return path->Add(file_name) && unlink(path->data) == 0;
}
static bool DeleteDir(char* dir_name,
PathBuffer* path) {
if (strcmp(dir_name, ".") == 0) return true;
if (strcmp(dir_name, "..") == 0) return true;
return path->Add(dir_name) && DeleteRecursively(path);
}
static bool DeleteRecursively(PathBuffer* path) {
// Do not recurse into links for deletion. Instead delete the link.
// If it's a file, delete it.
struct stat st;
if (TEMP_FAILURE_RETRY(lstat(path->data, &st)) == -1) {
return false;
} else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
return (unlink(path->data) == 0);
}
if (!path->Add(File::PathSeparator())) return false;
// Not a link. Attempt to open as a directory and recurse into the
// directory.
DIR* dir_pointer;
do {
dir_pointer = opendir(path->data);
} while (dir_pointer == NULL && errno == EINTR);
if (dir_pointer == NULL) {
return false;
}
// Iterate the directory and delete all files and directories.
int path_length = path->length;
int read = 0;
bool success = true;
dirent entry;
dirent* result;
while ((read = TEMP_FAILURE_RETRY(readdir_r(dir_pointer,
&entry,
&result))) == 0 &&
result != NULL &&
success) {
switch (entry.d_type) {
case DT_DIR:
success = success && DeleteDir(entry.d_name, path);
break;
case DT_REG:
case DT_LNK:
// Treat all links as files. This will delete the link which
// is what we want no matter if the link target is a file or a
// directory.
success = success && DeleteFile(entry.d_name, path);
break;
case DT_UNKNOWN: {
// On some file systems the entry type is not determined by
// readdir_r. For those we use lstat to determine the entry
// type.
struct stat entry_info;
if (!path->Add(entry.d_name)) {
success = false;
break;
}
int lstat_success = TEMP_FAILURE_RETRY(lstat(path->data, &entry_info));
if (lstat_success == -1) {
success = false;
break;
}
path->Reset(path_length);
if (S_ISDIR(entry_info.st_mode)) {
success = success && DeleteDir(entry.d_name, path);
} else if (S_ISREG(entry_info.st_mode) || S_ISLNK(entry_info.st_mode)) {
// Treat links as files. This will delete the link which is
// what we want no matter if the link target is a file or a
// directory.
success = success && DeleteFile(entry.d_name, path);
}
break;
}
default:
break;
}
path->Reset(path_length);
}
if ((read != 0) ||
(closedir(dir_pointer) == -1) ||
(remove(path->data) == -1)) {
return false;
}
return success;
}
bool Directory::List(const char* dir_name,
bool recursive,
bool follow_links,
DirectoryListing *listing) {
PathBuffer path;
if (!path.Add(dir_name)) {
PostError(listing, dir_name);
return false;
}
return ListRecursively(&path, recursive, follow_links, NULL, listing);
}
Directory::ExistsResult Directory::Exists(const char* dir_name) {
struct stat entry_info;
int success = TEMP_FAILURE_RETRY(stat(dir_name, &entry_info));
if (success == 0) {
if (S_ISDIR(entry_info.st_mode)) {
return EXISTS;
} else {
return DOES_NOT_EXIST;
}
} else {
if (errno == EACCES ||
errno == EBADF ||
errno == EFAULT ||
errno == ENOMEM ||
errno == EOVERFLOW) {
// Search permissions denied for one of the directories in the
// path or a low level error occured. We do not know if the
// directory exists.
return UNKNOWN;
}
ASSERT(errno == ELOOP ||
errno == ENAMETOOLONG ||
errno == ENOENT ||
errno == ENOTDIR);
return DOES_NOT_EXIST;
}
}
char* Directory::Current() {
size_t size = PATH_MAX;
char* buffer = NULL;
for (char* result = NULL; result == NULL; size *= 2) {
if ((buffer = reinterpret_cast<char*>(realloc(buffer, size))) == NULL) {
return NULL;
}
result = getcwd(buffer, size);
if (result == NULL && errno != ERANGE) {
return NULL;
}
}
return buffer;
}
bool Directory::SetCurrent(const char* path) {
int result = TEMP_FAILURE_RETRY(chdir(path));
return result == 0;
}
bool Directory::Create(const char* dir_name) {
// Create the directory with the permissions specified by the
// process umask.
int result = TEMP_FAILURE_RETRY(mkdir(dir_name, 0777));
// If the directory already exists, treat it as a success.
if (result == -1 && errno == EEXIST) {
return (Exists(dir_name) == EXISTS);
}
return (result == 0);
}
char* Directory::CreateTemp(const char* const_template) {
// Returns a new, unused directory name, modifying the contents of
// dir_template. Creates the directory with the permissions specified
// by the process umask.
// The return value must be freed by the caller.
PathBuffer path;
path.Add(const_template);
if (path.length == 0) {
path.Add("/tmp/temp_dir1_");
} else if ((path.data)[path.length - 1] == '/') {
path.Add("temp_dir_");
}
if (!path.Add("XXXXXX")) {
// Pattern has overflowed.
return NULL;
}
char* result;
do {
result = mkdtemp(path.data);
} while (result == NULL && errno == EINTR);
if (result == NULL) {
return NULL;
}
int length = strnlen(path.data, PATH_MAX);
result = static_cast<char*>(malloc(length + 1));
strncpy(result, path.data, length);
result[length] = '\0';
return result;
}
bool Directory::Delete(const char* dir_name, bool recursive) {
if (!recursive) {
if (File::GetType(dir_name, false) == File::kIsLink &&
File::GetType(dir_name, true) == File::kIsDirectory) {
return (TEMP_FAILURE_RETRY(unlink(dir_name)) == 0);
}
return (TEMP_FAILURE_RETRY(rmdir(dir_name)) == 0);
} else {
PathBuffer path;
if (!path.Add(dir_name)) {
return false;
}
return DeleteRecursively(&path);
}
}
bool Directory::Rename(const char* path, const char* new_path) {
ExistsResult exists = Exists(path);
if (exists != EXISTS) return false;
return (TEMP_FAILURE_RETRY(rename(path, new_path)) == 0);
}
} // namespace bin
} // namespace dart
#endif // defined(TARGET_OS_LINUX)