Kernel - Update VFS API to use off_t/size_t instead of Uint64
[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         inode.i_size += retLen;
123         retLen = 0;
124
125 ret:    // Makes sure the changes to the inode are committed
126         Ext2_int_WriteInode(disk, Node->Inode, &inode);
127         return Length - retLen;
128 }
129
130 /**
131  * \fn Uint32 Ext2_int_AllocateBlock(tExt2_Disk *Disk, Uint32 PrevBlock)
132  * \brief Allocate a block from the best possible location
133  * \param Disk  EXT2 Disk Information Structure
134  * \param PrevBlock     Previous block ID in the file
135  */
136 Uint32 Ext2_int_AllocateBlock(tExt2_Disk *Disk, Uint32 PrevBlock)
137 {
138          int    bpg = Disk->SuperBlock.s_blocks_per_group;
139         Uint    blockgroup = PrevBlock / bpg;
140         Uint    bitmap[Disk->BlockSize/sizeof(Uint)];
141         Uint    bitsperblock = 8*Disk->BlockSize;
142          int    i, j = 0;
143         Uint    block;
144         
145         // Are there any free blocks?
146         if(Disk->SuperBlock.s_free_blocks_count == 0)   return 0;
147         
148         if(Disk->Groups[blockgroup].bg_free_blocks_count > 0)
149         {
150                 // Search block group's bitmap
151                 for(i = 0; i < bpg; i++)
152                 {
153                         // Get the block in the bitmap block
154                         j = i & (bitsperblock-1);
155                         
156                         // Read in if needed
157                         if(j == 0) {
158                                 VFS_ReadAt(
159                                         Disk->FD,
160                                         (Uint64)Disk->Groups[blockgroup].bg_block_bitmap + i / bitsperblock,
161                                         Disk->BlockSize,
162                                         bitmap
163                                         );
164                         }
165                         
166                         // Fast Check
167                         if( bitmap[j/32] == 0xFFFFFFFF ) {
168                                 j = (j + 31) & ~31;
169                                 continue;
170                         }
171                         
172                         // Is the bit set?
173                         if( bitmap[j/32] & (1 << (j%32)) )
174                                 continue;
175                         
176                         // Ooh! We found one
177                         break;
178                 }
179                 if( i < bpg ) {
180                         Warning("[EXT2 ] Inconsistency detected, Group Free Block count is non-zero when no free blocks exist");
181                         goto    checkAll;       // Search the entire filesystem for a free block
182                         // Goto needed for neatness
183                 }
184                 
185                 // Mark as used
186                 bitmap[j/32] |= (1 << (j%32));
187                 VFS_WriteAt(
188                         Disk->FD,
189                         (Uint64)Disk->Groups[blockgroup].bg_block_bitmap + i / bitsperblock,
190                         Disk->BlockSize,
191                         bitmap
192                         );
193                 block = i;
194                 Disk->Groups[blockgroup].bg_free_blocks_count --;
195                 #if EXT2_UPDATE_WRITEBACK
196                 //Ext2_int_UpdateBlockGroup(Disk, blockgroup);
197                 #endif
198         }
199         else
200         {
201         checkAll:
202                 Log_Warning("EXT2", "TODO - Implement using blocks outside the current block group");
203                 return 0;
204         }
205         
206         // Reduce global count
207         Disk->SuperBlock.s_free_blocks_count --;
208         #if EXT2_UPDATE_WRITEBACK
209         Ext2_int_UpdateSuperblock(Disk);
210         #endif
211         
212         return block;
213 }
214
215 /**
216  * \brief Deallocates a block
217  */
218 void Ext2_int_DeallocateBlock(tExt2_Disk *Disk, Uint32 Block)
219 {
220 }
221
222 /**
223  * \brief Append a block to an inode
224  */
225 int Ext2_int_AppendBlock(tExt2_Disk *Disk, tExt2_Inode *Inode, Uint32 Block)
226 {
227          int    nBlocks;
228          int    dwPerBlock = Disk->BlockSize / 4;
229         Uint32  *blocks;
230         Uint32  id1, id2;
231         
232         nBlocks = (Inode->i_size + Disk->BlockSize - 1) / Disk->BlockSize;
233         
234         // Direct Blocks
235         if( nBlocks < 12 ) {
236                 Inode->i_block[nBlocks] = Block;
237                 return 0;
238         }
239         
240         blocks = malloc( Disk->BlockSize );
241         if(!blocks)     return 1;
242         
243         nBlocks -= 12;
244         // Single Indirect
245         if( nBlocks < dwPerBlock)
246         {
247                 // Allocate/Get Indirect block
248                 if( nBlocks == 0 ) {
249                         Inode->i_block[12] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
250                         if( !Inode->i_block[12] ) {
251                                 free(blocks);
252                                 return 1;
253                         }
254                         memset(blocks, 0, Disk->BlockSize); 
255                 }
256                 else
257                         VFS_ReadAt(Disk->FD, Inode->i_block[12]*Disk->BlockSize, Disk->BlockSize, blocks);
258                 
259                 blocks[nBlocks] = Block;
260                 
261                 VFS_WriteAt(Disk->FD, Inode->i_block[12]*Disk->BlockSize, Disk->BlockSize, blocks);
262                 free(blocks);
263                 return 0;
264         }
265         
266         nBlocks += dwPerBlock;
267         // Double Indirect
268         if( nBlocks < dwPerBlock*dwPerBlock )
269         {
270                 // Allocate/Get Indirect block
271                 if( nBlocks == 0 ) {
272                         Inode->i_block[13] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
273                         if( !Inode->i_block[13] ) {
274                                 free(blocks);
275                                 return 1;
276                         }
277                         memset(blocks, 0, Disk->BlockSize);
278                 }
279                 else
280                         VFS_ReadAt(Disk->FD, Inode->i_block[13]*Disk->BlockSize, Disk->BlockSize, blocks);
281                 
282                 // Allocate / Get Indirect lvl2 Block
283                 if( nBlocks % dwPerBlock == 0 ) {
284                         id1 = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
285                         if( !id1 ) {
286                                 free(blocks);
287                                 return 1;
288                         }
289                         blocks[nBlocks/dwPerBlock] = id1;
290                         // Write back indirect 1 block
291                         VFS_WriteAt(Disk->FD, Inode->i_block[13]*Disk->BlockSize, Disk->BlockSize, blocks);
292                         memset(blocks, 0, Disk->BlockSize);
293                 }
294                 else {
295                         id1 = blocks[nBlocks / dwPerBlock];
296                         VFS_ReadAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
297                 }
298                 
299                 blocks[nBlocks % dwPerBlock] = Block;
300                 
301                 VFS_WriteAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
302                 free(blocks);
303                 return 0;
304         }
305         
306         nBlocks -= dwPerBlock*dwPerBlock;
307         // Triple Indirect
308         if( nBlocks < dwPerBlock*dwPerBlock*dwPerBlock )
309         {
310                 // Allocate/Get Indirect block
311                 if( nBlocks == 0 ) {
312                         Inode->i_block[14] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
313                         if( !Inode->i_block[14] ) {
314                                 free(blocks);
315                                 return 1;
316                         }
317                         memset(blocks, 0, Disk->BlockSize);
318                 }
319                 else
320                         VFS_ReadAt(Disk->FD, Inode->i_block[14]*Disk->BlockSize, Disk->BlockSize, blocks);
321                 
322                 // Allocate / Get Indirect lvl2 Block
323                 if( (nBlocks/dwPerBlock) % dwPerBlock == 0 && nBlocks % dwPerBlock == 0 )
324                 {
325                         id1 = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
326                         if( !id1 ) {
327                                 free(blocks);
328                                 return 1;
329                         }
330                         blocks[nBlocks/dwPerBlock] = id1;
331                         // Write back indirect 1 block
332                         VFS_WriteAt(Disk->FD, Inode->i_block[14]*Disk->BlockSize, Disk->BlockSize, blocks);
333                         memset(blocks, 0, Disk->BlockSize);
334                 }
335                 else {
336                         id1 = blocks[nBlocks / (dwPerBlock*dwPerBlock)];
337                         VFS_ReadAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
338                 }
339                 
340                 // Allocate / Get Indirect Level 3 Block
341                 if( nBlocks % dwPerBlock == 0 ) {
342                         id2 = Ext2_int_AllocateBlock(Disk, id1);
343                         if( !id2 ) {
344                                 free(blocks);
345                                 return 1;
346                         }
347                         blocks[(nBlocks/dwPerBlock)%dwPerBlock] = id2;
348                         // Write back indirect 1 block
349                         VFS_WriteAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
350                         memset(blocks, 0, Disk->BlockSize);
351                 }
352                 else {
353                         id2 = blocks[(nBlocks/dwPerBlock)%dwPerBlock];
354                         VFS_ReadAt(Disk->FD, id2*Disk->BlockSize, Disk->BlockSize, blocks);
355                 }
356                 
357                 blocks[nBlocks % dwPerBlock] = Block;
358                 
359                 VFS_WriteAt(Disk->FD, id2*Disk->BlockSize, Disk->BlockSize, blocks);
360                 free(blocks);
361                 return 0;
362         }
363         
364         Warning("[EXT2 ] Inode %i cannot have a block appended to it, all indirects used");
365         free(blocks);
366         return 1;
367 }

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