Browse Source

Add fsck.mergerfs tool (#1483)

pull/1484/head
trapexit 3 months ago
committed by GitHub
parent
commit
77ef88b04c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      DEPENDENCIES
  2. 19
      Makefile
  3. 2
      buildtools/install-build-pkgs
  4. 1
      debian/compat
  5. 2
      debian/control
  6. 6
      debian/rules
  7. 2
      libfuse/Makefile
  8. 1
      mergerfs.spec
  9. 11527
      src/CLI11.hpp
  10. 1
      src/fs_clonefile.cpp
  11. 2
      src/fs_copydata_copy_file_range.cpp
  12. 4
      src/fs_copydata_readwrite.cpp
  13. 39
      src/fs_copyfile.cpp
  14. 9
      src/fs_copyfile.hpp
  15. 2
      src/fs_is_rofs.hpp
  16. 41
      src/fs_mktemp.cpp
  17. 10
      src/fs_mktemp.hpp
  18. 454
      src/fsck_mergerfs.cpp
  19. 6
      src/fsck_mergerfs.hpp
  20. 14
      src/fuse_ioctl.cpp
  21. 19
      src/mergerfs.cpp
  22. 15
      src/mergerfs_ioctl.hpp
  23. 17
      src/str.cpp
  24. 5
      src/str.hpp

1
DEPENDENCIES

@ -8,6 +8,7 @@
* concurrentqueue: https://github.com/cameron314/concurrentqueue
* scope_guard: https://github.com/Neargye/scope_guard
* subprocess: https://github.com/arun11299/cpp-subprocess
* CLI11: https://github.com/CLIUtils/CLI11
* boost
* download boost
* ./bootstrap.sh

19
Makefile

@ -22,7 +22,7 @@ LN = ln
FIND = find
INSTALL = install
MKTEMP = mktemp
STRIP = strip
STRIP ?= strip
SED = sed
GIT2DEBCL = ./buildtools/git2debcl
PKGCONFIG = pkg-config
@ -136,6 +136,7 @@ build/tests: build/mergerfs tests-objects
$(CXX) $(CXXFLAGS) $(TESTS_FLAGS) $(FUSE_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) $(TESTS_OBJS) -o $@ libfuse/build/libfuse.a $(LDFLAGS)
mergerfs: build/mergerfs
ln -fs "mergerfs" "build/fsck.mergerfs"
tests: build/tests
@ -181,7 +182,8 @@ install: install-base install-mount-tools install-preload install-man
install-base: build/mergerfs
$(MKDIR) -p "$(INSTALLBINDIR)"
$(INSTALL) -v -m 0755 build/mergerfs "$(INSTALLBINDIR)/mergerfs"
$(INSTALL) -v -m 0755 "build/mergerfs" "$(INSTALLBINDIR)/mergerfs"
ln -s "mergerfs" "${INSTALLBINDIR}/fsck.mergerfs"
install-mount-tools: install-base
$(MKDIR) -p "$(INSTALLBINDIR)"
@ -231,14 +233,14 @@ endif
signed-deb:
$(MAKE) distclean
$(MAKE) debian-changelog
# dpkg-source -b .
dpkg-buildpackage -nc
fakeroot dpkg-buildpackage -nc
deb:
$(MAKE) distclean
$(MAKE) debian-changelog
# dpkg-source -b .
dpkg-buildpackage -nc -uc -us
fakeroot dpkg-buildpackage -nc -uc -us
mkdir -p ./build/pkgs/
mv -v ../mergerfs*deb ./build/pkgs/
.PHONY: rpm-clean
rpm-clean:
@ -268,6 +270,11 @@ release:
--target=all \
--cleanup \
--branch=$(shell git branch --show-current)
release-sample:
./buildtools/build-release \
--target=debian.12.amd64 \
--cleanup \
--branch=$(shell git branch --show-current)
release-amd64:
./buildtools/build-release \
--target=amd64 \

2
buildtools/install-build-pkgs

@ -7,7 +7,7 @@ if [ -e /usr/bin/apt-get ]; then
install \
ca-certificates \
build-essential \
dpkg \
dpkg-dev \
git \
g++ \
debhelper \

1
debian/compat

@ -1 +0,0 @@
9

2
debian/control

@ -2,7 +2,7 @@ Source: mergerfs
Section: utils
Priority: optional
Maintainer: Antonio SJ Musumeci <trapexit@spawn.link>
Build-Depends: debhelper (>= 8.0.0)
Build-Depends: debhelper-compat (= 10)
Standards-Version: 3.9.4
Homepage: http://github.com/trapexit/mergerfs

6
debian/rules

@ -1,11 +1,13 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Uncomment this to turn on verbose mode.
export DH_VERBOSE=1
%:
dh $@ --parallel
dh $@ --parallel --max-parallel=6
override_dh_strip:
dh_strip --no-automatic-dbgsym
override_dh_auto_install:
$(MAKE) DESTDIR=$(CURDIR)/debian/mergerfs PREFIX=/usr install

2
libfuse/Makefile

@ -137,7 +137,7 @@ strip:
install-utils: mergerfs-fusermount mount.mergerfs strip
install -D build/mergerfs-fusermount "$(INSTALLBINDIR)/mergerfs-fusermount"
install -D build/mount.mergerfs "$(INSTALLSBINDIR)/mount.mergerfs"
chown root "$(INSTALLBINDIR)/mergerfs-fusermount"
chown root:root "$(INSTALLBINDIR)/mergerfs-fusermount"
chmod u+s "$(INSTALLBINDIR)/mergerfs-fusermount"
install: $(INSTALLUTILS)

1
mergerfs.spec

@ -34,6 +34,7 @@ make install PREFIX=%{_prefix} DESTDIR=%{buildroot}
%files
/usr/bin/mergerfs
/usr/bin/mergerfs-fusermount
/usr/bin/fsck.mergerfs
/sbin/mount.mergerfs
/usr/lib/mergerfs/preload.so
%doc %{_mandir}/*

11527
src/CLI11.hpp
File diff suppressed because it is too large
View File

1
src/fs_clonefile.cpp

@ -60,6 +60,7 @@ namespace fs
if(rv == -1)
return -1;
// TODO: should not copy "immutable" flag till last
rv = fs::attr::copy(src_fd_,dst_fd_);
if((rv == -1) && !l::ignorable_error(errno))
return -1;

2
src/fs_copydata_copy_file_range.cpp

@ -41,6 +41,8 @@ namespace l
rv = fs::copy_file_range(src_fd_,&src_off,dst_fd_,&dst_off,nleft,0);
if((rv == -1) && (errno == EINTR))
continue;
if((rv == -1) && (errno == EAGAIN))
continue;
if(rv == -1)
return -1;

4
src/fs_copydata_readwrite.cpp

@ -42,6 +42,8 @@ namespace l
rv = fs::write(fd_,buf_,nleft);
if((rv == -1) && (errno == EINTR))
continue;
if((rv == -1) && (errno == EAGAIN))
continue;
if(rv == -1)
return -1;
@ -76,6 +78,8 @@ namespace l
return totalwritten;
if((nr == -1) && (errno == EINTR))
continue;
if((nr == -1) && (errno == EAGAIN))
continue;
if(nr == -1)
return -1;

39
src/fs_copyfile.cpp

@ -0,0 +1,39 @@
#include "fs_copyfile.hpp"
#include "fs_clonefile.hpp"
#include "fs_close.hpp"
#include "fs_mktemp.hpp"
#include "fs_open.hpp"
#include "fs_rename.hpp"
#include "scope_guard.hpp"
#include <fcntl.h>
int
fs::copyfile(const std::filesystem::path &src_,
const std::filesystem::path &dst_)
{
int rv;
int src_fd;
int dst_fd;
std::string dst_tmppath;
src_fd = fs::open(src_,O_RDONLY|O_NOFOLLOW);
if(src_fd < 0)
return src_fd;
DEFER { fs::close(src_fd); };
std::tie(dst_fd,dst_tmppath) = fs::mktemp(dst_,O_RDWR);
if(dst_fd < 0)
return dst_fd;
DEFER { fs::close(dst_fd); };
rv = fs::clonefile(src_fd,dst_fd);
if(rv < 0)
return rv;
rv = fs::rename(dst_tmppath,dst_);
return rv;
}

9
src/fs_copyfile.hpp

@ -0,0 +1,9 @@
#pragma once
#include <filesystem>
namespace fs
{
int copyfile(const std::filesystem::path &src,
const std::filesystem::path &dst);
}

2
src/fs_is_rofs.hpp

@ -52,7 +52,7 @@ namespace fs
int fd;
std::string tmp_filepath;
std::tie(fd,tmp_filepath) = fs::mktemp_in_dir(path_,O_WRONLY);
std::tie(fd,tmp_filepath) = fs::mktemp_in_dir(path_,"",O_WRONLY);
if(fd < 0)
return (fd == -EROFS);

41
src/fs_mktemp.cpp

@ -22,12 +22,14 @@
#include "rnd.hpp"
#include <limits.h>
#include <unistd.h>
#include <algorithm>
#include <cstdlib>
#include <string>
#include <tuple>
#define PAD_LEN 16
#define PAD_LEN 6ULL
#define MAX_ATTEMPTS 3
static char const CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@ -38,16 +40,28 @@ namespace l
{
static
std::string
generate_tmp_path(std::string const base_)
generate_tmp_path(const std::string &dirpath_,
const std::string &filename_)
{
long name_max;
size_t substr_len;
fs::Path path;
std::string filename;
filename = '.';
for(int i = 0; i < PAD_LEN; i++)
name_max = pathconf(dirpath_.c_str(),_PC_NAME_MAX);
if(name_max == -1)
name_max = NAME_MAX;
substr_len = std::min(filename_.size(),
(size_t)(name_max - PAD_LEN - 2ULL));
filename = '.';
filename += filename_.substr(0,substr_len);
filename += '_';
for(size_t i = 0; i < PAD_LEN; i++)
filename += CHARS[RND::rand64(CHARS_SIZE)];
path = base_;
path = dirpath_;
path /= filename;
return path.string();
@ -57,7 +71,8 @@ namespace l
namespace fs
{
std::tuple<int,std::string>
mktemp_in_dir(const std::string dirpath_,
mktemp_in_dir(const std::string &dirpath_,
const std::string &filename_,
const int flags_)
{
int fd;
@ -67,12 +82,12 @@ namespace fs
fd = -1;
count = MAX_ATTEMPTS;
flags = (flags_ | O_EXCL | O_CREAT | O_TRUNC);
flags = (flags_ | O_EXCL | O_CREAT);
while(count-- > 0)
{
tmp_filepath = l::generate_tmp_path(dirpath_);
tmp_filepath = l::generate_tmp_path(dirpath_,filename_);
fd = fs::open(tmp_filepath,flags,S_IWUSR);
fd = fs::open(tmp_filepath,flags,S_IRUSR|S_IWUSR);
if((fd == -1) && (errno == EEXIST))
continue;
if(fd == -1)
@ -85,11 +100,13 @@ namespace fs
}
std::tuple<int,std::string>
mktemp(const std::string filepath_,
const int flags_)
mktemp(const std::string &filepath_,
const int flags_)
{
fs::Path filepath{filepath_};
return fs::mktemp_in_dir(filepath.parent_path(),flags_);
return fs::mktemp_in_dir(filepath.parent_path(),
filepath.filename(),
flags_);
}
}

10
src/fs_mktemp.hpp

@ -23,11 +23,13 @@
namespace fs
{
std::tuple<int,std::string>
mktemp(std::string const filepath,
int const flags);
std::tuple<int,std::string>
mktemp_in_dir(std::string const dirpath,
mktemp_in_dir(const std::string &dirpath,
const std::string &filename,
int const flags);
std::tuple<int,std::string>
mktemp(const std::string &filepath,
const int flags);
}

454
src/fsck_mergerfs.cpp

@ -0,0 +1,454 @@
#include "fsck_mergerfs.hpp"
#include "fs_lchmod.hpp"
#include "fs_lchown.hpp"
#include "fs_close.hpp"
#include "fs_ioctl.hpp"
#include "fs_lgetxattr.hpp"
#include "fs_lstat.hpp"
#include "fs_open.hpp"
#include "int_types.h"
#include "mergerfs_ioctl.hpp"
#include "str.hpp"
#include "fs_copyfile.hpp"
#include "fmt/core.h"
#include "fmt/chrono.h"
#include "CLI11.hpp"
#include <filesystem>
namespace FS = std::filesystem;
struct PathStat
{
PathStat(const std::string &path_)
: path(path_),
st{}
{
}
std::string path;
struct stat st;
};
using PathStatVec = std::vector<PathStat>;
using FixFunc = std::function<void(const PathStatVec&,const bool)>;
static
bool
_files_differ(const PathStatVec &paths_,
const bool check_size_)
{
if(paths_.size() <= 1)
return false;
const struct stat &st0 = paths_[0].st;
for(u64 i = 1; i < paths_.size(); i++)
{
const struct stat &st = paths_[i].st;
if(st.st_mode != st0.st_mode)
return true;
if(st.st_uid != st0.st_uid)
return true;
if(st.st_gid != st0.st_gid)
return true;
if(check_size_ &&
S_ISREG(st0.st_mode) &&
(st.st_size != st0.st_size))
return true;
}
return false;
}
static
bool
_files_same_type(const PathStatVec &paths_)
{
mode_t type;
if(paths_.size() <= 1)
return true;
type = (paths_[0].st.st_mode & S_IFMT);
for(u64 i = 1; i < paths_.size(); i++)
{
if((paths_[i].st.st_mode & S_IFMT) != type)
return false;
}
return true;
}
static
const char*
_file_type(const mode_t mode_)
{
switch(mode_ & S_IFMT)
{
case S_IFBLK:
return "block dev";
case S_IFCHR:
return "char dev";
case S_IFDIR:
return "directory";
case S_IFIFO:
return "fifo";
case S_IFLNK:
return "symlink";
case S_IFSOCK:
return "socket";
case S_IFREG:
return "regular";
}
return "unknown";
}
static
void
_compare_files(const std::string &mergerfs_path_,
PathStatVec &pathstats_,
const FixFunc fix_func_,
const bool check_size_,
const bool copy_file_)
{
int rv;
if(pathstats_.size() <= 1)
return;
for(auto &pathstat : pathstats_)
{
rv = fs::lstat(pathstat.path,&pathstat.st);
if(rv == -1)
pathstat.st.st_size = -errno;
}
if(!::_files_differ(pathstats_,check_size_))
return;
fmt::println("* {}",mergerfs_path_);
for(u64 i = 0; i < pathstats_.size(); i++)
{
const auto &path = pathstats_[i].path;
const auto &st = pathstats_[i].st;
fmt::println(" {}: {}",i,path);
if(st.st_size < 0)
fmt::println(" - err: {}",strerror(-st.st_size));
fmt::println(" -"
" uid: {};"
" gid: {};"
" mode: {:05o};"
" type: {};"
" size: {};"
" mtime: {:%Y-%m-%dT%H:%M:%S%z}",
st.st_uid,
st.st_gid,
st.st_mode,
::_file_type(st.st_mode),
st.st_size,
*std::localtime(&st.st_mtime));
}
if(!fix_func_)
return;
if(::_files_same_type(pathstats_))
fix_func_(pathstats_,copy_file_);
else
fmt::println(" X: WARNING - files are of different types."
" Requires manual intervention.");
}
static
int
_get_allpaths(const std::string &mergerfs_path_,
PathStatVec &pathstats_)
{
int fd;
int rv;
IOCTL_BUF buf;
std::vector<std::string> allpaths;
strcpy(buf,"allpaths");
fd = fs::open(mergerfs_path_,O_RDONLY|O_NOFOLLOW);
if(fd == -1)
return -errno;
rv = fs::ioctl(fd,IOCTL_FILE_INFO,buf);
fs::close(fd);
str::split_on_null(buf,rv,&allpaths);
for(const auto &path : allpaths)
pathstats_.emplace_back(path);
return 0;
}
static
u64
_read_u64(const u64 min_,
const u64 max_)
{
u64 rv;
while(true)
{
fmt::print("Select correct file: ");
std::cin >> rv;
if((rv >= min_) && (rv <= max_))
return rv;
std::cin.clear();
std::cin.ignore(100, '\n');
}
}
static
void
_setattr(const struct stat &st_,
const PathStatVec &pathstats_)
{
int rv;
for(const auto &path : pathstats_)
{
rv = fs::lchmod(path.path,st_.st_mode);
rv |= fs::lchown(path.path,st_.st_uid,st_.st_gid);
if(rv)
fmt::println("ERROR: problem setting mode or owner - {}",
strerror(errno));
}
}
static
void
_copy_files(const std::string &src_,
const PathStatVec &dsts_)
{
for(const auto &dst : dsts_)
{
int rv;
// TODO: Additional protection to confirm not the literal same
// file. fs::is_same_file(src,dst);
if(src_ == dst.path)
continue;
rv = fs::copyfile(src_,dst.path);
if(rv < 0)
fmt::print(stderr,
"ERROR: failed copying to {} - {}",
dst.path,
strerror(errno));
}
}
static
bool
_mtime_cmp(const struct stat &a_,
const struct stat &b_)
{
if(a_.st_mtim.tv_sec == b_.st_mtim.tv_sec)
return (a_.st_mtim.tv_nsec < b_.st_mtim.tv_nsec);
return (a_.st_mtim.tv_sec < b_.st_mtim.tv_sec);
}
static
bool
_pathstat_cmp(const PathStatVec::value_type &a_,
const PathStatVec::value_type &b_)
{
return ::_mtime_cmp(a_.st,b_.st);
}
static
const
PathStatVec::value_type*
_find_latest_mtime(const PathStatVec &pathstats_)
{
PathStatVec::const_iterator i;
i = std::max_element(pathstats_.begin(),
pathstats_.end(),
::_pathstat_cmp);
return &(*i);
}
static
const
PathStatVec::value_type*
_find_largest(const PathStatVec &pathstats_)
{
PathStatVec::const_iterator i;
i = std::max_element(pathstats_.begin(),
pathstats_.end(),
[](const PathStatVec::value_type &a_,
const PathStatVec::value_type &b_)
{
return (a_.st.st_size < b_.st.st_size);
});
return &(*i);
}
static
void
_fix_func_manual(const PathStatVec &pathstats_,
const bool copy_file_)
{
u64 idx;
idx = ::_read_u64(0ULL,pathstats_.size()-1);
if(copy_file_ && S_ISREG(pathstats_[idx].st.st_mode))
::_copy_files(pathstats_[idx].path,pathstats_);
else
::_setattr(pathstats_[idx].st,pathstats_);
}
static
void
_fix_func_newest(const PathStatVec &pathstats_,
const bool copy_file_)
{
const PathStatVec::value_type *pathst;
pathst = _find_latest_mtime(pathstats_);
if(!pathst)
return;
if(copy_file_ && S_ISREG(pathst->st.st_mode))
::_copy_files(pathst->path,pathstats_);
else
::_setattr(pathst->st,pathstats_);
};
static
void
_fix_func_largest(const PathStatVec &pathstats_,
const bool copy_file_)
{
const PathStatVec::value_type *pathst;
pathst = ::_find_largest(pathstats_);
if(!pathst)
return;
if(copy_file_ && S_ISREG(pathst->st.st_mode))
::_copy_files(pathst->path,pathstats_);
else
::_setattr(pathst->st,pathstats_);
}
static
FixFunc
_select_fix_func(const std::string &fix_)
{
if(fix_ == "none")
return {};
else if(fix_ == "manual")
return ::_fix_func_manual;
else if(fix_ == "newest")
return ::_fix_func_newest;
else if(fix_ == "largest")
return ::_fix_func_largest;
else
abort();
return {};
}
static
void
_fsck(const FS::path &path_,
const FixFunc fix_func_,
const bool check_size_,
const bool copy_file_)
{
int rv;
PathStatVec paths;
::_get_allpaths(path_,paths);
::_compare_files(path_,paths,fix_func_,check_size_,copy_file_);
auto opts = FS::directory_options::skip_permission_denied;
auto rdi = FS::recursive_directory_iterator(path_,opts);
for(const auto &de : rdi)
{
paths.clear();
rv = ::_get_allpaths(de.path(),paths);
if(rv == -1)
continue;
::_compare_files(de.path(),
paths,
fix_func_,
check_size_,
copy_file_);
}
}
int
fsck::main(int argc_,
char **argv_)
{
CLI::App app;
FS::path path;
std::string fix;
bool check_size;
bool copy_file;
FixFunc fix_func;
app.description("fsck.mergerfs:"
" A tool to help diagnose and solve mergerfs pool issues");
app.name("USAGE: fsck.mergerfs");
app.add_option("path",path)
->description("mergerfs path")
->check(CLI::ExistingDirectory)
->required();
app.add_option("--fix",fix)
->description("Will attempt to 'fix' the problem by chown+chmod or copying files based on a selected file.\n"
" * none: Do nothing. Just print details.\n"
" * manual: User selects source file.\n"
" * newest: Use file with most recent mtime.\n"
" * largest: Use file with largest size.")
->check(CLI::IsMember({"none","manual","newest","largest"}))
->default_val("none");
app.add_option("--check-size",check_size)
->description("Considers file size in calculating differences")
->default_val(false)
->default_str("false");
app.add_option("--copy-file",copy_file)
->description("Copy file rather than chown/chmod to fix")
->default_val(false)
->default_str("false");
try
{
app.parse(argc_,argv_);
}
catch(const CLI::ParseError &e)
{
return app.exit(e);
}
if((geteuid() != 0) && (fix != "none"))
fmt::println(stderr,"WARNING: fsck.mergerfs should be run as root to apply fixes");
fix_func = ::_select_fix_func(fix);
::_fsck(path,
fix_func,
check_size,
copy_file);
return 0;
}

6
src/fsck_mergerfs.hpp

@ -0,0 +1,6 @@
#pragma once
namespace fsck
{
int main(int argc, char **argv);
}

14
src/fuse_ioctl.cpp

@ -25,6 +25,7 @@
#include "fs_open.hpp"
#include "fs_path.hpp"
#include "gidcache.hpp"
#include "mergerfs_ioctl.hpp"
#include "str.hpp"
#include "ugid.hpp"
@ -34,19 +35,6 @@
#include <fcntl.h>
#include <string.h>
#ifndef _IOC_TYPE
#define _IOC_TYPE(X) (((X) >> 8) & 0xFF)
#endif
typedef char IOCTL_BUF[4096];
#define IOCTL_APP_TYPE 0xDF
#define IOCTL_FILE_INFO _IOWR(IOCTL_APP_TYPE,0,IOCTL_BUF)
#define IOCTL_GC _IO(IOCTL_APP_TYPE,1)
#define IOCTL_GC1 _IO(IOCTL_APP_TYPE,2)
#define IOCTL_INVALIDATE_ALL_NODES _IO(IOCTL_APP_TYPE,3)
#define IOCTL_INVALIDATE_GID_CACHE _IO(IOCTL_APP_TYPE,4)
// From linux/btrfs.h
#define BTRFS_IOCTL_MAGIC 0x94

19
src/mergerfs.cpp

@ -15,6 +15,7 @@
*/
#include "mergerfs.hpp"
#include "fsck_mergerfs.hpp"
#include "fs_path.hpp"
#include "fs_readahead.hpp"
@ -327,9 +328,25 @@ namespace l
}
}
static
int
_pick_app_and_run(int argc_,
char **argv_)
{
std::filesystem::path appname;
appname = argv_[0];
appname = appname.filename();
if(appname == "fsck.mergerfs")
return fsck::main(argc_,argv_);
return l::main(argc_,argv_);
}
int
main(int argc_,
char **argv_)
{
return l::main(argc_,argv_);
return ::_pick_app_and_run(argc_,argv_);
}

