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

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