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
lineis one line as read from/etc/passwdOn 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
Userobjects that are returned on success from the functionPasswdErrorobjects that are thrown as exceptions from the functionMiscellaneous
std::stringfunctionality 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.nposis 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)
Footnotes