Modules/EXT2 - Working on write support
[tpg/acess2.git] / KernelLand / Modules / Filesystems / Ext2 / write.c
1 /*
2  * Acess OS
3  * Ext2 Driver Version 1
4  */
5 /**
6  * \file write.c
7  * \brief Second Extended Filesystem Driver
8  * \todo Implement file full write support
9  */
10 #define DEBUG   1
11 #define VERBOSE 0
12 #include "ext2_common.h"
13
14 // === PROTOYPES ===
15 Uint32          Ext2_int_AllocateBlock(tExt2_Disk *Disk, Uint32 PrevBlock);
16 void    Ext2_int_DeallocateBlock(tExt2_Disk *Disk, Uint32 Block);
17  int    Ext2_int_AppendBlock(tExt2_Disk *Disk, tExt2_Inode *Inode, Uint32 Block);
18
19 // === CODE ===
20 /**
21  * \brief Write to a file
22  */
23 size_t Ext2_Write(tVFS_Node *Node, off_t Offset, size_t Length, const void *Buffer)
24 {
25         tExt2_Disk      *disk = Node->ImplPtr;
26         tExt2_Inode     inode;
27         Uint64  base;
28         Uint64  retLen;
29         Uint    block;
30         Uint64  allocSize;
31          int    bNewBlocks = 0;
32         
33         Debug_HexDump("Ext2_Write", Buffer, Length);
34         
35         Ext2_int_ReadInode(disk, Node->Inode, &inode);
36         
37         // Get the ammount of space already allocated
38         // - Round size up to block size
39         // - block size is a power of two, so this will work
40         allocSize = (inode.i_size + disk->BlockSize-1) & ~(disk->BlockSize-1);
41         
42         // Are we writing to inside the allocated space?
43         if( Offset > allocSize )        return 0;
44         
45         if( Offset < allocSize )
46         {
47                 // Will we go out of it?
48                 if(Offset + Length > allocSize) {
49                         bNewBlocks = 1;
50                         retLen = allocSize - Offset;
51                 } else
52                         retLen = Length;
53                 
54                 // Within the allocated space
55                 block = Offset / disk->BlockSize;
56                 Offset %= disk->BlockSize;
57                 base = Ext2_int_GetBlockAddr(disk, inode.i_block, block);
58                 
59                 // Write only block (if only one)
60                 if(Offset + retLen <= disk->BlockSize) {
61                         VFS_WriteAt(disk->FD, base+Offset, retLen, Buffer);
62                         if(!bNewBlocks) return Length;
63                         goto addBlocks; // Ugh! A goto, but it seems unavoidable
64                 }
65                 
66                 // Write First Block
67                 VFS_WriteAt(disk->FD, base+Offset, disk->BlockSize-Offset, Buffer);
68                 Buffer += disk->BlockSize-Offset;
69                 retLen -= disk->BlockSize-Offset;
70                 block ++;
71                 
72                 // Write middle blocks
73                 while(retLen > disk->BlockSize)
74                 {
75                         base = Ext2_int_GetBlockAddr(disk, inode.i_block, block);
76                         VFS_WriteAt(disk->FD, base, disk->BlockSize, Buffer);
77                         Buffer += disk->BlockSize;
78                         retLen -= disk->BlockSize;
79                         block ++;
80                 }
81                 
82                 // Write last block
83                 base = Ext2_int_GetBlockAddr(disk, inode.i_block, block);
84                 VFS_WriteAt(disk->FD, base, retLen, Buffer);
85                 if(!bNewBlocks) return Length;  // Writing in only allocated space
86         }
87         else
88                 base = Ext2_int_GetBlockAddr(disk, inode.i_block, allocSize/disk->BlockSize-1);
89         
90 addBlocks:
91         Log_Notice("EXT2", "File extending is untested");
92         
93         // Allocate blocks and copy data to them
94         retLen = Length - (allocSize-Offset);
95         while( retLen > disk->BlockSize )
96         {
97                 // Allocate a block
98                 block = Ext2_int_AllocateBlock(disk, base/disk->BlockSize);
99                 if(!block)      return Length - retLen;
100                 // Add it to this inode
101                 if( !Ext2_int_AppendBlock(disk, &inode, block) ) {
102                         Ext2_int_DeallocateBlock(disk, block);
103                         goto ret;
104                 }
105                 // Copy data to the node
106                 base = block * disk->BlockSize;
107                 VFS_WriteAt(disk->FD, base, disk->BlockSize, Buffer);
108                 // Update pointer and size remaining
109                 inode.i_size += disk->BlockSize;
110                 Buffer += disk->BlockSize;
111                 retLen -= disk->BlockSize;
112         }
113         // Last block :D
114         block = Ext2_int_AllocateBlock(disk, base/disk->BlockSize);
115         if(!block)      goto ret;
116         if( !Ext2_int_AppendBlock(disk, &inode, block) ) {
117                 Ext2_int_DeallocateBlock(disk, block);
118                 goto ret;
119         }
120         base = block * disk->BlockSize;
121         VFS_WriteAt(disk->FD, base, retLen, Buffer);
122         
123         // TODO: When should the size update be committed?
124         inode.i_size += retLen;
125         Node->Size += retLen;
126         Node->Flags |= VFS_FFLAG_DIRTY;
127         
128         retLen = 0;
129
130 ret:    // Makes sure the changes to the inode are committed
131         Ext2_int_WriteInode(disk, Node->Inode, &inode);
132         return Length - retLen;
133 }
134
135 /**
136  * \fn Uint32 Ext2_int_AllocateBlock(tExt2_Disk *Disk, Uint32 PrevBlock)
137  * \brief Allocate a block from the best possible location
138  * \param Disk  EXT2 Disk Information Structure
139  * \param PrevBlock     Previous block ID in the file
140  */
141 Uint32 Ext2_int_AllocateBlock(tExt2_Disk *Disk, Uint32 PrevBlock)
142 {
143          int    bpg = Disk->SuperBlock.s_blocks_per_group;
144         Uint    blockgroup = PrevBlock / bpg;
145         Uint    bitmap[Disk->BlockSize/sizeof(Uint)];
146         Uint    bitsperblock = 8*Disk->BlockSize;
147          int    i, j = 0;
148         Uint    block;
149         
150         // Are there any free blocks?
151         if(Disk->SuperBlock.s_free_blocks_count == 0)   return 0;
152         
153         if(Disk->Groups[blockgroup].bg_free_blocks_count > 0)
154         {
155                 // Search block group's bitmap
156                 for(i = 0; i < bpg; i++)
157                 {
158                         // Get the block in the bitmap block
159                         j = i & (bitsperblock-1);
160                         
161                         // Read in if needed
162                         if(j == 0) {
163                                 VFS_ReadAt(
164                                         Disk->FD,
165                                         (Uint64)Disk->Groups[blockgroup].bg_block_bitmap + i / bitsperblock,
166                                         Disk->BlockSize,
167                                         bitmap
168                                         );
169                         }
170                         
171                         // Fast Check
172                         if( bitmap[j/32] == 0xFFFFFFFF ) {
173                                 j = (j + 31) & ~31;
174                                 continue;
175                         }
176                         
177                         // Is the bit set?
178                         if( bitmap[j/32] & (1 << (j%32)) )
179                                 continue;
180                         
181                         // Ooh! We found one
182                         break;
183                 }
184                 if( i < bpg ) {
185                         Warning("[EXT2 ] Inconsistency detected, Group Free Block count is non-zero when no free blocks exist");
186                         goto    checkAll;       // Search the entire filesystem for a free block
187                         // Goto needed for neatness
188                 }
189                 
190                 // Mark as used
191                 bitmap[j/32] |= (1 << (j%32));
192                 VFS_WriteAt(
193                         Disk->FD,
194                         (Uint64)Disk->Groups[blockgroup].bg_block_bitmap + i / bitsperblock,
195                         Disk->BlockSize,
196                         bitmap
197                         );
198                 block = i;
199                 Disk->Groups[blockgroup].bg_free_blocks_count --;
200                 #if EXT2_UPDATE_WRITEBACK
201                 //Ext2_int_UpdateBlockGroup(Disk, blockgroup);
202                 #endif
203         }
204         else
205         {
206         checkAll:
207                 Log_Warning("EXT2", "TODO - Implement using blocks outside the current block group");
208                 return 0;
209         }
210         
211         // Reduce global count
212         Disk->SuperBlock.s_free_blocks_count --;
213         #if EXT2_UPDATE_WRITEBACK
214         Ext2_int_UpdateSuperblock(Disk);
215         #endif
216         
217         return block;
218 }
219
220 /**
221  * \brief Deallocates a block
222  */
223 void Ext2_int_DeallocateBlock(tExt2_Disk *Disk, Uint32 Block)
224 {
225 }
226
227 /**
228  * \brief Append a block to an inode
229  */
230 int Ext2_int_AppendBlock(tExt2_Disk *Disk, tExt2_Inode *Inode, Uint32 Block)
231 {
232          int    nBlocks;
233          int    dwPerBlock = Disk->BlockSize / 4;
234         Uint32  *blocks;
235         Uint32  id1, id2;
236         
237         nBlocks = (Inode->i_size + Disk->BlockSize - 1) / Disk->BlockSize;
238         
239         // Direct Blocks
240         if( nBlocks < 12 ) {
241                 Inode->i_block[nBlocks] = Block;
242                 return 0;
243         }
244         
245         blocks = malloc( Disk->BlockSize );
246         if(!blocks)     return 1;
247         
248         nBlocks -= 12;
249         // Single Indirect
250         if( nBlocks < dwPerBlock)
251         {
252                 // Allocate/Get Indirect block
253                 if( nBlocks == 0 ) {
254                         Inode->i_block[12] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
255                         if( !Inode->i_block[12] ) {
256                                 free(blocks);
257                                 return 1;
258                         }
259                         memset(blocks, 0, Disk->BlockSize); 
260                 }
261                 else
262                         VFS_ReadAt(Disk->FD, Inode->i_block[12]*Disk->BlockSize, Disk->BlockSize, blocks);
263                 
264                 blocks[nBlocks] = Block;
265                 
266                 VFS_WriteAt(Disk->FD, Inode->i_block[12]*Disk->BlockSize, Disk->BlockSize, blocks);
267                 free(blocks);
268                 return 0;
269         }
270         
271         nBlocks += dwPerBlock;
272         // Double Indirect
273         if( nBlocks < dwPerBlock*dwPerBlock )
274         {
275                 // Allocate/Get Indirect block
276                 if( nBlocks == 0 ) {
277                         Inode->i_block[13] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
278                         if( !Inode->i_block[13] ) {
279                                 free(blocks);
280                                 return 1;
281                         }
282                         memset(blocks, 0, Disk->BlockSize);
283                 }
284                 else
285                         VFS_ReadAt(Disk->FD, Inode->i_block[13]*Disk->BlockSize, Disk->BlockSize, blocks);
286                 
287                 // Allocate / Get Indirect lvl2 Block
288                 if( nBlocks % dwPerBlock == 0 ) {
289                         id1 = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
290                         if( !id1 ) {
291                                 free(blocks);
292                                 return 1;
293                         }
294                         blocks[nBlocks/dwPerBlock] = id1;
295                         // Write back indirect 1 block
296                         VFS_WriteAt(Disk->FD, Inode->i_block[13]*Disk->BlockSize, Disk->BlockSize, blocks);
297                         memset(blocks, 0, Disk->BlockSize);
298                 }
299                 else {
300                         id1 = blocks[nBlocks / dwPerBlock];
301                         VFS_ReadAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
302                 }
303                 
304                 blocks[nBlocks % dwPerBlock] = Block;
305                 
306                 VFS_WriteAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
307                 free(blocks);
308                 return 0;
309         }
310         
311         nBlocks -= dwPerBlock*dwPerBlock;
312         // Triple Indirect
313         if( nBlocks < dwPerBlock*dwPerBlock*dwPerBlock )
314         {
315                 // Allocate/Get Indirect block
316                 if( nBlocks == 0 ) {
317                         Inode->i_block[14] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
318                         if( !Inode->i_block[14] ) {
319                                 free(blocks);
320                                 return 1;
321                         }
322                         memset(blocks, 0, Disk->BlockSize);
323                 }
324                 else
325                         VFS_ReadAt(Disk->FD, Inode->i_block[14]*Disk->BlockSize, Disk->BlockSize, blocks);
326                 
327                 // Allocate / Get Indirect lvl2 Block
328                 if( (nBlocks/dwPerBlock) % dwPerBlock == 0 && nBlocks % dwPerBlock == 0 )
329                 {
330                         id1 = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
331                         if( !id1 ) {
332                                 free(blocks);
333                                 return 1;
334                         }
335                         blocks[nBlocks/dwPerBlock] = id1;
336                         // Write back indirect 1 block
337                         VFS_WriteAt(Disk->FD, Inode->i_block[14]*Disk->BlockSize, Disk->BlockSize, blocks);
338                         memset(blocks, 0, Disk->BlockSize);
339                 }
340                 else {
341                         id1 = blocks[nBlocks / (dwPerBlock*dwPerBlock)];
342                         VFS_ReadAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
343                 }
344                 
345                 // Allocate / Get Indirect Level 3 Block
346                 if( nBlocks % dwPerBlock == 0 ) {
347                         id2 = Ext2_int_AllocateBlock(Disk, id1);
348                         if( !id2 ) {
349                                 free(blocks);
350                                 return 1;
351                         }
352                         blocks[(nBlocks/dwPerBlock)%dwPerBlock] = id2;
353                         // Write back indirect 1 block
354                         VFS_WriteAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
355                         memset(blocks, 0, Disk->BlockSize);
356                 }
357                 else {
358                         id2 = blocks[(nBlocks/dwPerBlock)%dwPerBlock];
359                         VFS_ReadAt(Disk->FD, id2*Disk->BlockSize, Disk->BlockSize, blocks);
360                 }
361                 
362                 blocks[nBlocks % dwPerBlock] = Block;
363                 
364                 VFS_WriteAt(Disk->FD, id2*Disk->BlockSize, Disk->BlockSize, blocks);
365                 free(blocks);
366                 return 0;
367         }
368         
369         Warning("[EXT2 ] Inode %i cannot have a block appended to it, all indirects used");
370         free(blocks);
371         return 1;
372 }

UCC git Repository :: git.ucc.asn.au