Initial commit to Gerrit
[profile/ivi/quota.git] / quotaio_v2.c
1 /*
2  *      Implementation of new quotafile format
3  *
4  *      Jan Kara <jack@suse.cz> - sponsored by SuSE CR
5  */
6
7 #include "config.h"
8
9 #include <sys/types.h>
10 #include <errno.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <asm/byteorder.h>
16
17 #include "pot.h"
18 #include "common.h"
19 #include "quotaio_v2.h"
20 #include "dqblk_v2.h"
21 #include "quotaio.h"
22 #include "quotasys.h"
23 #include "quotaio_generic.h"
24
25 typedef char *dqbuf_t;
26
27 static int v2_check_file(int fd, int type, int fmt);
28 static int v2_init_io(struct quota_handle *h);
29 static int v2_new_io(struct quota_handle *h);
30 static int v2_write_info(struct quota_handle *h);
31 static struct dquot *v2_read_dquot(struct quota_handle *h, qid_t id);
32 static int v2_commit_dquot(struct dquot *dquot, int flags);
33 static int v2_scan_dquots(struct quota_handle *h, int (*process_dquot) (struct dquot *dquot, char *dqname));
34 static int v2_report(struct quota_handle *h, int verbose);
35
36 struct quotafile_ops quotafile_ops_2 = {
37 check_file:     v2_check_file,
38 init_io:        v2_init_io,
39 new_io:         v2_new_io,
40 write_info:     v2_write_info,
41 read_dquot:     v2_read_dquot,
42 commit_dquot:   v2_commit_dquot,
43 scan_dquots:    v2_scan_dquots,
44 report: v2_report
45 };
46
47 #define getdqbuf() smalloc(V2_DQBLKSIZE)
48 #define freedqbuf(buf) free(buf)
49
50 /*
51  *      Copy dquot from disk to memory
52  */
53 static void v2r0_disk2memdqblk(struct dquot *dquot, void *dp)
54 {
55         struct util_dqblk *m = &dquot->dq_dqb;
56         struct v2r0_disk_dqblk *d = dp, empty;
57
58         dquot->dq_id = __le32_to_cpu(d->dqb_id);
59         m->dqb_ihardlimit = __le32_to_cpu(d->dqb_ihardlimit);
60         m->dqb_isoftlimit = __le32_to_cpu(d->dqb_isoftlimit);
61         m->dqb_bhardlimit = __le32_to_cpu(d->dqb_bhardlimit);
62         m->dqb_bsoftlimit = __le32_to_cpu(d->dqb_bsoftlimit);
63         m->dqb_curinodes = __le32_to_cpu(d->dqb_curinodes);
64         m->dqb_curspace = __le64_to_cpu(d->dqb_curspace);
65         m->dqb_itime = __le64_to_cpu(d->dqb_itime);
66         m->dqb_btime = __le64_to_cpu(d->dqb_btime);
67
68         memset(&empty, 0, sizeof(struct v2r0_disk_dqblk));
69         empty.dqb_itime = __cpu_to_le64(1);
70         if (!memcmp(&empty, dp, sizeof(struct v2r0_disk_dqblk)))
71                 m->dqb_itime = 0;
72 }
73
74 /*
75  *      Copy dquot from memory to disk
76  */
77 static void v2r0_mem2diskdqblk(void *dp, struct dquot *dquot)
78 {
79         struct util_dqblk *m = &dquot->dq_dqb;
80         struct v2r0_disk_dqblk *d = dp;
81         struct qtree_mem_dqinfo *info = &dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree;
82
83         d->dqb_ihardlimit = __cpu_to_le32(m->dqb_ihardlimit);
84         d->dqb_isoftlimit = __cpu_to_le32(m->dqb_isoftlimit);
85         d->dqb_bhardlimit = __cpu_to_le32(m->dqb_bhardlimit);
86         d->dqb_bsoftlimit = __cpu_to_le32(m->dqb_bsoftlimit);
87         d->dqb_curinodes = __cpu_to_le32(m->dqb_curinodes);
88         d->dqb_curspace = __cpu_to_le64(m->dqb_curspace);
89         d->dqb_itime = __cpu_to_le64(m->dqb_itime);
90         d->dqb_btime = __cpu_to_le64(m->dqb_btime);
91         d->dqb_id = __cpu_to_le32(dquot->dq_id);
92         if (qtree_entry_unused(info, dp))
93                 d->dqb_itime = __cpu_to_le64(1);
94 }
95
96 static int v2r0_is_id(void *dp, struct dquot *dquot)
97 {
98         struct v2r0_disk_dqblk *d = dp;
99         struct qtree_mem_dqinfo *info = &dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree;
100
101         if (qtree_entry_unused(info, dp))
102                 return 0;
103         return __le32_to_cpu(d->dqb_id) == dquot->dq_id;
104 }
105
106 /*
107  *      Copy dquot from disk to memory
108  */
109 static void v2r1_disk2memdqblk(struct dquot *dquot, void *dp)
110 {
111         struct util_dqblk *m = &dquot->dq_dqb;
112         struct v2r1_disk_dqblk *d = dp, empty;
113
114         dquot->dq_id = __le32_to_cpu(d->dqb_id);
115         m->dqb_ihardlimit = __le64_to_cpu(d->dqb_ihardlimit);
116         m->dqb_isoftlimit = __le64_to_cpu(d->dqb_isoftlimit);
117         m->dqb_bhardlimit = __le64_to_cpu(d->dqb_bhardlimit);
118         m->dqb_bsoftlimit = __le64_to_cpu(d->dqb_bsoftlimit);
119         m->dqb_curinodes = __le64_to_cpu(d->dqb_curinodes);
120         m->dqb_curspace = __le64_to_cpu(d->dqb_curspace);
121         m->dqb_itime = __le64_to_cpu(d->dqb_itime);
122         m->dqb_btime = __le64_to_cpu(d->dqb_btime);
123
124         memset(&empty, 0, sizeof(struct v2r1_disk_dqblk));
125         empty.dqb_itime = __cpu_to_le64(1);
126         if (!memcmp(&empty, dp, sizeof(struct v2r1_disk_dqblk)))
127                 m->dqb_itime = 0;
128 }
129
130 /*
131  *      Copy dquot from memory to disk
132  */
133 static void v2r1_mem2diskdqblk(void *dp, struct dquot *dquot)
134 {
135         struct util_dqblk *m = &dquot->dq_dqb;
136         struct v2r1_disk_dqblk *d = dp;
137
138         d->dqb_ihardlimit = __cpu_to_le64(m->dqb_ihardlimit);
139         d->dqb_isoftlimit = __cpu_to_le64(m->dqb_isoftlimit);
140         d->dqb_bhardlimit = __cpu_to_le64(m->dqb_bhardlimit);
141         d->dqb_bsoftlimit = __cpu_to_le64(m->dqb_bsoftlimit);
142         d->dqb_curinodes = __cpu_to_le64(m->dqb_curinodes);
143         d->dqb_curspace = __cpu_to_le64(m->dqb_curspace);
144         d->dqb_itime = __cpu_to_le64(m->dqb_itime);
145         d->dqb_btime = __cpu_to_le64(m->dqb_btime);
146         d->dqb_id = __cpu_to_le32(dquot->dq_id);
147         if (qtree_entry_unused(&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree, dp))
148                 d->dqb_itime = __cpu_to_le64(1);
149 }
150
151 static int v2r1_is_id(void *dp, struct dquot *dquot)
152 {
153         struct v2r1_disk_dqblk *d = dp;
154         struct qtree_mem_dqinfo *info = &dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree;
155
156         if (qtree_entry_unused(info, dp))
157                 return 0;
158         return __le32_to_cpu(d->dqb_id) == dquot->dq_id;
159 }
160
161 static struct qtree_fmt_operations v2r0_fmt_ops = {
162         .mem2disk_dqblk = v2r0_mem2diskdqblk,
163         .disk2mem_dqblk = v2r0_disk2memdqblk,
164         .is_id = v2r0_is_id,
165 };
166
167 static struct qtree_fmt_operations v2r1_fmt_ops = {
168         .mem2disk_dqblk = v2r1_mem2diskdqblk,
169         .disk2mem_dqblk = v2r1_disk2memdqblk,
170         .is_id = v2r1_is_id,
171 };
172
173 /*
174  *      Copy dqinfo from disk to memory
175  */
176 static inline void v2_disk2memdqinfo(struct util_dqinfo *m, struct v2_disk_dqinfo *d)
177 {
178         m->dqi_bgrace = __le32_to_cpu(d->dqi_bgrace);
179         m->dqi_igrace = __le32_to_cpu(d->dqi_igrace);
180         m->u.v2_mdqi.dqi_flags = __le32_to_cpu(d->dqi_flags) & V2_DQF_MASK;
181         m->u.v2_mdqi.dqi_qtree.dqi_blocks = __le32_to_cpu(d->dqi_blocks);
182         m->u.v2_mdqi.dqi_qtree.dqi_free_blk = __le32_to_cpu(d->dqi_free_blk);
183         m->u.v2_mdqi.dqi_qtree.dqi_free_entry = __le32_to_cpu(d->dqi_free_entry);
184 }
185
186 /*
187  *      Copy dqinfo from memory to disk
188  */
189 static inline void v2_mem2diskdqinfo(struct v2_disk_dqinfo *d, struct util_dqinfo *m)
190 {
191         d->dqi_bgrace = __cpu_to_le32(m->dqi_bgrace);
192         d->dqi_igrace = __cpu_to_le32(m->dqi_igrace);
193         d->dqi_flags = __cpu_to_le32(m->u.v2_mdqi.dqi_flags & V2_DQF_MASK);
194         d->dqi_blocks = __cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_blocks);
195         d->dqi_free_blk = __cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_free_blk);
196         d->dqi_free_entry = __cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_free_entry);
197 }
198
199 /* Convert kernel quotablock format to utility one */
200 static inline void v2_kern2utildqblk(struct util_dqblk *u, struct v2_kern_dqblk *k)
201 {
202         u->dqb_ihardlimit = k->dqb_ihardlimit;
203         u->dqb_isoftlimit = k->dqb_isoftlimit;
204         u->dqb_bhardlimit = k->dqb_bhardlimit;
205         u->dqb_bsoftlimit = k->dqb_bsoftlimit;
206         u->dqb_curinodes = k->dqb_curinodes;
207         u->dqb_curspace = k->dqb_curspace;
208         u->dqb_itime = k->dqb_itime;
209         u->dqb_btime = k->dqb_btime;
210 }
211
212 /* Convert utility quotablock format to kernel one */
213 static inline void v2_util2kerndqblk(struct v2_kern_dqblk *k, struct util_dqblk *u)
214 {
215         k->dqb_ihardlimit = u->dqb_ihardlimit;
216         k->dqb_isoftlimit = u->dqb_isoftlimit;
217         k->dqb_bhardlimit = u->dqb_bhardlimit;
218         k->dqb_bsoftlimit = u->dqb_bsoftlimit;
219         k->dqb_curinodes = u->dqb_curinodes;
220         k->dqb_curspace = u->dqb_curspace;
221         k->dqb_itime = u->dqb_itime;
222         k->dqb_btime = u->dqb_btime;
223 }
224
225 static int v2_read_header(int fd, struct v2_disk_dqheader *h)
226 {
227         lseek(fd, 0, SEEK_SET);
228         if (read(fd, h, sizeof(struct v2_disk_dqheader)) != sizeof(struct v2_disk_dqheader))
229                 return 0;
230         return 1;
231 }
232
233 /*
234  *      Check whether given quota file is in our format
235  */
236 static int v2_check_file(int fd, int type, int fmt)
237 {
238         struct v2_disk_dqheader h;
239         int file_magics[] = INITQMAGICS;
240         int known_versions[] = INIT_V2_VERSIONS;
241         int version;
242
243         if (!v2_read_header(fd, &h))
244                 return 0;
245         if (fmt == QF_VFSV0)
246                 version = 0;
247         else if (fmt == QF_VFSV1)
248                 version = 1;
249         else
250                 return 0;
251
252         if (__le32_to_cpu(h.dqh_magic) != file_magics[type]) {
253                 if (__be32_to_cpu(h.dqh_magic) == file_magics[type])
254                         die(3, _("Your quota file is stored in wrong endianity. Please use convertquota(8) to convert it.\n"));
255                 return 0;
256         }
257         if (__le32_to_cpu(h.dqh_version) > known_versions[type])
258                 return 0;
259         if (version != __le32_to_cpu(h.dqh_version))
260                 return 0;
261         return 1;
262 }
263
264 /*
265  *      Open quotafile
266  */
267 static int v2_init_io(struct quota_handle *h)
268 {
269         if (QIO_ENABLED(h)) {
270                 if (kernel_iface == IFACE_GENERIC) {
271                         if (vfs_get_info(h) < 0)
272                                 return -1;
273                 }
274                 else {
275                         struct v2_kern_dqinfo kdqinfo;
276
277                         if (quotactl(QCMD(Q_V2_GETINFO, h->qh_type), h->qh_quotadev, 0, (void *)&kdqinfo) < 0) {
278                                 /* Temporary check just before fix gets to kernel */
279                                 if (errno == EPERM)     /* Don't have permission to get information? */
280                                         return 0;
281                                 return -1;
282                         }
283                         h->qh_info.dqi_bgrace = kdqinfo.dqi_bgrace;
284                         h->qh_info.dqi_igrace = kdqinfo.dqi_igrace;
285                         h->qh_info.u.v2_mdqi.dqi_flags = kdqinfo.dqi_flags;
286                         h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks = kdqinfo.dqi_blocks;
287                         h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_blk = kdqinfo.dqi_free_blk;
288                         h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_entry = kdqinfo.dqi_free_entry;
289                 }
290         }
291         if (h->qh_fd != -1) {
292                 struct v2_disk_dqinfo ddqinfo;
293                 struct v2_disk_dqheader header;
294
295                 if (!v2_read_header(h->qh_fd, &header))
296                         return -1;
297
298                 lseek(h->qh_fd, V2_DQINFOOFF, SEEK_SET);
299                 if (read(h->qh_fd, &ddqinfo, sizeof(ddqinfo)) != sizeof(ddqinfo))
300                         return -1;
301                 /* Convert everything */
302                 if (!QIO_ENABLED(h))
303                         v2_disk2memdqinfo(&h->qh_info, &ddqinfo);
304                 else    /* We need just the number of blocks */
305                         h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks = __le32_to_cpu(ddqinfo.dqi_blocks);
306
307                 if (__le32_to_cpu(header.dqh_version) == 0) {
308                         h->qh_info.u.v2_mdqi.dqi_qtree.dqi_entry_size = sizeof(struct v2r0_disk_dqblk);
309                         h->qh_info.u.v2_mdqi.dqi_qtree.dqi_ops = &v2r0_fmt_ops;
310                 } else {
311                         h->qh_info.u.v2_mdqi.dqi_qtree.dqi_entry_size = sizeof(struct v2r1_disk_dqblk);
312                         h->qh_info.u.v2_mdqi.dqi_qtree.dqi_ops = &v2r1_fmt_ops;
313                 }
314         } else {
315                 /* We don't have the file open -> we don't need quota tree operations */
316                 h->qh_info.u.v2_mdqi.dqi_qtree.dqi_ops = NULL;
317         }
318         return 0;
319 }
320
321 /*
322  *      Initialize new quotafile
323  */
324 static int v2_new_io(struct quota_handle *h)
325 {
326         int file_magics[] = INITQMAGICS;
327         struct v2_disk_dqheader ddqheader;
328         struct v2_disk_dqinfo ddqinfo;
329         int version;
330
331         if (h->qh_fmt == QF_VFSV0)
332                 version = 0;
333         else if (h->qh_fmt == QF_VFSV1)
334                 version = 1;
335         else
336                 return -1;
337
338         /* Write basic quota header */
339         ddqheader.dqh_magic = __cpu_to_le32(file_magics[h->qh_type]);
340         ddqheader.dqh_version = __cpu_to_le32(version);
341         lseek(h->qh_fd, 0, SEEK_SET);
342         if (write(h->qh_fd, &ddqheader, sizeof(ddqheader)) != sizeof(ddqheader))
343                 return -1;
344         /* Write information about quotafile */
345         h->qh_info.dqi_bgrace = MAX_DQ_TIME;
346         h->qh_info.dqi_igrace = MAX_IQ_TIME;
347         h->qh_info.u.v2_mdqi.dqi_flags = 0;
348         h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks = QT_TREEOFF + 1;
349         h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_blk = 0;
350         h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_entry = 0;
351         if (version == 0) {
352                 h->qh_info.u.v2_mdqi.dqi_qtree.dqi_entry_size = sizeof(struct v2r0_disk_dqblk);
353                 h->qh_info.u.v2_mdqi.dqi_qtree.dqi_ops = &v2r0_fmt_ops;
354         } else if (version == 1) {
355                 h->qh_info.u.v2_mdqi.dqi_qtree.dqi_entry_size = sizeof(struct v2r1_disk_dqblk);
356                 h->qh_info.u.v2_mdqi.dqi_qtree.dqi_ops = &v2r1_fmt_ops;
357         }
358         v2_mem2diskdqinfo(&ddqinfo, &h->qh_info);
359         lseek(h->qh_fd, V2_DQINFOOFF, SEEK_SET);
360         if (write(h->qh_fd, &ddqinfo, sizeof(ddqinfo)) != sizeof(ddqinfo))
361                 return -1;
362         return 0;
363 }
364
365 /*
366  *      Write information (grace times to file)
367  */
368 static int v2_write_info(struct quota_handle *h)
369 {
370         if (QIO_RO(h)) {
371                 errstr(_("Trying to write info to readonly quotafile on %s\n"), h->qh_quotadev);
372                 errno = EPERM;
373                 return -1;
374         }
375         if (QIO_ENABLED(h)) {
376                 if (kernel_iface == IFACE_GENERIC) {
377                         if (vfs_set_info(h, IIF_BGRACE | IIF_IGRACE))
378                                 return -1;
379                 }
380                 else {
381                         struct v2_kern_dqinfo kdqinfo;
382
383                         kdqinfo.dqi_bgrace = h->qh_info.dqi_bgrace;
384                         kdqinfo.dqi_igrace = h->qh_info.dqi_igrace;
385                         kdqinfo.dqi_flags = h->qh_info.u.v2_mdqi.dqi_flags;
386                         kdqinfo.dqi_blocks = h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks;
387                         kdqinfo.dqi_free_blk = h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_blk;
388                         kdqinfo.dqi_free_entry = h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_entry;
389                         if (quotactl(QCMD(Q_V2_SETGRACE, h->qh_type), h->qh_quotadev, 0, (void *)&kdqinfo) < 0 ||
390                             quotactl(QCMD(Q_V2_SETFLAGS, h->qh_type), h->qh_quotadev, 0, (void *)&kdqinfo) < 0)
391                                         return -1;
392                 }
393         }
394         else {
395                 struct v2_disk_dqinfo ddqinfo;
396
397                 v2_mem2diskdqinfo(&ddqinfo, &h->qh_info);
398                 lseek(h->qh_fd, V2_DQINFOOFF, SEEK_SET);
399                 if (write(h->qh_fd, &ddqinfo, sizeof(ddqinfo)) != sizeof(ddqinfo))
400                         return -1;
401         }
402         return 0;
403 }
404
405 /*
406  *  Read dquot (either from disk or from kernel)
407  *  User can use errno to detect errstr when NULL is returned
408  */
409 static struct dquot *v2_read_dquot(struct quota_handle *h, qid_t id)
410 {
411         if (QIO_ENABLED(h)) {
412                 struct dquot *dquot = get_empty_dquot();
413
414                 dquot->dq_id = id;
415                 dquot->dq_h = h;
416                 dquot->dq_dqb.u.v2_mdqb.dqb_off = 0;
417                 memset(&dquot->dq_dqb, 0, sizeof(struct util_dqblk));
418                 if (kernel_iface == IFACE_GENERIC) {
419                         if (vfs_get_dquot(dquot) < 0) {
420                                 free(dquot);
421                                 return NULL;
422                         }
423                 }
424                 else {
425                         struct v2_kern_dqblk kdqblk;
426
427                         if (quotactl(QCMD(Q_V2_GETQUOTA, h->qh_type), h->qh_quotadev, id, (void *)&kdqblk) < 0) {
428                                 free(dquot);
429                                 return NULL;
430                         }
431                         v2_kern2utildqblk(&dquot->dq_dqb, &kdqblk);
432                 }
433                 return dquot;
434         }
435         return qtree_read_dquot(h, id);
436 }
437
438 /* 
439  *  Commit changes of dquot to disk - it might also mean deleting it when quota became fake one and user has no blocks...
440  *  User can process use 'errno' to detect errstr
441  */
442 static int v2_commit_dquot(struct dquot *dquot, int flags)
443 {
444         struct util_dqblk *b = &dquot->dq_dqb;
445
446         if (QIO_RO(dquot->dq_h)) {
447                 errstr(_("Trying to write quota to readonly quotafile on %s\n"), dquot->dq_h->qh_quotadev);
448                 errno = EPERM;
449                 return -1;
450         }
451         if (QIO_ENABLED(dquot->dq_h)) {
452                 if (kernel_iface == IFACE_GENERIC) {
453                         if (vfs_set_dquot(dquot, flags) < 0)
454                                 return -1;
455                 }
456                 else {
457                         struct v2_kern_dqblk kdqblk;
458                         int cmd;
459
460                         if (flags == COMMIT_USAGE)
461                                 cmd = Q_V2_SETUSE;
462                         else if (flags == COMMIT_LIMITS)
463                                 cmd = Q_V2_SETQLIM;
464                         else if (flags & COMMIT_TIMES) {
465                                 errno = EINVAL;
466                                 return -1;
467                         }
468                         else
469                                 cmd = Q_V2_SETQUOTA;
470                         v2_util2kerndqblk(&kdqblk, &dquot->dq_dqb);
471                         if (quotactl(QCMD(cmd, dquot->dq_h->qh_type), dquot->dq_h->qh_quotadev,
472                              dquot->dq_id, (void *)&kdqblk) < 0)
473                                 return -1;
474                 }
475                 return 0;
476         }
477         if (!b->dqb_curspace && !b->dqb_curinodes && !b->dqb_bsoftlimit && !b->dqb_isoftlimit
478             && !b->dqb_bhardlimit && !b->dqb_ihardlimit)
479                 qtree_delete_dquot(dquot);
480         else
481                 qtree_write_dquot(dquot);
482         return 0;
483 }
484
485 static int v2_scan_dquots(struct quota_handle *h, int (*process_dquot) (struct dquot *, char *))
486 {
487         if (QIO_ENABLED(h))     /* Kernel uses same file? */
488                 if (quotactl(QCMD((kernel_iface == IFACE_GENERIC) ? Q_SYNC : Q_6_5_SYNC, h->qh_type),
489                              h->qh_quotadev, 0, NULL) < 0)
490                         die(4, _("Cannot sync quotas on device %s: %s\n"), h->qh_quotadev,
491                             strerror(errno));
492         return qtree_scan_dquots(h, process_dquot);
493 }
494
495 /* Report information about quotafile */
496 static int v2_report(struct quota_handle *h, int verbose)
497 {
498         if (verbose) {
499                 struct v2_mem_dqinfo *info = &h->qh_info.u.v2_mdqi;
500
501                 printf(_("Statistics:\nTotal blocks: %u\nData blocks: %u\nEntries: %u\nUsed average: %f\n"),
502                          info->dqi_qtree.dqi_blocks, info->dqi_data_blocks, info->dqi_used_entries,
503                          ((float)info->dqi_used_entries) / info->dqi_data_blocks);
504         }
505         return 0;
506 }