Second Extended Filesystem (ext2) and other improvements (#33)
[lunaix-os.git] / lunaix-os / kernel / fs / ext2 / dir.c
1 #include <lunaix/mm/valloc.h>
2 #include <lunaix/spike.h>
3 #include <klibc/string.h>
4
5 #include "ext2.h"
6
7 static inline bool
8 aligned_reclen(struct ext2b_dirent* dirent)
9 {
10     return !(dirent->rec_len % 4);
11 }
12
13 static int
14 __find_dirent_byname(struct v_inode* inode, struct hstr* name, 
15                      struct ext2_dnode* e_dnode_out)
16 {
17     int errno = 0;
18     struct ext2_iterator iter;
19     struct ext2b_dirent *dir = NULL, *prev = NULL;
20     bbuf_t prev_buf = NULL;
21
22     ext2dr_itbegin(&iter, inode);
23
24     while (ext2dr_itnext(&iter)) {
25         dir = iter.dirent;
26
27         if (dir->name_len != name->len) {
28             goto cont;
29         }
30
31         if (strneq(dir->name, name->value, name->len)) {
32             goto done;
33         }
34
35 cont:        
36         prev = dir;
37         if (prev_buf) {
38             fsblock_put(prev_buf);
39         }
40         prev_buf = fsblock_take(iter.sel_buf);
41     }
42     
43     errno = ENOENT;
44     goto _ret;
45
46 done:
47     e_dnode_out->self = (struct ext2_dnode_sub) {
48         .buf = fsblock_take(iter.sel_buf),
49         .dirent = dir
50     };
51
52     e_dnode_out->prev = (struct ext2_dnode_sub) {
53         .buf = fsblock_take(prev_buf),
54         .dirent = prev
55     };
56     
57 _ret:
58     fsblock_put(prev_buf);
59     ext2dr_itend(&iter);
60     return itstate_sel(&iter, errno);
61 }
62
63 static size_t
64 __dirent_realsize(struct ext2b_dirent* dirent)
65 {
66     return sizeof(*dirent) - sizeof(dirent->name) + dirent->name_len;
67 }
68
69 #define DIRENT_SLOT_MID     0
70 #define DIRENT_SLOT_LAST    1
71 #define DIRENT_SLOT_EMPTY   2
72
73 static int
74 __find_free_dirent_slot(struct v_inode* inode, size_t size, 
75                         struct ext2_dnode* e_dnode_out, size_t *reclen)
76 {
77     struct ext2_iterator iter;
78     struct ext2b_dirent *dir = NULL;
79     bbuf_t prev_buf = bbuf_null;
80     bool found = false;
81
82     ext2db_itbegin(&iter, inode);
83
84     size_t sz = 0;
85     unsigned int rec = 0, total_rec = 0;
86
87     while (!found && ext2db_itnext(&iter))
88     {
89         rec = 0;
90         do {
91             dir = (struct ext2b_dirent*)offset(iter.data, rec);
92
93             sz = dir->rec_len - __dirent_realsize(dir);
94             sz = ROUNDDOWN(sz, 4);
95             if (sz >= size) {
96                 found = true;
97                 break;
98             }
99
100             rec += dir->rec_len;
101             total_rec += dir->rec_len;
102         } while(rec < iter.blksz);
103
104         if (likely(prev_buf)) {
105             fsblock_put(prev_buf);
106         }
107         
108         prev_buf = fsblock_take(iter.sel_buf);
109     }
110
111     if (blkbuf_nullbuf(prev_buf)) {
112         // this dir is brand new
113         return DIRENT_SLOT_EMPTY;
114     }
115
116     e_dnode_out->prev = (struct ext2_dnode_sub) {
117         .buf = fsblock_take(prev_buf),
118         .dirent = dir
119     };
120
121     if (!found) {
122         // if prev is the last, and no more space left behind.
123         assert_fs(rec == iter.blksz);
124         
125         e_dnode_out->self.buf = bbuf_null;
126         ext2db_itend(&iter);
127         return itstate_sel(&iter, DIRENT_SLOT_LAST);
128     }
129
130     unsigned int dir_size;
131
132     dir_size = ROUNDUP(__dirent_realsize(dir), 4);
133     *reclen = dir_size;
134
135     rec = total_rec + dir_size;
136     dir = (struct ext2b_dirent*)offset(iter.data, rec);
137     
138     e_dnode_out->self = (struct ext2_dnode_sub) {
139         .buf = fsblock_take(iter.sel_buf),
140         .dirent = dir
141     };
142
143     ext2db_itend(&iter);
144     return DIRENT_SLOT_MID;
145 }
146
147 static inline void
148 __destruct_ext2_dnode(struct ext2_dnode* e_dno)
149 {
150     fsblock_put(e_dno->prev.buf);
151     fsblock_put(e_dno->self.buf);
152     vfree(e_dno);
153 }
154
155 static inline bool
156 __check_special(struct v_dnode* dnode)
157 {
158     return HSTR_EQ(&dnode->name, &vfs_dot)
159         || HSTR_EQ(&dnode->name, &vfs_ddot);
160 }
161
162 static bool
163 __check_empty_dir(struct v_inode* dir_ino)
164 {
165     struct ext2_iterator iter;
166     struct ext2b_dirent* dir;
167     
168     ext2dr_itbegin(&iter, dir_ino);
169     while (ext2dr_itnext(&iter))
170     {
171         dir = iter.dirent;
172         if (strneq(dir->name, vfs_dot.value, 1)) {
173             continue;
174         }
175
176         if (strneq(dir->name, vfs_ddot.value, 2)) {
177             continue;
178         }
179
180         ext2dr_itend(&iter);
181         return false;
182     }
183
184     ext2dr_itend(&iter);
185     return true;
186 }
187
188 void
189 ext2dr_itbegin(struct ext2_iterator* iter, struct v_inode* inode)
190 {
191     *iter = (struct ext2_iterator){
192         .pos = 0,
193         .inode = inode,
194         .blksz = inode->sb->blksize
195     };
196
197     iter->sel_buf = ext2db_get(inode, 0);
198     ext2_itcheckbuf(iter);
199 }
200
201 void
202 ext2dr_itreset(struct ext2_iterator* iter)
203 {
204     fsblock_put(iter->sel_buf);
205     iter->sel_buf = ext2db_get(iter->inode, 0);
206     ext2_itcheckbuf(iter);
207
208     iter->pos = 0;
209 }
210
211 int
212 ext2dr_itffw(struct ext2_iterator* iter, int count)
213 {
214     int i = 0;
215     while (i < count && ext2dr_itnext(iter)) {
216         i++;
217     }
218
219     return i;
220 }
221
222 void
223 ext2dr_itend(struct ext2_iterator* iter)
224 {
225     if (iter->sel_buf) {
226         fsblock_put(iter->sel_buf);
227     }
228 }
229
230 bool
231 ext2dr_itnext(struct ext2_iterator* iter)
232 {
233     struct ext2b_dirent* d;
234     unsigned int blkpos, db_index;
235     bbuf_t buf;
236     
237     buf = iter->sel_buf;
238
239     if (iter->has_error) {
240         return false;
241     }
242
243     if (likely(iter->dirent)) {
244         d = iter->dirent;
245         
246         assert_fs(!(d->rec_len % 4));
247         iter->pos += d->rec_len;
248
249         if (!d->rec_len || !d->inode) {
250             return false;
251         }
252     }
253
254     blkpos = iter->pos % iter->blksz;
255     db_index = iter->pos / iter->blksz;
256     
257     if (unlikely(iter->pos >= iter->blksz)) {
258         fsblock_put(buf);
259
260         buf = ext2db_get(iter->inode, db_index);
261         iter->sel_buf = buf;
262
263         if (!buf || !ext2_itcheckbuf(iter)) {
264             return false;
265         }
266     }
267
268     d = (struct ext2b_dirent*)offset(blkbuf_data(buf), blkpos);
269     iter->dirent = d;
270
271     return true;
272 }
273
274 int
275 ext2dr_open(struct v_inode* this, struct v_file* file)
276 {
277     struct ext2_file* e_file;
278
279     e_file = EXT2_FILE(file);
280
281     ext2dr_itbegin(&e_file->iter, this);
282
283     return itstate_sel(&e_file->iter, 0);
284 }
285
286 int
287 ext2dr_close(struct v_inode* this, struct v_file* file)
288 {
289     struct ext2_file* e_file;
290
291     e_file = EXT2_FILE(file);
292
293     ext2dr_itend(&e_file->iter);
294
295     return 0;
296 }
297
298 int
299 ext2dr_lookup(struct v_inode* inode, struct v_dnode* dnode)
300 {
301     int errno;
302     struct ext2b_dirent* dir;
303     struct ext2_dnode* e_dnode;
304     struct v_inode* dir_inode;
305
306     e_dnode = valloc(sizeof(struct ext2_dnode));
307     errno = __find_dirent_byname(inode, &dnode->name, e_dnode);
308     if (errno) {
309         vfree(e_dnode);
310         return errno;
311     }
312
313     dir = e_dnode->self.dirent;
314     if (!(dir_inode = vfs_i_find(inode->sb, dir->inode))) { 
315         dir_inode = vfs_i_alloc(inode->sb);
316         ext2ino_fill(dir_inode, dir->inode);
317     }
318
319     dnode->data = e_dnode;
320     vfs_assign_inode(dnode, dir_inode);
321
322     return 0;
323 }
324
325 #define FT_NUL  0
326 #define FT_REG  1
327 #define FT_DIR  2
328 #define FT_CHR  3
329 #define FT_BLK  4
330 #define FT_SYM  7
331 #define check_imode(val, imode)     (((val) & (imode)) == (imode)) 
332
333 static inline unsigned int
334 __imode_to_filetype(unsigned int imode)
335 {
336     if (check_imode(imode, IMODE_IFLNK)) {
337         return FT_SYM;
338     }
339
340     if (check_imode(imode, IMODE_IFBLK)) {
341         return FT_BLK;
342     }
343
344     if (check_imode(imode, IMODE_IFCHR)) {
345         return FT_CHR;
346     }
347
348     if (check_imode(imode, IMODE_IFDIR)) {
349         return FT_DIR;
350     }
351
352     if (check_imode(imode, IMODE_IFREG)) {
353         return FT_REG;
354     }
355
356     return FT_NUL;
357 }
358
359 static int
360 __dir_filetype(struct v_superblock* vsb, struct ext2b_dirent* dir)
361 {
362     int errno;
363     unsigned int type;
364
365     if (ext2_feature(vsb, FEAT_FILETYPE)) {
366         type = dir->file_type;
367     }
368     else {
369         struct ext2_fast_inode e_fino;
370
371         errno = ext2ino_get_fast(vsb, dir->inode, &e_fino);
372         if (errno) {
373             return errno;
374         }
375
376         type = __imode_to_filetype(e_fino.ino->i_mode);
377
378         fsblock_put(e_fino.buf);
379     }
380     
381     if (type == FT_DIR) {
382         return DT_DIR;
383     }
384     
385     if (type == FT_SYM) {
386         return DT_SYMLINK;
387     }
388
389     return DT_FILE;
390 }
391
392 int
393 ext2dr_read(struct v_file *file, struct dir_context *dctx)
394 {
395     struct ext2_file* e_file;
396     struct ext2b_dirent* dir;
397     struct ext2_iterator* iter;
398     struct v_superblock* vsb;
399     int dirtype;
400
401     e_file = EXT2_FILE(file);
402     vsb    = file->inode->sb;
403     iter   = &e_file->iter;
404
405     if (!ext2dr_itnext(&e_file->iter)) {
406         return itstate_sel(iter, 0);
407     }
408
409     dir = e_file->iter.dirent;
410     dirtype =  __dir_filetype(vsb, dir);
411     if (dirtype < 0) {
412         return dirtype;
413     }
414
415     fsapi_dir_report(dctx, dir->name, dir->name_len, dirtype);
416     
417     return 1;
418 }
419
420 int
421 ext2dr_seek(struct v_file* file, size_t offset)
422 {
423     struct ext2_file* e_file;
424     struct ext2_iterator* iter;
425     unsigned int fpos;
426
427     e_file = EXT2_FILE(file);
428     iter = &e_file->iter;
429     fpos = file->f_pos;
430
431     if (offset == fpos) {
432         return 0;
433     }
434
435     if (offset > fpos) {
436         fpos = ext2dr_itffw(iter, fpos - offset);
437         return 0;
438     }
439
440     if (!offset || offset < fpos) {
441         ext2dr_itreset(iter);
442     }
443
444     fpos = ext2dr_itffw(iter, offset);
445
446     return itstate_sel(iter, 0);
447 }
448
449 int
450 ext2dr_insert(struct v_inode* this, struct ext2b_dirent* dirent,
451               struct ext2_dnode** e_dno_out)
452 {
453     int errno;
454     size_t size, new_reclen, old_reclen;
455     struct ext2_inode* e_self;
456     struct ext2_dnode*  e_dno;
457     struct ext2b_dirent* prev_dirent;
458     bbuf_t buf;
459
460     e_self = EXT2_INO(this);
461     e_dno  = vzalloc(sizeof(*e_dno));
462     
463     size = __dirent_realsize(dirent);
464     errno = __find_free_dirent_slot(this, size, e_dno, &new_reclen);
465     if (errno < 0) {
466         goto failed;
467     }
468
469     if (errno == DIRENT_SLOT_EMPTY) {
470         if ((errno = ext2db_acquire(this, 0, &buf))) {
471             goto failed;
472         }
473
474         this->fsize += fsapi_block_size(this->sb);
475         ext2ino_update(this);
476         
477         old_reclen = fsapi_block_size(this->sb);
478         e_dno->self.buf = buf;
479         e_dno->self.dirent = blkbuf_data(buf);
480
481         goto place_dir;
482     }
483
484     prev_dirent = e_dno->prev.dirent;
485     old_reclen = prev_dirent->rec_len;
486
487     if (errno == DIRENT_SLOT_LAST) {
488         // prev is last record
489         if ((errno = ext2db_alloc(this, &buf))) {
490             goto failed;
491         }
492
493         this->fsize += fsapi_block_size(this->sb);
494         ext2ino_update(this);
495
496         new_reclen = __dirent_realsize(prev_dirent);
497         new_reclen = ROUNDUP(new_reclen, sizeof(int));
498         e_dno->self = (struct ext2_dnode_sub) {
499             .buf = buf,
500             .dirent = block_buffer(buf, struct ext2b_dirent)
501         };
502     }
503
504     /*
505                    --- +--------+ ---
506                     ^  |  prev  |  |
507                     |  +--------+  |
508                     |              |   new_reclen
509                     |              |
510                     |              v
511                     |  +--------+ ---  -
512                     |  | dirent |  |   | size
513         old_reclen  |  +--------+  |   - 
514                     |              |   dirent.reclen
515                     |              |
516                     v              v
517                    --- +--------+ ---
518                        |  next  |
519                        +--------+
520     */
521
522     old_reclen -= new_reclen;
523     prev_dirent->rec_len = new_reclen;
524     fsblock_dirty(e_dno->prev.buf);
525
526 place_dir:
527     dirent->rec_len = ROUNDUP(old_reclen, sizeof(int));
528     memcpy(e_dno->self.dirent, dirent, size);
529     fsblock_dirty(e_dno->self.buf);
530
531     if (!e_dno_out) {
532         __destruct_ext2_dnode(e_dno);
533     }
534     else {
535         *e_dno_out = e_dno;
536     }
537
538     return errno;
539
540 failed:
541     __destruct_ext2_dnode(e_dno);
542     return errno;
543 }
544
545 int
546 ext2dr_remove(struct ext2_dnode* e_dno)
547 {
548     struct ext2_dnode_sub *dir_prev, *dir;    
549     assert(e_dno->prev.dirent);
550
551     dir_prev = &e_dno->prev;
552     dir = &e_dno->self;
553
554     dir_prev->dirent->rec_len += dir->dirent->rec_len;
555     dir->dirent->rec_len = 0;
556     dir->dirent->inode = 0;
557
558     fsblock_dirty(dir_prev->buf);
559     fsblock_dirty(dir->buf);
560
561     __destruct_ext2_dnode(e_dno);
562
563     return 0;
564 }
565
566 int
567 ext2_rmdir(struct v_inode* this, struct v_dnode* dnode)
568 {
569     int errno;
570     struct v_inode* self;
571     struct ext2_dnode* e_dno;
572
573     self = dnode->inode;
574     e_dno  = EXT2_DNO(dnode);
575
576     if (__check_special(dnode)) {
577         return EINVAL;
578     }
579
580     if (!__check_empty_dir(self)) {
581         return ENOTEMPTY;
582     }
583
584     if ((errno = ext2ino_free(self))) {
585         return errno;
586     }
587
588     return ext2dr_remove(e_dno);
589 }
590
591 static int
592 __d_insert(struct v_inode* parent, struct v_inode* self,
593            struct ext2b_dirent* dirent,
594            struct hstr* name, struct ext2_dnode** e_dno_out)
595 {
596     ext2dr_setup_dirent(dirent, self, name);
597     
598     dirent->inode = self->id;
599     return ext2dr_insert(parent, dirent, e_dno_out);
600 }
601
602 int
603 ext2_mkdir(struct v_inode* this, struct v_dnode* dnode)
604 {
605     int errno;
606     struct ext2_inode *e_contain, *e_created;
607     struct v_inode* i_created;
608     struct ext2_dnode* e_dno = NULL;
609     struct ext2b_dirent dirent;
610
611     e_contain = EXT2_INO(this);
612
613     errno = ext2ino_make(this->sb, VFS_IFDIR, e_contain, &i_created);
614     if (errno) {
615         return errno;
616     }
617
618     e_created = EXT2_INO(i_created);
619
620     if ((errno = __d_insert(this, i_created, &dirent, &dnode->name, &e_dno))) {
621         goto cleanup1;
622     }
623
624     // link the created dir inode to dirent
625     ext2ino_linkto(e_created, &dirent);
626     dnode->data = e_dno;
627
628     // insert . and ..
629     // we don't need ext2ino_linkto here.
630
631     if ((errno = __d_insert(i_created, i_created, &dirent, &vfs_dot, NULL))) {
632         goto cleanup;
633     }
634
635     if ((errno = __d_insert(i_created, this, &dirent, &vfs_ddot, NULL))) {
636         goto cleanup;
637     }
638
639     vfs_assign_inode(dnode, i_created);
640     return 0;
641
642 cleanup:
643     __destruct_ext2_dnode(e_dno);
644
645 cleanup1:
646     dnode->data = NULL;
647     ext2ino_free(i_created);
648     vfs_i_free(i_created);
649
650     return errno;
651 }
652
653 void
654 ext2dr_setup_dirent(struct ext2b_dirent* dirent, 
655                     struct v_inode* inode, struct hstr* name)
656 {
657     unsigned int imode;
658     
659     imode = EXT2_INO(inode)->ino->i_mode;
660     *dirent = (struct ext2b_dirent){
661         .name_len = name->len
662     };
663     
664     strncpy(dirent->name, name->value, name->len);
665
666     if (ext2_feature(inode->sb, FEAT_FILETYPE)) {
667         dirent->file_type = __imode_to_filetype(imode);
668     }
669 }
670
671 int
672 ext2_rename(struct v_inode* from_inode, struct v_dnode* from_dnode,
673             struct v_dnode* to_dnode)
674 {
675     int errno;
676     struct v_inode* to_parent;
677
678     if (EXT2_DNO(to_dnode)) {
679         errno = ext2_unlink(to_dnode->inode, to_dnode);
680         if (errno) {
681             return errno;
682         }
683     }
684
685     errno = ext2_link(from_inode, to_dnode);
686     if (errno) {
687         return errno;
688     }
689
690     return ext2_unlink(from_inode, from_dnode);
691 }