Linux下回收站的设计与实现
概要:
学习Linux有一段时间了,最初为了更好的学习Linux,ghost了原先的WinXp,安装了fedora 7,但最后还是忍无可忍把它卸了(想把Linux作为桌面系统人大概都知道为什么吧)并在Xp下装了VM。因为配置不高,我的电脑运行VM是很吃力的(不比蜗牛快多少,汗),所以能在文本模式下操作的都尽量在文本模式下操作。
前几天一个不小心把很重要的文件给删了,在整整郁闷了240s后,最终决定写一个能在文本模式下操作的回收站。
功能介绍:

insert photo
recycle.setup: 安装回收站程序、创建相应的文件夹和文件
recycle.uninstall: 卸载回收站
recycle.clean:清空该用户的回收站
recycle.del file1 file2 … filen : 删除文件file1, file2 … filen并移至回收站
recycle.restore file1 file2 … filen: 还原文件file1, file2 … filen
recycle.show:显示该用户回收站中的信息
工作流程:
1. 删除文件或文件夹(recycle.del)
当用户要删除某个文件时,我们使用系统调用rename将文件移动到回收站的根目录下(/.recycle/),待用户需要时再将其还原。这样存在着一些问题:首先,用户可能删除具有相同文件名的文件,显然我们无法将它们一起放到/.recycle文件夹下面。其次,因为Linux是一个多用户的操作系统,一个用户(root出外)不因该有权限访问或修改他人的回收站中的内容。
对于第一个问题我们可以这样:当用户删除文件后,我们并不直接将其移动到回收站而是把它按照事先定义的重新命名方式将被删除文件重命名后再移动到回收站。为此再回收站目录下建立一个文本文件infotable,它保存如下信息:原始路径,重命名后的文件名,删除的日期。当删除文件时先向infotable注册一条新记录,如果注册成功则将文件移动到回收站下并重命名为新文件名。为了得到不重复的新文件名,再新建一个文本文件avaitable,它保存有一组数值(如:1,22,434,55,22……),表示所新删除的文件可以取这些数值做为新的文件名,并保证它们不会重复。
对于安全性我们可以为每个用户建立一个文件夹,命名为该用户的ID号。文件夹的属性为“drwx------”并再各文件夹下保存有各自的infotable和avaitable。
删除文件的工作流程见下图:

2. 还原文件或文件夹(recycle.restore)
用户同过参数告诉还原程序要恢复文件的新文件名,还原程序再infotable表中查找它的原始路径,并将其重新移动到路径上去;再该文件的新文件名追加到avaitable文件中(说明该文名可以用了),最后删除文件infotable中的相关记录。
恢复文件流程见下图:
3. 安装以及初始化(recycle.setup)
首先root允许recycle.setup程序,该程序新建一个/.recycle文件夹(mode=0777)并复制回收站程序到/usr/bin目录下。
普通用户初次使用时也要先运行recycle.setup,它根据用户的ID在/.recycle下创建一个子文件夹,接着建立一个空的infotable和avaitable文件,最后将约定的数字写入到avaitable文件中,数据间以换行分隔。
4. 显示回收站信息(recycle.show)
用户输入命令reclcye.show,程序显示读取infotable文件并根据需求显示出用户所需的信息。
5. 清空用户回收站(recycle.clear)
每个用户只有清空自己回收站的权力,清空回收站程序首先得到该用户的ID,接着删除该用户在/.recycle文件夹,最后重新关键avaitable和infotable文件。
回收站目录结构:

数据结构:
infotbale文件中存放的数据结构
struct information{
char path[PATH_MAX]; //被删除的文件的原始位置
char newname[NEWNAMELEN]; //新文件名
date_t time; //被删除的日期
}
源代码:

/**//************************************************
* common.h
* this file include and define most useful file and macro
**************************************************/

#include<stdio.h>
#include<dirent.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
#include<time.h>


//#define TESTDEL
//#define TESTRES
//#define TESTCLE

//显示错误信息
#define oop(msg) {perror(msg);}
#define oope(msg) {perror(msg);exit(1);}
#define oopr(msg) {perror(msg);return ;}


//要安装的命令
#define OCOMMDEL "./recycle.del"
#define OCOMMSHO "./recycle.show"
#define OCOMMRES "./recycle.restore"
#define OCOMMUNI "./recycle.uninstall"
#define OCOMMSET "./recycle.setup"
#define OCOMMCLE "./recycle.clear"
//要复制的新路径
#define NCOMMCLE "/usr/bin/recycle.clear"
#define NCOMMDEL "/usr/bin/recycle.del"
#define NCOMMSHO "/usr/bin/recycle.show"
#define NCOMMRES "/usr/bin/recycle.restore"
#define NCOMMUNI "/bin/recycle.uninstall"
#define NCOMMSET "/usr/bin/recycle.setup"


