Line data Source code
1 : #include <stdint.h>
2 : #include <string.h>
3 : #include <stdlib.h>
4 : #include <stdio.h>
5 : #include <unistd.h>
6 : #include <errno.h>
7 : #include <limits.h>
8 : #include <ctype.h>
9 : #include <uuid.h>
10 :
11 : #include "lang.h"
12 : #include "parser.h"
13 :
14 : const char* ast_type_string[] = {
15 : [AST_NONE] = "NONE",
16 : // Statements
17 : [AST_ST_SET] = "SET",
18 : [AST_ST_GET] = "GET",
19 :
20 : // Expressions
21 : [AST_EX_ID] = "IDENTIFIER",
22 : [AST_EX_NUMBER] = "NUMBER",
23 : [AST_EX_STRING] = "STRING",
24 : [AST_EX_ADDRESS] = "ADDRESS",
25 : [AST_EX_PATH] = "PATH",
26 : [AST_EX_SUBSCRIPT] = "SUBSCRIPT",
27 : [AST_EX_OFFSET] = "OFFSET",
28 : [AST_EX_BLOCKSPEC] = "BLOCKSPEC",
29 : [AST_EX_STRUCTSPEC] = "STRUCTSPEC",
30 : [AST_EX_FIELDSPEC] = "FIELDSPEC",
31 : [AST_EX_TYPESPEC] = "TYPESPEC",
32 :
33 : // Keywords
34 : [AST_KW_STATE] = "STATE",
35 : };
36 :
37 : /**
38 : * Initialize an expression node of the given type from a source string.
39 : * Currently just converts numerical values and string values where
40 : * appropriate. String values are duplicted into newly allocated buffers as the
41 : * text from the parser will go away.
42 : * Returns 0 on success or non-zero with errno set on failure
43 : */
44 32 : static int ast_expr_init(struct ast_node *expr, ast_node_t type, const char *str)
45 : {
46 32 : int ret = 0;
47 32 : switch (type) {
48 0 : case AST_EX_OFFSET:
49 0 : str++; // Cut off the +
50 8 : case AST_EX_NUMBER:
51 8 : ret = sscanf(str, "%"SCNi64, &expr->ast_num);
52 8 : if (ret != 1) {
53 0 : return 1;
54 : }
55 8 : break;
56 16 : case AST_EX_ID:
57 : case AST_EX_PATH:
58 : case AST_EX_STRING:
59 16 : expr->ast_str = strdup(str);
60 16 : if (expr->ast_str == NULL) {
61 0 : return 1;
62 : }
63 16 : break;
64 8 : case AST_EX_ADDRESS:
65 : case AST_EX_SUBSCRIPT:
66 : case AST_EX_BLOCKSPEC:
67 : case AST_EX_STRUCTSPEC:
68 : case AST_EX_FIELDSPEC:
69 : case AST_EX_TYPESPEC:
70 : case AST_KW_STATE:
71 8 : break;
72 0 : default:
73 0 : errno = EINVAL;
74 0 : return 1;
75 : }
76 32 : return 0;
77 : }
78 :
79 : /**
80 : * Create a new AST node of a given type from a source string.
81 : * Returns a pointer to the new node or NULL on failure with errno set.
82 : */
83 40 : struct ast_node *ast_new(ast_node_t type, const char *text)
84 : {
85 : struct ast_node *node;
86 40 : node = (struct ast_node *)calloc(1, sizeof(struct ast_node));
87 40 : if (node == NULL) {
88 0 : goto return_fail;
89 : }
90 :
91 40 : if (type > _AST_EX_START && ast_expr_init(node, type, text)) {
92 0 : goto return_free;
93 : }
94 :
95 40 : node->ast_text = strdup(text);
96 40 : if (node->ast_text == NULL) {
97 0 : goto return_free;
98 : }
99 40 : node->ast_type = type;
100 :
101 40 : return node;
102 :
103 0 : return_free:
104 0 : if (node->ast_text) {
105 0 : free(node->ast_text);
106 : }
107 0 : if (node->ast_str) {
108 0 : free(node->ast_str);
109 : }
110 0 : free(node);
111 0 : return_fail:
112 0 : fprintf(stderr, "Failed to create new value from %s: %s\n", text, strerror(errno));
113 0 : return NULL;
114 : }
115 :
116 : /**
117 : * Free the memory allocated for an AST node and set its pointer to NULL
118 : */
119 88 : void ast_destroy(struct ast_node **node)
120 : {
121 88 : if (*node == NULL) {
122 48 : return;
123 : }
124 40 : ast_destroy(&(*node)->ast_left);
125 40 : ast_destroy(&(*node)->ast_right);
126 40 : switch((*node)->ast_type) {
127 16 : case AST_EX_ID:
128 : case AST_EX_PATH:
129 : case AST_EX_STRING:
130 16 : free((*node)->ast_str);
131 16 : break;
132 24 : default:
133 24 : break;
134 : }
135 40 : free((*node)->ast_text);
136 40 : free(*node);
137 40 : *node = NULL;
138 : }
139 :
140 0 : static void ast_string_unescape(char *str)
141 : {
142 : int head, tail;
143 0 : for (head = tail = 0; str[head] != '\0'; head++, tail++) {
144 0 : if (str[head] == '\\' && str[head+1] != '\0')
145 0 : head++;
146 0 : str[tail] = str[head];
147 : }
148 0 : str[tail] = '\0';
149 0 : }
150 :
151 1 : static uint64_t ast_lookup_path(char *path, struct lgfs2_sbd *sbd)
152 : {
153 1 : char *c = NULL;
154 : struct lgfs2_inode *ip, *iptmp;
155 : char *segment;
156 1 : uint64_t bn = 0;
157 :
158 1 : segment = strtok_r(path, "/", &c);
159 1 : ip = lgfs2_inode_read(sbd, sbd->sd_root_dir.in_addr);
160 :
161 1 : while (ip != NULL) {
162 1 : int err = 0;
163 :
164 1 : if (segment == NULL) { // No more segments
165 1 : bn = ip->i_num.in_addr;
166 1 : lgfs2_inode_put(&ip);
167 1 : return bn;
168 : }
169 0 : ast_string_unescape(segment);
170 0 : iptmp = lgfs2_lookupi(ip, segment, strlen(segment));
171 0 : err = errno;
172 0 : if (ip != iptmp)
173 0 : lgfs2_inode_put(&ip);
174 0 : if (iptmp == NULL) {
175 0 : errno = err;
176 0 : break;
177 : }
178 0 : ip = iptmp;
179 0 : segment = strtok_r(NULL, "/", &c);
180 : }
181 :
182 0 : return 0;
183 : }
184 :
185 : enum block_id {
186 : ID_SB = 0,
187 : ID_MASTER,
188 : ID_ROOT,
189 : ID_RINDEX,
190 :
191 : ID_END
192 : };
193 :
194 : /**
195 : * Names of blocks which can be uniquely identified in the fs
196 : */
197 : static const char *block_ids[] = {
198 : [ID_SB] = "sb",
199 : [ID_MASTER] = "master",
200 : [ID_ROOT] = "root",
201 : [ID_RINDEX] = "rindex",
202 :
203 : [ID_END] = NULL
204 : };
205 :
206 7 : static uint64_t ast_lookup_id(const char *id, struct lgfs2_sbd *sbd)
207 : {
208 7 : uint64_t bn = 0;
209 : int i;
210 7 : for (i = 0; i < ID_END; i++) {
211 7 : if (!strcmp(id, block_ids[i])) {
212 7 : break;
213 : }
214 : }
215 7 : switch (i) {
216 7 : case ID_SB:
217 7 : bn = LGFS2_SB_ADDR(sbd);
218 7 : break;
219 0 : case ID_MASTER:
220 0 : bn = sbd->sd_meta_dir.in_addr;
221 0 : break;
222 0 : case ID_ROOT:
223 0 : bn = sbd->sd_root_dir.in_addr;
224 0 : break;
225 0 : case ID_RINDEX:
226 0 : bn = sbd->md.riinode->i_num.in_addr;
227 0 : break;
228 0 : default:
229 0 : return 0;
230 : }
231 7 : return bn;
232 : }
233 :
234 0 : static uint64_t ast_lookup_rgrp(uint64_t rgnum, struct lgfs2_sbd *sbd)
235 : {
236 0 : uint64_t i = rgnum;
237 : struct osi_node *n;
238 :
239 0 : for (n = osi_first(&sbd->rgtree); n != NULL && i > 0; n = osi_next(n), i--);
240 0 : if (n != NULL && i == 0)
241 0 : return ((struct lgfs2_rgrp_tree *)n)->rt_addr;
242 0 : fprintf(stderr, "Resource group number out of range: %"PRIu64"\n", rgnum);
243 0 : return 0;
244 : }
245 :
246 0 : static uint64_t ast_lookup_subscript(struct ast_node *id, struct ast_node *index,
247 : struct lgfs2_sbd *sbd)
248 : {
249 0 : uint64_t bn = 0;
250 0 : const char *name = id->ast_str;
251 0 : if (!strcmp(name, "rgrp")) {
252 0 : bn = ast_lookup_rgrp(index->ast_num, sbd);
253 : } else {
254 0 : fprintf(stderr, "Unrecognized identifier %s\n", name);
255 : }
256 0 : return bn;
257 : }
258 :
259 : /**
260 : * Look up a block and return its number. The kind of lookup depends on the
261 : * type of the ast node.
262 : */
263 8 : static uint64_t ast_lookup_block_num(struct ast_node *ast, struct lgfs2_sbd *sbd)
264 : {
265 8 : uint64_t bn = 0;
266 8 : switch (ast->ast_type) {
267 0 : case AST_EX_OFFSET:
268 0 : bn = ast_lookup_block_num(ast->ast_left, sbd) + ast->ast_num;
269 0 : break;
270 0 : case AST_EX_ADDRESS:
271 0 : if (lgfs2_check_range(sbd, ast->ast_num))
272 0 : break;
273 0 : bn = ast->ast_num;
274 0 : break;
275 1 : case AST_EX_PATH:
276 1 : bn = ast_lookup_path(ast->ast_str, sbd);
277 1 : break;
278 7 : case AST_EX_ID:
279 7 : bn = ast_lookup_id(ast->ast_str, sbd);
280 7 : break;
281 0 : case AST_EX_SUBSCRIPT:
282 0 : bn = ast_lookup_subscript(ast->ast_left, ast->ast_left->ast_left, sbd);
283 0 : break;
284 0 : default:
285 0 : break;
286 : }
287 8 : return bn;
288 : }
289 :
290 8 : static uint64_t ast_lookup_block(struct ast_node *node, struct lgfs2_sbd *sbd)
291 : {
292 8 : uint64_t bn = ast_lookup_block_num(node, sbd);
293 8 : if (bn == 0) {
294 0 : fprintf(stderr, "Block not found: %s\n", node->ast_text);
295 0 : return 0;
296 : }
297 8 : return bn;
298 : }
299 :
300 : static const char *bitstate_strings[] = {
301 : [GFS2_BLKST_FREE] = "Free",
302 : [GFS2_BLKST_USED] = "Used",
303 : [GFS2_BLKST_UNLINKED] = "Unlinked",
304 : [GFS2_BLKST_DINODE] = "Dinode"
305 : };
306 :
307 : /**
308 : * Print a representation of an arbitrary field of an arbitrary GFS2 block to stdout
309 : * Returns 0 if successful, 1 otherwise
310 : */
311 204 : static int field_print(char *buf, uint64_t addr, const struct lgfs2_metadata *mtype,
312 : const struct lgfs2_metafield *field)
313 : {
314 204 : const char *fieldp = buf + field->offset;
315 :
316 204 : printf("%s\t%"PRIu64"\t%u\t%u\t%s\t", mtype->name, addr, field->offset, field->length, field->name);
317 204 : if (field->flags & LGFS2_MFF_UUID) {
318 : char readable_uuid[36+1];
319 : uuid_t uuid;
320 :
321 7 : memcpy(uuid, fieldp, sizeof(uuid_t));
322 7 : uuid_unparse(uuid, readable_uuid);
323 7 : printf("'%s'\n", readable_uuid);
324 197 : } else if (field->flags & LGFS2_MFF_STRING) {
325 14 : printf("'%s'\n", fieldp);
326 : } else {
327 183 : switch(field->length) {
328 0 : case 1:
329 0 : printf("%"PRIu8"\n", *(uint8_t *)fieldp);
330 0 : break;
331 4 : case 2:
332 4 : printf("%"PRIu16"\n", be16_to_cpu(*(__be16 *)fieldp));
333 4 : break;
334 87 : case 4:
335 87 : printf("%"PRIu32"\n", be32_to_cpu(*(__be32 *)fieldp));
336 87 : break;
337 91 : case 8:
338 91 : printf("%"PRIu64"\n", be64_to_cpu(*(__be64 *)fieldp));
339 91 : break;
340 1 : default:
341 : // "Reserved" field so just print 0
342 1 : printf("0\n");
343 1 : return 1;
344 : }
345 : }
346 203 : return 0;
347 : }
348 :
349 : /**
350 : * Print a representation of an arbitrary GFS2 block to stdout
351 : */
352 8 : int lgfs2_lang_result_print(struct lgfs2_lang_result *result)
353 : {
354 : int i;
355 8 : if (result->lr_mtype != NULL) {
356 212 : for (i = 0; i < result->lr_mtype->nfields; i++) {
357 204 : field_print(result->lr_buf, result->lr_blocknr,
358 204 : result->lr_mtype, &result->lr_mtype->fields[i]);
359 : }
360 : } else {
361 0 : printf("%"PRIu64": %s\n", result->lr_blocknr, bitstate_strings[result->lr_state]);
362 : }
363 8 : return 0;
364 : }
365 :
366 0 : static int ast_get_bitstate(uint64_t bn, struct lgfs2_sbd *sbd)
367 : {
368 0 : int ret = 0;
369 0 : int state = 0;
370 0 : struct lgfs2_rgrp_tree *rgd = lgfs2_blk2rgrpd(sbd, bn);
371 0 : if (rgd == NULL) {
372 0 : fprintf(stderr, "Could not find resource group for block %"PRIu64"\n", bn);
373 0 : return -1;
374 : }
375 :
376 0 : ret = lgfs2_rgrp_read(sbd, rgd);
377 0 : if (ret != 0) {
378 0 : fprintf(stderr, "Failed to read resource group for block %"PRIu64": %d\n", bn, ret);
379 0 : return -1;
380 : }
381 :
382 0 : state = lgfs2_get_bitmap(sbd, bn, rgd);
383 0 : if (state == -1) {
384 0 : fprintf(stderr, "Failed to acquire bitmap state for block %"PRIu64"\n", bn);
385 0 : return -1;
386 : }
387 :
388 0 : lgfs2_rgrp_relse(sbd, rgd);
389 0 : return state;
390 : }
391 :
392 0 : static void result_lookup_mtype(struct lgfs2_lang_result *result)
393 : {
394 0 : const uint32_t mh_type = lgfs2_get_block_type(result->lr_buf);
395 0 : uint64_t addr = result->lr_blocknr;
396 :
397 0 : result->lr_mtype = NULL;
398 0 : if (mh_type == 0) {
399 0 : fprintf(stderr, "Could not determine type for block %"PRIu64"\n", addr);
400 0 : return;
401 : }
402 0 : result->lr_mtype = lgfs2_find_mtype(mh_type);
403 0 : if (result->lr_mtype == NULL)
404 0 : fprintf(stderr, "Could not determine meta type for block %"PRIu64"\n", addr);
405 : }
406 :
407 8 : static char *lang_read_block(int fd, unsigned bsize, uint64_t addr)
408 : {
409 8 : off_t off = addr * bsize;
410 : char *buf;
411 :
412 8 : buf = calloc(1, bsize);
413 8 : if (buf == NULL) {
414 0 : perror("Failed to read block");
415 0 : return NULL;
416 : }
417 8 : if (pread(fd, buf, bsize, off) != bsize) {
418 0 : fprintf(stderr, "Failed to read block %"PRIu64": %s\n", addr, strerror(errno));
419 0 : free(buf);
420 0 : return NULL;
421 : }
422 8 : return buf;
423 : }
424 :
425 : /**
426 : * Interpret the get statement.
427 : */
428 0 : static struct lgfs2_lang_result *ast_interp_get(struct lgfs2_lang_state *state,
429 : struct ast_node *ast, struct lgfs2_sbd *sbd)
430 : {
431 0 : struct lgfs2_lang_result *result = calloc(1, sizeof(struct lgfs2_lang_result));
432 0 : if (result == NULL) {
433 0 : fprintf(stderr, "Failed to allocate memory for result\n");
434 0 : return NULL;
435 : }
436 :
437 0 : if (ast->ast_right->ast_right == NULL) {
438 0 : result->lr_blocknr = ast_lookup_block(ast->ast_right, sbd);
439 0 : if (result->lr_blocknr == 0) {
440 0 : free(result);
441 0 : return NULL;
442 : }
443 0 : result->lr_buf = lang_read_block(sbd->device_fd, sbd->sd_bsize, result->lr_blocknr);
444 0 : if (result->lr_buf == NULL) {
445 0 : free(result);
446 0 : return NULL;
447 : }
448 0 : result_lookup_mtype(result);
449 :
450 0 : } else if (ast->ast_right->ast_right->ast_type == AST_KW_STATE) {
451 0 : result->lr_blocknr = ast_lookup_block_num(ast->ast_right, sbd);
452 0 : if (result->lr_blocknr == 0) {
453 0 : free(result);
454 0 : return NULL;
455 : }
456 0 : result->lr_state = ast_get_bitstate(result->lr_blocknr, sbd);
457 : }
458 :
459 0 : return result;
460 : }
461 :
462 : /**
463 : * Set a field of a gfs2 block of a given type to a given value.
464 : * Returns AST_INTERP_* to signal success, an invalid field/value or an error.
465 : */
466 8 : static int ast_field_set(char *buf, const struct lgfs2_metafield *field,
467 : struct ast_node *val)
468 : {
469 8 : int err = 0;
470 :
471 8 : if (field->flags & LGFS2_MFF_UUID) {
472 : uuid_t uuid;
473 :
474 0 : if (uuid_parse(val->ast_str, uuid) != 0) {
475 0 : fprintf(stderr, "Invalid UUID\n");
476 0 : return AST_INTERP_INVAL;
477 : }
478 0 : err = lgfs2_field_assign(buf, field, uuid);
479 8 : } else if (field->flags & LGFS2_MFF_STRING) {
480 0 : err = lgfs2_field_assign(buf, field, val->ast_str);
481 : } else {
482 : /* coverity[overrun-buffer-val:SUPPRESS] False positive */
483 8 : err = lgfs2_field_assign(buf, field, &val->ast_num);
484 : }
485 :
486 8 : if (err) {
487 0 : fprintf(stderr, "Invalid field assignment: %s (size %d) = %s\n",
488 0 : field->name, field->length, val->ast_text);
489 0 : return AST_INTERP_INVAL;
490 : }
491 8 : return AST_INTERP_SUCCESS;
492 : }
493 :
494 8 : static const struct lgfs2_metadata *lang_find_mtype(struct ast_node *node, const char *buf)
495 : {
496 8 : const struct lgfs2_metadata *mtype = NULL;
497 :
498 8 : if (node->ast_type == AST_EX_TYPESPEC) {
499 0 : mtype = lgfs2_find_mtype_name(node->ast_str);
500 0 : if (mtype == NULL)
501 0 : fprintf(stderr, "Invalid block type: %s\n", node->ast_text);
502 : } else {
503 8 : mtype = lgfs2_find_mtype(lgfs2_get_block_type(buf));
504 8 : if (mtype == NULL)
505 0 : fprintf(stderr, "Unrecognised block at: %s\n", node->ast_text);
506 : }
507 :
508 8 : return mtype;
509 : }
510 :
511 8 : static int lang_write_result(int fd, unsigned bsize, struct lgfs2_lang_result *result)
512 : {
513 8 : off_t off = bsize * result->lr_blocknr;
514 :
515 8 : if (pwrite(fd, result->lr_buf, bsize, off) != bsize) {
516 0 : fprintf(stderr, "Failed to write modified block %"PRIu64": %s\n",
517 0 : result->lr_blocknr, strerror(errno));
518 0 : return -1;
519 : }
520 8 : return 0;
521 : }
522 :
523 : /**
524 : * Interpret an assignment (set)
525 : */
526 8 : static struct lgfs2_lang_result *ast_interp_set(struct lgfs2_lang_state *state,
527 : struct ast_node *ast, struct lgfs2_sbd *sbd)
528 : {
529 8 : struct ast_node *lookup = ast->ast_right;
530 : struct ast_node *fieldspec;
531 : struct ast_node *fieldname;
532 : struct ast_node *fieldval;
533 8 : int ret = 0;
534 :
535 8 : struct lgfs2_lang_result *result = calloc(1, sizeof(struct lgfs2_lang_result));
536 8 : if (result == NULL) {
537 0 : fprintf(stderr, "Failed to allocate memory for result\n");
538 0 : return NULL;
539 : }
540 :
541 8 : result->lr_blocknr = ast_lookup_block(lookup, sbd);
542 8 : if (result->lr_blocknr == 0)
543 0 : goto out_err;
544 8 : result->lr_buf = lang_read_block(sbd->device_fd, sbd->sd_bsize, result->lr_blocknr);
545 8 : if (result->lr_buf == NULL)
546 0 : goto out_err;
547 :
548 8 : result->lr_mtype = lang_find_mtype(lookup->ast_right, result->lr_buf);
549 8 : if (result->lr_mtype == NULL) {
550 0 : fprintf(stderr, "Unrecognised block at: %s\n", lookup->ast_str);
551 0 : goto out_err;
552 : }
553 :
554 8 : if (lookup->ast_right->ast_type == AST_EX_TYPESPEC) {
555 0 : struct gfs2_meta_header mh = {
556 0 : .mh_magic = cpu_to_be32(GFS2_MAGIC),
557 0 : .mh_type = cpu_to_be32(result->lr_mtype->mh_type),
558 0 : .mh_format = cpu_to_be32(result->lr_mtype->mh_format),
559 : };
560 0 : memcpy(result->lr_buf, &mh, sizeof(mh));
561 0 : lookup = lookup->ast_right;
562 : }
563 :
564 8 : for (fieldspec = lookup->ast_right;
565 16 : fieldspec != NULL && fieldspec->ast_type == AST_EX_FIELDSPEC;
566 8 : fieldspec = fieldspec->ast_left) {
567 : const struct lgfs2_metafield *mfield;
568 :
569 8 : fieldname = fieldspec->ast_right;
570 8 : fieldval = fieldname->ast_right;
571 :
572 8 : mfield = lgfs2_find_mfield_name(fieldname->ast_str, result->lr_mtype);
573 8 : if (mfield == NULL) {
574 0 : fprintf(stderr, "No field '%s' found in '%s'\n",
575 0 : fieldname->ast_str, result->lr_mtype->name);
576 0 : goto out_err;
577 : }
578 :
579 8 : ret = ast_field_set(result->lr_buf, mfield, fieldval);
580 8 : if (ret != AST_INTERP_SUCCESS) {
581 0 : goto out_err;
582 : }
583 : }
584 :
585 8 : ret = lang_write_result(sbd->device_fd, sbd->sd_bsize, result);
586 8 : if (ret != 0)
587 0 : goto out_err;
588 :
589 8 : return result;
590 0 : out_err:
591 0 : lgfs2_lang_result_free(&result);
592 0 : return NULL;
593 : }
594 :
595 8 : static struct lgfs2_lang_result *ast_interpret_node(struct lgfs2_lang_state *state,
596 : struct ast_node *ast, struct lgfs2_sbd *sbd)
597 : {
598 8 : struct lgfs2_lang_result *result = NULL;
599 :
600 8 : if (ast->ast_type == AST_ST_SET) {
601 8 : result = ast_interp_set(state, ast, sbd);
602 0 : } else if (ast->ast_type == AST_ST_GET) {
603 0 : result = ast_interp_get(state, ast, sbd);
604 : } else {
605 0 : fprintf(stderr, "Invalid AST node type: %d\n", ast->ast_type);
606 : }
607 8 : return result;
608 : }
609 :
610 16 : struct lgfs2_lang_result *lgfs2_lang_result_next(struct lgfs2_lang_state *state,
611 : struct lgfs2_sbd *sbd)
612 : {
613 : struct lgfs2_lang_result *result;
614 16 : if (state->ls_interp_curr == NULL) {
615 8 : return NULL;
616 : }
617 8 : result = ast_interpret_node(state, state->ls_interp_curr, sbd);
618 8 : if (result == NULL) {
619 0 : return NULL;
620 : }
621 8 : state->ls_interp_curr = state->ls_interp_curr->ast_left;
622 8 : return result;
623 : }
624 :
625 8 : void lgfs2_lang_result_free(struct lgfs2_lang_result **result)
626 : {
627 8 : if (*result == NULL) {
628 0 : fprintf(stderr, "Warning: attempted to free a null result\n");
629 0 : return;
630 : }
631 8 : free((*result)->lr_buf);
632 8 : free(*result);
633 8 : *result = NULL;
634 : }
|