Abstract
The bash shell utility mkdir
is a tool used to create directories in most POSIX environments. I thought it would be fun to implement such functionality from C++ and maybe forge in some of my own features in the future. I will be using the C++ library Boost to handle the command line options and the Standard Template Libary (std) to work work with the filesystem. I would appreciate some sustains, nays, and comments on the 50 so lines of code I have written.
Code
mkdir.cpp
#include <boost/program_options.hpp>
namespace po = boost::program_options;
#include <filesystem>
#include <iostream>
#include <string.h>
#include <string>
#include <vector>
// Define the LOG() macro in order to be able to add
// a logger in the future without too much refactoring
//#define LOG(x) BOOST_LOG_TRIVIAL(x)
#define LOG(x) std::cout
int main(int argc, char **argv) {
// Declare the supported options.
po::options_description desc("Allowed options");
desc.add_options()
("help,h", "produce help message")
("parents,p", po::bool_switch(), "no error if existing, make parent directories as needed")
("verbose,v", po::bool_switch(), "print a message for each created directory");
const po::parsed_options parsed = po::command_line_parser(argc, argv)
.options(desc).allow_unregistered().run();
const std::vector<std::string> unrecognized =
collect_unrecognized(parsed.options, po::include_positional);
po::variables_map vm;
po::store(parsed, vm);
po::notify(vm);
for (const auto &opt : unrecognized) {
if (opt[0] == '-') {
LOG(error) << "Unknown option '" << opt << "'\n";
return 1;
}
}
if (vm.count("help") || argc <= 1) {
LOG(info) << desc;
return 1;
}
std::error_code ec;
for (const auto &dir_name : unrecognized) {
auto path = std::filesystem::absolute(std::filesystem::path(dir_name));
if (vm["parents"].as<bool>())
std::filesystem::create_directories(path, ec);
else
std::filesystem::create_directory(path, ec);
if (errno) {
LOG(error) << "Cannot create directory '" << dir_name
<< "': " << strerror(errno) << '\n';
errno = 0;
} else {
LOG(info) << "Created directory '" << dir_name << "'\n";
}
}
return 0;
}
Try it!
In order to compile this utility the following packages are needed:
sudo apt install build-essential libboost-program-options-dev
Then you can compile mkdir.cpp
with:
g++ mkdir.cpp -o mkdir -lboost_program_options -std=c++2a
Then you can test it out with:
./mkdir <options> <directory names>
4 Answers 4
Many filesystems allow directory name to start with -
. To support such names (not just in the mkdir
context, but in general) POSIX-compliant utility shall treat --
as an "end-of-options" option.
Boost supports this idea. Everything after --
is treated as a positional argument. However, as you include_positional
while collecting unrecognized, the program still treat such directories as options, and refuses to process them.
Since you use the error_code
overload, you should use ec
, rather than errno
to report errors. And if you do want to use errno
, you should not rely on <cerrno>
being magically included, but #include <cerrno>
explicitly.
-
\$\begingroup\$ I had no idea about the end of options option! also, I will revise it to use std::error_code \$\endgroup\$Alex Angel– Alex Angel2021年11月01日 21:15:26 +00:00Commented Nov 1, 2021 at 21:15
Exit codes
Invoking the help of commands is usually not considered an error, so an exit code of 0 would be expected.
When the real mkdir
fails to create a directory, it exits with a non-zero value.
Decompose work to functions
Currently the main
function does everything:
- Argument parsing
- Perform appropriate actions
If you plan to build this further, it already makes sense to decompose a bit more now.
Unimplemented features
The verbose flag is not implemented ;-)
Use braces around one line in if
blocks. See this Q&A and EXP19-C misra rule.
You do not need return 0;
at the end. That is the default return value in C++ programs.
-
\$\begingroup\$ Although return 0 is not needed I would never suggest omitting it. Leaving it in make's it clear to the reader what is happening, rather than relying on them knowing the somewhat esoteric rule that
int main
doesn't need an explicit return value, but every other function that returns an int does. \$\endgroup\$AShelly– AShelly2021年11月02日 18:21:41 +00:00Commented Nov 2, 2021 at 18:21
POSIX mkdir
also supports the -m
option for setting the permissions of the directory as it is created (avoiding race conditions). Consider supporting that as an enhancement.
Since std::filesystem
doesn't give expose that aspect of the mkdir()
system call, you will need to call it directly (and perhaps #ifdef
to support non-POSIX platforms). The alternative is a suitable call to std::filesystem::permissions()
immediately after creating the directory, but that leaves a window of time in which the initial default permissions are effective. That's the kind of thing that leads to security bugs, and a weakness in std::filesystem
if I'm correct.
-m
option is missing,mkdir -p foo
succeeds even whenfoo
is a regular file,mkdir -p foo/bar
prints an error message but actually works,-lboost_options
doesn't work on Ubuntu (-lboost_program_options
does), andlibboost-program-options-dev
takes 145 MB of disk space. \$\endgroup\$libboost-program-options-dev
so it wasn't a runtime dependency, and other users of pre-built binaries would only pay for the disk space consumed by the components actually used? \$\endgroup\$/bin/mkdir
is 88 kilobytes), but yeah, far better than having to install such a ridiculously large library for a program with such limited functionality. Thanks \$\endgroup\$