您好,欢迎来到抵帆知识网。
搜索
您的当前位置:首页文件管理实验报告

文件管理实验报告

来源:抵帆知识网
昆明理工大学信息工程与自动化学院学生实验报告

( 2012 —2013 学年 第 二 学期 )

课程名称:操作系统 开课实验室: 年 月 日 年级、专业、班 实验项目名称 教师评 教师签名: 学号 文件管理 姓名 成绩 指导教师 杨云飞 语 年 月 日 一、实验目的

用C或C++语言编写和调试一个简单的文件系统,模拟文件管理的基本功能。从而对各种文件操作命令的实质内容和执行过程有比较深入的了解。

二、实验原理及基本技术路线图(方框原理图)

用C模拟实现文件系统的管理;要求设计一个多级目录结构的文件系统,能正确描述文件控制块,采用合理的外存分配方式,能实现基本的目录及文件的操作,包括创建、删除、重命名、复制、移动等功能,并对文件有一定的存取权限控制。

文件通常存放在外存(如磁盘、磁带)上,可以作为一个单位存放和 实施相应的操作(如打开、关闭、读、写等) 。为了加快对文件的检索,往

往将文件控制块集中在一起进行管理。这种文件控制块的有序集合称为文件目录。文件控制块就是其中的目录项。下图示例一种目录的组织形式。模拟一个文件系统,包括目录文件,普通文件,并实现对它们的一些基本操作。

-1-

假定每个目录文件最多只能占用一个块;一个目录项包括文件名(下一级目录名),文件类型,文件长度,指向文件内容(下一级目录)的指针内容。普通文件可以只用目录项(FCB)代表。

三、所用仪器、材料(设备名称、型号、规格等)。 计算机一台

四、实验方法、步骤

//利用交互式命令实现树型目录结构和文件管理,同时利用位示图表示外存的分配情况,新建文件时分配必要的空间,模拟文件分配表记录文件在外存上的存储方式。了解系统对文件的操作。

//在文件中保存目录内容,创建文件或子目录可以用命令行命令:MD、CD、RD、MK(创建文件)、DEL(删除文件)和DIR

#include #include #include #include using namespace std;

#define beginsize 5

-2-

#define LENGTH 3 typedef struct {

int data[LENGTH]; }Indireone; typedef struct {

Indireone * first[LENGTH]; }Indiretwo; typedef struct {

Indiretwo * second[LENGTH]; }Indirethree; typedef struct Node {

int begin[beginsize]; Indireone * one; Indiretwo * two; Indirethree * three; }Mixtab;

typedef struct NODE {

char name[50];

int type;//是文件还是目录 int size;//如果是文件给出大小 struct NODE *next;//兄弟结点 -3-

struct NODE * sub;//子节点 struct NODE * father;//父亲节点 Mixtab * table; }FCB; //文件控制块 FCB * root; FCB * present; FCB * finding; char stringname[300]; int Bitmap[16][16];//位示图 int leftbit=0; void Initall() { int i,j;

srand( time(NULL) ); for(i=0;i<16;i++) {//初始化位示图 for(j=0;j<16;j++) { Bitmap[i][j]=rand()%2;

}

}

root=(FCB *)malloc(sizeof(FCB)); strcpy(root->name,\"\\\\\"); root->type=0; root->size=0; root->next=NULL; -4-

root->father=root; root->sub=NULL; for(i=0;i<16;i++) { for(j=0;j<16;j++) { if(Bitmap[i][j]==0) { leftbit++;

}

}

} }

//判断分配外存时候是不是足够int Judgeenough(int n) {

if(leftbit>=n)

return 1; else return 0; }

//添加时候用

void Addpoint(FCB * f) {

FCB * temp;

if(present->sub==NULL) { present->sub=f;

-5-

} else { temp=present->sub; while(temp->next!=NULL) { temp=temp->next;

}

temp->next=f; f->next=NULL;

} }

//删除时候用 void Delpoint(FCB *f) {

FCB * temp=present->sub; if(temp==f) { present->sub=temp->next; delete(f);

} else { while(temp->next!=f) { temp=temp->next; }

-6-

} }

temp->next=f->next; delete(f);

//查找是不是已经存在 int Isexist(char ary[],int x) {

FCB * temp;

