2021SC@SDUSC
项目环境:
- 树莓派4b
- Ubuntu Desktop 21.04
读取文件:
下面是读取文件函数 readFile 的实现;在该函数中,文件名 name 作为形参传入。
首先读取 inode :
PInode pInode = new Inode();
getInode(pInode, id);
然后,依次遍历 10 个直接索引,读取数据块。这其中要分几种情况进行讨论。首先,如果文件的长度超过了 blocksize ,那么长度就要更新为原有的长度减去 getData 获取的 blocksize 的长度,若文件长度未超过 blocksize ,则 len 的长度更新为 len 减去 blocks ize 的长度,并将缓冲区删除。之后的遍历一个一级索引也是类似的操作。
for(i = 0; i < 10; i++)
{
blockId = pInode->addr[i];
if(blockId > 0)
{
if(len > blockSize)
{
len -= getData(blockId, buff, blockSize, 0);
printf("%s", buff);
}
else
{
len -= getData(blockId, buff, len, 0);
printf("%s\n", buff);
delete buff;
return 0;
}
}
else
{
printf("\n");
delete buff;
return 0;
}
}
下面考虑遍历二级索引:
for(i = 0; i < totalItem; i++)
{
blockId = getItem(addrBlockId, i);
if(blockId > 0)
{
if(len > blockSize)
{
len -= getData(blockId, buff, blockSize, 0);
printf("%s", buff);
}
else
{
len -= getData(blockId, buff, len, 0);
printf("%s\n", buff);
delete buff;
return 0;
}
}
else
{
printf("\n");
delete buff;
return 0;
}
}
与遍历一级索引是类似的。
文件写入:
下面是文件写入 Write 的实现。
首先,我们依然是读取节点 PInode pInode = new Inode(); getInode(pInode, id); 然后,我们定义一个缓冲区 char* buff = new char[blockSize+1]; 对于 10 个直接索引对应的数据块,我们可以进行直接读取。
for(i = 0; i < 10; i++)
{
blockId = pInode->addr[i];
if(blockId > 0)
{
num = waitForInput(buff, blockSize);
writeData(blockId, buff, num, 0);
len += num;
if(num < blockSize)
{
pInode->length = len;
time(&(pInode->time));
updateInode(*pInode);
delete buff;
return 0;
}
else
{
printf("You have input %ld Byte dat, continue?[Y/N]", len);
ch = getchar();
if(ch != 'Y' && ch != 'y')
{
pInode->length = len;
time(&(pInode->time));
updateInode(*pInode);
delete buff;
return 0;
}
}
}
需要注意,同写入文件一样,需要对需要写入的数据大小与 blocksize 的大小进行一下比较,当 num 小于 blocksize 的时候,可以直接进行写入,并更新索引节点 pnode ,然后删除缓冲区。
写入数据之后,将空闲数据块的数目 blockFree 自减,并对其进行更新,然后更新数据块比特图,然后更新索引节点 pnode 。
if (blockId > 0)
{
superBlock.blockFree--;
updateSuperBlock(superBlock);
blockBitmap[blockId] = 1;
updateBlockBitmap(blockBitmap, blockId);
pInode->addr[i] = blockId;
time(&(pInode->time));
updateInode(*pInode);
num = waitForInput(buff, blockSize);
writeData(blockId, buff, num, 0);
len += num;
if (num < blockSize)
{
pInode->length = len;
time(&(pInode->time));
updateInode(*pInode);
delete buff;
return 0;
}
else
{
printf("You have input %ld Byte dat, continue?[Y/N]", len);
ch = getchar();
if (ch != 'Y' && ch != 'y')
{
pInode->length = len;
time(&(pInode->time));
updateInode(*pInode);
delete buff;
return 0;
}
}
}
现在,写入一个一级索引:
unsigned addrBlockId = pInode->addr[10];
int itemTotal = blockSize/itemSize;
删除文件:
首先读取 inode:PInode pInode = new Inode(); getInode(pInode, id);
遍历 10 个一级索引,首先 blockId 指向 PNode ,然后一次更新 superblock ,释放 block ,然后更新 blockbitmap。
int i;
unsigned int blockId;
for (i = 0; i < 10; i++)
{
blockId = pInode->addr[i];
if (blockId > 0)
{
superBlock.blockFree++;
updateSuperBlock(superBlock);
releaseBlock(blockId);
blockBitmap[blockId] = 0;
updateBlockBitmap(blockBitmap, blockId);
}
}
释放一级索引块并更新 superblock superBlock.blockFree++; updateSuperBlock(superBlock) 然后释放 block 和 blockbitmap ,然后更新这两个数据项:
releaseBlock(addrBlockId);
blockBitmap[addrBlockId] = 0;
updateBlockBitmap(blockBitmap, addrBlockId);
最后,我们更新目录 inode 以及删除 curlink:
curInode.length
time(&(curInode.time));
updateInode(curInode);
removeFcbLinkNode(curLink, *pInode);
重命名:
这个功能的实现很简单,直接使用 strcpy 函数将 newname 赋予文件即可,然后再进行更新
strcpy(inode.name, newName);
time(&(inode.time));
updateInode(inode);
// update curLink
FcbLink link = curLink->next;
while (link != NULL)
{
if (strcmp(link->fcb.name, name) == 0)
{
strcpy(link->fcb.name, newName);
return 0;
}
link = link->next;
}
修改权限:
现在我们来进行修改文件权限的函数实现 chmod ,它的第一个参数 name 是文件名,即要需要被检查的文件,第二个参数 type 代表需要被规定的新的权限。
具体来讲,我们先获取 inode ,如果这个 isDir 被判定为 1 之后,先调用 cd 函数返回目录,然后初始化一个进程控制块 FCBlink ,然后只要这个链的指向不为空就一直遍历下去,并调用 chmod 函数。最终跳出循环之后,我们将 inode 的 type置为我们想要设置的 type ,并进行更新。
int FileSystem::chmod(char* name, unsigned char type)
{
unsigned int id = findChildInode(curLink, name);
if(id > 0)
{
Inode inode;
getInode(&inode, id);
if(inode.isDir == 1)
{
cd(name);
FcbLink link = curLink->next;
while(link != NULL)
{
chmod(link->fcb.name, type);
link = link->next;
}
cd((char *)"..");
}
inode.type = type;
time(&(inode.time));
updateInode(inode);
return 0;
}
else
{
printf("no such file or directory:%s\n", name);
return -1;
}
}
登录与退出:
void FileSystem::login()
{
char username[10];
char password[10];
while(1)
{
printf("username:");
fgets(username, sizeof(username), stdin);
if(username[strlen(username)-1] == '\n')
username[strlen(username)-1] = '\0';
system("stty -echo");
printf("password:");
fgets(password, sizeof(password), stdin);
if(password[strlen(password)-1] == '\n')
password[strlen(password)-1] = '\0';
system("stty echo");
printf("\n");
if(strcmp(username,user.username)==0 && strcmp(password,user.password)==0)
break;
else
{
printf("username or password is not correct,please try again.\n");
}
}
}
void FileSystem::logout()
{
updateResource();
printf("%s has logout\n", user.username);
openFileSystem();
login();
}
Updaterecourse 函数用来保存数据,重新打开系统并进行登录验证,由于命令循环未退出,故会继续接受命令。
退出文件系统:
void FileSystem::exit()
{
printf("Bye!\n");
stopHandle(0);
}
实现简单,不再赘述。
查看文件系统信息:
查看文件系统信息,分别打印总数据块数目,每个数据块大小,仍然空闲的数据块数量,总索引节点数目。
void FileSystem::systemInfo()
{
printf("Sum of block number:%d\n",superBlock.blockNum);
printf("Each block size:%d\n",superBlock.blockSize);
printf("Free of block number:%d\n",superBlock.blockFree);
printf("Sum of inode number:%d\n",superBlock.inodeNum);
}
清屏:
void FileSystem::clr()
{
system("clear");
}
处理用户命令:
其实就是直接使用 switch 语句,对不同的输入命令调用相应的函数进行处理:
switch(analyse(input))
{
case HELP:
help();
break;
case LS://执行ls或ls-l
if(strcmp(cmd[1], "-l") == 0)
ls_l();
else
ls();
break;
case CD:
cd(cmd[1]);
break;
case MKDIR:
createFile(cmd[1],1);
break;
case TOUCH:
createFile(cmd[1],0);
break;
case CAT:
read(cmd[1]);
break;
case WRITE:
write(cmd[1]);
break;
case RM:
if(strcmp(cmd[1], "-r") == 0)
del(cmd[2]);
else
del(cmd[1]);
break;
case MV:
mv(cmd[1], cmd[2]);
break;
case CP:
if(strcmp(cmd[1], "-r") == 0)
cp(cmd[2], cmd[3]);
else
cp(cmd[1], cmd[2]);
break;
case CHMOD:
if(strcmp(cmd[1], "-r") == 0)
chmod(cmd[3], cmd[2][0]-'0');
else
chmod(cmd[2], cmd[1][0]-'0');
break;
case LOGOUT:
logout();
break;
case EXIT:
exit();
break;
case SYSINFO:
systemInfo();
break;
case CLEAR:
clr();
break;
case ACCOUNT:
account();
break;
default:
break;
}
}while(1);
取出用户数据:
使用 C 语言的内置函数 rewind 返回一个指向当前文件的开头的指针,然后调用 fread 函数进行读取内容到缓冲数组 user 中,这里将 count 参数设置为 1.
void FileSystem::getUser(User* pUser)
{
if(fp == NULL || pUser == NULL)
{
return;
}
rewind(fp);
fread(pUser, userSize, 1, fp);
}
设置用户数据:
实现的原理跟刚才的 get 其实基本一致,只不过得到指向当前文件的开头的指针后,应该向缓冲区中写入内容而不再是读出。
void FileSystem::setUser(User user)
{
if(fp == NULL)
{
return;
}
rewind(fp);
fwrite(&user, userSize, 1, fp);
}
SuperBlock:
void Fvoid FileSystem::getSuperBlock(SuperBlock* pSuper)
{
if(fp == NULL || pSuper == NULL)
{
return;
}
fseek(fp, sOffset, SEEK_SET);
fread(pSuper, superBlockSize, 1, fp);
}
void FileSystem::updateSuperBlock(SuperBlock super)
{
if(fp == NULL)
{
return;
}
fseek(fp, sOffset, SEEK_SET);
fwrite(&super, superBlockSize, 1, fp);
}
大致原理与之前的操作相同,只不过这里使用 fseek 函数,实现对 SEEK_SET 起始,偏移量为 sOffset 个字节处的 superblock ,然后调用 fread 函数对其进行读取。
更新的原理也是一样,只不过在找到 SEEK_SET 起始,偏移量为 sOffset 个字节处的 superblock 之后,需要将读取操作改为写入操作来实现对此处的 superblock 的内容的更新。
空闲分区管理:
unsigned int FileSystem::getAvailableBlockId()
{
if(superBlock.blockFree <= 0)
{
printf("no free block\n");
return 0;
}
int i;
for(i = 0; i < blockNum; i++)
{
if(blockBitmap[i] == 0)
{
//printf("find block %d\n", i);
releaseBlock(i);
return i;
}
}
printf("no free block\n");
return 0;
}
如上所示,通过一个 superblock 的对象(也命名为 superblock )的属性 BlockFree 属性来判断是否还有空闲分区。如果该值小于等于 0 ,那么说明已经没有空闲块了,这是打印输出错误信息并且返回值为 0.
接下来就对整个位图进行遍历,并且同时判断 blockBitmap[i] 的值,如果找到一个值为 0 ,则释放该空闲区域,并返回该块的索引。
下面是实现取出位图的操作:
void FileSystem::getBlockBitmap(unsigned char* bitmap)
{
if(fp == NULL || bitmap == NULL)
{
return;
}
fseek(fp, bbOffset, SEEK_SET);
fread(bitmap, blockBitmapSize, 1, fp);
}
这个操作的实现和之前的读取用户文件信息很相似,都是先调用 fseek 找到指定位置区域,然后用 fread 函数进行读取,这里不再赘述。
void FileSystem::updateBlockBitmap(unsigned char* bitmap, unsigned int start, unsigned int count)
{
if(fp == NULL)
{
return;
}
fseek(fp, bbOffset+start, SEEK_SET);
fwrite(bitmap+start, count, 1, fp);
}
上述函数 updateBlockBitmap 实现对比特位图的指定区域的信息更新。
inodeImage区域:
unsigned int FileSystem::getAvailableInodeId()
{
unsigned int i;
for(i = 0; i < blockNum; i++)
{
if(inodeBitmap[i] == 0)
{
return i;
}
}
return 0;
}
在 getAvailableInodeId 函数中,寻找一个空闲的 i 节点块 0号已被分配给根目录,返回值大于 0 表示找到。
接下来的取出,更新,释放等操作与前面对其他区域的读写操作均类似,这里不再赘述,直接上代码。
void FileSystem::getInodeBitmap(unsigned char* bitmap)
{
if(fp == NULL || bitmap == NULL)
{
return;
}
fseek(fp, ibOffset, SEEK_SET);
fread(bitmap, inodeBitmapSize, 1, fp);
}
void FileSystem::updateInodeBitmap(unsigned char* bitmap, unsigned int index)
{
this->updateInodeBitmap(bitmap, index, 1);
}
void FileSystem::updateInodeBitmap(unsigned char* bitmap, unsigned int start, unsigned int count)
{
if(fp == NULL)
{
return;
}
fseek(fp, ibOffset+start, SEEK_SET);
fwrite(bitmap+start, count, 1, fp);
}
void FileSystem::getInode(PInode pInode, unsigned int id)
{
if(fp == NULL || pInode == NULL)
{
return;
}
fseek(fp, iOffset+inodeSize*id, SEEK_SET);
fread(pInode, inodeSize, 1, fp);
}
void FileSystem::updateInode(Inode inode)
{
if(fp == NULL )
{
return;
}
fseek(fp, iOffset+inodeSize*inode.id, SEEK_SET);
fwrite(&inode, inodeSize, 1, fp);
}
void FileSystem::releaseInode(unsigned int id)
{
if(fp == NULL )
{
return;
}
fseek(fp, iOffset+inodeSize*id, SEEK_SET);
int i;
for(i = 0; i < inodeSize; i++)
{
fputc(0, fp);
}
}