#define RECPATH "/.recycle"
#define INFOTAB "infotable"
#define AVAITAB "avaitable"
#define INFOTMP "infotmp"


#define AVAIMAX 10 //回收站最大支持的文件数
#define PATH_MIN 32
#define NEWNAMELEN 4



struct information...{
time_t time;
char newname[NEWNAMELEN];
char oldpath[PATH_MAX];
};



/**//****************************************
*clear.c
*****************************************/


#include"common.h"



void commUser(uid_t uid)...{
char path[PATH_MIN];
int fd;
FILE *fp;
int avai;
//make user's private directory
snprintf(path,PATH_MIN,"%s/%d",RECPATH,uid);
if(mkdir(path,0700)!=0) oope("make user's directory error: ");
chdir(path);
//make user's infotable file
fd=open(INFOTAB,O_WRONLY|O_CREAT|O_EXCL,0700);
if(fd==-1) oope("create infomation table error: ");
close(fd);
//make user's avaitable file
fp=fopen(AVAITAB,"w");
if(fp==NULL) oope("can not use stream: ");
for(avai=0;avai<AVAIMAX;avai++)
if(fprintf(fp,"%d ",avai)<0) perror("write avaitab error: ");
fclose(fp);
}


int main()...{
uid_t uid;
char path[PATH_MIN];
uid=getuid();
snprintf(path,PATH_MIN,"%s/%d",RECPATH,uid);

if(fork()==0)...{
execlp("rm","rm","-rf",path,NULL);
}

else...{
wait(NULL);
commUser(uid);
}
}


/**//**********************************************
* del.c
***********************************************/

#include"common.h"

uid_t uid;


void lock();
void unlock();
void delete();
int getAvai();//get a availavle file name from avaitable file
void setAvai();//update avaitable file
int logInfotab();//write information to infotable file


int logInfotab(char *oldpath,char *newname)...{
int fd;
char infopath[PATH_MIN];
struct information info;
snprintf(infopath,PATH_MIN,"%s/%d/%s",RECPATH,uid,INFOTAB);
fd=open(infopath,O_WRONLY|O_APPEND);
if(fd==-1) return -1;
strcpy(info.oldpath,oldpath);
strcpy(info.newname,newname);
time(&info.time);
write(fd,&info,sizeof(info));
#ifdef TESTDEL
printf("write to infotable: %s %s %s",info.oldpath,info.newname,ctime(&info.time));
#endif
return 0;
}


void setAvai()...{
int len;
FILE *fp1,*fp2;
char avaipath[PATH_MIN],temppath[PATH_MIN],buf[NEWNAMELEN];
snprintf(avaipath,PATH_MIN,"%s/%d/%s",RECPATH,uid,AVAITAB);
fp1=fopen(avaipath,"r");
if(fp1==NULL) oope(avaipath);
snprintf(temppath,PATH_MIN,"%s/%d/temp",RECPATH,uid);
fp2=fopen(temppath,"w");
if(fp2==NULL) oope(temppath);
#ifdef TESTDEL
printf("open %s open %s ",avaipath,temppath);
#endif
fgets(buf,NEWNAMELEN,fp1);
while(fgets(buf,NEWNAMELEN,fp1)!=NULL)
fputs(buf,fp2);
fclose(fp1);
fclose(fp2);
unlink(avaipath);
rename(temppath,avaipath);
// unlink(avaipath);
}