if(present->sub==NULL) { } else { } }

-7-

temp=present->sub; while(temp!=NULL) { } return 0;

if((!strcmp(temp->name,ary))&&(temp->type==x)) { }

temp=temp->next;

finding=temp; return 1;

return 0;

//创建目录 void Mdlist() {

char listname[50]; cin>>listname; FCB * temp; if(Isexist(listname,0)) { } else { } }

//创建文件 void Mkfile()

{//处理二级时候有错误 char listname[50]; int length,i,j,k,leg=0;

-8-

temp=(FCB *)malloc(sizeof (FCB)); temp->father=present; strcpy(temp->name,listname); temp->next=NULL; temp->sub=NULL; temp->type=0; temp->size=0; Addpoint(temp);

cout<<\"子目录或文件\"<cin>>listname; FCB * temp; if(getchar()=='\\n') { } else { }

if(Isexist(listname,1)) { }

else if(Judgeenough(length)==0) { } else {

temp=(FCB *)malloc(sizeof (FCB)); temp->father=present; strcpy(temp->name,listname); temp->next=NULL; temp->sub=NULL; temp->type=1; temp->size=length;

-9-

cout<<\"外存不够。\"<cout<<\"子目录或文件\"<>length; length=0;

Addpoint(temp); if(length!=0) {

leftbit=leftbit-length;

Mixtab * m=(Mixtab *)malloc(sizeof (Mixtab)); for (k=0;km->one=NULL; m->two=NULL; m->three=NULL; if(length<=beginsize) {//直接地址够用时候

{

for(k=0;kbegin[k]=-1;

//

for(i=0;i<16;i++) {

for(j=0;j<16;j++) {

if(Bitmap[i][j]==0) {

m->begin[k]=i*16+j; Bitmap[i][j]=1; leg=1; break;

-10-

}

}

}

}

if(leg==1) { }

leg=0; break;

}//if(length<=beginsize)

else if(length<=(beginsize+LENGTH)) {//用一级索引的时候

for(k=0;kfor(i=0;i<16;i++) {

for(j=0;j<16;j++) { } if(leg==1)

-11-

if(Bitmap[i][j]==0) { }

m->begin[k]=i*16+j; Bitmap[i][j]=1; leg=1; break;

}

}

{ }

leg=0; break;

leg=0;

Indireone * tempone=(Indireone *)malloc(sizeof (Indireone)); for (k=0;kfor(k=0;k<(length-beginsize);k++) {//一级索引分配

for(i=0;i<16;i++) {

for(j=0;j<16;j++) { } if(leg==1)

-12-

if(Bitmap[i][j]==0) { }

tempone->data[k]=i*16+j; Bitmap[i][j]=1; leg=1; break;

tempone->data[k]=-1;

}

}

{ }

leg=0; break;

m->one=tempone;

}//else if(length<=(beginsize+LENGTH))

else if(length<=(beginsize+LENGTH+LENGTH*LENGTH)) {//当用二级索引时候

for(k=0;kfor(i=0;i<16;i++) {

for(j=0;j<16;j++) { } if(leg==1) {

leg=0;

-13-

if(Bitmap[i][j]==0) { }

m->begin[k]=i*16+j; Bitmap[i][j]=1; leg=1; break;

}

}

}

break;

leg=0;

Indireone * tempone=(Indireone *)malloc(sizeof (Indireone)); for(k=0;k-14-

for(i=0;i<16;i++) { }

for(j=0;j<16;j++) { } if(leg==1) { }

leg=0; break;

if(Bitmap[i][j]==0) { }

tempone->data[k]=i*16+j; Bitmap[i][j]=1; leg=1; break;

m->one=tempone; leg=0;

Indiretwo * temptwo=(Indiretwo *)malloc(sizeof (Indiretwo)); int twostep=(length-beginsize-LENGTH)/LENGTH; for(k=0;kif(twostep==0) {

//当只有一层时候

Indireone * tone=(Indireone *)malloc(sizeof (Indireone)); for (k=0;kfor(k=0;k<(length-beginsize-LENGTH);k++) {

for(i=0;i<16;i++) {

for(j=0;j<16;j++) {

if(Bitmap[i][j]==0) {

tone->data[k]=i*16+j; Bitmap[i][j]=1; leg=1; break;

-15-

tone->data[k]=-1; temptwo->first[k]=NULL;

} else

}

}

}

}

if(leg==1) { }

leg=0; break;

temptwo->first[twostep]=tone;

{//当有多层时候

leg=0;

for(int n=0;nIndireone * tone=(Indireone *)malloc(sizeof (Indireone)); for(k=0;kfor(i=0;i<16;i++) {

for(j=0;j<16;j++) {

if(Bitmap[i][j]==0) {

tone->data[k]=i*16+j; Bitmap[i][j]=1;

-16-

}

}

}

}

}

leg=1; break;

if(leg==1) { }

leg=0; break;

temptwo->first[n]=tone;

leg=0; //分配散的

Indireone * teone=(Indireone *)malloc(sizeof (Indireone)); for (k=0;kfor(k=0;k<(length-beginsize-LENGTH-twostep*LENGTH);k++) {

for(i=0;i<16;i++) {

for(j=0;j<16;j++) {

if(Bitmap[i][j]==0)

-17-

teone->data[k]=-1;

} } //

}

}

}

}

{ }

teone->data[k]=i*16+j; Bitmap[i][j]=1; leg=1; break;

if(leg==1) { }

leg=0; break;

temptwo->first[twostep]=teone;

m->two=temptwo;

}//else if(length<=(beginsize+LENGTH+LENGTH*LENGTH)) temp->table=m;

}//if(length!=0) else { }

temp->table=NULL;

-18-

void Showtime() {

setlocale(LC_TIME,\"\"); time_t TIME; struct tm *TM; char ch1[81]; time(&TIME); TM=gmtime(&TIME); strftime(ch1,80,\"%x\ cout<//查找 * ?

int Searchar(char ch,char array[]) {

for(int i=0;i}

} return -1; }

//*时候查找a在b中是不是存在int Searchstring(char a[],char b[]) { int j,i,t; -19-

if(strlen(a)>strlen(b)) return -1;

else { for(i=0;i<=(strlen(b)-strlen(a));i++) { t=i;

for(j=0;jbreak;

else t++;

}

if(j==(strlen(a)))

return i; }

} return -1; } //

void Dir1(FCB * f,int listnum,int filenum,int allsize) {

FCB * temp,* temp2; if(f->sub!=NULL) { temp=f->sub; Showtime();

-20-

if(temp->type==0) { } else { }

if(temp->next!=NULL) {

temp2=temp->next; while(temp2!=NULL) {

Showtime(); if(temp2->type==0) { } else {

cout<<\"\\ \"<size<<\"\\"<name<allsize+=temp2->size;

-21-

cout<<\"\

\\\"<name<cout<<\"\\ \"<size<<\"\\"<name<size;

cout<<\"\

\\\"<name<} else { }

}

}

}

temp2=temp2->next;

cout<<\"\\ \"<size<<\"\\"<name<size;

cout<<\"\ \"<void Dirall() {

int listnum=2; int filenum=0; int allsize=0; int i;

char filename[50]; Showtime();

cout<<\"\

\"<<\"\\.\"<cout<<\"\\"<<\"\\..\"<-22-

{ } else {

cin>>filename;

if ((!strcmp(filename,\".\"))||(!strcmp(filename,\"*\"))) { }

else if (!strcmp(filename,\"..\")) { } else {

if(Isexist(filename,0)||Isexist(filename,1)) {//处理单个 //

Dir1(finding,listnum,filenum,allsize); if(finding->type==0) { } else {

cout<<\"\\ \"<size<<\"\\"<name<-23-

cout<<\"\

\\\"<name<Dir1(present->father,listnum,filenum,allsize); Dir1(present,listnum,filenum,allsize); Dir1(present,listnum,filenum,allsize);

}

}

filenum++;

allsize+=finding->size;

////处理* ?的情况

else if(Searchar('*',filename)!=-1) {//当有*时候

char tempfilename2[50]; char tempfilename1[50]; int place=Searchar('*',filename);

if ((place>0)&&(place<(strlen(filename)-1))) {//当*在中间时候 }

if(place==0) {//当*在最开始时候

for (i=place+1;i-24-

tempfilename2[i-place-1]=filename[i]; for (i=0;i<=place-1;i++)

{//============================================ }

for (i=place+1;itempfilename2[i-place-1]=filename[i]; tempfilename1[i]=filename[i];

}

else if (place==(strlen(filename)-1)) {//当*在最后时候 }

FCB * temp=present->sub; while(temp!=NULL) {

if (place==0) {//

if(Searchstring(tempfilename2,temp->name)!=-1) {//找到时候 }

-25-

if(temp->type==0) { } else { }

cout<<\"\\ \"<size<<\"\\"<name<size;

cout<<\"\

\\\"<name<for (i=0;i<=place-1;i++)

{//============================================ }

tempfilename1[i]=filename[i];

}

else if(place==(strlen(filename)-1)) {// } else {

int t1=Searchstring(tempfilename1,temp->name); int t2=Searchstring(tempfilename2,temp->name); if(Searchstring(tempfilename1,temp->name)!=-1) {// }

if(temp->type==0) { } else { }

cout<<\"\\ \"<size<<\"\\"<name<size;

cout<<\"\

\\\"<name<if((Searchstring(tempfilename2,temp->name)!=-1)&&(Searchstring(tempfilename1,temp->name)!=-1)&&(t2>t1))

{//

if(temp->type==0) {

-26-

} }

}

}

}

}

}

} else { }

cout<<\"\

\\\"<name<cout<<\"\\ \"<size<<\"\\"<name<size;

temp=temp->next;

else if(Searchar('?',filename)!=-1) {//当有?时候 }

cout<<\"\ \"<int place=Searchar('?',filename);

//删除子目录 void Delelist() {

char listname[50];

-27-

cin>>listname;

if(Isexist(listname,0)==0) { cout<<\"系统找不到指定的文件。\"<} else {//finding if(finding->sub!=NULL) { cout<<\"目录不是空的。\"<{//删除目录,有错误 Delpoint(finding);

}

} }

//删除文件 void Delefile() { int x,y;

char filename[50]; cin>>filename;

if(Isexist(filename,1)==0) { cout<<\"系统找不到指定的文件。\"<else {

Mixtab * mtemp; mtemp=finding->table; if (mtemp->one==NULL) {//如果只存直接 }

else if ((mtemp->one!=NULL)&&(mtemp->two==NULL)) {//存了一级索引

for (int k=0;kleftbit=leftbit+beginsize; //处理一级索引 Indireone * onetemp;

-29-

x=(mtemp->begin[k])/16; y=(mtemp->begin[k])%16; Bitmap[x][y]=0; int start=0;

while ((mtemp->begin[start]!=-1)&&(startx=(mtemp->begin[start])/16; y=(mtemp->begin[start])%16; Bitmap[x][y]=0; start++; leftbit++;

onetemp=mtemp->one; int start =0;

while((onetemp->data[start]!=-1)&&(startdata[start])/16; y=(onetemp->data[start])%16; Bitmap[x][y]=0; start++; leftbit++;

}

}

else if (mtemp->two!=NULL) {//二级 int k,i,j;

for (k=0;kbegin[k])/16; y=(mtemp->begin[k])%16; Bitmap[x][y]=0;

}

leftbit=leftbit+beginsize; Indireone * onetemp; onetemp=mtemp->one; for (k=0;kx=(onetemp->data[k])/16;

-30-

}

y=(onetemp->data[k])%16; Bitmap[x][y]=0;

leftbit=leftbit+LENGTH; Indiretwo * twotemp; twotemp=mtemp->two; onetemp=twotemp->first[0]; //

int flg=0; //处理二级结构

for (flg=0;flg1) {

for (i=0;ifor (j=0;j-31-

x=(twotemp->first[i]->data[j])/16; y=(twotemp->first[i]->data[j])%16; Bitmap[x][y]=0;

if (twotemp->first[flg]==NULL) {// }

break;

} }

} else

}

}

leftbit=leftbit+(flg-1)*LENGTH;

int start=0;

while ((twotemp->first[flg-1]->data[start]!=-1)&&(startx=(twotemp->first[flg-1]->data[start])/16; y=(twotemp->first[flg-1]->data[start])%16; Bitmap[x][y]=0; leftbit++; start++;

{//三级 }

delete(mtemp); Delpoint(finding);

//改变目录 void ChangeFile() {

char listname[50]; cin>>listname; if(present==root)

-32-

{ } else {

if(!strcmp(listname,\"..\")) {

char temp[50]; int loc1,loc2;

loc1=strlen(stringname); loc2=strlen(present->name); strncpy(temp,stringname,loc1-loc2); temp[loc1-loc2-1]='\\0'; strcpy(stringname,temp); present=present->father;

-33-

if((!strcmp(listname,\".\"))||(!strcmp(listname,\"..\"))) { }

else if(Isexist(listname,0)) { } else { }

cout<<\"系统找不到指定的路径。\"<strcat(stringname,finding->name);

} }

}

else if(!strcmp(listname,\".\")) { }

else if(!strcmp(listname,\"\\\\\")) { }

else if(Isexist(listname,0)==0) { } else { }

present=finding; strcat(stringname,\"\\\\\");

strcat(stringname,finding->name);

cout<<\"系统找不到指定的路径。\"<strcpy(stringname,\"\\\\\");

//分析的单独的节点 void Looktree(FCB * f) { int a[100]; int n=-1; FCB * temp;

-34-

if (f->father==present) { if (f->next==NULL) { cout<<\"└─\"<name<} else

cout<<\"├─\"<name<father; while(temp!=present) { n++;

if(temp->next==NULL)

{//是0时候输出空格,else输出竖线 a[n]=0;

}

else a[n]=1; temp=temp->father;

} //记住了n for(int i=n;i>=0;i--) { if(a[i]==1)

cout<<\"│ \";

-35-

else cout<<\" \";

}

if(f->next==NULL) cout<<\"└─\"<name<else

cout<<\"├─\"<name<//树形结构查看 void Treeview(FCB * f) {//查看的是目录 FCB * temp,* temp2; if(present->sub==NULL) { cout<<\"没有子文件夹\"<} else { temp=f->sub; while(temp!=NULL) { Looktree(temp);

if(temp->sub!=NULL)//== { temp2=temp->sub; while(temp2!=NULL)

{

-36-

} } //

}

// }

}

Looktree(temp2); Treeview(temp2); temp2=temp2->next;

temp=temp->next;

void Test() { int i,j;

for(i=0;i<16;i++) { } } //

void Help() {

cout<<\"有关某个命令的详细信息,请键入 HELP 命令名\"<-37-

for(j=0;j<16;j++) { }

cout<cout<cout<<\"CD\ 显示当前目录的名称或将其更改。\"<cout<<\"DEL\ 删除至少一个文件。\"<cout<<\"DIR\ 显示一个文件中的文件和子目录。\"<char choice[30]; Initall(); present=root; Help();

strcpy(stringname,\"\\\\\"); cout<\"; while(true) {

if((!strcmp(choice,\"help\"))||(!strcmp(choice,\"helP\"))||(!strcmp(choice,\"heLp\"))||(!strcmp(choice,\"heLp\"))||(!strcmp(choice,\"hE

-38-

cin>>choice;

lp\"))||(!strcmp(choice,\"hElP\"))||(!strcmp(choice,\"hELp\"))||(!strcmp(choice,\"hELP\"))||(!strcmp(choice,\"Help\"))||(!strcmp(choice,\"HelP\"))||(!strcmp(choice,\"HeLp\"))||(!strcmp(choice,\"HeLP\"))||(!strcmp(choice,\"HElp\"))||(!strcmp(choice,\"HElP\"))||(!strcmp(choice,\"HELp\"))||(!strcmp(choice,\"HELP\")))

{//帮助 }

else if((!strcmp(choice,\"cd\"))||(!strcmp(choice,\"Cd\"))||(!strcmp(choice,\"cD\"))||(!strcmp(choice,\"CD\"))) {//改变子目录 }

else if((!strcmp(choice,\"md\"))||(!strcmp(choice,\"Md\"))||(!strcmp(choice,\"mD\"))||(!strcmp(choice,\"MD\"))) {//创建子目录 }

else if((!strcmp(choice,\"rd\"))||(!strcmp(choice,\"rD\"))||(!strcmp(choice,\"Rd\"))||(!strcmp(choice,\"RD\"))) {//删除子目录 } else

Delelist(); Mdlist(); ChangeFile(); Help();

if((!strcmp(choice,\"dir\"))||(!strcmp(choice,\"diR\"))||(!strcmp(choice,\"dIr\"))||(!strcmp(choice,\"dIR\"))||(!strcmp(choice,\"Dir\"))||(!strcmp(choice,\"DiR\"))||(!strcmp(choice,\"DIr\"))||(!strcmp(choice,\"DIR\")))

{//列当前目录内容 }

else if((!strcmp(choice,\"mk\"))||(!strcmp(choice,\"mK\"))||(!strcmp(choice,\"Mk\"))||(!strcmp(choice,\"MK\"))) {//创建一个文件

Mkfile();

-39-

Dirall();

} else

if((!strcmp(choice,\"del\"))||(!strcmp(choice,\"deL\"))||(!strcmp(choice,\"dEl\"))||(!strcmp(choice,\"dEL\"))||(!strcmp(choice,\"Del\"))||(!strcmp(choice,\"DeL\"))||(!strcmp(choice,\"DEl\"))||(!strcmp(choice,\"DEL\")))

{//删除一个文件 } else

Delefile();

if((!strcmp(choice,\"exit\"))||(!strcmp(choice,\"exiT\"))||(!strcmp(choice,\"exIt\"))||(!strcmp(choice,\"exIT\"))||(!strcmp(choice,\"eXit\"))||(!strcmp(choice,\"eXiT\"))||(!strcmp(choice,\"eXIt\"))||(!strcmp(choice,\"eXIT\"))||(!strcmp(choice,\"Exit\"))||(!strcmp(choice,\"ExiT\"))||(!strcmp(choice,\"ExIt\"))||(!strcmp(choice,\"ExIT\"))||(!strcmp(choice,\"EXit\"))||(!strcmp(choice,\"EXiT\"))||(!strcmp(choice,\"EXIt\"))||(!strcmp(choice,\"EXIT\")))

{ } else

exit(0);

if((!strcmp(choice,\"tree\"))||(!strcmp(choice,\"treE\"))||(!strcmp(choice,\"trEe\"))||(!strcmp(choice,\"trEE\"))||(!strcmp(choice,\"tRee\"))||(!strcmp(choice,\"tReE\"))||(!strcmp(choice,\"tREe\"))||(!strcmp(choice,\"tREE\"))||(!strcmp(choice,\"Tree\"))||(!strcmp(choice,\"TreE\"))||(!strcmp(choice,\"TrEe\"))||(!strcmp(choice,\"TrEE\"))||(!strcmp(choice,\"TRee\"))||(!strcmp(choice,\"TReE\"))||(!strcmp(choice,\"TREe\"))||(!strcmp(choice,\"TREE\")))

{ } else

Treeview(present);

if((!strcmp(choice,\"cls\"))||(!strcmp(choice,\"clS\"))||(!strcmp(choice,\"cLs\"))||(!strcmp(choice,\"cLS\"))||(!strcmp(choice,\"Cls\"))||(!strcmp(choice,\"ClS\"))||(!strcmp(choice,\"CLs\"))||(!strcmp(choice,\"CLS\")))

{

-40-

}

}

system(\"cls\");

else if(!strcmp(choice,\"test\")) { } else { }

cout<\";

cout<<\"'\"<return 0; }

五、实验过程原始记录(数据、图表、计算等)

-41-

-42-

六、实验结果、分析和结论(误差分析与数据处理、成果总结等。其中,绘制曲线图时必须用计算纸)

本次试验使我熟悉文件管理系统的设计方法;加深对所学各种文件操作的了解及其操作方法的特点。通过模拟文件系统的实现,深入理解了操作系统中文件系统的理论知识,加深对教材中的重要算法的理解。同时通过编程实现这些算法,更好地掌握操作系统的原理及实现方法提高综合运用各专业课知识的能

-43-

力。

本实验是文件管理系统,做实验之时因为知识有限所以只能去网上去查找一些相关知识,以及看老师给的例子,很多地方都不明白。但是时间紧迫,我们就边做边理解、讨论,这样造成一个非常大的困难,不知道在编程中,我遇到了很多以前没有遇到的问题和知识,在通过和其他同学讨论后,上网查询等方法后,解决了大部分的问题,对C++语言的理解也加深了很多。

-44-

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- dfix.cn 版权所有 湘ICP备2024080961号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务