1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2025-02-19 21:57:27 +03:00

Merge pull request #21162 from poettering/homed-cifs-improvements

homed: various cifs backend improvements
This commit is contained in:
Lennart Poettering 2021-10-28 08:17:05 +02:00 committed by GitHub
commit 93a5fe3e65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 284 additions and 87 deletions

View File

@ -411,7 +411,12 @@ useful when `cifs` is used as storage mechanism for the user's home directory,
see above.
`cifsService` → A string indicating the Windows File Share service (CIFS) to
mount as home directory of the user on login.
mount as home directory of the user on login. Should be in format
`//<host>/<service>/<directory/…>`. The directory part is optional. If missing
the top-level directory of the CIFS share is used.
`cifsExtraMountOptions` → A string with additional mount options to pass to
`mount.cifs` when mounting the home directory CIFS share.
`imagePath` → A string with an absolute file system path to the file, directory
or block device to use for storage backing the home directory. If the `luks`
@ -705,15 +710,16 @@ that may be used in this section are identical to the equally named ones in the
`notAfterUSec`, `storage`, `diskSize`, `diskSizeRelative`, `skeletonDirectory`,
`accessMode`, `tasksMax`, `memoryHigh`, `memoryMax`, `cpuWeight`, `ioWeight`,
`mountNoDevices`, `mountNoSuid`, `mountNoExecute`, `cifsDomain`,
`cifsUserName`, `cifsService`, `imagePath`, `uid`, `gid`, `memberOf`,
`fileSystemType`, `partitionUuid`, `luksUuid`, `fileSystemUuid`, `luksDiscard`,
`luksOfflineDiscard`, `luksCipher`, `luksCipherMode`, `luksVolumeKeySize`,
`luksPbkdfHashAlgorithm`, `luksPbkdfType`, `luksPbkdfTimeCostUSec`,
`luksPbkdfMemoryCost`, `luksPbkdfParallelThreads`, `rateLimitIntervalUSec`,
`rateLimitBurst`, `enforcePasswordPolicy`, `autoLogin`, `stopDelayUSec`,
`killProcesses`, `passwordChangeMinUSec`, `passwordChangeMaxUSec`,
`passwordChangeWarnUSec`, `passwordChangeInactiveUSec`, `passwordChangeNow`,
`pkcs11TokenUri`, `fido2HmacCredential`.
`cifsUserName`, `cifsService`, `cifsExtraMountOptions`, `imagePath`, `uid`,
`gid`, `memberOf`, `fileSystemType`, `partitionUuid`, `luksUuid`,
`fileSystemUuid`, `luksDiscard`, `luksOfflineDiscard`, `luksCipher`,
`luksCipherMode`, `luksVolumeKeySize`, `luksPbkdfHashAlgorithm`,
`luksPbkdfType`, `luksPbkdfTimeCostUSec`, `luksPbkdfMemoryCost`,
`luksPbkdfParallelThreads`, `rateLimitIntervalUSec`, `rateLimitBurst`,
`enforcePasswordPolicy`, `autoLogin`, `stopDelayUSec`, `killProcesses`,
`passwordChangeMinUSec`, `passwordChangeMaxUSec`, `passwordChangeWarnUSec`,
`passwordChangeInactiveUSec`, `passwordChangeNow`, `pkcs11TokenUri`,
`fido2HmacCredential`.
## Fields in the `binding` section

View File

