blob: 43e3aeb529ead35f55eb7b6da9e31845efdb692e [file] [log] [blame]
Mike Marshall5db11c22015-07-17 10:38:12 -04001/*
2 * (C) 2001 Clemson University and The University of Chicago
3 *
4 * See COPYING in top-level directory.
5 */
6
7#include "protocol.h"
Mike Marshall575e9462015-12-04 12:56:14 -05008#include "orangefs-kernel.h"
9#include "orangefs-bufmap.h"
Mike Marshall5db11c22015-07-17 10:38:12 -040010
11struct readdir_handle_s {
Yi Liu8bb8aef2015-11-24 15:12:14 -050012 struct orangefs_readdir_response_s readdir_response;
Mike Marshall5db11c22015-07-17 10:38:12 -040013 void *dents_buf;
14};
15
16/*
Mike Marshall1808f8c2016-01-15 13:10:52 -050017 * decode routine used by kmod to deal with the blob sent from
18 * userspace for readdirs. The blob contains zero or more of these
19 * sub-blobs:
20 * __u32 - represents length of the character string that follows.
21 * string - between 1 and ORANGEFS_NAME_MAX bytes long.
22 * padding - (if needed) to cause the __u32 plus the string to be
23 * eight byte aligned.
24 * khandle - sizeof(khandle) bytes.
Mike Marshall5db11c22015-07-17 10:38:12 -040025 */
Al Viro80928952015-10-09 18:11:10 -040026static long decode_dirents(char *ptr, size_t size,
Yi Liu8bb8aef2015-11-24 15:12:14 -050027 struct orangefs_readdir_response_s *readdir)
Mike Marshall5db11c22015-07-17 10:38:12 -040028{
29 int i;
Yi Liu8bb8aef2015-11-24 15:12:14 -050030 struct orangefs_readdir_response_s *rd =
31 (struct orangefs_readdir_response_s *) ptr;
Mike Marshall5db11c22015-07-17 10:38:12 -040032 char *buf = ptr;
Mike Marshall1808f8c2016-01-15 13:10:52 -050033 int khandle_size = sizeof(struct orangefs_khandle);
34 size_t offset = offsetof(struct orangefs_readdir_response_s,
35 dirent_array);
36 /* 8 reflects eight byte alignment */
37 int smallest_blob = khandle_size + 8;
38 __u32 len;
39 int aligned_len;
40 int sizeof_u32 = sizeof(__u32);
41 long ret;
Mike Marshall5db11c22015-07-17 10:38:12 -040042
Mike Marshall1808f8c2016-01-15 13:10:52 -050043 gossip_debug(GOSSIP_DIR_DEBUG, "%s: size:%zu:\n", __func__, size);
44
45 /* size is = offset on empty dirs, > offset on non-empty dirs... */
46 if (size < offset) {
47 gossip_err("%s: size:%zu: offset:%zu:\n",
48 __func__,
49 size,
50 offset);
51 ret = -EINVAL;
52 goto out;
53 }
54
55 if ((size == offset) && (readdir->orangefs_dirent_outcount != 0)) {
56 gossip_err("%s: size:%zu: dirent_outcount:%d:\n",
57 __func__,
58 size,
59 readdir->orangefs_dirent_outcount);
60 ret = -EINVAL;
61 goto out;
62 }
Al Viro80928952015-10-09 18:11:10 -040063
Mike Marshall5db11c22015-07-17 10:38:12 -040064 readdir->token = rd->token;
Yi Liu8bb8aef2015-11-24 15:12:14 -050065 readdir->orangefs_dirent_outcount = rd->orangefs_dirent_outcount;
66 readdir->dirent_array = kcalloc(readdir->orangefs_dirent_outcount,
Mike Marshall5db11c22015-07-17 10:38:12 -040067 sizeof(*readdir->dirent_array),
68 GFP_KERNEL);
Mike Marshall1808f8c2016-01-15 13:10:52 -050069 if (readdir->dirent_array == NULL) {
70 gossip_err("%s: kcalloc failed.\n", __func__);
71 ret = -ENOMEM;
72 goto out;
73 }
Al Viro80928952015-10-09 18:11:10 -040074
Mike Marshall1808f8c2016-01-15 13:10:52 -050075 buf += offset;
76 size -= offset;
Al Viro80928952015-10-09 18:11:10 -040077
Yi Liu8bb8aef2015-11-24 15:12:14 -050078 for (i = 0; i < readdir->orangefs_dirent_outcount; i++) {
Mike Marshall1808f8c2016-01-15 13:10:52 -050079 if (size < smallest_blob) {
80 gossip_err("%s: size:%zu: smallest_blob:%d:\n",
81 __func__,
82 size,
83 smallest_blob);
84 ret = -EINVAL;
85 goto free;
86 }
Al Viro80928952015-10-09 18:11:10 -040087
88 len = *(__u32 *)buf;
Mike Marshall1808f8c2016-01-15 13:10:52 -050089 if ((len < 1) || (len > ORANGEFS_NAME_MAX)) {
90 gossip_err("%s: len:%d:\n", __func__, len);
91 ret = -EINVAL;
92 goto free;
93 }
Al Viro80928952015-10-09 18:11:10 -040094
Mike Marshall1808f8c2016-01-15 13:10:52 -050095 gossip_debug(GOSSIP_DIR_DEBUG,
96 "%s: size:%zu: len:%d:\n",
97 __func__,
98 size,
99 len);
100
101 readdir->dirent_array[i].d_name = buf + sizeof_u32;
Al Viro9be68b02015-10-09 17:43:15 -0400102 readdir->dirent_array[i].d_length = len;
Al Viro80928952015-10-09 18:11:10 -0400103
Martin Brandenburg7d221482016-01-04 15:05:28 -0500104 /*
Mike Marshall1808f8c2016-01-15 13:10:52 -0500105 * Calculate "aligned" length of this string and its
106 * associated __u32 descriptor.
Martin Brandenburg7d221482016-01-04 15:05:28 -0500107 */
Mike Marshall1808f8c2016-01-15 13:10:52 -0500108 aligned_len = ((sizeof_u32 + len + 1) + 7) & ~7;
109 gossip_debug(GOSSIP_DIR_DEBUG,
110 "%s: aligned_len:%d:\n",
111 __func__,
112 aligned_len);
Al Viro80928952015-10-09 18:11:10 -0400113
Mike Marshall1808f8c2016-01-15 13:10:52 -0500114 /*
115 * The end of the blob should coincide with the end
116 * of the last sub-blob.
117 */
118 if (size < aligned_len + khandle_size) {
119 gossip_err("%s: ran off the end of the blob.\n",
120 __func__);
121 ret = -EINVAL;
122 goto free;
123 }
124 size -= aligned_len + khandle_size;
125
126 buf += aligned_len;
Al Viro80928952015-10-09 18:11:10 -0400127
Mike Marshall5db11c22015-07-17 10:38:12 -0400128 readdir->dirent_array[i].khandle =
Yi Liu8bb8aef2015-11-24 15:12:14 -0500129 *(struct orangefs_khandle *) buf;
Mike Marshall1808f8c2016-01-15 13:10:52 -0500130 buf += khandle_size;
Mike Marshall5db11c22015-07-17 10:38:12 -0400131 }
Mike Marshall1808f8c2016-01-15 13:10:52 -0500132 ret = buf - ptr;
133 gossip_debug(GOSSIP_DIR_DEBUG, "%s: returning:%ld:\n", __func__, ret);
134 goto out;
135
136free:
Al Viro80928952015-10-09 18:11:10 -0400137 kfree(readdir->dirent_array);
138 readdir->dirent_array = NULL;
Mike Marshall1808f8c2016-01-15 13:10:52 -0500139
140out:
141 return ret;
Mike Marshall5db11c22015-07-17 10:38:12 -0400142}
143
144static long readdir_handle_ctor(struct readdir_handle_s *rhandle, void *buf,
Martin Brandenburgee3b8d32016-02-17 12:55:42 -0500145 size_t size)
Mike Marshall5db11c22015-07-17 10:38:12 -0400146{
147 long ret;
148
149 if (buf == NULL) {
150 gossip_err
151 ("Invalid NULL buffer specified in readdir_handle_ctor\n");
152 return -ENOMEM;
153 }
Mike Marshall5db11c22015-07-17 10:38:12 -0400154 rhandle->dents_buf = buf;
Al Viro80928952015-10-09 18:11:10 -0400155 ret = decode_dirents(buf, size, &rhandle->readdir_response);
Mike Marshall5db11c22015-07-17 10:38:12 -0400156 if (ret < 0) {
157 gossip_err("Could not decode readdir from buffer %ld\n", ret);
Mike Marshall5db11c22015-07-17 10:38:12 -0400158 gossip_debug(GOSSIP_DIR_DEBUG, "vfree %p\n", buf);
159 vfree(buf);
160 rhandle->dents_buf = NULL;
161 }
162 return ret;
163}
164
Al Viro82d37f12016-02-13 21:04:51 -0500165static void readdir_handle_dtor(struct readdir_handle_s *rhandle)
Mike Marshall5db11c22015-07-17 10:38:12 -0400166{
167 if (rhandle == NULL)
168 return;
169
170 /* kfree(NULL) is safe */
171 kfree(rhandle->readdir_response.dirent_array);
172 rhandle->readdir_response.dirent_array = NULL;
173
Mike Marshall5db11c22015-07-17 10:38:12 -0400174 if (rhandle->dents_buf) {
175 gossip_debug(GOSSIP_DIR_DEBUG, "vfree %p\n",
176 rhandle->dents_buf);
177 vfree(rhandle->dents_buf);
178 rhandle->dents_buf = NULL;
179 }
180}
181
182/*
183 * Read directory entries from an instance of an open directory.
Mike Marshall5db11c22015-07-17 10:38:12 -0400184 */
Yi Liu8bb8aef2015-11-24 15:12:14 -0500185static int orangefs_readdir(struct file *file, struct dir_context *ctx)
Mike Marshall5db11c22015-07-17 10:38:12 -0400186{
Yi Liu8bb8aef2015-11-24 15:12:14 -0500187 struct orangefs_bufmap *bufmap = NULL;
Mike Marshall5db11c22015-07-17 10:38:12 -0400188 int ret = 0;
189 int buffer_index;
Mike Marshall88309aa2015-09-23 16:48:40 -0400190 /*
191 * ptoken supports Orangefs' distributed directory logic, added
192 * in 2.9.2.
193 */
Mike Marshall5db11c22015-07-17 10:38:12 -0400194 __u64 *ptoken = file->private_data;
195 __u64 pos = 0;
196 ino_t ino = 0;
197 struct dentry *dentry = file->f_path.dentry;
Yi Liu8bb8aef2015-11-24 15:12:14 -0500198 struct orangefs_kernel_op_s *new_op = NULL;
199 struct orangefs_inode_s *orangefs_inode = ORANGEFS_I(dentry->d_inode);
Mike Marshall5db11c22015-07-17 10:38:12 -0400200 int buffer_full = 0;
201 struct readdir_handle_s rhandle;
202 int i = 0;
203 int len = 0;
204 ino_t current_ino = 0;
205 char *current_entry = NULL;
206 long bytes_decoded;
207
Mike Marshall88309aa2015-09-23 16:48:40 -0400208 gossip_debug(GOSSIP_DIR_DEBUG,
209 "%s: ctx->pos:%lld, ptoken = %llu\n",
210 __func__,
211 lld(ctx->pos),
212 llu(*ptoken));
Mike Marshall5db11c22015-07-17 10:38:12 -0400213
214 pos = (__u64) ctx->pos;
215
216 /* are we done? */
Yi Liu8bb8aef2015-11-24 15:12:14 -0500217 if (pos == ORANGEFS_READDIR_END) {
Mike Marshall5db11c22015-07-17 10:38:12 -0400218 gossip_debug(GOSSIP_DIR_DEBUG,
219 "Skipping to termination path\n");
220 return 0;
221 }
222
223 gossip_debug(GOSSIP_DIR_DEBUG,
Yi Liu8bb8aef2015-11-24 15:12:14 -0500224 "orangefs_readdir called on %s (pos=%llu)\n",
Mike Marshall5db11c22015-07-17 10:38:12 -0400225 dentry->d_name.name, llu(pos));
226
Mike Marshall5db11c22015-07-17 10:38:12 -0400227 rhandle.dents_buf = NULL;
228 memset(&rhandle.readdir_response, 0, sizeof(rhandle.readdir_response));
229
Yi Liu8bb8aef2015-11-24 15:12:14 -0500230 new_op = op_alloc(ORANGEFS_VFS_OP_READDIR);
Mike Marshall5db11c22015-07-17 10:38:12 -0400231 if (!new_op)
232 return -ENOMEM;
233
Martin Brandenburgee3b8d32016-02-17 12:55:42 -0500234 /*
235 * Only the indices are shared. No memory is actually shared, but the
236 * mechanism is used.
237 */
Mike Marshall5db11c22015-07-17 10:38:12 -0400238 new_op->uses_shared_memory = 1;
Yi Liu8bb8aef2015-11-24 15:12:14 -0500239 new_op->upcall.req.readdir.refn = orangefs_inode->refn;
Martin Brandenburg7d221482016-01-04 15:05:28 -0500240 new_op->upcall.req.readdir.max_dirent_count =
241 ORANGEFS_MAX_DIRENT_COUNT_READDIR;
Mike Marshall5db11c22015-07-17 10:38:12 -0400242
243 gossip_debug(GOSSIP_DIR_DEBUG,
244 "%s: upcall.req.readdir.refn.khandle: %pU\n",
245 __func__,
246 &new_op->upcall.req.readdir.refn.khandle);
247
Mike Marshall5db11c22015-07-17 10:38:12 -0400248 new_op->upcall.req.readdir.token = *ptoken;
249
250get_new_buffer_index:
Martin Brandenburg7d221482016-01-04 15:05:28 -0500251 ret = orangefs_readdir_index_get(&bufmap, &buffer_index);
Mike Marshall5db11c22015-07-17 10:38:12 -0400252 if (ret < 0) {
Martin Brandenburg7d221482016-01-04 15:05:28 -0500253 gossip_lerr("orangefs_readdir: orangefs_readdir_index_get() failure (%d)\n",
Mike Marshall5db11c22015-07-17 10:38:12 -0400254 ret);
255 goto out_free_op;
256 }
257 new_op->upcall.req.readdir.buf_index = buffer_index;
258
259 ret = service_operation(new_op,
Yi Liu8bb8aef2015-11-24 15:12:14 -0500260 "orangefs_readdir",
Mike Marshall5db11c22015-07-17 10:38:12 -0400261 get_interruptible_flag(dentry->d_inode));
262
263 gossip_debug(GOSSIP_DIR_DEBUG,
264 "Readdir downcall status is %d. ret:%d\n",
265 new_op->downcall.status,
266 ret);
267
Martin Brandenburgee3b8d32016-02-17 12:55:42 -0500268 orangefs_readdir_index_put(buffer_index);
269
Mike Marshall5db11c22015-07-17 10:38:12 -0400270 if (ret == -EAGAIN && op_state_purged(new_op)) {
Martin Brandenburgee3b8d32016-02-17 12:55:42 -0500271 /* Client-core indices are invalid after it restarted. */
Mike Marshall5db11c22015-07-17 10:38:12 -0400272 gossip_debug(GOSSIP_DIR_DEBUG,
273 "%s: Getting new buffer_index for retry of readdir..\n",
274 __func__);
Mike Marshall5db11c22015-07-17 10:38:12 -0400275 goto get_new_buffer_index;
276 }
277
278 if (ret == -EIO && op_state_purged(new_op)) {
279 gossip_err("%s: Client is down. Aborting readdir call.\n",
280 __func__);
Mike Marshall5db11c22015-07-17 10:38:12 -0400281 goto out_free_op;
282 }
283
284 if (ret < 0 || new_op->downcall.status != 0) {
285 gossip_debug(GOSSIP_DIR_DEBUG,
286 "Readdir request failed. Status:%d\n",
287 new_op->downcall.status);
Mike Marshall5db11c22015-07-17 10:38:12 -0400288 if (ret >= 0)
289 ret = new_op->downcall.status;
290 goto out_free_op;
291 }
292
293 bytes_decoded =
294 readdir_handle_ctor(&rhandle,
295 new_op->downcall.trailer_buf,
Martin Brandenburgee3b8d32016-02-17 12:55:42 -0500296 new_op->downcall.trailer_size);
Mike Marshall5db11c22015-07-17 10:38:12 -0400297 if (bytes_decoded < 0) {
Yi Liu8bb8aef2015-11-24 15:12:14 -0500298 gossip_err("orangefs_readdir: Could not decode trailer buffer into a readdir response %d\n",
Mike Marshall5db11c22015-07-17 10:38:12 -0400299 ret);
300 ret = bytes_decoded;
Mike Marshall5db11c22015-07-17 10:38:12 -0400301 goto out_free_op;
302 }
303
304 if (bytes_decoded != new_op->downcall.trailer_size) {
Yi Liu8bb8aef2015-11-24 15:12:14 -0500305 gossip_err("orangefs_readdir: # bytes decoded (%ld) "
Mike Marshall88309aa2015-09-23 16:48:40 -0400306 "!= trailer size (%ld)\n",
307 bytes_decoded,
308 (long)new_op->downcall.trailer_size);
Mike Marshall5db11c22015-07-17 10:38:12 -0400309 ret = -EINVAL;
310 goto out_destroy_handle;
311 }
312
Mike Marshall88309aa2015-09-23 16:48:40 -0400313 /*
Yi Liu8bb8aef2015-11-24 15:12:14 -0500314 * orangefs doesn't actually store dot and dot-dot, but
Mike Marshall88309aa2015-09-23 16:48:40 -0400315 * we need to have them represented.
316 */
Mike Marshall5db11c22015-07-17 10:38:12 -0400317 if (pos == 0) {
318 ino = get_ino_from_khandle(dentry->d_inode);
319 gossip_debug(GOSSIP_DIR_DEBUG,
320 "%s: calling dir_emit of \".\" with pos = %llu\n",
321 __func__,
322 llu(pos));
323 ret = dir_emit(ctx, ".", 1, ino, DT_DIR);
Mike Marshall88309aa2015-09-23 16:48:40 -0400324 pos += 1;
Mike Marshall5db11c22015-07-17 10:38:12 -0400325 }
326
327 if (pos == 1) {
328 ino = get_parent_ino_from_dentry(dentry);
329 gossip_debug(GOSSIP_DIR_DEBUG,
330 "%s: calling dir_emit of \"..\" with pos = %llu\n",
331 __func__,
332 llu(pos));
333 ret = dir_emit(ctx, "..", 2, ino, DT_DIR);
Mike Marshall88309aa2015-09-23 16:48:40 -0400334 pos += 1;
Mike Marshall5db11c22015-07-17 10:38:12 -0400335 }
336
Mike Marshall88309aa2015-09-23 16:48:40 -0400337 /*
Yi Liu8bb8aef2015-11-24 15:12:14 -0500338 * we stored ORANGEFS_ITERATE_NEXT in ctx->pos last time around
Mike Marshall88309aa2015-09-23 16:48:40 -0400339 * to prevent "finding" dot and dot-dot on any iteration
340 * other than the first.
341 */
Yi Liu8bb8aef2015-11-24 15:12:14 -0500342 if (ctx->pos == ORANGEFS_ITERATE_NEXT)
Mike Marshall88309aa2015-09-23 16:48:40 -0400343 ctx->pos = 0;
344
345 for (i = ctx->pos;
Yi Liu8bb8aef2015-11-24 15:12:14 -0500346 i < rhandle.readdir_response.orangefs_dirent_outcount;
Mike Marshall88309aa2015-09-23 16:48:40 -0400347 i++) {
Mike Marshall5db11c22015-07-17 10:38:12 -0400348 len = rhandle.readdir_response.dirent_array[i].d_length;
349 current_entry = rhandle.readdir_response.dirent_array[i].d_name;
Yi Liu8bb8aef2015-11-24 15:12:14 -0500350 current_ino = orangefs_khandle_to_ino(
Mike Marshall5db11c22015-07-17 10:38:12 -0400351 &(rhandle.readdir_response.dirent_array[i].khandle));
352
353 gossip_debug(GOSSIP_DIR_DEBUG,
Mike Marshall88309aa2015-09-23 16:48:40 -0400354 "calling dir_emit for %s with len %d"
355 ", ctx->pos %ld\n",
Mike Marshall5db11c22015-07-17 10:38:12 -0400356 current_entry,
357 len,
Mike Marshall88309aa2015-09-23 16:48:40 -0400358 (unsigned long)ctx->pos);
359 /*
360 * type is unknown. We don't return object type
361 * in the dirent_array. This leaves getdents
362 * clueless about type.
363 */
Mike Marshall5db11c22015-07-17 10:38:12 -0400364 ret =
365 dir_emit(ctx, current_entry, len, current_ino, DT_UNKNOWN);
Mike Marshall88309aa2015-09-23 16:48:40 -0400366 if (!ret)
367 break;
Mike Marshall5db11c22015-07-17 10:38:12 -0400368 ctx->pos++;
Mike Marshall88309aa2015-09-23 16:48:40 -0400369 gossip_debug(GOSSIP_DIR_DEBUG,
Mike Marshall5db11c22015-07-17 10:38:12 -0400370 "%s: ctx->pos:%lld\n",
371 __func__,
372 lld(ctx->pos));
373
Mike Marshall5db11c22015-07-17 10:38:12 -0400374 }
375
Mike Marshall54804942015-10-05 13:44:24 -0400376 /*
Mike Marshall88309aa2015-09-23 16:48:40 -0400377 * we ran all the way through the last batch, set up for
378 * getting another batch...
379 */
380 if (ret) {
Mike Marshall5db11c22015-07-17 10:38:12 -0400381 *ptoken = rhandle.readdir_response.token;
Yi Liu8bb8aef2015-11-24 15:12:14 -0500382 ctx->pos = ORANGEFS_ITERATE_NEXT;
Mike Marshall5db11c22015-07-17 10:38:12 -0400383 }
384
385 /*
386 * Did we hit the end of the directory?
387 */
Yi Liu8bb8aef2015-11-24 15:12:14 -0500388 if (rhandle.readdir_response.token == ORANGEFS_READDIR_END &&
Mike Marshall5db11c22015-07-17 10:38:12 -0400389 !buffer_full) {
Mike Marshall88309aa2015-09-23 16:48:40 -0400390 gossip_debug(GOSSIP_DIR_DEBUG,
Yi Liu8bb8aef2015-11-24 15:12:14 -0500391 "End of dir detected; setting ctx->pos to ORANGEFS_READDIR_END.\n");
392 ctx->pos = ORANGEFS_READDIR_END;
Mike Marshall5db11c22015-07-17 10:38:12 -0400393 }
394
Mike Marshall5db11c22015-07-17 10:38:12 -0400395out_destroy_handle:
Al Viro82d37f12016-02-13 21:04:51 -0500396 readdir_handle_dtor(&rhandle);
Mike Marshall5db11c22015-07-17 10:38:12 -0400397out_free_op:
398 op_release(new_op);
Yi Liu8bb8aef2015-11-24 15:12:14 -0500399 gossip_debug(GOSSIP_DIR_DEBUG, "orangefs_readdir returning %d\n", ret);
Mike Marshall5db11c22015-07-17 10:38:12 -0400400 return ret;
401}
402
Yi Liu8bb8aef2015-11-24 15:12:14 -0500403static int orangefs_dir_open(struct inode *inode, struct file *file)
Mike Marshall5db11c22015-07-17 10:38:12 -0400404{
405 __u64 *ptoken;
406
407 file->private_data = kmalloc(sizeof(__u64), GFP_KERNEL);
408 if (!file->private_data)
409 return -ENOMEM;
410
411 ptoken = file->private_data;
Yi Liu8bb8aef2015-11-24 15:12:14 -0500412 *ptoken = ORANGEFS_READDIR_START;
Mike Marshall5db11c22015-07-17 10:38:12 -0400413 return 0;
414}
415
Yi Liu8bb8aef2015-11-24 15:12:14 -0500416static int orangefs_dir_release(struct inode *inode, struct file *file)
Mike Marshall5db11c22015-07-17 10:38:12 -0400417{
Yi Liu8bb8aef2015-11-24 15:12:14 -0500418 orangefs_flush_inode(inode);
Mike Marshall5db11c22015-07-17 10:38:12 -0400419 kfree(file->private_data);
420 return 0;
421}
422
Yi Liu8bb8aef2015-11-24 15:12:14 -0500423/** ORANGEFS implementation of VFS directory operations */
424const struct file_operations orangefs_dir_operations = {
Mike Marshall5db11c22015-07-17 10:38:12 -0400425 .read = generic_read_dir,
Yi Liu8bb8aef2015-11-24 15:12:14 -0500426 .iterate = orangefs_readdir,
427 .open = orangefs_dir_open,
428 .release = orangefs_dir_release,
Mike Marshall5db11c22015-07-17 10:38:12 -0400429};