Modules/Ext2 - Bugfixing to 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, Uint Flags)
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         // TODO: Handle (Flags & VFS_IOFLAG_NOBLOCK)    
36
37         Ext2_int_ReadInode(disk, Node->Inode, &inode);
38         
39         // Get the ammount of space already allocated
40         // - Round size up to block size
41         // - block size is a power of two, so this will work
42         allocSize = (inode.i_size + disk->BlockSize-1) & ~(disk->BlockSize-1);
43         
44         // Are we writing to inside the allocated space?
45         if( Offset > allocSize )        return 0;
46         
47         if( Offset < allocSize )
48         {
49                 // Will we go out of it?
50                 if(Offset + Length > allocSize) {
51                         bNewBlocks = 1;
52                         retLen = allocSize - Offset;
53                 } else
54                         retLen = Length;
55                 
56                 // Within the allocated space
57                 block = Offset / disk->BlockSize;
58                 Offset %= disk->BlockSize;
59                 base = Ext2_int_GetBlockAddr(disk, inode.i_block, block);
60                 
61                 // Write only block (if only one)
62                 if(Offset + retLen <= disk->BlockSize) {
63                         VFS_WriteAt(disk->FD, base+Offset, retLen, Buffer);
64                         if(!bNewBlocks) return Length;
65                         goto addBlocks; // Ugh! A goto, but it seems unavoidable
66                 }
67                 
68                 // Write First Block
69                 VFS_WriteAt(disk->FD, base+Offset, disk->BlockSize-Offset, Buffer);
70                 Buffer += disk->BlockSize-Offset;
71                 retLen -= disk->BlockSize-Offset;
72                 block ++;
73                 
74                 // Write middle blocks
75                 while(retLen > disk->BlockSize)
76                 {
77                         base = Ext2_int_GetBlockAddr(disk, inode.i_block, block);
78                         VFS_WriteAt(disk->FD, base, disk->BlockSize, Buffer);
79                         Buffer += disk->BlockSize;
80                         retLen -= disk->BlockSize;
81                         block ++;
82                 }
83                 
84                 // Write last block
85                 base = Ext2_int_GetBlockAddr(disk, inode.i_block, block);
86                 VFS_WriteAt(disk->FD, base, retLen, Buffer);
87                 if(!bNewBlocks) return Length;  // Writing in only allocated space
88         }
89         else
90                 base = Ext2_int_GetBlockAddr(disk, inode.i_block, allocSize/disk->BlockSize-1);
91         
92 addBlocks:
93         Log_Notice("EXT2", "File extending is untested");
94         
95         // Allocate blocks and copy data to them
96         retLen = Length - (allocSize-Offset);
97         while( retLen > disk->BlockSize )
98         {
99                 // Allocate a block
100                 block = Ext2_int_AllocateBlock(disk, base/disk->BlockSize);
101                 if(!block)      return Length - retLen;
102                 // Add it to this inode
103                 if( Ext2_int_AppendBlock(disk, &inode, block) ) {
104                         Log_Warning("Ext2", "Appending %x to inode %p:%X failed",
105                                 block, disk, Node->Inode);
106                         Ext2_int_DeallocateBlock(disk, block);
107                         goto ret;
108                 }
109                 // Copy data to the node
110                 base = block * disk->BlockSize;
111                 VFS_WriteAt(disk->FD, base, disk->BlockSize, Buffer);
112                 // Update pointer and size remaining
113                 inode.i_size += disk->BlockSize;
114                 Buffer += disk->BlockSize;
115                 retLen -= disk->BlockSize;
116         }
117         // Last block :D
118         block = Ext2_int_AllocateBlock(disk, base/disk->BlockSize);
119         if(!block)      goto ret;
120         if( Ext2_int_AppendBlock(disk, &inode, block) ) {
121                 Log_Warning("Ext2", "Appending %x to inode %p:%X failed",
122                         block, disk, Node->Inode);
123                 Ext2_int_DeallocateBlock(disk, block);
124                 goto ret;
125         }
126         base = block * disk->BlockSize;
127         VFS_WriteAt(disk->FD, base, retLen, Buffer);
128         
129         // TODO: When should the size update be committed?
130         inode.i_size += retLen;
131         Node->Size += retLen;
132         Node->Flags |= VFS_FFLAG_DIRTY;
133         
134         retLen = 0;
135
136 ret:    // Makes sure the changes to the inode are committed
137         Ext2_int_WriteInode(disk, Node->Inode, &inode);
138         return Length - retLen;
139 }
140
141 /**
142  * \fn Uint32 Ext2_int_AllocateBlock(tExt2_Disk *Disk, Uint32 PrevBlock)
143  * \brief Allocate a block from the best possible location
144  * \param Disk  EXT2 Disk Information Structure
145  * \param PrevBlock     Previous block ID in the file
146  */
147 Uint32 Ext2_int_AllocateBlock(tExt2_Disk *Disk, Uint32 PrevBlock)
148 {
149          int    bpg = Disk->SuperBlock.s_blocks_per_group;
150         Uint    firstgroup = PrevBlock / bpg;
151         Uint    blockgroup = firstgroup;
152         tExt2_Group     *bg;
153
154         // TODO: Need to do locking on the bitmaps      
155
156         // Are there any free blocks?
157         if(Disk->SuperBlock.s_free_blocks_count == 0)
158                 return 0;
159
160         // First: Check the next block after `PrevBlock`
161          int    iblock = (PrevBlock + 1) % Disk->SuperBlock.s_blocks_per_group;
162         if( iblock != 0 && Disk->Groups[blockgroup].bg_free_blocks_count > 0 )
163         {
164                 LOG("Checking %i:%i", blockgroup, iblock);
165                 
166                 bg = &Disk->Groups[blockgroup];
167                 
168                 const int sector_size = 512;
169                 Uint8 buf[sector_size];
170                  int    byte = (iblock/8) % sector_size;
171                 Uint8   bit = 1 << (iblock % 8);
172                  int    ofs = (iblock/8) / sector_size * sector_size;
173                 byte %= sector_size;
174                 Uint64  vol_ofs = Disk->BlockSize*bg->bg_block_bitmap+ofs;
175                 VFS_ReadAt(Disk->FD, vol_ofs, sector_size, buf);
176
177                 LOG("buf@%llx[%i] = %02x (& %02x)", vol_ofs, byte, buf[byte], bit);
178         
179                 if( (buf[byte] & bit) == 0 )
180                 {
181                         // Free block - nice and contig allocation
182                         buf[byte] |= bit;
183                         VFS_WriteAt(Disk->FD, vol_ofs, sector_size, buf);
184
185                         bg->bg_free_blocks_count --;
186                         Disk->SuperBlock.s_free_blocks_count --;
187                         #if EXT2_UPDATE_WRITEBACK
188                         Ext2_int_UpdateSuperblock(Disk);
189                         #endif
190                         return PrevBlock + 1;
191                 }
192                 // Used... darnit
193                 // Fall through and search further
194         }
195
196         // Second: Search for a group with free blocks
197         while( blockgroup < Disk->GroupCount && Disk->Groups[blockgroup].bg_free_blocks_count == 0 )
198                 blockgroup ++;
199         if( Disk->Groups[blockgroup].bg_free_blocks_count == 0 )
200         {
201                 LOG("Roll over");
202                 blockgroup = 0;
203                 while( blockgroup < firstgroup && Disk->Groups[blockgroup].bg_free_blocks_count == 0 )
204                         blockgroup ++;
205         }
206         if( Disk->Groups[blockgroup].bg_free_blocks_count == 0 ) {
207                 Log_Notice("Ext2", "Ext2_int_AllocateBlock - Out of blockss on %p, but superblock says some free",
208                         Disk);
209                 return 0;
210         }
211         LOG("BG%i has free blocks", blockgroup);
212
213         // Search the bitmap for a free block
214         bg = &Disk->Groups[blockgroup]; 
215          int    ofs = 0;
216         do {
217                 const int sector_size = 512;
218                 Uint8 buf[sector_size];
219                 Uint64  vol_ofs = Disk->BlockSize*bg->bg_block_bitmap+ofs;
220                 VFS_ReadAt(Disk->FD, vol_ofs, sector_size, buf);
221
222                 int byte, bit;
223                 for( byte = 0; byte < sector_size && buf[byte] == 0xFF; byte ++ )
224                         ;
225                 if( byte < sector_size )
226                 {
227                         LOG("buf@%llx[%i] = %02x", vol_ofs, byte, buf[byte]);
228                         for( bit = 0; bit < 8 && buf[byte] & (1 << bit); bit ++)
229                                 ;
230                         ASSERT(bit != 8);
231                         buf[byte] |= 1 << bit;
232                         VFS_WriteAt(Disk->FD, vol_ofs, sector_size, buf);
233
234                         bg->bg_free_blocks_count --;
235                         Disk->SuperBlock.s_free_blocks_count --;
236
237                         #if EXT2_UPDATE_WRITEBACK
238                         Ext2_int_UpdateSuperblock(Disk);
239                         #endif
240
241                         Uint32  ret = blockgroup * Disk->SuperBlock.s_blocks_per_group + byte * 8 + bit;
242                         Log_Debug("Ext2", "Ext2_int_AllocateBlock - Allocated 0x%x", ret);
243                         return ret;
244                 }
245         } while(ofs < Disk->SuperBlock.s_blocks_per_group / 8);
246         
247         Log_Notice("Ext2", "Ext2_int_AllocateBlock - Out of block in group %p:%i but header reported free",
248                 Disk, blockgroup);
249         return 0;
250 }
251
252 /**
253  * \brief Deallocates a block
254  */
255 void Ext2_int_DeallocateBlock(tExt2_Disk *Disk, Uint32 Block)
256 {
257         Log_Warning("Ext2", "TODO: Impliment Ext2_int_DeallocateBlock");
258 }
259
260 /**
261  * \brief Append a block to an inode
262  */
263 int Ext2_int_AppendBlock(tExt2_Disk *Disk, tExt2_Inode *Inode, Uint32 Block)
264 {
265          int    nBlocks;
266          int    dwPerBlock = Disk->BlockSize / 4;
267         Uint32  *blocks;
268         Uint32  id1, id2;
269         
270         nBlocks = (Inode->i_size + Disk->BlockSize - 1) / Disk->BlockSize;
271         
272         // Direct Blocks
273         if( nBlocks < 12 ) {
274                 Inode->i_block[nBlocks] = Block;
275                 return 0;
276         }
277         
278         blocks = malloc( Disk->BlockSize );
279         if(!blocks)     return 1;
280         
281         nBlocks -= 12;
282         // Single Indirect
283         if( nBlocks < dwPerBlock)
284         {
285                 // Allocate/Get Indirect block
286                 if( nBlocks == 0 ) {
287                         Inode->i_block[12] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
288                         if( !Inode->i_block[12] ) {
289                                 Log_Warning("Ext2", "Allocating indirect block failed");
290                                 free(blocks);
291                                 return 1;
292                         }
293                         memset(blocks, 0, Disk->BlockSize); 
294                 }
295                 else
296                         VFS_ReadAt(Disk->FD, Inode->i_block[12]*Disk->BlockSize, Disk->BlockSize, blocks);
297                 
298                 blocks[nBlocks] = Block;
299                 
300                 VFS_WriteAt(Disk->FD, Inode->i_block[12]*Disk->BlockSize, Disk->BlockSize, blocks);
301                 free(blocks);
302                 return 0;
303         }
304         
305         nBlocks += dwPerBlock;
306         // Double Indirect
307         if( nBlocks < dwPerBlock*dwPerBlock )
308         {
309                 // Allocate/Get Indirect block
310                 if( nBlocks == 0 ) {
311                         Inode->i_block[13] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
312                         if( !Inode->i_block[13] ) {
313                                 Log_Warning("Ext2", "Allocating double indirect block failed");
314                                 free(blocks);
315                                 return 1;
316                         }
317                         memset(blocks, 0, Disk->BlockSize);
318                 }
319                 else
320                         VFS_ReadAt(Disk->FD, Inode->i_block[13]*Disk->BlockSize, Disk->BlockSize, blocks);
321                 
322                 // Allocate / Get Indirect lvl2 Block
323                 if( nBlocks % dwPerBlock == 0 ) {
324                         id1 = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
325                         if( !id1 ) {
326                                 free(blocks);
327                                 Log_Warning("Ext2", "Allocating double indirect block (l2) failed");
328                                 return 1;
329                         }
330                         blocks[nBlocks/dwPerBlock] = id1;
331                         // Write back indirect 1 block
332                         VFS_WriteAt(Disk->FD, Inode->i_block[13]*Disk->BlockSize, Disk->BlockSize, blocks);
333                         memset(blocks, 0, Disk->BlockSize);
334                 }
335                 else {
336                         id1 = blocks[nBlocks / dwPerBlock];
337                         VFS_ReadAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
338                 }
339                 
340                 blocks[nBlocks % dwPerBlock] = Block;
341                 
342                 VFS_WriteAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
343                 free(blocks);
344                 return 0;
345         }
346         
347         nBlocks -= dwPerBlock*dwPerBlock;
348         // Triple Indirect
349         if( nBlocks < dwPerBlock*dwPerBlock*dwPerBlock )
350         {
351                 // Allocate/Get Indirect block
352                 if( nBlocks == 0 ) {
353                         Inode->i_block[14] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
354                         if( !Inode->i_block[14] ) {
355                                 Log_Warning("Ext2", "Allocating triple indirect block failed");
356                                 free(blocks);
357                                 return 1;
358                         }
359                         memset(blocks, 0, Disk->BlockSize);
360                 }
361                 else
362                         VFS_ReadAt(Disk->FD, Inode->i_block[14]*Disk->BlockSize, Disk->BlockSize, blocks);
363                 
364                 // Allocate / Get Indirect lvl2 Block
365                 if( (nBlocks/dwPerBlock) % dwPerBlock == 0 && nBlocks % dwPerBlock == 0 )
366                 {
367                         id1 = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
368                         if( !id1 ) {
369                                 Log_Warning("Ext2", "Allocating triple indirect block (l2) failed");
370                                 free(blocks);
371                                 return 1;
372                         }
373                         blocks[nBlocks/dwPerBlock] = id1;
374                         // Write back indirect 1 block
375                         VFS_WriteAt(Disk->FD, Inode->i_block[14]*Disk->BlockSize, Disk->BlockSize, blocks);
376                         memset(blocks, 0, Disk->BlockSize);
377                 }
378                 else {
379                         id1 = blocks[nBlocks / (dwPerBlock*dwPerBlock)];
380                         VFS_ReadAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
381                 }
382                 
383                 // Allocate / Get Indirect Level 3 Block
384                 if( nBlocks % dwPerBlock == 0 ) {
385                         id2 = Ext2_int_AllocateBlock(Disk, id1);
386                         if( !id2 ) {
387                                 Log_Warning("Ext2", "Allocating triple indirect block (l3) failed");
388                                 free(blocks);
389                                 return 1;
390                         }
391                         blocks[(nBlocks/dwPerBlock)%dwPerBlock] = id2;
392                         // Write back indirect 1 block
393                         VFS_WriteAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
394                         memset(blocks, 0, Disk->BlockSize);
395                 }
396                 else {
397                         id2 = blocks[(nBlocks/dwPerBlock)%dwPerBlock];
398                         VFS_ReadAt(Disk->FD, id2*Disk->BlockSize, Disk->BlockSize, blocks);
399                 }
400                 
401                 blocks[nBlocks % dwPerBlock] = Block;
402                 
403                 VFS_WriteAt(Disk->FD, id2*Disk->BlockSize, Disk->BlockSize, blocks);
404                 free(blocks);
405                 return 0;
406         }
407         
408         Log_Warning("Ext2", "Inode ?? cannot have a block appended to it, all indirects used");
409         free(blocks);
410         return 1;
411 }

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