Skip to content

Commit cb02a55

Browse files
committed
mbop: rework for-all-users
References: GXH-111
1 parent 0b23b03 commit cb02a55

File tree

6 files changed

+154
-61
lines changed

6 files changed

+154
-61
lines changed

doc/gromox-mbop.8

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ gromox\-mbop \(em Mailbox operations utility
99
.SH Global options
1010
.TP
1111
\fB\-c\fP
12-
Continuous operation mode. If a command in a series (e.g. with for\-all\-users)
12+
Continuous operation mode. If a command in a series (e.g. with foreach.*)
1313
fails, do not stop.
1414
.TP
1515
\fB\-d\fP \fI/var/lib/gromox/user/1/2\fP
@@ -32,11 +32,11 @@ clear\-rwz: delete IPM.RuleOrganizer FAI messages from the inbox
3232
.IP \(bu 4
3333
delmsg: issue "delete_message" RPCs for a mailbox
3434
.IP \(bu 4
35-
echo\-username: return username (diagnostic for use with for\-all\-users)
35+
echo\-username: return username (for use with foreach.*)
3636
.IP \(bu 4
3737
emptyfld: remove objects from folders
3838
.IP \(bu 4
39-
for\-all\-users: iterate over all users
39+
foreach.*: iterate over security objects
4040
.IP \(bu 4
4141
get\-freebusy: test FB schedule lookups
4242
.IP \(bu 4
@@ -172,9 +172,38 @@ gromox\-mbop \-u [email protected] emptyfld \-\-soft \-\-nuke\-folders DELETED
172172
.IP \(bu 4
173173
Timed deletion of trash:
174174
gromox\-mbop \-u [email protected] emptyfld \-Rt 1week \-\-soft DELETED
175-
.SH for\-all\-users
175+
.SH foreach.*
176176
.SS Synopsis
177-
\fBfor\-all\-users\fP [\fB\-j\fP \fIjobs\fP] \fIcommand\fP [command-args...]
177+
\fBforeach.\fP\fIfilter\fP[\fB\.\fP\fIfilter\fP]* [\fB\-j\fP \fIjobs\fP]
178+
\fIcommand\fP [command-args...]
179+
.SS Description
180+
Iterates over security objects and executes one of the other commands
181+
repeatedly. Filter specifications limit the types of security objects.
182+
.SS Filters
183+
.IP \(bu 4
184+
secobj: limit to objects that can be used in ACLs
185+
.IP \(bu 4
186+
user: regular users
187+
.IP \(bu 4
188+
dl: distribution lists
189+
.IP \(bu 4
190+
sharedmb: shared mailboxes
191+
.IP \(bu 4
192+
room: room objects
193+
.IP \(bu 4
194+
equipment: equipment objects
195+
.IP \(bu 4
196+
contact: GAB contact objects
197+
.IP \(bu 4
198+
active: active entities
199+
.IP \(bu 4
200+
susp: entities marked as "suspended"
201+
.IP \(bu 4
202+
deleted: entities marked as "deleted"
203+
.IP \(bu 4
204+
mb: entity has a mailbox directory defined
205+
.IP \(bu 4
206+
here: entity has current server as homeserver
178207
.SS Options
179208
.TP
180209
\fB\-j\fP \fIjobs\fP
@@ -187,7 +216,7 @@ Default: \fI1\fP
187216
Pseudoaction for running one of the other subcommand (e.g. ping, unload.)
188217
.SS Examples
189218
.IP \(bu 4
190-
Command concatenation: gromox\-mbop for\-all\-users \\( purge\-softdelete -r /
219+
Command concatenation: gromox\-mbop foreach.mb.local \\( purge\-softdelete -r /
191220
\\) \\( purge\-datafiles \\)
192221
.SH get\-freebusy
193222
.SS Synopsis

exch/mysql_adaptor/sql2.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,3 +715,33 @@ BOOL SVC_mysql_adaptor(enum plugin_op reason, const struct dlfuncs &data)
715715
}
716716
return TRUE;
717717
}
718+
719+
int mysql_adaptor_mbop_userlist(std::vector<sql_user> &out) try
720+
{
721+
auto qstr = "SELECT u.id, u.username, u.address_status, u.maildir, "
722+
"dt.propval_str AS dtypx, sv.hostname "
723+
"FROM users AS u " JOIN_WITH_DISPLAYTYPE
724+
"LEFT JOIN servers AS sv ON u.homeserver=sv.id";
725+
auto conn = g_sqlconn_pool.get_wait();
726+
if (!conn || !conn->query(qstr)) {
727+
mlog(LV_ERR, "Error obtaining user list");
728+
return ENOMEM;
729+
}
730+
auto result = conn->store_result();
731+
if (result == nullptr)
732+
return ENOMEM;
733+
std::vector<sql_user> gv(result.num_rows());
734+
for (size_t i = 0; i < gv.size(); ++i) {
735+
auto row = result.fetch_row();
736+
gv[i].id = strtoul(row[0], nullptr, 0);
737+
gv[i].username = row[1];
738+
gv[i].addr_status = strtoul(row[2], nullptr, 0);
739+
gv[i].maildir = znul(row[3]);
740+
gv[i].dtypx = static_cast<enum display_type>(strtoul(row[4], nullptr, 0));
741+
gv[i].homeserver = znul(row[5]);
742+
}
743+
out = std::move(gv);
744+
return 0;
745+
} catch (const std::bad_alloc &) {
746+
return ENOMEM;
747+
}

include/gromox/mysql_adaptor.hpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,22 @@ enum { /* for PR_ATTR_HIDDEN_*GROMOX* */
7171
};
7272

7373
/**
74-
* @dtex: %DT_* type as specified for PR_DISPLAY_TYPE_EX.
75-
* @hidden: hide bits for the address book
74+
* @dtypx: %DT_* type as specified for PR_DISPLAY_TYPE_EX
75+
* @hidden: hide bits for the address book
7676
* @list_type: mlist_type value; only interpret field when
7777
* addr_type==ADDRESS_TYPE_MLIST.
78+
*
79+
* Whether the fields are filled or not depends on the mysql_adaptor_get*()
80+
* function you are using.
7881
*/
7982
struct sql_user {
8083
enum display_type dtypx = DT_MAILUSER;
8184
unsigned int id = 0;
85+
unsigned int addr_status = AF_USER_DELETED;
8286
enum mlist_type list_type = mlist_type::normal;
8387
uint32_t hidden = 0;
8488
unsigned int list_priv = 0;
85-
std::string username, maildir;
89+
std::string username, homeserver, maildir;
8690
std::vector<std::string> aliases; /* email addresses */
8791
std::map<unsigned int, std::string> propvals;
8892
};
@@ -128,6 +132,7 @@ extern GX_EXPORT BOOL mysql_adaptor_get_mlist_memb(const char *username, const c
128132
extern GX_EXPORT gromox::errno_t mysql_adaptor_get_homeserver(const char *ent, bool is_pvt, std::pair<std::string, std::string> &);
129133
extern GX_EXPORT gromox::errno_t mysql_adaptor_scndstore_hints(unsigned int pri, std::vector<sql_user> &hints);
130134
extern GX_EXPORT int mysql_adaptor_domain_list_query(const char *dom);
135+
extern GX_EXPORT int mysql_adaptor_mbop_userlist(std::vector<sql_user> &);
131136

132137
/**
133138
* Determines whether an arbitrary actor can generally open/read the primary

tools/genimport.cpp

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -561,29 +561,6 @@ int gi_setup_from_user(const char *username)
561561
return EXIT_SUCCESS;
562562
}
563563

564-
int gi_get_users(gi_user_list_t &out) try
565-
{
566-
auto sqh = sql_login();
567-
if (sqh == nullptr)
568-
return -1;
569-
if (mysql_query(sqh.get(), "SELECT username, maildir FROM users") != 0) {
570-
fprintf(stderr, "exm: mysql_query: %s\n", mysql_error(sqh.get()));
571-
return -1;
572-
}
573-
DB_RESULT result = mysql_store_result(sqh.get());
574-
if (result == nullptr) {
575-
fprintf(stderr, "exm: mysql_store: %s\n", mysql_error(sqh.get()));
576-
return -1;
577-
}
578-
DB_ROW row;
579-
while ((row = result.fetch_row()) != nullptr)
580-
if (row[0] != nullptr && row[1] != nullptr)
581-
out.emplace_back(row[0], row[1]);
582-
return 0;
583-
} catch (const std::bad_alloc &) {
584-
return -1;
585-
}
586-
587564
namespace {
588565

589566
static constexpr std::pair<const char *, uint8_t> fld_special_names[] = {

tools/genimport.hpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ using gi_folder_map_t = std::unordered_map<uint32_t, tgt_folder>;
8181
using message_content_ptr = std::unique_ptr<MESSAGE_CONTENT, gromox::mc_delete>;
8282
using propname_array_ptr = std::unique_ptr<PROPNAME_ARRAY, gi_delete>;
8383
using tarray_set_ptr = std::unique_ptr<TARRAY_SET, gi_delete>;
84-
using gi_user_list_t = std::vector<std::pair<std::string, std::string>>;
8584

8685
enum {
8786
DELIVERY_TWOSTEP = 0x8000U,
@@ -110,4 +109,3 @@ extern int gi_setup_from_dir(const char *);
110109
extern int gi_startup_client(unsigned int maxconn = 1);
111110
extern eid_t gi_lookup_eid_by_name(const char *dir, const char *name);
112111
extern void gi_shutdown();
113-
extern int gi_get_users(gi_user_list_t &out);

tools/mbop_main.cpp

Lines changed: 81 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,6 @@ static constexpr HXoption g_options_table[] = {
397397
HXOPT_AUTOHELP,
398398
HXOPT_TABLEEND,
399399
};
400-
static constexpr static_module g_dfl_svc_plugins[] =
401-
{{"libgxs_mysql_adaptor.so", SVC_mysql_adaptor}};
402400

403401
static int main(int argc, char **argv)
404402
{
@@ -411,12 +409,6 @@ static int main(int argc, char **argv)
411409
return EXIT_PARAM;
412410
}
413411
textmaps_init();
414-
service_init({nullptr, g_dfl_svc_plugins, 1});
415-
auto cl_0 = make_scope_exit(service_stop);
416-
if (service_run_early() != 0 || service_run() != 0) {
417-
fprintf(stderr, "service_run: failed\n");
418-
return EXIT_FAILURE;
419-
}
420412
if (!mysql_adaptor_set_user_lang(g_dstuser.c_str(), g_language)) {
421413
fprintf(stderr, "Update of UI language rejected\n");
422414
return EXIT_FAILURE;
@@ -640,7 +632,7 @@ static int main(int argc, char **argv)
640632

641633
}
642634

643-
namespace for_all_wrap {
635+
namespace foreach_wrap {
644636

645637
static unsigned int g_numthreads = 1;
646638
static constexpr HXoption g_options_table[] = {
@@ -651,29 +643,81 @@ static constexpr HXoption g_options_table[] = {
651643

652644
static int help()
653645
{
654-
fprintf(stderr, "Usage: for-all-users [-j jobs] command [args...]\n");
646+
fprintf(stderr, "Usage: foreach[.filter]* [-j jobs] command [args...]\n");
647+
fprintf(stderr, " filter := secobj | user | mlist | sharedmb | contact |\n");
648+
fprintf(stderr, " active | susp | deleted | mb\n");
655649
global::command_overview();
656650
return EXIT_PARAM;
657651
}
658652

653+
static int filter_users(const char *mode, std::vector<sql_user> &ul)
654+
{
655+
if (strcmp(mode, "for-all-users") == 0)
656+
return 0;
657+
if (strncmp(mode, "foreach.", 8) != 0) {
658+
mlog(LV_ERR, "Unknown command: %s", mode);
659+
return -1;
660+
}
661+
std::string this_server;
662+
auto err = canonical_hostname(this_server);
663+
if (err != 0) {
664+
mlog(LV_ERR, "canonical_hostname: %s", strerror(err));
665+
return err;
666+
}
667+
668+
const char *dot = "";
669+
for (mode += 8; dot != nullptr; mode = dot + 1) {
670+
dot = strchr(mode, '.');
671+
auto filter = dot != nullptr ? std::string_view{mode, static_cast<size_t>(dot - mode)} :
672+
std::string_view{mode};
673+
if (filter == "secobj")
674+
continue;
675+
else if (filter == "user")
676+
std::erase_if(ul, [](const sql_user &u) { return (u.dtypx & DTE_MASK_LOCAL) != DT_MAILUSER; });
677+
else if (filter == "dl")
678+
std::erase_if(ul, [](const sql_user &u) { return (u.dtypx & DTE_MASK_LOCAL) != DT_DISTLIST; });
679+
else if (filter == "sharedmb")
680+
std::erase_if(ul, [](const sql_user &u) { return (u.addr_status & AF_USER__MASK) != AF_USER_SHAREDMBOX; });
681+
else if (filter == "contact")
682+
std::erase_if(ul, [](const sql_user &u) { return (u.addr_status & AF_USER__MASK) != AF_USER_CONTACT || (u.dtypx & DTE_MASK_LOCAL) != DT_REMOTE_MAILUSER; });
683+
else if (filter == "active")
684+
std::erase_if(ul, [](const sql_user &u) { return (u.addr_status & AF_USER__MASK) != AF_USER_NORMAL; });
685+
else if (filter == "susp")
686+
std::erase_if(ul, [](const sql_user &u) { return (u.addr_status & AF_USER__MASK) != AF_USER_SUSPENDED; });
687+
else if (filter == "deleted")
688+
std::erase_if(ul, [](const sql_user &u) { return (u.addr_status & AF_USER__MASK) != AF_USER_DELETED; });
689+
else if (filter == "mb")
690+
std::erase_if(ul, [](const sql_user &u) { return u.maildir.empty(); });
691+
else if (filter == "local")
692+
std::erase_if(ul, [&](const sql_user &u) { return strcasecmp(u.homeserver.c_str(), this_server.c_str()) != 0; });
693+
else {
694+
mlog(LV_ERR, "Unknown filter: %.*s\n", static_cast<int>(filter.size()), filter.data());
695+
return -1;
696+
}
697+
}
698+
return 0;
699+
}
700+
659701
static int main(int argc, char **argv)
660702
{
661703
if (HX_getopt5(g_options_table, argv, &argc, &argv,
662704
HXOPT_RQ_ORDER | HXOPT_USAGEONERR) != HXOPT_ERR_SUCCESS)
663705
return EXIT_PARAM;
664706
auto cl_0 = make_scope_exit([=]() { HX_zvecfree(argv); });
665707
if (global::g_arg_username != nullptr || global::g_arg_userdir != nullptr) {
666-
fprintf(stderr, "Cannot use -d/-u with for-all-users\n");
708+
fprintf(stderr, "Cannot use -d/-u with foreach.*\n");
667709
return EXIT_PARAM;
668710
} else if (g_numthreads == 0) {
669711
g_numthreads = gx_concurrency();
670712
}
713+
auto fe_mode = argv[0];
671714
--argc;
672715
++argv;
673716
if (argc == 0)
674717
return help();
675-
gi_user_list_t ul;
676-
if (gi_get_users(ul) != 0)
718+
719+
std::vector<sql_user> ul;
720+
if (mysql_adaptor_mbop_userlist(ul) != 0 || filter_users(fe_mode, ul) != 0)
677721
return EXIT_FAILURE;
678722
auto ret = gi_startup_client(g_numthreads);
679723
if (ret != 0)
@@ -688,49 +732,49 @@ static int main(int argc, char **argv)
688732
if (HX_getopt5(empty_options_table, argv, nullptr, nullptr,
689733
HXOPT_RQ_ORDER | HXOPT_USAGEONERR) != HXOPT_ERR_SUCCESS)
690734
return EXIT_PARAM;
691-
for (auto &&[username, maildir] : ul) {
735+
for (const auto &user : ul) {
692736
sem.acquire();
693737
if (ret != EXIT_SUCCESS && !global::g_continuous_mode)
694738
break;
695-
futs.emplace_back(std::async([](std::string *maildir, Sem *sem, int *ret) {
739+
futs.emplace_back(std::async([](const std::string *maildir, Sem *sem, int *ret) {
696740
if (!exmdb_client::ping_store(maildir->c_str()))
697741
*ret = EXIT_FAILURE;
698742
sem->release();
699-
}, &maildir, &sem, &ret));
743+
}, &user.maildir, &sem, &ret));
700744
}
701745
} else if (strcmp(argv[0], "unload") == 0) {
702746
if (HX_getopt5(empty_options_table, argv, nullptr, nullptr,
703747
HXOPT_RQ_ORDER | HXOPT_USAGEONERR) != HXOPT_ERR_SUCCESS)
704748
return EXIT_PARAM;
705-
for (auto &&[username, maildir] : ul) {
749+
for (const auto &user : ul) {
706750
sem.acquire();
707751
if (ret != EXIT_SUCCESS && !global::g_continuous_mode)
708752
return ret;
709-
futs.emplace_back(std::async([](std::string *maildir, Sem *sem, int *ret) {
753+
futs.emplace_back(std::async([](const std::string *maildir, Sem *sem, int *ret) {
710754
if (!exmdb_client::unload_store(maildir->c_str()))
711755
*ret = EXIT_FAILURE;
712756
sem->release();
713-
}, &maildir, &sem, &ret));
757+
}, &user.maildir, &sem, &ret));
714758
}
715759
} else if (strcmp(argv[0], "vacuum") == 0) {
716760
if (HX_getopt5(empty_options_table, argv, nullptr, nullptr,
717761
HXOPT_RQ_ORDER | HXOPT_USAGEONERR) != HXOPT_ERR_SUCCESS)
718762
return EXIT_PARAM;
719-
for (auto &&[username, maildir] : ul) {
763+
for (const auto &user : ul) {
720764
sem.acquire();
721765
if (ret != EXIT_SUCCESS && !global::g_continuous_mode)
722766
return ret;
723-
futs.emplace_back(std::async([](std::string *maildir, Sem *sem, int *ret) {
767+
futs.emplace_back(std::async([](const std::string *maildir, Sem *sem, int *ret) {
724768
if (!exmdb_client::vacuum(maildir->c_str()))
725769
*ret = EXIT_FAILURE;
726770
sem->release();
727-
}, &maildir, &sem, &ret));
771+
}, &user.maildir, &sem, &ret));
728772
}
729773
} else {
730-
for (auto &&[username, maildir] : ul) {
774+
for (auto &&user : ul) {
731775
/* cmd_parser is not thread-safe (global state), cannot parallelize */
732-
g_dstuser = std::move(username);
733-
g_storedir_s = std::move(maildir);
776+
g_dstuser = std::move(user.username);
777+
g_storedir_s = std::move(user.maildir);
734778
g_storedir = g_storedir_s.c_str();
735779
ret = global::cmd_parser(argc, argv);
736780
if (ret == EXIT_PARAM)
@@ -962,6 +1006,9 @@ static int single_user_wrap(int argc, char **argv)
9621006
return ret;
9631007
}
9641008

1009+
static constexpr static_module g_dfl_svc_plugins[] =
1010+
{{"libgxs_mysql_adaptor.so", SVC_mysql_adaptor}};
1011+
9651012
int main(int argc, char **argv)
9661013
{
9671014
setvbuf(stdout, nullptr, _IOLBF, 0);
@@ -973,8 +1020,15 @@ int main(int argc, char **argv)
9731020
++argv;
9741021
if (argc == 0)
9751022
return global::help();
976-
if (strcmp(argv[0], "for-all-users") == 0)
977-
return for_all_wrap::main(argc, argv);
1023+
service_init({nullptr, g_dfl_svc_plugins, 1});
1024+
auto cl_1 = make_scope_exit(service_stop);
1025+
if (service_run_early() != 0 || service_run() != 0) {
1026+
fprintf(stderr, "service_run: failed\n");
1027+
return EXIT_FAILURE;
1028+
}
1029+
if (strncmp(argv[0], "foreach.", 8) == 0 ||
1030+
strncmp(argv[0], "for-all-", 8) == 0)
1031+
return foreach_wrap::main(argc, argv);
9781032
else
9791033
return single_user_wrap(argc, argv);
9801034
}

0 commit comments

Comments
 (0)