Usermode/libposix - Misc
[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                         Log_Warning("Ext2", "Appending %x to inode %p:%X failed",
103                                 block, disk, Node->Inode);
104                         Ext2_int_DeallocateBlock(disk, block);
105                         goto ret;
106                 }
107                 // Copy data to the node
108                 base = block * disk->BlockSize;
109                 VFS_WriteAt(disk->FD, base, disk->BlockSize, Buffer);
110                 // Update pointer and size remaining
111                 inode.i_size += disk->BlockSize;
112                 Buffer += disk->BlockSize;
113                 retLen -= disk->BlockSize;
114         }
115         // Last block :D
116         block = Ext2_int_AllocateBlock(disk, base/disk->BlockSize);
117         if(!block)      goto ret;
118         if( Ext2_int_AppendBlock(disk, &inode, block) ) {
119                 Log_Warning("Ext2", "Appending %x to inode %p:%X failed",
120                         block, disk, Node->Inode);
121                 Ext2_int_DeallocateBlock(disk, block);
122                 goto ret;
123         }
124         base = block * disk->BlockSize;
125         VFS_WriteAt(disk->FD, base, retLen, Buffer);
126         
127         // TODO: When should the size update be committed?
128         inode.i_size += retLen;
129         Node->Size += retLen;
130         Node->Flags |= VFS_FFLAG_DIRTY;
131         
132         retLen = 0;
133
134 ret:    // Makes sure the changes to the inode are committed
135         Ext2_int_WriteInode(disk, Node->Inode, &inode);
136         return Length - retLen;
137 }
138
139 /**
140  * \fn Uint32 Ext2_int_AllocateBlock(tExt2_Disk *Disk, Uint32 PrevBlock)
141  * \brief Allocate a block from the best possible location
142  * \param Disk  EXT2 Disk Information Structure
143  * \param PrevBlock     Previous block ID in the file
144  */
145 Uint32 Ext2_int_AllocateBlock(tExt2_Disk *Disk, Uint32 PrevBlock)
146 {
147          int    bpg = Disk->SuperBlock.s_blocks_per_group;
148         Uint    firstgroup = PrevBlock / bpg;
149         Uint    blockgroup = firstgroup;
150         tExt2_Group     *bg;
151
152         // TODO: Need to do locking on the bitmaps      
153
154         // Are there any free blocks?
155         if(Disk->SuperBlock.s_free_blocks_count == 0)
156                 return 0;
157
158         // First: Check the next block after \a PrevBlock
159         if( (PrevBlock + 1) % Disk->SuperBlock.s_blocks_per_group != 0
160          && Disk->Groups[blockgroup].bg_free_blocks_count > 0 )
161         {
162                 bg = &Disk->Groups[blockgroup];
163                 const int sector_size = 512;
164                 Uint8 buf[sector_size];
165                  int    iblock = (PrevBlock + 1) % Disk->SuperBlock.s_blocks_per_group;
166                  int    byte = iblock / 8;
167                  int    ofs = byte / sector_size * sector_size;
168                 byte %= sector_size;
169                 VFS_ReadAt(Disk->FD, Disk->BlockSize*bg->bg_block_bitmap+ofs, sector_size, buf);
170                 
171                 if( (buf[byte] & (1 << (iblock%8))) == 0 )
172                 {
173                         // Free block - nice and contig allocation
174                         buf[byte] |= (1 << (iblock%8));
175                         VFS_WriteAt(Disk->FD, Disk->BlockSize*bg->bg_block_bitmap+ofs, sector_size, buf);
176
177                         bg->bg_free_blocks_count --;
178                         Disk->SuperBlock.s_free_blocks_count --;
179                         #if EXT2_UPDATE_WRITEBACK
180                         Ext2_int_UpdateSuperblock(Disk);
181                         #endif
182                         return PrevBlock + 1;
183                 }
184                 // Used... darnit
185                 // Fall through and search further
186         }
187
188         // Second: Search for a group with free blocks
189         while( blockgroup < Disk->GroupCount && Disk->Groups[blockgroup].bg_free_blocks_count == 0 )
190                 blockgroup ++;
191         if( Disk->Groups[blockgroup].bg_free_blocks_count == 0 )
192         {
193                 blockgroup = 0;
194                 while( blockgroup < firstgroup && Disk->Groups[blockgroup].bg_free_blocks_count == 0 )
195                         blockgroup ++;
196         }
197         if( Disk->Groups[blockgroup].bg_free_blocks_count == 0 ) {
198                 Log_Notice("Ext2", "Ext2_int_AllocateBlock - Out of blockss on %p, but superblock says some free",
199                         Disk);
200                 return 0;
201         }
202
203         // Search the bitmap for a free block
204         bg = &Disk->Groups[blockgroup]; 
205          int    ofs = 0;
206         do {
207                 const int sector_size = 512;
208                 Uint8 buf[sector_size];
209                 VFS_ReadAt(Disk->FD, Disk->BlockSize*bg->bg_block_bitmap+ofs, sector_size, buf);
210
211                 int byte, bit;
212                 for( byte = 0; byte < sector_size && buf[byte] != 0xFF; byte ++ )
213                         ;
214                 if( byte < sector_size )
215                 {
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, Disk->BlockSize*bg->bg_block_bitmap+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_Debug("Ext2", "Ext2_int_AllocateBlock - 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(tExt2_Disk *Disk, tExt2_Inode *Inode, Uint32 Block)
252 {
253          int    nBlocks;
254          int    dwPerBlock = Disk->BlockSize / 4;
255         Uint32  *blocks;
256         Uint32  id1, id2;
257         
258         nBlocks = (Inode->i_size + Disk->BlockSize - 1) / Disk->BlockSize;
259         
260         // Direct Blocks
261         if( nBlocks < 12 ) {
262                 Inode->i_block[nBlocks] = Block;
263                 return 0;
264         }
265         
266         blocks = malloc( Disk->BlockSize );
267         if(!blocks)     return 1;
268         
269         nBlocks -= 12;
270         // Single Indirect
271         if( nBlocks < dwPerBlock)
272         {
273                 // Allocate/Get Indirect block
274                 if( nBlocks == 0 ) {
275                         Inode->i_block[12] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
276                         if( !Inode->i_block[12] ) {
277                                 Log_Warning("Ext2", "Allocating indirect block failed");
278                                 free(blocks);
279                                 return 1;
280                         }
281                         memset(blocks, 0, Disk->BlockSize); 
282                 }
283                 else
284                         VFS_ReadAt(Disk->FD, Inode->i_block[12]*Disk->BlockSize, Disk->BlockSize, blocks);
285                 
286                 blocks[nBlocks] = Block;
287                 
288                 VFS_WriteAt(Disk->FD, Inode->i_block[12]*Disk->BlockSize, Disk->BlockSize, blocks);
289                 free(blocks);
290                 return 0;
291         }
292         
293         nBlocks += dwPerBlock;
294         // Double Indirect
295         if( nBlocks < dwPerBlock*dwPerBlock )
296         {
297                 // Allocate/Get Indirect block
298                 if( nBlocks == 0 ) {
299                         Inode->i_block[13] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
300                         if( !Inode->i_block[13] ) {
301                                 Log_Warning("Ext2", "Allocating double indirect block failed");
302                                 free(blocks);
303                                 return 1;
304                         }
305                         memset(blocks, 0, Disk->BlockSize);
306                 }
307                 else
308                         VFS_ReadAt(Disk->FD, Inode->i_block[13]*Disk->BlockSize, Disk->BlockSize, blocks);
309                 
310                 // Allocate / Get Indirect lvl2 Block
311                 if( nBlocks % dwPerBlock == 0 ) {
312                         id1 = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
313                         if( !id1 ) {
314                                 free(blocks);
315                                 Log_Warning("Ext2", "Allocating double indirect block (l2) failed");
316                                 return 1;
317                         }
318                         blocks[nBlocks/dwPerBlock] = id1;
319                         // Write back indirect 1 block
320                         VFS_WriteAt(Disk->FD, Inode->i_block[13]*Disk->BlockSize, Disk->BlockSize, blocks);
321                         memset(blocks, 0, Disk->BlockSize);
322                 }
323                 else {
324                         id1 = blocks[nBlocks / dwPerBlock];
325                         VFS_ReadAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
326                 }
327                 
328                 blocks[nBlocks % dwPerBlock] = Block;
329                 
330                 VFS_WriteAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
331                 free(blocks);
332                 return 0;
333         }
334         
335         nBlocks -= dwPerBlock*dwPerBlock;
336         // Triple Indirect
337         if( nBlocks < dwPerBlock*dwPerBlock*dwPerBlock )
338         {
339                 // Allocate/Get Indirect block
340                 if( nBlocks == 0 ) {
341                         Inode->i_block[14] = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
342                         if( !Inode->i_block[14] ) {
343                                 Log_Warning("Ext2", "Allocating triple indirect block failed");
344                                 free(blocks);
345                                 return 1;
346                         }
347                         memset(blocks, 0, Disk->BlockSize);
348                 }
349                 else
350                         VFS_ReadAt(Disk->FD, Inode->i_block[14]*Disk->BlockSize, Disk->BlockSize, blocks);
351                 
352                 // Allocate / Get Indirect lvl2 Block
353                 if( (nBlocks/dwPerBlock) % dwPerBlock == 0 && nBlocks % dwPerBlock == 0 )
354                 {
355                         id1 = Ext2_int_AllocateBlock(Disk, Inode->i_block[0]);
356                         if( !id1 ) {
357                                 Log_Warning("Ext2", "Allocating triple indirect block (l2) failed");
358                                 free(blocks);
359                                 return 1;
360                         }
361                         blocks[nBlocks/dwPerBlock] = id1;
362                         // Write back indirect 1 block
363                         VFS_WriteAt(Disk->FD, Inode->i_block[14]*Disk->BlockSize, Disk->BlockSize, blocks);
364                         memset(blocks, 0, Disk->BlockSize);
365                 }
366                 else {
367                         id1 = blocks[nBlocks / (dwPerBlock*dwPerBlock)];
368                         VFS_ReadAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
369                 }
370                 
371                 // Allocate / Get Indirect Level 3 Block
372                 if( nBlocks % dwPerBlock == 0 ) {
373                         id2 = Ext2_int_AllocateBlock(Disk, id1);
374                         if( !id2 ) {
375                                 Log_Warning("Ext2", "Allocating triple indirect block (l3) failed");
376                                 free(blocks);
377                                 return 1;
378                         }
379                         blocks[(nBlocks/dwPerBlock)%dwPerBlock] = id2;
380                         // Write back indirect 1 block
381                         VFS_WriteAt(Disk->FD, id1*Disk->BlockSize, Disk->BlockSize, blocks);
382                         memset(blocks, 0, Disk->BlockSize);
383                 }
384                 else {
385                         id2 = blocks[(nBlocks/dwPerBlock)%dwPerBlock];
386                         VFS_ReadAt(Disk->FD, id2*Disk->BlockSize, Disk->BlockSize, blocks);
387                 }
388                 
389                 blocks[nBlocks % dwPerBlock] = Block;
390                 
391                 VFS_WriteAt(Disk->FD, id2*Disk->BlockSize, Disk->BlockSize, blocks);
392                 free(blocks);
393                 return 0;
394         }
395         
396         Log_Warning("Ext2", "Inode ?? cannot have a block appended to it, all indirects used");
397         free(blocks);
398         return 1;
399 }

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