parent
c72ea84bcd
commit
cec2dd3b3f
|
@ -314,7 +314,7 @@ void rumpuser_bio(int fd, int op, void *data, size_t dlen, int64_t off,
|
||||||
Packet *p = backend()->alloc();
|
Packet *p = backend()->alloc();
|
||||||
|
|
||||||
if (verbose)
|
if (verbose)
|
||||||
PDBG("fd: %d op: %d len: %zu off: %lx p %p bio %p sync %u", fd, op, dlen, off,
|
PDBG("fd: %d op: %d len: %zu off: %lx p %p bio %p sync %u", fd, op, dlen, (unsigned long)off,
|
||||||
p, donearg, !!(op & RUMPUSER_BIO_SYNC));
|
p, donearg, !!(op & RUMPUSER_BIO_SYNC));
|
||||||
|
|
||||||
p->opcode= op & RUMPUSER_BIO_WRITE ? Block::Packet_descriptor::WRITE :
|
p->opcode= op & RUMPUSER_BIO_WRITE ? Block::Packet_descriptor::WRITE :
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
/* Genode includes */
|
/* Genode includes */
|
||||||
#include <util/string.h>
|
#include <util/string.h>
|
||||||
|
#include <file_system/util.h>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,18 +41,6 @@ static inline bool is_basename(char const *path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if character 'c' occurs in null-terminated string 'str'
|
|
||||||
*/
|
|
||||||
static inline bool string_contains(char const *str, char c)
|
|
||||||
{
|
|
||||||
for (; *str; str++)
|
|
||||||
if (*str == c)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if null-terminated string 'substr' occurs in null-terminated
|
* Return true if null-terminated string 'substr' occurs in null-terminated
|
||||||
* string 'str'
|
* string 'str'
|
||||||
|
@ -85,9 +74,9 @@ static inline bool valid_filename(char const *str)
|
||||||
if (str[0] == 0) return false;
|
if (str[0] == 0) return false;
|
||||||
|
|
||||||
/* must not contain '/' or '\' or ':' */
|
/* must not contain '/' or '\' or ':' */
|
||||||
if (string_contains(str, '/') ||
|
if (File_system::string_contains(str, '/') ||
|
||||||
string_contains(str, '\\') ||
|
File_system::string_contains(str, '\\') ||
|
||||||
string_contains(str, ':'))
|
File_system::string_contains(str, ':'))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -105,8 +94,8 @@ static inline bool valid_path(char const *str)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* must not contain '\' or ':' */
|
/* must not contain '\' or ':' */
|
||||||
if (string_contains(str, '\\') ||
|
if (File_system::string_contains(str, '\\') ||
|
||||||
string_contains(str, ':'))
|
File_system::string_contains(str, ':'))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* must not contain "/../" */
|
/* must not contain "/../" */
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
/* Genode includes */
|
/* Genode includes */
|
||||||
#include <util/string.h>
|
#include <util/string.h>
|
||||||
|
#include <file_system/util.h>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,18 +41,6 @@ static inline bool is_basename(char const *path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if character 'c' occurs in null-terminated string 'str'
|
|
||||||
*/
|
|
||||||
static inline bool string_contains(char const *str, char c)
|
|
||||||
{
|
|
||||||
for (; *str; str++)
|
|
||||||
if (*str == c)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if null-terminated string 'substr' occurs in null-terminated
|
* Return true if null-terminated string 'substr' occurs in null-terminated
|
||||||
* string 'str'
|
* string 'str'
|
||||||
|
@ -85,9 +74,9 @@ static inline bool valid_filename(char const *str)
|
||||||
if (str[0] == 0) return false;
|
if (str[0] == 0) return false;
|
||||||
|
|
||||||
/* must not contain '/' or '\' or ':' */
|
/* must not contain '/' or '\' or ':' */
|
||||||
if (string_contains(str, '/') ||
|
if (File_system::string_contains(str, '/') ||
|
||||||
string_contains(str, '\\') ||
|
File_system::string_contains(str, '\\') ||
|
||||||
string_contains(str, ':'))
|
File_system::string_contains(str, ':'))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -105,8 +94,8 @@ static inline bool valid_path(char const *str)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* must not contain '\' or ':' */
|
/* must not contain '\' or ':' */
|
||||||
if (string_contains(str, '\\') ||
|
if (File_system::string_contains(str, '\\') ||
|
||||||
string_contains(str, ':'))
|
File_system::string_contains(str, ':'))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* must not contain "/../" */
|
/* must not contain "/../" */
|
||||||
|
|
|
@ -7,57 +7,237 @@
|
||||||
#ifndef _FILE_SYSTEM__UTIL_H_
|
#ifndef _FILE_SYSTEM__UTIL_H_
|
||||||
#define _FILE_SYSTEM__UTIL_H_
|
#define _FILE_SYSTEM__UTIL_H_
|
||||||
|
|
||||||
/**
|
#include <file_system_session/file_system_session.h>
|
||||||
* Return base-name portion of null-terminated path string
|
#include <os/path.h>
|
||||||
*/
|
|
||||||
static inline char const *basename(char const *path)
|
|
||||||
{
|
|
||||||
char const *start = path;
|
|
||||||
|
|
||||||
for (; *path; path++)
|
namespace File_system {
|
||||||
if (*path == '/')
|
|
||||||
start = path + 1;
|
|
||||||
|
|
||||||
return start;
|
/**
|
||||||
}
|
* Return true if character 'c' occurs in null-terminated string 'str'
|
||||||
|
*/
|
||||||
|
inline bool string_contains(char const *str, char c)
|
||||||
|
{
|
||||||
|
for (; *str; str++)
|
||||||
|
if (*str == c)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return base-name portion of null-terminated path string
|
||||||
|
*/
|
||||||
|
static inline char const *basename(char const *path)
|
||||||
|
{
|
||||||
|
char const *start = path;
|
||||||
|
|
||||||
|
for (; *path; path++)
|
||||||
|
if (*path == '/')
|
||||||
|
start = path + 1;
|
||||||
|
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if specified path is a base name (contains no path delimiters)
|
* Return true if specified path is a base name (contains no path delimiters)
|
||||||
*/
|
*/
|
||||||
static inline bool is_basename(char const *path)
|
static inline bool is_basename(char const *path)
|
||||||
{
|
{
|
||||||
for (; *path; path++)
|
for (; *path; path++)
|
||||||
if (*path == '/')
|
if (*path == '/')
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if character 'c' occurs in null-terminated string 'str'
|
* Return true if 'str' is a valid node name
|
||||||
*/
|
*/
|
||||||
static inline bool string_contains(char const *str, char c)
|
static inline bool valid_name(char const *str)
|
||||||
{
|
{
|
||||||
for (; *str; str++)
|
if (string_contains(str, '/')) return false;
|
||||||
if (*str == c)
|
|
||||||
return true;
|
/* must have at least one character */
|
||||||
return false;
|
if (str[0] == 0) return false;
|
||||||
}
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if 'str' is a valid node name
|
* Open a directory, ensuring all parent directories exists.
|
||||||
*/
|
*/
|
||||||
static inline bool valid_name(char const *str)
|
static inline Dir_handle ensure_dir(Session &fs, char const *path)
|
||||||
{
|
{
|
||||||
if (string_contains(str, '/')) return false;
|
try {
|
||||||
|
return fs.dir(path, false);
|
||||||
|
} catch (Lookup_failed) {
|
||||||
|
try {
|
||||||
|
return fs.dir(path, true);
|
||||||
|
} catch (Lookup_failed) {
|
||||||
|
Genode::Path<MAX_PATH_LEN> target(path);
|
||||||
|
target.strip_last_element();
|
||||||
|
target.remove_trailing('/');
|
||||||
|
fs.close(ensure_dir(fs, target.base()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fs.dir(path, true);
|
||||||
|
}
|
||||||
|
|
||||||
/* must have at least one character */
|
|
||||||
if (str[0] == 0) return false;
|
|
||||||
|
|
||||||
return true;
|
/**
|
||||||
|
* Collect pending packet acknowledgements, freeing the space occupied
|
||||||
|
* by the packet in the bulk buffer
|
||||||
|
*
|
||||||
|
* This function should be called prior enqueing new packets into the
|
||||||
|
* packet stream to free up space in the bulk buffer.
|
||||||
|
*/
|
||||||
|
static void collect_acknowledgements(Session::Tx::Source &source)
|
||||||
|
{
|
||||||
|
while (source.ack_avail())
|
||||||
|
source.release_packet(source.get_acked_packet());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read file content
|
||||||
|
*/
|
||||||
|
static inline size_t read(Session &fs, File_handle const &file_handle,
|
||||||
|
void *dst, size_t count, seek_off_t seek_offset = 0)
|
||||||
|
{
|
||||||
|
bool success = true;
|
||||||
|
Session::Tx::Source &source = *fs.tx();
|
||||||
|
|
||||||
|
size_t const max_packet_size = source.bulk_buffer_size() / 2;
|
||||||
|
|
||||||
|
size_t remaining_count = count;
|
||||||
|
|
||||||
|
while (remaining_count && success) {
|
||||||
|
|
||||||
|
collect_acknowledgements(source);
|
||||||
|
|
||||||
|
size_t const curr_packet_size = min(remaining_count, max_packet_size);
|
||||||
|
|
||||||
|
Packet_descriptor
|
||||||
|
packet(source.alloc_packet(curr_packet_size),
|
||||||
|
0,
|
||||||
|
file_handle,
|
||||||
|
File_system::Packet_descriptor::READ,
|
||||||
|
curr_packet_size,
|
||||||
|
seek_offset);
|
||||||
|
|
||||||
|
/* pass packet to server side */
|
||||||
|
source.submit_packet(packet);
|
||||||
|
|
||||||
|
packet = source.get_acked_packet();
|
||||||
|
success = packet.succeeded();
|
||||||
|
|
||||||
|
size_t const read_num_bytes = min(packet.length(), curr_packet_size);
|
||||||
|
|
||||||
|
/* copy-out payload into destination buffer */
|
||||||
|
memcpy(dst, source.packet_content(packet), read_num_bytes);
|
||||||
|
|
||||||
|
source.release_packet(packet);
|
||||||
|
|
||||||
|
/* prepare next iteration */
|
||||||
|
seek_offset += read_num_bytes;
|
||||||
|
dst = (void *)((Genode::addr_t)dst + read_num_bytes);
|
||||||
|
remaining_count -= read_num_bytes;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we received less bytes than requested, we reached the end
|
||||||
|
* of the file.
|
||||||
|
*/
|
||||||
|
if (read_num_bytes < curr_packet_size)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count - remaining_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write file content
|
||||||
|
*/
|
||||||
|
static inline size_t write(Session &fs, File_handle const &file_handle,
|
||||||
|
void const *src, size_t count, seek_off_t seek_offset = 0)
|
||||||
|
{
|
||||||
|
bool success = true;
|
||||||
|
Session::Tx::Source &source = *fs.tx();
|
||||||
|
|
||||||
|
size_t const max_packet_size = source.bulk_buffer_size() / 2;
|
||||||
|
|
||||||
|
size_t remaining_count = count;
|
||||||
|
|
||||||
|
while (remaining_count && success) {
|
||||||
|
|
||||||
|
collect_acknowledgements(source);
|
||||||
|
|
||||||
|
size_t const curr_packet_size = min(remaining_count, max_packet_size);
|
||||||
|
|
||||||
|
Packet_descriptor
|
||||||
|
packet(source.alloc_packet(curr_packet_size),
|
||||||
|
0,
|
||||||
|
file_handle,
|
||||||
|
File_system::Packet_descriptor::WRITE,
|
||||||
|
curr_packet_size,
|
||||||
|
seek_offset);
|
||||||
|
|
||||||
|
/* copy-out source buffer into payload */
|
||||||
|
memcpy(source.packet_content(packet), src, curr_packet_size);
|
||||||
|
|
||||||
|
/* pass packet to server side */
|
||||||
|
source.submit_packet(packet);
|
||||||
|
|
||||||
|
packet = source.get_acked_packet();;
|
||||||
|
success = packet.succeeded();
|
||||||
|
source.release_packet(packet);
|
||||||
|
|
||||||
|
/* prepare next iteration */
|
||||||
|
seek_offset += curr_packet_size;
|
||||||
|
src = (void *)((Genode::addr_t)src + curr_packet_size);
|
||||||
|
remaining_count -= curr_packet_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count - remaining_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Handle_guard
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
Session &_session;
|
||||||
|
Node_handle _handle;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Handle_guard(Session &session, Node_handle handle)
|
||||||
|
: _session(session), _handle(handle) { }
|
||||||
|
|
||||||
|
~Handle_guard() { _session.close(_handle); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Packet_guard
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
Session::Tx::Source &_source;
|
||||||
|
File_system::Packet_descriptor _packet;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Packet_guard(Session::Tx::Source &source,
|
||||||
|
File_system::Packet_descriptor packet)
|
||||||
|
: _source(source), _packet(packet) { }
|
||||||
|
|
||||||
|
~Packet_guard()
|
||||||
|
{
|
||||||
|
_source.release_packet(_packet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* _FILE_SYSTEM__UTIL_H_ */
|
#endif /* _FILE_SYSTEM__UTIL_H_ */
|
||||||
|
|
|
@ -82,6 +82,10 @@ struct File_system::Node_handle
|
||||||
Node_handle(int v) : value(v) { }
|
Node_handle(int v) : value(v) { }
|
||||||
|
|
||||||
bool valid() const { return value != -1; }
|
bool valid() const { return value != -1; }
|
||||||
|
|
||||||
|
bool operator == (Node_handle const &other) const { return other.value == value; }
|
||||||
|
bool operator != (Node_handle const &other) const { return other.value != value; }
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <root/component.h>
|
#include <root/component.h>
|
||||||
#include <cap_session/connection.h>
|
#include <cap_session/connection.h>
|
||||||
#include <file_system_session/connection.h>
|
#include <file_system_session/connection.h>
|
||||||
|
#include <file_system/util.h>
|
||||||
#include <util/arg_string.h>
|
#include <util/arg_string.h>
|
||||||
#include <base/rpc_server.h>
|
#include <base/rpc_server.h>
|
||||||
#include <base/env.h>
|
#include <base/env.h>
|
||||||
|
@ -23,104 +24,6 @@
|
||||||
#include <os/path.h>
|
#include <os/path.h>
|
||||||
|
|
||||||
|
|
||||||
/*********************************************
|
|
||||||
** Utilities for accessing the file system **
|
|
||||||
*********************************************/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* XXX The following generic utilities should be moved to a public place.
|
|
||||||
* They are based on those found in the 'libc_fs' plugin. We should
|
|
||||||
* unify them.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace File_system {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect pending packet acknowledgements, freeing the space occupied
|
|
||||||
* by the packet in the bulk buffer
|
|
||||||
*
|
|
||||||
* This function should be called prior enqueing new packets into the
|
|
||||||
* packet stream to free up space in the bulk buffer.
|
|
||||||
*/
|
|
||||||
static void collect_acknowledgements(Session::Tx::Source &source)
|
|
||||||
{
|
|
||||||
while (source.ack_avail())
|
|
||||||
source.release_packet(source.get_acked_packet());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read file content
|
|
||||||
*/
|
|
||||||
static inline size_t read(Session &fs, File_handle const &file_handle,
|
|
||||||
void *dst, size_t count, off_t seek_offset = 0)
|
|
||||||
{
|
|
||||||
Session::Tx::Source &source = *fs.tx();
|
|
||||||
|
|
||||||
size_t const max_packet_size = source.bulk_buffer_size() / 2;
|
|
||||||
|
|
||||||
size_t remaining_count = count;
|
|
||||||
|
|
||||||
while (remaining_count) {
|
|
||||||
|
|
||||||
collect_acknowledgements(source);
|
|
||||||
|
|
||||||
size_t const curr_packet_size = min(remaining_count, max_packet_size);
|
|
||||||
|
|
||||||
Packet_descriptor
|
|
||||||
packet(source.alloc_packet(curr_packet_size),
|
|
||||||
0,
|
|
||||||
file_handle,
|
|
||||||
File_system::Packet_descriptor::READ,
|
|
||||||
curr_packet_size,
|
|
||||||
seek_offset);
|
|
||||||
|
|
||||||
/* pass packet to server side */
|
|
||||||
source.submit_packet(packet);
|
|
||||||
|
|
||||||
packet = source.get_acked_packet();
|
|
||||||
|
|
||||||
size_t const read_num_bytes = min(packet.length(), curr_packet_size);
|
|
||||||
|
|
||||||
/* copy-out payload into destination buffer */
|
|
||||||
memcpy(dst, source.packet_content(packet), read_num_bytes);
|
|
||||||
|
|
||||||
source.release_packet(packet);
|
|
||||||
|
|
||||||
/* prepare next iteration */
|
|
||||||
seek_offset += read_num_bytes;
|
|
||||||
dst = (void *)((Genode::addr_t)dst + read_num_bytes);
|
|
||||||
remaining_count -= read_num_bytes;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we received less bytes than requested, we reached the end
|
|
||||||
* of the file.
|
|
||||||
*/
|
|
||||||
if (read_num_bytes < curr_packet_size)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count - remaining_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct Handle_guard
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
|
|
||||||
Session &_session;
|
|
||||||
Node_handle _handle;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
Handle_guard(Session &session, Node_handle handle)
|
|
||||||
: _session(session), _handle(handle) { }
|
|
||||||
|
|
||||||
~Handle_guard() { _session.close(_handle); }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*****************
|
/*****************
|
||||||
** ROM service **
|
** ROM service **
|
||||||
*****************/
|
*****************/
|
||||||
|
|
|
@ -17,18 +17,7 @@
|
||||||
|
|
||||||
/* Genode includes */
|
/* Genode includes */
|
||||||
#include <util/string.h>
|
#include <util/string.h>
|
||||||
|
#include <file_system/util.h>
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if character 'c' occurs in null-terminated string 'str'
|
|
||||||
*/
|
|
||||||
static inline bool string_contains(char const *str, char c)
|
|
||||||
{
|
|
||||||
for (; *str; str++)
|
|
||||||
if (*str == c)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,9 +53,9 @@ static inline bool valid_filename(char const *str)
|
||||||
if (str[0] == 0) return false;
|
if (str[0] == 0) return false;
|
||||||
|
|
||||||
/* must not contain '/' or '\' or ':' */
|
/* must not contain '/' or '\' or ':' */
|
||||||
if (string_contains(str, '/') ||
|
if (File_system::string_contains(str, '/') ||
|
||||||
string_contains(str, '\\') ||
|
File_system::string_contains(str, '\\') ||
|
||||||
string_contains(str, ':'))
|
File_system::string_contains(str, ':'))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -85,8 +74,8 @@ static inline bool valid_path(char const *str)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* must not contain '\' or ':' */
|
/* must not contain '\' or ':' */
|
||||||
if (string_contains(str, '\\') ||
|
if (File_system::string_contains(str, '\\') ||
|
||||||
string_contains(str, ':'))
|
File_system::string_contains(str, ':'))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* must not contain "/../" */
|
/* must not contain "/../" */
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include <util/list.h>
|
#include <util/list.h>
|
||||||
#include <util/string.h>
|
#include <util/string.h>
|
||||||
#include <util/xml_node.h>
|
#include <util/xml_node.h>
|
||||||
|
#include <file_system/util.h>
|
||||||
|
|
||||||
/* local includes */
|
/* local includes */
|
||||||
#include <buffer.h>
|
#include <buffer.h>
|
||||||
|
@ -47,9 +48,9 @@ static inline bool valid_filename(char const *str)
|
||||||
if (str[0] == 0) return false;
|
if (str[0] == 0) return false;
|
||||||
|
|
||||||
/* must not contain '/' or '\' or ':' */
|
/* must not contain '/' or '\' or ':' */
|
||||||
if (string_contains(str, '/') ||
|
if (File_system::string_contains(str, '/') ||
|
||||||
string_contains(str, '\\') ||
|
File_system::string_contains(str, '\\') ||
|
||||||
string_contains(str, ':'))
|
File_system::string_contains(str, ':'))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user