@ -688,10 +688,17 @@
<term><option>--cifs-domain=</option><replaceable>DOMAIN</replaceable></term>
<term><option>--cifs-user-name=</option><replaceable>USER</replaceable></term>
<term><option>--cifs-service=</option><replaceable>SERVICE</replaceable></term>
<term><option>--cifs-extra-mount-options=</option><replaceable>OPTIONS</replaceable></term>
<listitem><para>Configures the Windows File Sharing (CIFS) domain and user to associate with the home
directory/user account, as well as the file share ("service") to mount as directory. The latter is used when
<literal>cifs</literal> storage is selected.</para></listitem>
directory/user account, as well as the file share ("service") to mount as directory. The latter is
used when <literal>cifs</literal> storage is selected. The file share should be specified in format
<literal>//<replaceable>host</replaceable>/<replaceable>share</replaceable>/<replaceable>directory/…</replaceable></literal>. The
directory part is optional — if not specified the home directory will be placed in the top-level
directory of the share. The <option>--cifs-extra-mount-options=</option> setting allows specifying
additional mount options when mounting the share, see <citerefentry
project='man-pages'><refentrytitle>mount.cifs</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for details.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -12,6 +12,7 @@
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hostname-util.h"
#include "log.h"
#include "macro.h"
#include "missing_fcntl.h"
@ -952,3 +953,78 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) {
return -EINTR;
}
int parse_cifs_service(
const char *s,
char **ret_host,
char **ret_service,
char **ret_path) {
_cleanup_free_ char *h = NULL, *ss = NULL, *x = NULL;
const char *p, *e, *d;
char delimiter;
/* Parses a CIFS service in form of //host/service/path… and splitting it in three parts. The last
* part is optional, in which case NULL is returned there. To maximize compatibility syntax with
* backslashes instead of slashes is accepted too. */
if (!s)
return -EINVAL;
p = startswith(s, "//");
if (!p) {
p = startswith(s, "\\\\");
if (!p)
return -EINVAL;
}
delimiter = s[0];
e = strchr(p, delimiter);
if (!e)
return -EINVAL;
h = strndup(p, e - p);
if (!h)
return -ENOMEM;
if (!hostname_is_valid(h, 0))
return -EINVAL;
e++;
d = strchrnul(e, delimiter);
ss = strndup(e, d - e);
if (!ss)
return -ENOMEM;
if (!filename_is_valid(ss))
return -EINVAL;
if (!isempty(d)) {
x = strdup(skip_leading_chars(d, CHAR_TO_STR(delimiter)));
if (!x)
return -EINVAL;
/* Make sure to convert Windows-style "\" → Unix-style / */
for (char *i = x; *i; i++)
if (*i == delimiter)
*i = '/';
if (!path_is_valid(x))
return -EINVAL;
path_simplify(x);
if (!path_is_normalized(x))
return -EINVAL;
}
if (ret_host)
*ret_host = TAKE_PTR(h);
if (ret_service)
*ret_service = TAKE_PTR(ss);
if (ret_path)
*ret_path = TAKE_PTR(x);
return 0;
}

View File

@ -106,3 +106,5 @@ static inline int conservative_rename(const char *oldpath, const char *newpath)
}
int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size);
int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char **ret_path);

View File

