haiku/src/tools/rm_attrs.cpp
Ingo Weinhold bc96e8f30c Add more robust generic attribute emulation variant
The new configure option "--use-xattr-ref" enables an xattr assisted
variant of the generic attribute emulation. Instead of using the inode
ID of a node to identify its attribute directory, we use a reasonably
unique random 128 bit number, which we generate and attach as an
attribute to the node. This way, when a node changes its inode ID
(defragmentation?) or the inode ID of a removed node with a left-over
attribute directory is reused, attributes won't get mixed up.

The old method is still used for symlinks (since on Linux only
priviledged users can write attributes on symlinks), but those usually
only have a rather boring BEOS:TYPE attribute, so mix-ups wouldn't be
that problematic anyway.
2013-06-07 02:27:48 +02:00

234 lines
4.5 KiB
C++

/*
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
// exported by the generic attribute support in libroot_build.so
extern "C" bool __get_attribute_dir_path(const struct stat* st,
const char* path, char* buffer);
class Path {
public:
bool Init(const char* path)
{
size_t len = strlen(path);
if (len == 0 || len >= PATH_MAX)
return false;
strcpy(fPath, path);
fPathLen = len;
return true;
}
const char* GetPath() const
{
return fPath;
}
char* Buffer()
{
return fPath;
}
void BufferChanged()
{
fPathLen = strlen(fPath);
}
bool PushLeaf(const char* leaf)
{
size_t leafLen = strlen(leaf);
int separatorLen = (fPath[fPathLen - 1] == '/' ? 0 : 1);
if (fPathLen + separatorLen + leafLen >= PATH_MAX)
return false;
if (separatorLen > 0)
fPath[fPathLen++] = '/';
strcpy(fPath + fPathLen, leaf);
fPathLen += leafLen;
return true;
}
bool PopLeaf()
{
char* lastSlash = strrchr(fPath, '/');
if (lastSlash == NULL || lastSlash == fPath)
return false;
*lastSlash = '\0';
fPathLen = lastSlash - fPath;
return true;
}
char fPath[PATH_MAX];
size_t fPathLen;
};
static bool remove_entry(Path& entry, bool recursive, bool force,
bool removeAttributes);
static void
remove_dir_contents(Path& path, bool force, bool removeAttributes)
{
// open the dir
DIR* dir = opendir(path.GetPath());
if (dir == NULL) {
fprintf(stderr, "Error: Failed to open dir \"%s\": %s\n",
path.GetPath(), strerror(errno));
return;
}
// iterate through the entries
errno = 0;
while (dirent* entry = readdir(dir)) {
// skip "." and ".."
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
if (!path.PushLeaf(entry->d_name)) {
fprintf(stderr, "Error: Path name of entry too long: dir: \"%s\", "
"entry: \"%s\"\n", path.GetPath(), entry->d_name);
continue;
}
remove_entry(path, true, force, removeAttributes);
path.PopLeaf();
errno = 0;
}
if (errno != 0) {
fprintf(stderr, "Error: Failed to read directory \"%s\": %s\n",
path.GetPath(), strerror(errno));
}
// close
closedir(dir);
}
static bool
remove_entry(Path& path, bool recursive, bool force, bool removeAttributes)
{
// stat the file
struct stat st;
if (lstat(path.GetPath(), &st) < 0) {
// errno == 0 shouldn't happen, but found on OpenSUSE Linux 10.3
if (force && (errno == ENOENT || errno == 0))
return true;
fprintf(stderr, "Error: Failed to remove \"%s\": %s\n", path.GetPath(),
strerror(errno));
return false;
}
// remove the file's attributes
if (removeAttributes) {
Path attrDirPath;
if (__get_attribute_dir_path(&st, path.GetPath(),
attrDirPath.Buffer())) {
attrDirPath.BufferChanged();
remove_entry(attrDirPath, true, true, false);
}
}
if (S_ISDIR(st.st_mode)) {
if (!recursive) {
fprintf(stderr, "Error: \"%s\" is a directory.\n", path.GetPath());
return false;
}
// remove the contents
remove_dir_contents(path, force, removeAttributes);
// remove the directory
if (rmdir(path.GetPath()) < 0) {
fprintf(stderr, "Error: Failed to remove directory \"%s\": %s\n",
path.GetPath(), strerror(errno));
return false;
}
} else {
// remove the entry
if (unlink(path.GetPath()) < 0) {
fprintf(stderr, "Error: Failed to remove entry \"%s\": %s\n",
path.GetPath(), strerror(errno));
return false;
}
}
return true;
}
int
main(int argc, const char* const* argv)
{
bool recursive = false;
bool force = false;
// parse parameters
int argi = 1;
for (argi = 1; argi < argc; argi++) {
const char *arg = argv[argi];
if (arg[0] != '-')
break;
if (arg[1] == '\0') {
fprintf(stderr, "Error: Invalid option \"-\"\n");
exit(1);
}
for (int i = 1; arg[i]; i++) {
switch (arg[i]) {
case 'f':
force = true;
break;
case 'r':
recursive = true;
break;
default:
fprintf(stderr, "Error: Unknown option \"-%c\"\n", arg[i]);
exit(1);
}
}
}
// check params
if (argi >= argc) {
fprintf(stderr, "Usage: %s [ -rf ] <file>...\n", argv[0]);
exit(1);
}
// remove loop
for (; argi < argc; argi++) {
Path path;
if (!path.Init(argv[argi])) {
fprintf(stderr, "Error: Invalid path: \"%s\".\n", argv[argi]);
continue;
}
remove_entry(path, recursive, force, true);
}
return 0;
}