m_storetkl.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. // One include for all cross-platform compatibility thangs
  2. #include "unrealircd.h"
  3. #define TKL_DB "tkl.db"
  4. #define TKL_DB_VERSION 1000
  5. // Muh macros lol
  6. #ifndef _WIN32
  7. #define OpenFile(fd, file, flags) fd = open(file, flags, S_IRUSR | S_IWUSR)
  8. #else
  9. #define OpenFile(fd, file, flags) fd = open(file, flags, S_IREAD | S_IWRITE)
  10. #endif
  11. #define R_SAFE(x) \
  12. do { \
  13. if((x)) \
  14. { \
  15. close(fd); \
  16. config_warn("[storetkl] Read error from the persistent storage file '%s/%s' on server %s", PERMDATADIR, TKL_DB, me.name); \
  17. free(filepath); \
  18. return -1; \
  19. } \
  20. } while (0)
  21. #define W_SAFE(x) \
  22. do { \
  23. if((x)) \
  24. { \
  25. close(fd); \
  26. config_warn("[storetkl] Write error from the persistent storage file '%s/%s' on server %s", PERMDATADIR, TKL_DB, me.name); \
  27. free(filepath); \
  28. return -1; \
  29. } \
  30. } while (0)
  31. #define IsMDErr(x, y, z) \
  32. do { \
  33. if(!(x)) { \
  34. config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER(y).name, ModuleGetErrorStr((z)->handle)); \
  35. return MOD_FAILED; \
  36. } \
  37. } while(0)
  38. // Quality fowod declarations
  39. void storetkl_moddata_free(ModData *md);
  40. int storetkl_hook_tkl_add(aClient *cptr, aClient *sptr, aTKline *tkl, int parc, char *parv[]);
  41. int storetkl_hook_tkl_del(aClient *cptr, aClient *sptr, aTKline *tkl, int parc, char *parv[]);
  42. int readDB(void);
  43. int writeDB(aTKline *origtkl, char what);
  44. static inline int read_data(int fd, void *buf, size_t count);
  45. static inline int write_data(int fd, void *buf, size_t count);
  46. static int write_str(int fd, char *x);
  47. static int read_str(int fd, char **x);
  48. // Muh globals
  49. static ModDataInfo *storetklMDI; // For storing that we read the DB at startup
  50. Hook *hookAddTKL, *hookDelTKL; // Muh hooks
  51. static ModuleInfo *storetklMI = NULL; // Store ModuleInfo so we can use it to check for errors in MOD_LOAD
  52. static unsigned tkl_db_version = TKL_DB_VERSION; // TKL DB version kek
  53. // Dat dere module header
  54. ModuleHeader MOD_HEADER(m_storetkl) = {
  55. "m_storetkl", // Module name
  56. "$Id: v1.01 2017/11/26 Gottem$", // Version
  57. "Store TKL entries persistently across IRCd restarts", // Description
  58. "3.2-b8-1", // Modversion, not sure wat do
  59. NULL
  60. };
  61. // Initialisation routine (register hooks, commands and modes or create structs etc)
  62. MOD_INIT(m_storetkl) {
  63. if(!(storetklMDI = findmoddata_byname("storetkl_inited", MODDATATYPE_CLIENT))) { // Attempt to find active moddata (like in case of a rehash)
  64. ModDataInfo mreq; // No moddata, let's request that shit
  65. memset(&mreq, 0, sizeof(mreq)); // Set 'em lol
  66. mreq.type = MODDATATYPE_CLIENT; // Apply to servers only (CLIENT actually includes clients but we'll disregard that =])
  67. mreq.name = "storetkl_inited"; // Name it
  68. mreq.free = storetkl_moddata_free; // Function to free 'em
  69. mreq.serialize = NULL; // Shouldn't be necessary but let's =]
  70. mreq.unserialize = NULL; // Ditto
  71. mreq.sync = 0; // Ditto
  72. storetklMDI = ModDataAdd(modinfo->handle, mreq); // Add 'em yo
  73. IsMDErr(storetklMDI, m_storetkl, modinfo); // Check for errors when adding (like hitting the slot limit xd)
  74. readDB(); // Read DB if we good
  75. moddata_client((&me), storetklMDI).i = 1; // Set ModData
  76. }
  77. // Low priority hooks to make sure we go after everything else =]
  78. hookAddTKL = HookAdd(modinfo->handle, HOOKTYPE_TKL_ADD, 999, storetkl_hook_tkl_add);
  79. hookDelTKL = HookAdd(modinfo->handle, HOOKTYPE_TKL_DEL, 999, storetkl_hook_tkl_del);
  80. storetklMI = modinfo; // Store module info yo
  81. return MOD_SUCCESS; // Let MOD_LOAD handle errors
  82. }
  83. // Actually load the module here (also command overrides as they may not exist in MOD_INIT yet)
  84. MOD_LOAD(m_storetkl) {
  85. // Did the module throw an error during initialisation?
  86. if(ModuleGetError(storetklMI->handle) != MODERR_NOERROR) {
  87. // Display error string kek
  88. config_error("A critical error occurred when loading module %s: %s", MOD_HEADER(m_storetkl).name, ModuleGetErrorStr(storetklMI->handle));
  89. return MOD_FAILED; // No good
  90. }
  91. return MOD_SUCCESS; // We good
  92. }
  93. // Called on unload/rehash obv
  94. MOD_UNLOAD(m_storetkl) {
  95. return MOD_SUCCESS; // We good
  96. }
  97. // Required function for ModData kek
  98. void storetkl_moddata_free(ModData *md) {
  99. if(md->i) // gg
  100. md->i = 0; // ez
  101. }
  102. // TKL_ADD/DEL hook functions
  103. int storetkl_hook_tkl_add(aClient *cptr, aClient *sptr, aTKline *tkl, int parc, char *parv[]) {
  104. writeDB(tkl, '+'); // Literally all we need lol
  105. return HOOK_CONTINUE; // We good
  106. }
  107. int storetkl_hook_tkl_del(aClient *cptr, aClient *sptr, aTKline *tkl, int parc, char *parv[]) {
  108. writeDB(tkl, '-'); // Literally all we need lol
  109. return HOOK_CONTINUE; // We good
  110. }
  111. int writeDB(aTKline *origtkl, char what) {
  112. int fd; // File descriptor
  113. size_t count; // Amount of X:Lines
  114. int index; // For iterating over all the types in the tklines "hash"
  115. aTKline *tkl; // Actual iter8or =]
  116. size_t pathlen = strlen(PERMDATADIR) + strlen(TKL_DB) + 1; // Includes a slash lol
  117. char *filepath; // Full path obv
  118. filepath = malloc(pathlen + 1); // Includes a nullbyet yo
  119. snprintf(filepath, pathlen + 1, "%s/%s", PERMDATADIR, TKL_DB); // Store 'em
  120. OpenFile(fd, filepath, O_CREAT | O_WRONLY | O_TRUNC); // Open ze fiel
  121. if(fd == -1) { // Error opening that shit
  122. config_warn("[storetkl] Unable to open the persistent storage file '%s/%s' for writing on server %s: %s", PERMDATADIR, TKL_DB, me.name, strerror(errno));
  123. free(filepath);
  124. return -1; // Gtfo
  125. }
  126. W_SAFE(write_data(fd, &tkl_db_version, sizeof(tkl_db_version))); // Write our DB version =]
  127. count = 0; // Let's count 'em X:Lines
  128. for(index = 0; index < TKLISTLEN; index++) { // All X:Lines share the same list, sort of
  129. for(tkl = tklines[index]; tkl; tkl = tkl->next) { // As long as this particular TKL type has entries
  130. if(origtkl && what == '-' && origtkl == tkl) // Hook fires before Unreal actually removes the X:Line
  131. continue; // So skip it
  132. count++; // Increment countur
  133. }
  134. }
  135. W_SAFE(write_data(fd, &count, sizeof(count))); // Amount of X:Lines to expect when reading the DB
  136. for(index = 0; index < TKLISTLEN; index++) { // Iter8 'em
  137. for(tkl = tklines[index]; tkl; tkl = tkl->next) { // again =]
  138. if(origtkl && what == '-' && origtkl == tkl) // Hook fires before Unreal actually removes the X:Line
  139. continue; // So skip it
  140. // Since we can't just write 'tkl' in its entirety, we have to get the relevant variables instead
  141. // These will be used to reconstruct the proper internal m_tkl() call ;]
  142. W_SAFE(write_data(fd, &tkl->type, sizeof(tkl->type))); // Integer (G:Line, Q:Line, etc; also refer to TKL_*)
  143. W_SAFE(write_data(fd, &tkl->subtype, sizeof(tkl->subtype))); // Unsigned short (only used for spamfilters but set to 0 for errythang else anyways)
  144. W_SAFE(write_str(fd, tkl->usermask)); // User mask (targets for spamfilter, like cp)
  145. W_SAFE(write_str(fd, tkl->hostmask)); // Host mask (action for spamfilter, like block)
  146. W_SAFE(write_str(fd, tkl->reason)); // Ban reason (TKL time for spamfilters in case of G:Line action etc)
  147. W_SAFE(write_str(fd, tkl->setby)); // Set by who
  148. W_SAFE(write_data(fd, &tkl->expire_at, sizeof(tkl->expire_at))); // Expiration timestamp
  149. W_SAFE(write_data(fd, &tkl->set_at, sizeof(tkl->set_at))); // Set-at timestamp
  150. if(tkl->ptr.spamf) { // Obv only exists when this pertains een spamfilterin0
  151. W_SAFE(write_str(fd, "SPAMF")); // Write a string so we know to expect more when reading the DB
  152. W_SAFE(write_data(fd, &tkl->ptr.spamf->action, sizeof(tkl->ptr.spamf->action))); // Unsigned short (block, GZ:Line, etc; also refer to BAN_ACT_*)
  153. W_SAFE(write_str(fd, tkl->ptr.spamf->tkl_reason)); // Underscore-escaped string of why the spamfilter was set
  154. W_SAFE(write_data(fd, &tkl->ptr.spamf->tkl_duration, sizeof(tkl->ptr.spamf->tkl_duration))); // How long to set a ban for (if applicable)
  155. W_SAFE(write_str(fd, tkl->ptr.spamf->expr->str)); // Actual expression/regex/etc
  156. W_SAFE(write_data(fd, &tkl->ptr.spamf->expr->type, sizeof(tkl->ptr.spamf->expr->type))); // Integer (expression type [simple/POSIX/PCRE]; see also enum MatchType)
  157. }
  158. else
  159. W_SAFE(write_str(fd, "NOSPAMF")); // No spamfilter, so let's write that too =]
  160. }
  161. }
  162. close(fd); // Don't f0get to close 'em lol
  163. free(filepath);
  164. return 0; // We good (non-zero = error)
  165. }
  166. int readDB(void) {
  167. int fd; // File descriptor
  168. size_t count; // Amount of X:Lines
  169. int i; // For iterating over all the entries in ze DB
  170. int num = 0; // Amount of X:Lines we actually ended up re-adding
  171. int rewrite = 0; // If we got expired X:Lines etc, let's rewrite (i.e. clean up) the DB file =]
  172. unsigned version; // For checking 'em DB version
  173. aTKline *tkl; // Iter8or
  174. size_t pathlen = strlen(PERMDATADIR) + strlen(TKL_DB) + 1; // Includes a slash lol
  175. char *filepath; // Full path obv
  176. filepath = malloc(pathlen + 1); // Includes a nullbyet yo
  177. snprintf(filepath, pathlen + 1, "%s/%s", PERMDATADIR, TKL_DB); // Store 'em
  178. // Let's send a message saying we loading some shi ;]
  179. ircd_log(LOG_ERROR, "[storetkl] Reading stored X:Lines from '%s'", filepath);
  180. sendto_realops("[storetkl] Reading stored X:Lines from '%s'", filepath); // Probably won't be seen ever, but just in case ;]
  181. OpenFile(fd, filepath, O_RDONLY); // Open 'em
  182. if(fd == -1) { // Error when opening
  183. if(errno != ENOENT) // If file doesn't even exists, don't show a warning =]
  184. config_warn("[storetkl] Unable to open the persistent storage file '%s' for reading on server %s: %s", filepath, me.name, strerror(errno));
  185. free(filepath);
  186. return -1; // And return error
  187. }
  188. R_SAFE(read_data(fd, &version, sizeof(version))); // Read the DB version
  189. if(version != tkl_db_version) { // Got een mismatch yo (probably never happens tho lol)
  190. // N.B.: I can probably heck something to provide backwards compatibility, but there is no need for this atm
  191. config_warn("File '%s' has a wrong database version (expected: %u, got: %u) on server %s", filepath, tkl_db_version, version, me.name); // Display warning familia
  192. close(fd); // Close 'em
  193. free(filepath);
  194. return -1; // And gtfo
  195. }
  196. R_SAFE(read_data(fd, &count, sizeof(count))); // Read how many X:Lines to expect
  197. for(i = 1; i <= count; i++) { // Iterate 'em all
  198. int type; // G:Line, Q:Line, etc; also refer to TKL_*
  199. unsigned short subtype; // Only used for spamfilters but set to 0 for errythang else anyways
  200. int parc = 0; // Amount of arguments (required by m_tkl())
  201. char *usermask = NULL; // User mask (targets for spamfilter, like cp)
  202. char *hostmask = NULL; // Host mask (action for spamfilter, like block)
  203. char *reason = NULL; // Ban reason (TKL time for spamfilters in case of G:Line action etc)
  204. char *setby = NULL; // Set by who
  205. char tklflag; // G, z, Z, f, etc
  206. char *tkltype = NULL; // Cuz m_tkl() needs a char* and not char for this
  207. TS expire_at, set_at; // Expiration and set-at timestamps (simply a typedef of 'long' afaik)
  208. char setTime[100], expTime[100], spamfTime[100]; // To convert TS timestamp shit to char arrays =]
  209. char *spamf_check = NULL; // Check for SPAMF/NOSPAMF ;]
  210. int spamf = 0; // "Boolean" to set if we got SPAMF
  211. unsigned short spamf_action; // Block, GZ:Line, etc; also refer to BAN_ACT_*
  212. char *spamf_tkl_reason = NULL; // Underscore-escaped string of why the spamfilter was set
  213. TS spamf_tkl_duration; // How long to set a ban for (if applicable)
  214. char *spamf_expr = NULL; // Actual expression/regex/etc
  215. MatchType matchtype; // Like simple/posix/regex, is simply an enum thingy ;]
  216. char *spamf_matchtype = "simple"; // Let's default to simple for spamfilters
  217. int doadd = 1; // Do we need to call m_tkl()?
  218. aTKline *tkl; // Iter8or for checking existing X:Lines to prevent dupes =]
  219. char *tkllayer[13] = { // Dem m_tkl() args =]
  220. me.name, // 0: Server name
  221. "+", // 1: Direction (always add in this case yo)
  222. NULL, // 2: Type, like G
  223. NULL, // 3: User mask (targets for spamfilter)
  224. NULL, // 4: Host mask (action for spamfilter)
  225. NULL, // 5: Set by who
  226. NULL, // 6: Expiration time
  227. NULL, // 7: Set-at time
  228. NULL, // 8: Reason (TKL time for spamfilters in case of G:Line action etc)
  229. NULL, // 9: Spamfilter only: TKL reason (w/ underscores and all etc)
  230. NULL, // 10: Spamfilter only: Match type (simple/posix/regex)
  231. NULL, // 11: Spamfilter only: Match string/regex etc
  232. NULL, // 12: Some functions rely on the post-last entry being NULL =]
  233. };
  234. // Now read that shit
  235. R_SAFE(read_data(fd, &type, sizeof(type)));
  236. R_SAFE(read_data(fd, &subtype, sizeof(subtype)));
  237. R_SAFE(read_str(fd, &usermask));
  238. R_SAFE(read_str(fd, &hostmask));
  239. R_SAFE(read_str(fd, &reason));
  240. R_SAFE(read_str(fd, &setby));
  241. R_SAFE(read_data(fd, &expire_at, sizeof(expire_at)));
  242. R_SAFE(read_data(fd, &set_at, sizeof(set_at)));
  243. R_SAFE(read_str(fd, &spamf_check));
  244. if(!strcmp(spamf_check, "SPAMF")) { // Oh but wait, there's more =]
  245. spamf = 1; // Flip "boolean"
  246. // Read m0awr
  247. R_SAFE(read_data(fd, &spamf_action, sizeof(spamf_action)));
  248. R_SAFE(read_str(fd, &spamf_tkl_reason));
  249. R_SAFE(read_data(fd, &spamf_tkl_duration, sizeof(spamf_tkl_duration)));
  250. R_SAFE(read_str(fd, &spamf_expr));
  251. R_SAFE(read_data(fd, &matchtype, sizeof(matchtype)));
  252. }
  253. tkltype = malloc(sizeof(char) * 2); // For tklflag + nullbyet yo
  254. tklflag = tkl_typetochar(type); // gg ez (turns an int to char)
  255. tkltype[0] = tklflag; // Set 'em
  256. tkltype[1] = '\0'; // Ayyy
  257. if(expire_at != 0 && expire_at <= TStime()) { // Check if the X:Line is not permanent and would expire immediately after setting
  258. // Send message about not re-adding shit
  259. if(tklflag == 'F') {
  260. ircd_log(LOG_ERROR, "[storetkl] Not re-adding spamfilter '%s' [%s] because it should be expired", spamf_expr, spamf_tkl_reason);
  261. sendto_realops("[storetkl] Not re-adding spamfilter '%s' [%s] because it should be expired", spamf_expr, spamf_tkl_reason); // Probably won't be seen ever, but just in case ;]
  262. }
  263. else {
  264. ircd_log(LOG_ERROR, "[storetkl] Not re-adding %c:Line '%s@%s' [%s] because it should be expired", tklflag, usermask, hostmask, reason);
  265. sendto_realops("[storetkl] Not re-adding %c:Line '%s@%s' [%s] because it should be expired", tklflag, usermask, hostmask, reason); // Probably won't be seen ever, but just in case ;]
  266. }
  267. rewrite++; // Increment countur lol
  268. free(tkltype); // Cuz we malloc'd em lol
  269. if(spamf_check) free(spamf_check); // read_str() does a MyMalloc, so let's free to prevent mem0ry issues
  270. if(usermask) free(usermask); // Ditto
  271. if(hostmask) free(hostmask); // Ditto
  272. if(reason) free(reason); // Ditto
  273. if(setby) free(setby); // Ditto
  274. continue; // Next one pls
  275. }
  276. ircsnprintf(setTime, sizeof(setTime), "%li", set_at); // Convert
  277. ircsnprintf(expTime, sizeof(expTime), "%li", expire_at); // 'em
  278. if(spamf && tklflag == 'f') // Cuz apparently 'f' means it was added through the conf or is built-in ('F' is ok tho)
  279. doadd = 0;
  280. // Build TKL args
  281. parc = 9; // Minimum of 9 args is required
  282. // All of these except [8] are the same for all (only odd one is spamfilter ofc)
  283. tkllayer[2] = tkltype;
  284. tkllayer[3] = usermask;
  285. tkllayer[4] = hostmask;
  286. tkllayer[5] = setby;
  287. tkllayer[6] = expTime;
  288. tkllayer[7] = setTime;
  289. tkllayer[8] = reason;
  290. if(spamf) { // If we got a spamfilter
  291. parc = 12; // Need m0ar args
  292. for(tkl = tklines[tkl_hash(tklflag)]; doadd && tkl; tkl = tkl->next) { // Check for existing spamfilters
  293. // We can assume it's the same spamfilter if all of the following match: spamfilter expression, targets, TKL reason, action, matchtype and TKL duration
  294. if(!strcmp(tkl->ptr.spamf->expr->str, spamf_expr) && !strcmp(tkl->usermask, usermask) && !strcmp(tkl->ptr.spamf->tkl_reason, spamf_tkl_reason) &&
  295. tkl->ptr.spamf->action == spamf_action && tkl->ptr.spamf->expr->type == matchtype && tkl->ptr.spamf->tkl_duration == spamf_tkl_duration) {
  296. doadd = 0; // So don't proceed with adding
  297. break; // And fuck off
  298. }
  299. }
  300. if(doadd) { // If above loop didn't find a valid entry, let's build the rest of the args =]
  301. ircsnprintf(spamfTime, sizeof(spamfTime), "%li", spamf_tkl_duration); // Convert TKL duration
  302. tkllayer[8] = spamfTime; // Replaces reason in other X:Lines
  303. tkllayer[9] = spamf_tkl_reason;
  304. if(matchtype == MATCH_PCRE_REGEX) // Premium enum yo
  305. spamf_matchtype = "regex"; // Set string
  306. else if(matchtype == MATCH_TRE_REGEX) // ayy
  307. spamf_matchtype = "posix"; // lmao
  308. tkllayer[10] = spamf_matchtype;
  309. tkllayer[11] = spamf_expr;
  310. }
  311. }
  312. else { // Not a spamfilter
  313. for(tkl = tklines[tkl_hash(tklflag)]; tkl; tkl = tkl->next) { // Still gotta check for dupes tho
  314. // Here we only need to have a match for a few fields =]
  315. if(!strcmp(tkl->usermask, usermask) && !strcmp(tkl->hostmask, hostmask) && !strcmp(tkl->reason, reason) && tkl->expire_at == expire_at) {
  316. doadd = 0;
  317. break;
  318. }
  319. }
  320. }
  321. if(doadd) { // Still need to add?
  322. m_tkl(&me, &me, parc, tkllayer); // Ayyyy
  323. num++; // Muh counter lel
  324. }
  325. free(tkltype); // Shit was malloc'd yo
  326. if(spamf_check) free(spamf_check); // read_str() does a MyMalloc, so let's free to prevent mem0ry issues
  327. if(usermask) free(usermask); // D
  328. if(hostmask) free(hostmask); // i
  329. if(reason) free(reason); // tt
  330. if(setby) free(setby); // o
  331. }
  332. close(fd); // Don't forget to close 'em
  333. free(filepath);
  334. if(num) {
  335. // Send message about re-adding shit
  336. ircd_log(LOG_ERROR, "[storetkl] Re-added %d X:Lines", num);
  337. sendto_realops("[storetkl] Re-added %d X:Lines", num); // Probably won't be seen ever, but just in case ;]
  338. }
  339. if(rewrite) {
  340. // Send message about rewriting DB file
  341. ircd_log(LOG_ERROR, "[storetkl] Rewriting DB file due to %d skipped/expired X:Line%s", rewrite, (rewrite > 1 ? "s" : ""));
  342. sendto_realops("[storetkl] Rewriting DB file due to %d skipped/expired X:Line%s", rewrite, (rewrite > 1 ? "s" : "")); // Probably won't be seen ever, but just in case ;]
  343. return writeDB(NULL, 0); // Pass error code through here =]
  344. }
  345. return 0; // We good (non-zero = error)
  346. }
  347. static inline int read_data(int fd, void *buf, size_t count) {
  348. if((size_t)read(fd, buf, count) < count)
  349. return -1;
  350. return 0;
  351. }
  352. static inline int write_data(int fd, void *buf, size_t count) {
  353. if((size_t)write(fd, buf, count) < count)
  354. return -1;
  355. return 0;
  356. }
  357. static int write_str(int fd, char *x) {
  358. size_t count = x ? strlen(x) : 0;
  359. if(write_data(fd, &count, sizeof count))
  360. return -1;
  361. if(count) {
  362. if(write_data(fd, x, sizeof(char) * count))
  363. return -1;
  364. }
  365. return 0;
  366. }
  367. static int read_str(int fd, char **x) {
  368. size_t count;
  369. if(read_data(fd, &count, sizeof count))
  370. return -1;
  371. if(!count) {
  372. *x = NULL;
  373. return 0;
  374. }
  375. *x = (char *)MyMalloc(sizeof(char) * count + 1);
  376. if(read_data(fd, *x, sizeof(char) * count)) {
  377. MyFree(*x);
  378. *x = NULL;
  379. return -1;
  380. }
  381. (*x)[count] = 0;
  382. return 0;
  383. }