blob: 0fe19b23f47ac7504f142e775f7c294cb71372f9 [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 "sdk/lib/fdio/cleanpath.h"
#include <limits.h>
#include <string.h>
#include <zircon/compiler.h>
namespace fdio_internal {
// Cleans an input path, transforming it to out, according to the
// rules defined by "Lexical File Names in Plan 9 or Getting Dot-Dot Right",
// accessible at: https://9p.io/sys/doc/lexnames.html
//
// Code heavily inspired by Go's filepath.Clean function, from:
// https://golang.org/src/path/filepath/path.go
//
// Sets is_dir to 'true' if the path is a directory, and 'false' otherwise.
bool CleanPath(const char* in, PathBuffer* out, bool* is_dir) {
if (in[0] == 0) {
out->Set(".");
*is_dir = true;
return true;
}
bool rooted = (in[0] == '/');
size_t in_index = 0; // Index of the next byte to read
if (rooted) {
out->Append('/');
in_index++;
*is_dir = true;
}
size_t dotdot = out->length(); // The output index at which '..' cannot be cleaned further.
auto is_separator = [](char c) { return c == 0 || c == '/'; };
auto can_increment = [](size_t i) { return unlikely(i < PATH_MAX - 1); };
while (in[in_index] != 0) {
*is_dir = true;
if (in[in_index] == '/') {
// 1. Reduce multiple slashes to a single slash
if (!can_increment(in_index)) {
return false;
}
in_index++;
} else if (in[in_index] == '.' && is_separator(in[in_index + 1])) {
// 2. Eliminate . path name elements (the current directory)
if (!can_increment(in_index)) {
return false;
}
in_index++;
} else if (in[in_index] == '.' && in[in_index + 1] == '.' && is_separator(in[in_index + 2])) {
if (!can_increment(in_index + 1)) {
return false;
}
in_index += 2;
if (out->length() > dotdot) {
// 3. Eliminate .. path elements (the parent directory) and the element that
// precedes them.
size_t last_elem = out->length() - 1;
while (last_elem > dotdot && (*out)[last_elem] != '/') {
last_elem--;
}
out->Resize(last_elem);
} else if (rooted) {
// 4. Eliminate .. elements that begin a rooted path, that is, replace /.. by / at
// the beginning of a path.
continue;
} else if (!rooted) {
if (out->length() > 0) {
out->Append('/');
}
// 5. Leave intact .. elements that begin a non-rooted path.
out->Append(std::string_view(".."));
dotdot = out->length();
}
} else {
*is_dir = false;
if ((rooted && out->length() != 1) || (!rooted && out->length() != 0)) {
// Add '/' before normal path component, for non-root components.
out->Append('/');
}
while (!is_separator(in[in_index])) {
if (!can_increment(in_index)) {
return false;
}
out->Append(in[in_index++]);
}
}
}
if (out->length() == 0) {
out->Append('.');
*is_dir = true;
}
return true;
}
} // namespace fdio_internal