blob: d4616b0d039bee371835b572f83e973c24edba7b [file] [log] [blame] [edit]
// 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>
namespace fdio_internal {
#define IS_SEPARATOR(c) ((c) == '/' || (c) == 0)
// Checks that if we increment this index forward, we'll
// still have enough space for a null terminator within
// PATH_MAX bytes.
#define CHECK_CAN_INCREMENT(i) \
if (unlikely((i) + 1 >= PATH_MAX)) { \
return ZX_ERR_BAD_PATH; \
}
// 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
//
// out is expected to be PATH_MAX bytes long.
// Sets is_dir to 'true' if the path is a directory, and 'false' otherwise.
zx_status_t cleanpath(const char* in, char* out, size_t* outlen, bool* is_dir) {
if (in[0] == 0) {
strcpy(out, ".");
*outlen = 1;
*is_dir = true;
return ZX_OK;
}
bool rooted = (in[0] == '/');
size_t in_index = 0; // Index of the next byte to read
size_t out_index = 0; // Index of the next byte to write
if (rooted) {
out[out_index++] = '/';
in_index++;
*is_dir = true;
}
size_t dotdot = out_index; // The output index at which '..' cannot be cleaned further.
while (in[in_index] != 0) {
*is_dir = true;
if (in[in_index] == '/') {
// 1. Reduce multiple slashes to a single slash
CHECK_CAN_INCREMENT(in_index);
in_index++;
} else if (in[in_index] == '.' && IS_SEPARATOR(in[in_index + 1])) {
// 2. Eliminate . path name elements (the current directory)
CHECK_CAN_INCREMENT(in_index);
in_index++;
} else if (in[in_index] == '.' && in[in_index + 1] == '.' && IS_SEPARATOR(in[in_index + 2])) {
CHECK_CAN_INCREMENT(in_index + 1);
in_index += 2;
if (out_index > dotdot) {
// 3. Eliminate .. path elements (the parent directory) and the element that
// precedes them.
out_index--;
while (out_index > dotdot && out[out_index] != '/') {
out_index--;
}
} 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_index > 0) {
out[out_index++] = '/';
}
// 5. Leave intact .. elements that begin a non-rooted path.
out[out_index++] = '.';
out[out_index++] = '.';
dotdot = out_index;
}
} else {
*is_dir = false;
if ((rooted && out_index != 1) || (!rooted && out_index != 0)) {
// Add '/' before normal path component, for non-root components.
out[out_index++] = '/';
}
while (!IS_SEPARATOR(in[in_index])) {
CHECK_CAN_INCREMENT(in_index);
out[out_index++] = in[in_index++];
}
}
}
if (out_index == 0) {
strcpy(out, ".");
*outlen = 1;
*is_dir = true;
return ZX_OK;
}
// Append null character
*outlen = out_index;
out[out_index++] = 0;
return ZX_OK;
}
} // namespace fdio_internal