15
src/mergerfs_ioctl.hpp

@ -0,0 +1,15 @@
#pragma once
#include <sys/ioctl.h>
#ifndef _IOC_TYPE
#define _IOC_TYPE(X) (((X) >> 8) & 0xFF)
#endif
typedef char IOCTL_BUF[4096];
#define IOCTL_APP_TYPE 0xDF
#define IOCTL_FILE_INFO _IOWR(IOCTL_APP_TYPE,0,IOCTL_BUF)
#define IOCTL_GC _IO(IOCTL_APP_TYPE,1)
#define IOCTL_GC1 _IO(IOCTL_APP_TYPE,2)
#define IOCTL_INVALIDATE_ALL_NODES _IO(IOCTL_APP_TYPE,3)
#define IOCTL_INVALIDATE_GID_CACHE _IO(IOCTL_APP_TYPE,4)

17
src/str.cpp

@ -68,6 +68,23 @@ namespace str
return str::split(str_.c_str(),delimiter_,result_);
}
void
split_on_null(const char *str_,
const size_t len_,
std::vector<std::string> *result_)
{
const char *start;
const char *end;
start = str_;
end = start + len_;
while(start < end)
{
result_->emplace_back(start);
start += (result_->back().size() + 1);
}
}
void
rsplit1(const string &str_,
const char delimiter_,

5
src/str.hpp

@ -40,6 +40,11 @@ namespace str
const char delimiter,
std::set<std::string> *result);
void
split_on_null(const char *str,
const size_t len,
std::vector<std::string> *result);
void
rsplit1(const std::string &str,
const char delimiter,

Loading…
Cancel
Save