int getAvai(char *newname)...{
int len;
FILE *fp;
char avaipath[PATH_MIN];
snprintf(avaipath,PATH_MIN,"%s/%d/%s",RECPATH,uid,AVAITAB);
fp=fopen(avaipath,"r");
if(fgets(newname,NEWNAMELEN,fp)==NULL) return -1;
newname[strlen(newname)]='

/**//********************************************
* restore.c
*********************************************/


#include"common.h"

uid_t uid;


int main(int ac,char *av[])...{
char flag[ac];
struct information infobuf;
FILE *fpi,*fpt,*fpa;
char infopath[PATH_MIN],temppath[PATH_MIN],avaipath[PATH_MIN];
char namepath[PATH_MAX],namebuf[NEWNAMELEN];
size_t infosize;
int i,count;
//initalization
uid=getuid();
snprintf(infopath,PATH_MIN,"%s/%d/%s",RECPATH,uid,INFOTAB);
fpi=fopen(infopath,"r");
if(fpi==NULL) oope(infopath);
snprintf(temppath,PATH_MIN,"%s/%d/%s",RECPATH,uid,INFOTMP);
fpt=fopen(temppath,"w");
if(fpt==NULL) oope(temppath);
snprintf(avaipath,PATH_MIN,"%s/%d/%s",RECPATH,uid,AVAITAB);
fpa=fopen(avaipath,"a");
if(fpa==NULL) oope(avaipath);
bzero(flag,ac);
infosize=sizeof(struct information);
count=0;
//begin to restore

while(fread(&infobuf,infosize,1,fpi)>0)...{

for(i=1;i<ac;i++) ...{
if(flag[1]!=0) continue;
if(strcmp(av[i],infobuf.newname)==0) break;
}

if(i==ac)...{ //not found information
fwrite(&infobuf,infosize,1,fpt);
}

else...{ //found information
strcpy(namebuf,infobuf.newname);
snprintf(namepath,PATH_MAX,"%s/%d/%s",RECPATH,uid,infobuf.newname);
#ifdef TESTRES
printf("rename from '%s' to '%s' ",namepath,infobuf.oldpath);
#endif
if(rename(namepath,infobuf.oldpath)!=0)
oope(infobuf.oldpath);
fprintf(fpa,"%s ",namebuf);
}
}

while(fread(&infobuf,infosize,1,fpi)>0) ...{
fwrite(&infobuf,infosize,1,fpt);
}
remove(infopath);
rename(temppath,infopath);
fclose(fpi);
fclose(fpa);
}











/**//**********************************************************
* setup.c
***********************************************************/

#include"common.h"

void commUser();
void superUser();



void superUser(uid_t uid)...{
if(link(OCOMMDEL,NCOMMDEL)!=0) oope("copy command recycle.delete error: ");
if(link(OCOMMSHO,NCOMMSHO)!=0) oope("copy command recycle.show error: ");
if(link(OCOMMRES,NCOMMRES)!=0) oope("copy command recycle.restore error: ");
if(link(OCOMMSET,NCOMMSET)!=0) oope("copy command recycle.setup error: ");
if(link(OCOMMUNI,NCOMMUNI)!=0) oope("copy command recycle.uninstall error: ");
if(link(OCOMMCLE,NCOMMCLE)!=0) oope("copy command recycle.clear error: ");
chmod(NCOMMUNI,0700);
if(mkdir(RECPATH,0777)!=0) oope("make diretory /.recycle error: ");
chmod(RECPATH,0777);
commUser(uid);
}


void commUser(uid_t uid)...{
char path[PATH_MIN];
int fd;
FILE *fp;
int avai;
//make user's private directory
snprintf(path,PATH_MIN,"%s/%d",RECPATH,uid);
if(mkdir(path,0700)!=0) oope("make user's directory error: ");
chdir(path);
//make user's infotable file
fd=open(INFOTAB,O_WRONLY|O_CREAT|O_EXCL,0700);
if(fd==-1) oope("create infomation table error: ");
close(fd);
//make user's avaitable file
fp=fopen(AVAITAB,"w");
if(fp==NULL) oope("can not use stream: ");
for(avai=0;avai<AVAIMAX;avai++)
if(fprintf(fp,"%d ",avai)<0) perror("write avaitab error: ");
fclose(fp);
}


int main(int ac,char *av[])...{
uid_t uid;
uid=getuid();
if(uid==0) superUser(uid);
else commUser(uid);
puts("setup finished");
}


/**//*************************************
* show.c
*************************************/

#include"common.h"

#define MAXLINE 15

short showtime=0;
uid_t uid;
char infopath[PATH_MIN];



int main(int ac,char *av[])...{
FILE *fp;
int n;
struct information info;
uid=getuid();
snprintf(infopath,PATH_MIN,"%s/%d/%s",RECPATH,uid,INFOTAB);
fp=fopen(infopath,"r");
if(fp==NULL) oope(infopath);
if(ac!=1) showtime=1;
n=0;

while(fread(&info,sizeof(info),1,fp)>0)...{
printf("%s %s ",info.newname,info.oldpath);
if(showtime) printf("%s",ctime(&info.time));
else printf(" ");
}
}



/**//****************************************************
* uninstall.c
****************************************************/

#include"common.h"


int main()...{
char c;
puts("warning: if you want to uninstall recycle, all files belong to it will faild.");
puts("input y/Y to ensure unistall, other will exit uninstall");
c=getchar();
if(c!='y' && c!='Y') exit(0);
unlink(NCOMMDEL);
unlink(NCOMMSHO);
unlink(NCOMMRES);
unlink(NCOMMUNI);
unlink(NCOMMSET);
unlink(NCOMMCLE);

if(fork()==0) ...{execlp("rm","rm","-rf",RECPATH,NULL);}

else ...{wait(NULL);puts("uninstall success");}
}


/**//*******************************
* make.sh
*********************************/


#!/bin/tcsh
gcc -o recycle.setup setup.c
gcc -o recycle.uninstall uninstall.c
gcc -o recycle.del del.c
gcc -o recycle.show show.c
gcc -o recycle.restore restore.c
gcc -o recycle.clear clear.c
recycle.uninstall
./recycle.setup
