m_storetkl.c 19 KB

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