Exercise: Parse A Line From /etc/passwd
¶
Problem¶
/etc/passwd
contains lines of the form
jfasch:x:1000:1000:Joerg Faschingbauer:/home/jfasch:/bin/bash
We want to parse such content conveniently in C++ [1]
Requirements¶
Library Function (lib/parse-passwd.cpp
)¶
Write a function
User parse_passwd_line(std::string line);
The parameter
line
is one line as read from/etc/passwd
On success, the function returns an object of type
User
(definition see below)On failure, the function throws an exception object of type
PasswdError
(definition see below)
The header file lib/parse-passwd.h
contains the type definitions
and the function declaration, the implementation file
lib/parse-passwd.cpp
contains only the function skeleton which
does nothing, and which has to be implemented.
Important
lib/parse-passwd.cpp
is the only file that must be modified in
this exercise!
#pragma once
#include <string>
/** User
Represents the data from one line of /etc/passwd, e.g.
"jfasch:x:1000:1000:Joerg Faschingbauer:/home/jfasch:/bin/bash"
See ``man -s 5 passwd``
*/
struct User
{
User() : uid(-1), gid(-1) {} // <--- default constructor
std::string name;
std::string passwd;
int uid;
int gid;
std::string descr;
std::string homedir;
std::string shell;
};
/** PasswdError
Exception class to be throw on error from parse_passwd_line() (see
below)
*/
struct PasswdError
{
enum Code { // <--- nested type definition (error codes)
LINE_INVALID,
};
PasswdError(Code code, std::string msg) // <--- default constructor
: code(code), msg(msg) {}
Code code; // <--- struct member
std::string msg; // <--- struct member
};
/** parse_passwd_line
Given a line, parse from it items of ``struct User``.
Throw exceptions of type ``PasswdError`` as they are encountered.
See the test suite for detailed specs.
*/
User parse_passwd_line(std::string line);
Tests (tests/suite-passwd-line.cpp
)¶
The specification of the parse_passwd_line()
function is in
./tests/suite-passwd-line.cpp
. The other test suites in tests/
are implementation hints (see below).
Do not modify the tests!
Program (bin/read-passwd.cpp
)¶
Finally, after all tests pass, the program built from
bin/read-passwd.cpp
(this file must not be modified) should give
an output similar to this:
$ ./bin/read-passwd /etc/passwd
...
User.name jfasch
.passwd x
.uid 1000
.gid 1000
.descr Jörg Faschingbauer
.homedir /home/jfasch
.shell /bin/bash
...
Hints¶
How To Proceed¶
Comment out all tests
Build the project
In a top-down manner …
Comment back in one test after the other
Fix
parse_passwd_line()
until the current succeeds
During the implementation of parse_passwd_line()
you will need to
use
User
objects that are returned on success from the functionPasswdError
objects that are thrown as exceptions from the functionMiscellaneous
std::string
functionality to dissect a line
User
Usage (tests/suite-passwd-user.cpp
)¶
parse_passwd_line()
returns an object of type User
. See below
how such objects are initialized and used.
#include <parse-passwd.h>
#include <gtest/gtest.h>
// how to create a User object
TEST(passwd_user_suite, object_construction_and_member_access)
{
User user; // <--- calls default constructor
// all default-initialized (even the numbers)
ASSERT_EQ(user.name, "");
ASSERT_EQ(user.passwd, "");
ASSERT_EQ(user.uid, -1);
ASSERT_EQ(user.gid, -1);
ASSERT_EQ(user.descr, "");
ASSERT_EQ(user.homedir, "");
ASSERT_EQ(user.shell, "");
}
PasswdError
Usage (tests/suite-passwd-error.cpp
)¶
The exception type PasswdError
has a nested enum
type, which
is a bit hard to use for C++ beginners. See
tests/suite-passwd-error.cpp
for usage examples - especially how
you use the type in a throw
statement.
#include <parse-passwd.h>
#include <gtest/gtest.h>
// how to create an error
TEST(passwd_error_suite, object_construction_and_member_access)
{
PasswdError error(/*code*/PasswdError::LINE_INVALID, /*msg*/"howdy");
PasswdError::Code code = error.code;
std::string msg = error.msg;
ASSERT_EQ(code, PasswdError::LINE_INVALID);
ASSERT_EQ(msg, "howdy");
}
// how to throw an error
static User parse_passwd_line__fake(std::string line)
{
User u;
// lets say we see an error during parsing the line
if (false /*error :-)*/) {
throw PasswdError(PasswdError::LINE_INVALID, "howdy");
}
return u; // never reached. in this fake-function, we only
// demonstrate how to cause an error.
}
// how to call code that might throw an error of type PasswdError
TEST(passwd_error_suite, throwing_and_catching)
{
try {
parse_passwd_line__fake("some:line:with:an:error");
}
catch (const PasswdError& error) { // <--- catch-block entered if a PasswdError object is thrown
PasswdError::Code code = error.code;
std::string msg = error.msg;
ASSERT_EQ(code, PasswdError::LINE_INVALID);
ASSERT_EQ(msg, "howdy");
}
}
std::string
Usage¶
Dissecting lines like
jfasch:x:1000:1000:Joerg Faschingbauer:/home/jfasch:/bin/bash
will likely require the following tools.
Searching for the field delimiter
':'
. See std::string::find(). Take care of not-found conditions;s.npos
is typically returned by string searches.Given start and end positions of a fields, you need to take a substring. See std::string::substr().
Numeric fields (UID and GID) have to be converted from strings. See std::stoi(). Don’t forget to handle error conditions (see the example in the corresponding section in std::string)
Dependencies¶
Footnotes