diff options
Diffstat (limited to 'fs/ext4/crypto_fname.c')
-rw-r--r-- | fs/ext4/crypto_fname.c | 280 |
1 files changed, 145 insertions, 135 deletions
diff --git a/fs/ext4/crypto_fname.c b/fs/ext4/crypto_fname.c index ca2f5948c1ac..fded02f72299 100644 --- a/fs/ext4/crypto_fname.c +++ b/fs/ext4/crypto_fname.c | |||
@@ -66,6 +66,7 @@ static int ext4_fname_encrypt(struct ext4_fname_crypto_ctx *ctx, | |||
66 | int res = 0; | 66 | int res = 0; |
67 | char iv[EXT4_CRYPTO_BLOCK_SIZE]; | 67 | char iv[EXT4_CRYPTO_BLOCK_SIZE]; |
68 | struct scatterlist sg[1]; | 68 | struct scatterlist sg[1]; |
69 | int padding = 4 << (ctx->flags & EXT4_POLICY_FLAGS_PAD_MASK); | ||
69 | char *workbuf; | 70 | char *workbuf; |
70 | 71 | ||
71 | if (iname->len <= 0 || iname->len > ctx->lim) | 72 | if (iname->len <= 0 || iname->len > ctx->lim) |
@@ -73,6 +74,7 @@ static int ext4_fname_encrypt(struct ext4_fname_crypto_ctx *ctx, | |||
73 | 74 | ||
74 | ciphertext_len = (iname->len < EXT4_CRYPTO_BLOCK_SIZE) ? | 75 | ciphertext_len = (iname->len < EXT4_CRYPTO_BLOCK_SIZE) ? |
75 | EXT4_CRYPTO_BLOCK_SIZE : iname->len; | 76 | EXT4_CRYPTO_BLOCK_SIZE : iname->len; |
77 | ciphertext_len = ext4_fname_crypto_round_up(ciphertext_len, padding); | ||
76 | ciphertext_len = (ciphertext_len > ctx->lim) | 78 | ciphertext_len = (ciphertext_len > ctx->lim) |
77 | ? ctx->lim : ciphertext_len; | 79 | ? ctx->lim : ciphertext_len; |
78 | 80 | ||
@@ -101,7 +103,7 @@ static int ext4_fname_encrypt(struct ext4_fname_crypto_ctx *ctx, | |||
101 | /* Create encryption request */ | 103 | /* Create encryption request */ |
102 | sg_init_table(sg, 1); | 104 | sg_init_table(sg, 1); |
103 | sg_set_page(sg, ctx->workpage, PAGE_SIZE, 0); | 105 | sg_set_page(sg, ctx->workpage, PAGE_SIZE, 0); |
104 | ablkcipher_request_set_crypt(req, sg, sg, iname->len, iv); | 106 | ablkcipher_request_set_crypt(req, sg, sg, ciphertext_len, iv); |
105 | res = crypto_ablkcipher_encrypt(req); | 107 | res = crypto_ablkcipher_encrypt(req); |
106 | if (res == -EINPROGRESS || res == -EBUSY) { | 108 | if (res == -EINPROGRESS || res == -EBUSY) { |
107 | BUG_ON(req->base.data != &ecr); | 109 | BUG_ON(req->base.data != &ecr); |
@@ -198,106 +200,57 @@ static int ext4_fname_decrypt(struct ext4_fname_crypto_ctx *ctx, | |||
198 | return oname->len; | 200 | return oname->len; |
199 | } | 201 | } |
200 | 202 | ||
203 | static const char *lookup_table = | ||
204 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; | ||
205 | |||
201 | /** | 206 | /** |
202 | * ext4_fname_encode_digest() - | 207 | * ext4_fname_encode_digest() - |
203 | * | 208 | * |
204 | * Encodes the input digest using characters from the set [a-zA-Z0-9_+]. | 209 | * Encodes the input digest using characters from the set [a-zA-Z0-9_+]. |
205 | * The encoded string is roughly 4/3 times the size of the input string. | 210 | * The encoded string is roughly 4/3 times the size of the input string. |
206 | */ | 211 | */ |
207 | int ext4_fname_encode_digest(char *dst, char *src, u32 len) | 212 | static int digest_encode(const char *src, int len, char *dst) |
208 | { | 213 | { |
209 | static const char *lookup_table = | 214 | int i = 0, bits = 0, ac = 0; |
210 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_+"; | 215 | char *cp = dst; |
211 | u32 current_chunk, num_chunks, i; | 216 | |
212 | char tmp_buf[3]; | 217 | while (i < len) { |
213 | u32 c0, c1, c2, c3; | 218 | ac += (((unsigned char) src[i]) << bits); |
214 | 219 | bits += 8; | |
215 | current_chunk = 0; | 220 | do { |
216 | num_chunks = len/3; | 221 | *cp++ = lookup_table[ac & 0x3f]; |
217 | for (i = 0; i < num_chunks; i++) { | 222 | ac >>= 6; |
218 | c0 = src[3*i] & 0x3f; | 223 | bits -= 6; |
219 | c1 = (((src[3*i]>>6)&0x3) | ((src[3*i+1] & 0xf)<<2)) & 0x3f; | 224 | } while (bits >= 6); |
220 | c2 = (((src[3*i+1]>>4)&0xf) | ((src[3*i+2] & 0x3)<<4)) & 0x3f; | ||
221 | c3 = (src[3*i+2]>>2) & 0x3f; | ||
222 | dst[4*i] = lookup_table[c0]; | ||
223 | dst[4*i+1] = lookup_table[c1]; | ||
224 | dst[4*i+2] = lookup_table[c2]; | ||
225 | dst[4*i+3] = lookup_table[c3]; | ||
226 | } | ||
227 | if (i*3 < len) { | ||
228 | memset(tmp_buf, 0, 3); | ||
229 | memcpy(tmp_buf, &src[3*i], len-3*i); | ||
230 | c0 = tmp_buf[0] & 0x3f; | ||
231 | c1 = (((tmp_buf[0]>>6)&0x3) | ((tmp_buf[1] & 0xf)<<2)) & 0x3f; | ||
232 | c2 = (((tmp_buf[1]>>4)&0xf) | ((tmp_buf[2] & 0x3)<<4)) & 0x3f; | ||
233 | c3 = (tmp_buf[2]>>2) & 0x3f; | ||
234 | dst[4*i] = lookup_table[c0]; | ||
235 | dst[4*i+1] = lookup_table[c1]; | ||
236 | dst[4*i+2] = lookup_table[c2]; | ||
237 | dst[4*i+3] = lookup_table[c3]; | ||
238 | i++; | 225 | i++; |
239 | } | 226 | } |
240 | return (i * 4); | 227 | if (bits) |
228 | *cp++ = lookup_table[ac & 0x3f]; | ||
229 | return cp - dst; | ||
241 | } | 230 | } |
242 | 231 | ||
243 | /** | 232 | static int digest_decode(const char *src, int len, char *dst) |
244 | * ext4_fname_hash() - | ||
245 | * | ||
246 | * This function computes the hash of the input filename, and sets the output | ||
247 | * buffer to the *encoded* digest. It returns the length of the digest as its | ||
248 | * return value. Errors are returned as negative numbers. We trust the caller | ||
249 | * to allocate sufficient memory to oname string. | ||
250 | */ | ||
251 | static int ext4_fname_hash(struct ext4_fname_crypto_ctx *ctx, | ||
252 | const struct ext4_str *iname, | ||
253 | struct ext4_str *oname) | ||
254 | { | 233 | { |
255 | struct scatterlist sg; | 234 | int i = 0, bits = 0, ac = 0; |
256 | struct hash_desc desc = { | 235 | const char *p; |
257 | .tfm = (struct crypto_hash *)ctx->htfm, | 236 | char *cp = dst; |
258 | .flags = CRYPTO_TFM_REQ_MAY_SLEEP | 237 | |
259 | }; | 238 | while (i < len) { |
260 | int res = 0; | 239 | p = strchr(lookup_table, src[i]); |
261 | 240 | if (p == NULL || src[i] == 0) | |
262 | if (iname->len <= EXT4_FNAME_CRYPTO_DIGEST_SIZE) { | 241 | return -2; |
263 | res = ext4_fname_encode_digest(oname->name, iname->name, | 242 | ac += (p - lookup_table) << bits; |
264 | iname->len); | 243 | bits += 6; |
265 | oname->len = res; | 244 | if (bits >= 8) { |
266 | return res; | 245 | *cp++ = ac & 0xff; |
267 | } | 246 | ac >>= 8; |
268 | 247 | bits -= 8; | |
269 | sg_init_one(&sg, iname->name, iname->len); | 248 | } |
270 | res = crypto_hash_init(&desc); | 249 | i++; |
271 | if (res) { | ||
272 | printk(KERN_ERR | ||
273 | "%s: Error initializing crypto hash; res = [%d]\n", | ||
274 | __func__, res); | ||
275 | goto out; | ||
276 | } | ||
277 | res = crypto_hash_update(&desc, &sg, iname->len); | ||
278 | if (res) { | ||
279 | printk(KERN_ERR | ||
280 | "%s: Error updating crypto hash; res = [%d]\n", | ||
281 | __func__, res); | ||
282 | goto out; | ||
283 | } | ||
284 | res = crypto_hash_final(&desc, | ||
285 | &oname->name[EXT4_FNAME_CRYPTO_DIGEST_SIZE]); | ||
286 | if (res) { | ||
287 | printk(KERN_ERR | ||
288 | "%s: Error finalizing crypto hash; res = [%d]\n", | ||
289 | __func__, res); | ||
290 | goto out; | ||
291 | } | 250 | } |
292 | /* Encode the digest as a printable string--this will increase the | 251 | if (ac) |
293 | * size of the digest */ | 252 | return -1; |
294 | oname->name[0] = 'I'; | 253 | return cp - dst; |
295 | res = ext4_fname_encode_digest(oname->name+1, | ||
296 | &oname->name[EXT4_FNAME_CRYPTO_DIGEST_SIZE], | ||
297 | EXT4_FNAME_CRYPTO_DIGEST_SIZE) + 1; | ||
298 | oname->len = res; | ||
299 | out: | ||
300 | return res; | ||
301 | } | 254 | } |
302 | 255 | ||
303 | /** | 256 | /** |
@@ -405,6 +358,7 @@ struct ext4_fname_crypto_ctx *ext4_get_fname_crypto_ctx( | |||
405 | if (IS_ERR(ctx)) | 358 | if (IS_ERR(ctx)) |
406 | return ctx; | 359 | return ctx; |
407 | 360 | ||
361 | ctx->flags = ei->i_crypt_policy_flags; | ||
408 | if (ctx->has_valid_key) { | 362 | if (ctx->has_valid_key) { |
409 | if (ctx->key.mode != EXT4_ENCRYPTION_MODE_AES_256_CTS) { | 363 | if (ctx->key.mode != EXT4_ENCRYPTION_MODE_AES_256_CTS) { |
410 | printk_once(KERN_WARNING | 364 | printk_once(KERN_WARNING |
@@ -517,6 +471,7 @@ int ext4_fname_crypto_namelen_on_disk(struct ext4_fname_crypto_ctx *ctx, | |||
517 | u32 namelen) | 471 | u32 namelen) |
518 | { | 472 | { |
519 | u32 ciphertext_len; | 473 | u32 ciphertext_len; |
474 | int padding = 4 << (ctx->flags & EXT4_POLICY_FLAGS_PAD_MASK); | ||
520 | 475 | ||
521 | if (ctx == NULL) | 476 | if (ctx == NULL) |
522 | return -EIO; | 477 | return -EIO; |
@@ -524,6 +479,7 @@ int ext4_fname_crypto_namelen_on_disk(struct ext4_fname_crypto_ctx *ctx, | |||
524 | return -EACCES; | 479 | return -EACCES; |
525 | ciphertext_len = (namelen < EXT4_CRYPTO_BLOCK_SIZE) ? | 480 | ciphertext_len = (namelen < EXT4_CRYPTO_BLOCK_SIZE) ? |
526 | EXT4_CRYPTO_BLOCK_SIZE : namelen; | 481 | EXT4_CRYPTO_BLOCK_SIZE : namelen; |
482 | ciphertext_len = ext4_fname_crypto_round_up(ciphertext_len, padding); | ||
527 | ciphertext_len = (ciphertext_len > ctx->lim) | 483 | ciphertext_len = (ciphertext_len > ctx->lim) |
528 | ? ctx->lim : ciphertext_len; | 484 | ? ctx->lim : ciphertext_len; |
529 | return (int) ciphertext_len; | 485 | return (int) ciphertext_len; |
@@ -539,10 +495,13 @@ int ext4_fname_crypto_alloc_buffer(struct ext4_fname_crypto_ctx *ctx, | |||
539 | u32 ilen, struct ext4_str *crypto_str) | 495 | u32 ilen, struct ext4_str *crypto_str) |
540 | { | 496 | { |
541 | unsigned int olen; | 497 | unsigned int olen; |
498 | int padding = 4 << (ctx->flags & EXT4_POLICY_FLAGS_PAD_MASK); | ||
542 | 499 | ||
543 | if (!ctx) | 500 | if (!ctx) |
544 | return -EIO; | 501 | return -EIO; |
545 | olen = ext4_fname_crypto_round_up(ilen, EXT4_CRYPTO_BLOCK_SIZE); | 502 | if (padding < EXT4_CRYPTO_BLOCK_SIZE) |
503 | padding = EXT4_CRYPTO_BLOCK_SIZE; | ||
504 | olen = ext4_fname_crypto_round_up(ilen, padding); | ||
546 | crypto_str->len = olen; | 505 | crypto_str->len = olen; |
547 | if (olen < EXT4_FNAME_CRYPTO_DIGEST_SIZE*2) | 506 | if (olen < EXT4_FNAME_CRYPTO_DIGEST_SIZE*2) |
548 | olen = EXT4_FNAME_CRYPTO_DIGEST_SIZE*2; | 507 | olen = EXT4_FNAME_CRYPTO_DIGEST_SIZE*2; |
@@ -571,9 +530,13 @@ void ext4_fname_crypto_free_buffer(struct ext4_str *crypto_str) | |||
571 | * ext4_fname_disk_to_usr() - converts a filename from disk space to user space | 530 | * ext4_fname_disk_to_usr() - converts a filename from disk space to user space |
572 | */ | 531 | */ |
573 | int _ext4_fname_disk_to_usr(struct ext4_fname_crypto_ctx *ctx, | 532 | int _ext4_fname_disk_to_usr(struct ext4_fname_crypto_ctx *ctx, |
574 | const struct ext4_str *iname, | 533 | struct dx_hash_info *hinfo, |
575 | struct ext4_str *oname) | 534 | const struct ext4_str *iname, |
535 | struct ext4_str *oname) | ||
576 | { | 536 | { |
537 | char buf[24]; | ||
538 | int ret; | ||
539 | |||
577 | if (ctx == NULL) | 540 | if (ctx == NULL) |
578 | return -EIO; | 541 | return -EIO; |
579 | if (iname->len < 3) { | 542 | if (iname->len < 3) { |
@@ -587,18 +550,33 @@ int _ext4_fname_disk_to_usr(struct ext4_fname_crypto_ctx *ctx, | |||
587 | } | 550 | } |
588 | if (ctx->has_valid_key) | 551 | if (ctx->has_valid_key) |
589 | return ext4_fname_decrypt(ctx, iname, oname); | 552 | return ext4_fname_decrypt(ctx, iname, oname); |
590 | else | 553 | |
591 | return ext4_fname_hash(ctx, iname, oname); | 554 | if (iname->len <= EXT4_FNAME_CRYPTO_DIGEST_SIZE) { |
555 | ret = digest_encode(iname->name, iname->len, oname->name); | ||
556 | oname->len = ret; | ||
557 | return ret; | ||
558 | } | ||
559 | if (hinfo) { | ||
560 | memcpy(buf, &hinfo->hash, 4); | ||
561 | memcpy(buf+4, &hinfo->minor_hash, 4); | ||
562 | } else | ||
563 | memset(buf, 0, 8); | ||
564 | memcpy(buf + 8, iname->name + iname->len - 16, 16); | ||
565 | oname->name[0] = '_'; | ||
566 | ret = digest_encode(buf, 24, oname->name+1); | ||
567 | oname->len = ret + 1; | ||
568 | return ret + 1; | ||
592 | } | 569 | } |
593 | 570 | ||
594 | int ext4_fname_disk_to_usr(struct ext4_fname_crypto_ctx *ctx, | 571 | int ext4_fname_disk_to_usr(struct ext4_fname_crypto_ctx *ctx, |
572 | struct dx_hash_info *hinfo, | ||
595 | const struct ext4_dir_entry_2 *de, | 573 | const struct ext4_dir_entry_2 *de, |
596 | struct ext4_str *oname) | 574 | struct ext4_str *oname) |
597 | { | 575 | { |
598 | struct ext4_str iname = {.name = (unsigned char *) de->name, | 576 | struct ext4_str iname = {.name = (unsigned char *) de->name, |
599 | .len = de->name_len }; | 577 | .len = de->name_len }; |
600 | 578 | ||
601 | return _ext4_fname_disk_to_usr(ctx, &iname, oname); | 579 | return _ext4_fname_disk_to_usr(ctx, hinfo, &iname, oname); |
602 | } | 580 | } |
603 | 581 | ||
604 | 582 | ||
@@ -640,10 +618,11 @@ int ext4_fname_usr_to_hash(struct ext4_fname_crypto_ctx *ctx, | |||
640 | const struct qstr *iname, | 618 | const struct qstr *iname, |
641 | struct dx_hash_info *hinfo) | 619 | struct dx_hash_info *hinfo) |
642 | { | 620 | { |
643 | struct ext4_str tmp, tmp2; | 621 | struct ext4_str tmp; |
644 | int ret = 0; | 622 | int ret = 0; |
623 | char buf[EXT4_FNAME_CRYPTO_DIGEST_SIZE+1]; | ||
645 | 624 | ||
646 | if (!ctx || !ctx->has_valid_key || | 625 | if (!ctx || |
647 | ((iname->name[0] == '.') && | 626 | ((iname->name[0] == '.') && |
648 | ((iname->len == 1) || | 627 | ((iname->len == 1) || |
649 | ((iname->name[1] == '.') && (iname->len == 2))))) { | 628 | ((iname->name[1] == '.') && (iname->len == 2))))) { |
@@ -651,59 +630,90 @@ int ext4_fname_usr_to_hash(struct ext4_fname_crypto_ctx *ctx, | |||
651 | return 0; | 630 | return 0; |
652 | } | 631 | } |
653 | 632 | ||
633 | if (!ctx->has_valid_key && iname->name[0] == '_') { | ||
634 | if (iname->len != 33) | ||
635 | return -ENOENT; | ||
636 | ret = digest_decode(iname->name+1, iname->len, buf); | ||
637 | if (ret != 24) | ||
638 | return -ENOENT; | ||
639 | memcpy(&hinfo->hash, buf, 4); | ||
640 | memcpy(&hinfo->minor_hash, buf + 4, 4); | ||
641 | return 0; | ||
642 | } | ||
643 | |||
644 | if (!ctx->has_valid_key && iname->name[0] != '_') { | ||
645 | if (iname->len > 43) | ||
646 | return -ENOENT; | ||
647 | ret = digest_decode(iname->name, iname->len, buf); | ||
648 | ext4fs_dirhash(buf, ret, hinfo); | ||
649 | return 0; | ||
650 | } | ||
651 | |||
654 | /* First encrypt the plaintext name */ | 652 | /* First encrypt the plaintext name */ |
655 | ret = ext4_fname_crypto_alloc_buffer(ctx, iname->len, &tmp); | 653 | ret = ext4_fname_crypto_alloc_buffer(ctx, iname->len, &tmp); |
656 | if (ret < 0) | 654 | if (ret < 0) |
657 | return ret; | 655 | return ret; |
658 | 656 | ||
659 | ret = ext4_fname_encrypt(ctx, iname, &tmp); | 657 | ret = ext4_fname_encrypt(ctx, iname, &tmp); |
660 | if (ret < 0) | 658 | if (ret >= 0) { |
661 | goto out; | 659 | ext4fs_dirhash(tmp.name, tmp.len, hinfo); |
662 | 660 | ret = 0; | |
663 | tmp2.len = (4 * ((EXT4_FNAME_CRYPTO_DIGEST_SIZE + 2) / 3)) + 1; | ||
664 | tmp2.name = kmalloc(tmp2.len + 1, GFP_KERNEL); | ||
665 | if (tmp2.name == NULL) { | ||
666 | ret = -ENOMEM; | ||
667 | goto out; | ||
668 | } | 661 | } |
669 | 662 | ||
670 | ret = ext4_fname_hash(ctx, &tmp, &tmp2); | ||
671 | if (ret > 0) | ||
672 | ext4fs_dirhash(tmp2.name, tmp2.len, hinfo); | ||
673 | ext4_fname_crypto_free_buffer(&tmp2); | ||
674 | out: | ||
675 | ext4_fname_crypto_free_buffer(&tmp); | 663 | ext4_fname_crypto_free_buffer(&tmp); |
676 | return ret; | 664 | return ret; |
677 | } | 665 | } |
678 | 666 | ||
679 | /** | 667 | int ext4_fname_match(struct ext4_fname_crypto_ctx *ctx, struct ext4_str *cstr, |
680 | * ext4_fname_disk_to_htree() - converts a filename from disk space to htree-access string | 668 | int len, const char * const name, |
681 | */ | 669 | struct ext4_dir_entry_2 *de) |
682 | int ext4_fname_disk_to_hash(struct ext4_fname_crypto_ctx *ctx, | ||
683 | const struct ext4_dir_entry_2 *de, | ||
684 | struct dx_hash_info *hinfo) | ||
685 | { | 670 | { |
686 | struct ext4_str iname = {.name = (unsigned char *) de->name, | 671 | int ret = -ENOENT; |
687 | .len = de->name_len}; | 672 | int bigname = (*name == '_'); |
688 | struct ext4_str tmp; | ||
689 | int ret; | ||
690 | 673 | ||
691 | if (!ctx || | 674 | if (ctx->has_valid_key) { |
692 | ((iname.name[0] == '.') && | 675 | if (cstr->name == NULL) { |
693 | ((iname.len == 1) || | 676 | struct qstr istr; |
694 | ((iname.name[1] == '.') && (iname.len == 2))))) { | 677 | |
695 | ext4fs_dirhash(iname.name, iname.len, hinfo); | 678 | ret = ext4_fname_crypto_alloc_buffer(ctx, len, cstr); |
696 | return 0; | 679 | if (ret < 0) |
680 | goto errout; | ||
681 | istr.name = name; | ||
682 | istr.len = len; | ||
683 | ret = ext4_fname_encrypt(ctx, &istr, cstr); | ||
684 | if (ret < 0) | ||
685 | goto errout; | ||
686 | } | ||
687 | } else { | ||
688 | if (cstr->name == NULL) { | ||
689 | cstr->name = kmalloc(32, GFP_KERNEL); | ||
690 | if (cstr->name == NULL) | ||
691 | return -ENOMEM; | ||
692 | if ((bigname && (len != 33)) || | ||
693 | (!bigname && (len > 43))) | ||
694 | goto errout; | ||
695 | ret = digest_decode(name+bigname, len-bigname, | ||
696 | cstr->name); | ||
697 | if (ret < 0) { | ||
698 | ret = -ENOENT; | ||
699 | goto errout; | ||
700 | } | ||
701 | cstr->len = ret; | ||
702 | } | ||
703 | if (bigname) { | ||
704 | if (de->name_len < 16) | ||
705 | return 0; | ||
706 | ret = memcmp(de->name + de->name_len - 16, | ||
707 | cstr->name + 8, 16); | ||
708 | return (ret == 0) ? 1 : 0; | ||
709 | } | ||
697 | } | 710 | } |
698 | 711 | if (de->name_len != cstr->len) | |
699 | tmp.len = (4 * ((EXT4_FNAME_CRYPTO_DIGEST_SIZE + 2) / 3)) + 1; | 712 | return 0; |
700 | tmp.name = kmalloc(tmp.len + 1, GFP_KERNEL); | 713 | ret = memcmp(de->name, cstr->name, cstr->len); |
701 | if (tmp.name == NULL) | 714 | return (ret == 0) ? 1 : 0; |
702 | return -ENOMEM; | 715 | errout: |
703 | 716 | kfree(cstr->name); | |
704 | ret = ext4_fname_hash(ctx, &iname, &tmp); | 717 | cstr->name = NULL; |
705 | if (ret > 0) | ||
706 | ext4fs_dirhash(tmp.name, tmp.len, hinfo); | ||
707 | ext4_fname_crypto_free_buffer(&tmp); | ||
708 | return ret; | 718 | return ret; |
709 | } | 719 | } |