@ -14,6 +14,7 @@
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "fs-util.h"
#include "glyph-util.h"
#include "home-util.h"
#include "homectl-fido2.h"
@ -2160,6 +2161,8 @@ static int help(int argc, char *argv[], void *userdata) {
" --cifs-domain=DOMAIN CIFS (Windows) domain\n"
" --cifs-user-name=USER CIFS (Windows) user name\n"
" --cifs-service=SERVICE CIFS (Windows) service to mount as home area\n"
" --cifs-extra-mount-options=OPTIONS\n"
" CIFS (Windows) extra mount options\n"
"\n%4$sLogin Behaviour User Record Properties:%5$s\n"
" --stop-delay=SECS How long to leave user services running after\n"
" logout\n"
@ -2216,6 +2219,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_CIFS_DOMAIN,
ARG_CIFS_USER_NAME,
ARG_CIFS_SERVICE,
ARG_CIFS_EXTRA_MOUNT_OPTIONS,
ARG_TASKS_MAX,
ARG_MEMORY_HIGH,
ARG_MEMORY_MAX,
@ -2308,6 +2312,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "cifs-user-name", required_argument, NULL, ARG_CIFS_USER_NAME },
{ "cifs-domain", required_argument, NULL, ARG_CIFS_DOMAIN },
{ "cifs-service", required_argument, NULL, ARG_CIFS_SERVICE },
{ "cifs-extra-mount-options", required_argument, NULL, ARG_CIFS_EXTRA_MOUNT_OPTIONS },
{ "rate-limit-interval", required_argument, NULL, ARG_RATE_LIMIT_INTERVAL },
{ "rate-limit-burst", required_argument, NULL, ARG_RATE_LIMIT_BURST },
{ "stop-delay", required_argument, NULL, ARG_STOP_DELAY },
@ -2447,16 +2452,16 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_ICON_NAME:
case ARG_CIFS_USER_NAME:
case ARG_CIFS_DOMAIN:
case ARG_CIFS_SERVICE: {
case ARG_CIFS_EXTRA_MOUNT_OPTIONS: {
const char *field =
c == ARG_EMAIL_ADDRESS ? "emailAddress" :
c == ARG_LOCATION ? "location" :
c == ARG_ICON_NAME ? "iconName" :
c == ARG_CIFS_USER_NAME ? "cifsUserName" :
c == ARG_CIFS_DOMAIN ? "cifsDomain" :
c == ARG_CIFS_SERVICE ? "cifsService" :
NULL;
c == ARG_EMAIL_ADDRESS ? "emailAddress" :
c == ARG_LOCATION ? "location" :
c == ARG_ICON_NAME ? "iconName" :
c == ARG_CIFS_USER_NAME ? "cifsUserName" :
c == ARG_CIFS_DOMAIN ? "cifsDomain" :
c == ARG_CIFS_EXTRA_MOUNT_OPTIONS ? "cifsExtraMountOptions" :
NULL;
assert(field);
@ -2475,6 +2480,25 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_CIFS_SERVICE:
if (isempty(optarg)) {
r = drop_from_identity("cifsService");
if (r < 0)
return r;
break;
}
r = parse_cifs_service(optarg, NULL, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to validate CIFS service name: %s", optarg);
r = json_variant_set_field_string(&arg_identity_extra, "cifsService", optarg);
if (r < 0)
return log_error_errno(r, "Failed to set cifsService field: %m");
break;
case ARG_PASSWORD_HINT:
if (isempty(optarg)) {
r = drop_from_identity("passwordHint");

View File

@ -7,6 +7,7 @@
#include "fs-util.h"
#include "homework-cifs.h"
#include "homework-mount.h"
#include "mkdir.h"
#include "mount-util.h"
#include "process-util.h"
#include "stat-util.h"
@ -18,83 +19,125 @@ int home_setup_cifs(
HomeSetupFlags flags,
HomeSetup *setup) {
_cleanup_free_ char *chost = NULL, *cservice = NULL, *cdir = NULL, *chost_and_service = NULL, *j = NULL;
char **pw;
int r;
assert(h);
assert(setup);
assert(user_record_storage(h) == USER_CIFS);
assert(setup);
assert(!setup->undo_mount);
assert(setup->root_fd < 0);
if (FLAGS_SET(flags, HOME_SETUP_ALREADY_ACTIVATED))
if (FLAGS_SET(flags, HOME_SETUP_ALREADY_ACTIVATED)) {
setup->root_fd = open(user_record_home_directory(h), O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
else {
bool mounted = false;
char **pw;
int r;
if (setup->root_fd < 0)
return log_error_errno(errno, "Failed to open home directory: %m");
r = home_unshare_and_mkdir();
return 0;
}
if (!h->cifs_service)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks CIFS service, refusing.");
r = parse_cifs_service(h->cifs_service, &chost, &cservice, &cdir);
if (r < 0)
return log_error_errno(r, "Failed parse CIFS service specification: %m");
/* Just the host and service part, without the directory */
chost_and_service = strjoin("//", chost, "/", cservice);
if (!chost_and_service)
return log_oom();
r = home_unshare_and_mkdir();
if (r < 0)
return r;
STRV_FOREACH(pw, h->password) {
_cleanup_(unlink_and_freep) char *p = NULL;
_cleanup_free_ char *options = NULL;
_cleanup_(fclosep) FILE *f = NULL;
pid_t mount_pid;
int exit_status;
r = fopen_temporary(NULL, &f, &p);
if (r < 0)
return r;
return log_error_errno(r, "Failed to create temporary credentials file: %m");
STRV_FOREACH(pw, h->password) {
_cleanup_(unlink_and_freep) char *p = NULL;
_cleanup_free_ char *options = NULL;
_cleanup_(fclosep) FILE *f = NULL;
pid_t mount_pid;
int exit_status;
fprintf(f,
"username=%s\n"
"password=%s\n",
user_record_cifs_user_name(h),
*pw);
r = fopen_temporary(NULL, &f, &p);
if (r < 0)
return log_error_errno(r, "Failed to create temporary credentials file: %m");
if (h->cifs_domain)
fprintf(f, "domain=%s\n", h->cifs_domain);
fprintf(f,
"username=%s\n"
"password=%s\n",
user_record_cifs_user_name(h),
*pw);
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write temporary credentials file: %m");
if (h->cifs_domain)
fprintf(f, "domain=%s\n", h->cifs_domain);
f = safe_fclose(f);
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write temporary credentials file: %m");
if (asprintf(&options, "credentials=%s,uid=" UID_FMT ",forceuid,gid=" GID_FMT ",forcegid,file_mode=0%3o,dir_mode=0%3o",
p, h->uid, user_record_gid(h), user_record_access_mode(h), user_record_access_mode(h)) < 0)
return log_oom();
f = safe_fclose(f);
if (asprintf(&options, "credentials=%s,uid=" UID_FMT ",forceuid,gid=" GID_FMT ",forcegid,file_mode=0%3o,dir_mode=0%3o",
p, h->uid, user_record_gid(h), user_record_access_mode(h), user_record_access_mode(h)) < 0)
if (h->cifs_extra_mount_options)
if (!strextend_with_separator(&options, ",", h->cifs_extra_mount_options))
return log_oom();
r = safe_fork("(mount)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_STDOUT_TO_STDERR, &mount_pid);
if (r < 0)
return r;
if (r == 0) {
/* Child */
execl("/bin/mount", "/bin/mount", "-n", "-t", "cifs",
h->cifs_service, HOME_RUNTIME_WORK_DIR,
"-o", options, NULL);
r = safe_fork("(mount)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_STDOUT_TO_STDERR, &mount_pid);
if (r < 0)
return r;
if (r == 0) {
/* Child */
execl("/bin/mount", "/bin/mount", "-n", "-t", "cifs",
chost_and_service, HOME_RUNTIME_WORK_DIR,
"-o", options, NULL);
log_error_errno(errno, "Failed to execute mount: %m");
_exit(EXIT_FAILURE);
}
log_error_errno(errno, "Failed to execute mount: %m");
_exit(EXIT_FAILURE);
}
exit_status = wait_for_terminate_and_check("mount", mount_pid, WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS);
if (exit_status < 0)
return exit_status;
if (exit_status != EXIT_SUCCESS)
return -EPROTO;
mounted = true;
exit_status = wait_for_terminate_and_check("mount", mount_pid, WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS);
if (exit_status < 0)
return exit_status;
if (exit_status == EXIT_SUCCESS) {
setup->undo_mount = true;
break;
}
if (!mounted)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY),
"Failed to mount home directory with supplied password.");
setup->root_fd = open(HOME_RUNTIME_WORK_DIR, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
if (pw[1])
log_info("CIFS mount failed with password #%zu, trying next password.", (size_t) (pw - h->password) + 1);
}
if (!setup->undo_mount)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY),
"Failed to mount home directory, supplied password(s) possibly wrong.");
/* Adjust MS_SUID and similar flags */
r = mount_nofollow_verbose(LOG_ERR, NULL, HOME_RUNTIME_WORK_DIR, NULL, MS_BIND|MS_REMOUNT|user_record_mount_flags(h), NULL);
if (r < 0)
return r;
if (cdir) {
j = path_join(HOME_RUNTIME_WORK_DIR, cdir);
if (!j)
return log_oom();
if (FLAGS_SET(flags, HOME_SETUP_CIFS_MKDIR)) {
r = mkdir_p(j, 0700);
if (r < 0)
return log_error_errno(r, "Failed to create CIFS subdirectory: %m");
}
}
setup->root_fd = open(j ?: HOME_RUNTIME_WORK_DIR, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
if (setup->root_fd < 0)
return log_error_errno(errno, "Failed to open home directory: %m");
setup->mount_suffix = TAKE_PTR(cdir);
return 0;
}
@ -104,7 +147,7 @@ int home_activate_cifs(
PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL;
const char *hdo, *hd;
int r;
@ -113,23 +156,20 @@ int home_activate_cifs(
assert(setup);
assert(ret_home);
if (!h->cifs_service)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks CIFS service, refusing.");
assert_se(hdo = user_record_home_directory(h));
hd = strdupa_safe(hdo); /* copy the string out, since it might change later in the home record object */
r = home_setup_cifs(h, 0, setup);
r = home_setup(h, 0, cache, setup, &header_home);
if (r < 0)
return r;
r = home_refresh(h, setup, NULL, cache, NULL, &new_home);
r = home_refresh(h, setup, header_home, cache, NULL, &new_home);
if (r < 0)
return r;
setup->root_fd = safe_close(setup->root_fd);
r = home_move_mount(NULL, hd);
r = home_move_mount(setup->mount_suffix, hd);
if (r < 0)
return r;
@ -161,7 +201,7 @@ int home_create_cifs(UserRecord *h, HomeSetup *setup, UserRecord **ret_home) {
return log_error_errno(errno, "Unable to detect whether /sbin/mount.cifs exists: %m");
}
r = home_setup_cifs(h, 0, setup);
r = home_setup_cifs(h, HOME_SETUP_CIFS_MKDIR, setup);
if (r < 0)
return r;

View File

@ -87,17 +87,17 @@ int home_unshare_and_mount(const char *node, const char *fstype, bool discard, u
return home_mount_node(node, fstype, discard, flags);
}
int home_move_mount(const char *user_name_and_realm, const char *target) {
int home_move_mount(const char *mount_suffix, const char *target) {
_cleanup_free_ char *subdir = NULL;
const char *d;
int r;
assert(target);
/* If user_name_and_realm is set, then we'll mount a subdir of the source mount into the host. If
* it's NULL we'll move the mount itself */
if (user_name_and_realm) {
subdir = path_join(HOME_RUNTIME_WORK_DIR, user_name_and_realm);
/* If 'mount_suffix' is set, then we'll mount a subdir of the source mount into the host. If it's
* NULL we'll move the mount itself */
if (mount_suffix) {
subdir = path_join(HOME_RUNTIME_WORK_DIR, mount_suffix);
if (!subdir)
return log_oom();

View File

@ -379,6 +379,8 @@ int home_setup_done(HomeSetup *setup) {
if (setup->do_drop_caches)
drop_caches_now();
setup->mount_suffix = mfree(setup->mount_suffix);
return r;
}

View File

@ -37,6 +37,8 @@ typedef struct HomeSetup {
uint64_t partition_offset;
uint64_t partition_size;
char *mount_suffix; /* The directory to use as home dir is this path below /run/systemd/user-home-mount */
} HomeSetup;
typedef struct PasswordCache {
@ -66,6 +68,9 @@ static inline bool password_cache_contains(const PasswordCache *cache, const cha
/* Various flags for the operation of setting up a home directory */
typedef enum HomeSetupFlags {
HOME_SETUP_ALREADY_ACTIVATED = 1 << 0, /* Open an already activated home, rather than activate it afresh */
/* CIFS backend: */
HOME_SETUP_CIFS_MKDIR = 1 << 1, /* Create CIFS subdir when missing */
} HomeSetupFlags;
int home_setup_done(HomeSetup *setup);

View File

@ -271,6 +271,7 @@ static UserRecord* user_record_free(UserRecord *h) {
free(h->cifs_service);
free(h->cifs_user_name);
free(h->cifs_domain);
free(h->cifs_extra_mount_options);
free(h->image_path);
free(h->image_path_auto);
@ -1267,6 +1268,7 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "cifsExtraMountOptions", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_extra_mount_options), 0 },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
@ -1612,6 +1614,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "cifsExtraMountOptions", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_extra_mount_options), 0 },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },

View File

@ -304,6 +304,7 @@ typedef struct UserRecord {
char *cifs_domain;
char *cifs_user_name;
char *cifs_service;
char *cifs_extra_mount_options;
char *image_path;
char *image_path_auto; /* when none is configured explicitly, this is where we place the implicit image */

View File

@ -921,6 +921,36 @@ static void test_rmdir_parents(void) {
assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
}
static void test_parse_cifs_service_one(const char *f, const char *h, const char *s, const char *d, int ret) {
_cleanup_free_ char *a = NULL, *b = NULL, *c = NULL;
assert_se(parse_cifs_service(f, &a, &b, &c) == ret);
assert_se(streq_ptr(a, h));
assert_se(streq_ptr(b, s));
assert_se(streq_ptr(c, d));
}
static void test_parse_cifs_service(void) {
log_info("/* %s */", __func__);
test_parse_cifs_service_one("//foo/bar/baz", "foo", "bar", "baz", 0);
test_parse_cifs_service_one("\\\\foo\\bar\\baz", "foo", "bar", "baz", 0);
test_parse_cifs_service_one("//foo/bar", "foo", "bar", NULL, 0);
test_parse_cifs_service_one("\\\\foo\\bar", "foo", "bar", NULL, 0);
test_parse_cifs_service_one("//foo/bar/baz/uuu", "foo", "bar", "baz/uuu", 0);
test_parse_cifs_service_one("\\\\foo\\bar\\baz\\uuu", "foo", "bar", "baz/uuu", 0);
test_parse_cifs_service_one(NULL, NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("abc", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("abc/cde/efg", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("//foo/bar/baz/..", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("//foo///", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("//foo/.", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("//foo/a/.", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("//./a", NULL, NULL, NULL, -EINVAL);
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_INFO);
@ -940,6 +970,7 @@ int main(int argc, char *argv[]) {
test_chmod_and_chown();
test_conservative_rename();
test_rmdir_parents();
test_parse_cifs_service();
return 0;
}