Algorithm 版 (精华区)

发信人: Lerry (想不开·撞树), 信区: Algorithm
标  题: parkerd.c
发信站: 哈工大紫丁香 (2002年06月09日21:27:58 星期天), 站内信件

/*
 *    NoseyParker, the search engine for FTP archives
 *    Copyright (C) 1993-96 by Jiri A. Randus
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *    The author can be reached as follows:
 *      Internet:   <Jiri.Randus@vslib.cz>
 *      Phone:      ++42 48 5227374
 *      SnailMail:  Jiri Randus
 *                  KIN HF TU v Liberci
 *                  Halkova 6
 *            46117 Liberec
 *                  Czech Republic
 */
#define PARKERD_VERSION   "3.23"
#include "parker.h"
char ThisHost[MAX];
struct FileDBList {char *file; struct FileDBList *next;};
struct FileDBList *FDBhead=NULL;
struct CMDS {char *command; void (*cmd)(char *); char *help;} Cmds[];
FILE *idx;
char *exline[MAX]={ GREP, "-i", NULL, NULL};
int Counter=-1;
char RestartK[MAX]="";
int SearchFiles=0;
char FilesPath[MAX];
char FilesFile[MAX];
char FilesSize[MAX];
void Quit(char *);
void Chop(char *s)
{
  char *ptr;
  if((ptr=strchr(s,CR))!=NULL) *ptr='\0';
  if((ptr=strchr(s,LF))!=NULL) *ptr='\0';
}
void main(void)
{
  int i;
  char Command[MAX];
  dup2(0,1);
  dup2(0,2);
  gethostname(ThisHost,MAX);
#ifndef DEBUG
  if(chdir(PARKER_HOME)) {
    printf("550 Can't chdir to PARKER_HOME %s\n", PARKER_HOME);
    exit(101);
  }
#endif
  setlinebuf(stdin);
  setlinebuf(stdout);
  printf("220 This is %s, NoseyParker %s, Server V%s\n",ThisHost,VERSION, PA
RKERD_VERSION);
  while(1)
  {
#ifndef DEBUG
    if(chdir(PARKER_HOME)) {
      printf("550 Can't chdir to PARKER_HOME %s\n", PARKER_HOME);
      exit(101);
    }
#endif
    fflush(stdout);
    fgets(Command,MAX,stdin);
    if(feof(stdin)) Quit(" ");
    Chop(Command);
    for(i=0;Cmds[i].command;i++)
    if(!strncasecmp(Cmds[i].command,Command,strlen(Cmds[i].command))) {
      Cmds[i].cmd(Command);
      break;
    }
    if(!Cmds[i].command) puts("500 Command not understood");
  }
}
void Quit(char *CLine)
{
  *CLine='\0';
  printf("221 %s closing connection\n",ThisHost);
  exit(0);
}
void Reset(char *CLine)
{
  *CLine='\0';
  Counter=-1;
  *RestartK='\0';
  SearchFiles=0;
  puts("205 Reset OK");
}
void Files(char *CLine)
{
  *CLine='\0';
  SearchFiles=1;
  printf("206 FileSeach OK\n");
}
void Hosts(char *CLine)
{
  *CLine='\0';
  SearchFiles=0;
  printf("207 HostsSearch OK\n");
}
void Hits(char *CLine)
{
  char *ptr;
  if((ptr=strchr(CLine,' '))!=NULL) sscanf(ptr,"%d",&Counter);
  else Counter=-1;
  if(Counter==0) Counter=-1;
  printf("208 Ok, the hitcounter set to %d %s\n",Counter,
    (Counter==-1)?"(Unlimited)":"");
}
void Restart(char *CLine)
{
  char *ptr;
  if((ptr=strchr(CLine,' '))!=NULL)
  {
    strcpy(RestartK,ptr+1);
    printf("209 Ok, the restart point set to %s\n",RestartK);
  }
  else
  {
    *RestartK='\0';
    printf("209 Ok, the restart point unset\n");
  }
}
void TopDir(char *CLine)
{
  char *ptr;
  FILE *f;
  char genbuf[MAX];
  if(!(ptr=strchr(CLine,' '))) {
    printf("501 Ho host specified\n");
    return;
  }
  sprintf(genbuf,"%s/%sDB.%s.topdir",PARKER_HOME,SEEDDB,ptr+1);
  if(!(f=fopen(genbuf,"r"))) {
    printf("554 Cannot open %s\n",genbuf);
    return;
  }
  fgets(genbuf,MAX,f);
  fclose(f);
  Chop(genbuf);
  printf("216 %s\n",genbuf);
  return;
}
void Status(char *CLine)
{
  FILE *status;
  char buf[MAX];
  *CLine='\0';
  if(!(status=fopen(STATUSTXT,"r"))) {
    puts("550 No status available");
    return;
  }
  while(!feof(status))
  {
    fgets(buf,MAX,status);
    if(feof(status)) break;
    printf("211-%s",buf);
  }
  fclose(status);
  puts("211 *** end of status ***");
}
void Help(char *CLine)
{
  int i;
  *CLine='\0';
  for(i=0;Cmds[i].command;i++)
    printf("214- %s\n",Cmds[i].help);
  puts("214-");
  puts("214 end of help");
}
void Egrep(char *seekfor, int count)
{
  char genbuf[MAX];
  char path[MAX];
  int j;
  char *ptr;
  int fd[2];
  FILE *pathdb, *dbfile, *egrep;
  DIR *serverdir;
  struct dirent *dirp;
  long offset,oldoffset;
  pid_t child;
  if(!(serverdir=opendir("."))) {puts("550 Can't open dir");exit(106);}
  j=3;
  pipe(fd);
  if((child=fork())==-1) {puts("545 Fork failed"); return;}
  if(!child) {
    dup2(fd[0],0);
    close(fd[0]);
    close(fd[1]);
    exline[2]=seekfor;
    exline[3]=NULL;
    execve(GREPPATH, exline, NULL);
    perror("500 Grep exec failed");
    exit(1);
  }
  close(fd[0]);
  if(!(pathdb=fopen(SEEDPATHDB,"r"))) {
    perror("550 Cannot open SEEDPATHDB");
    return;
  }
  if(!(egrep=fdopen(fd[1],"w"))) {
    perror("555 Cannot fdopen pipe to egrep");
    return;
  }
  else while((dirp=readdir(serverdir))!=NULL)
  {
    oldoffset=-1;
    if((strncmp(dirp->d_name,EXCLUDEFILES,strlen(EXCLUDEFILES))) &&
       (strcmp(dirp->d_name,CORE)) &&
       (*dirp->d_name!='.') &&
       ((dbfile=fopen(dirp->d_name,"r"))!=NULL))
    while(1) {
      fgets(genbuf,MAX,dbfile);
      if(feof(dbfile)) break;
      ptr=NULL;
      ptr=strrchr(genbuf,' ');
      *ptr++='\0';
      sscanf(ptr,"%ld",&offset);
      if(offset==-1) fprintf(egrep,"200-%s:%s\n",dirp->d_name,genbuf);
      else if(offset==oldoffset)
        fprintf(egrep,"200-%s:%s/%s\n",dirp->d_name,path,genbuf);
      else {
        oldoffset=offset;
        fseek(pathdb,offset,SEEK_SET);
        fgets(path,MAX,pathdb);
        Chop(path);
        if(!path[1]) *path='\0';
        if(!(fprintf(egrep,"200-%s:%s/%s\n",dirp->d_name,path,genbuf)))
        { /* Did the client close the connection prematurely? */
          break;
        }
      }
    }
  }
  fclose(egrep);
  close(fd[1]);
  fclose(pathdb);
  closedir(serverdir);
  kill(child, SIGTERM);
  wait(NULL);
  puts("\n257 End of E search");
}
void LoadFDB(void)
{
  FILE *filedb;
  char buffer[MAX];
  struct FileDBList *FDBptr;
  struct FileDBList *FDBptr2;
  if(!(filedb=fopen(SEEDFILEDB,"r"))) {
    perror("550 Cannot open SEEDFILEDB");
    exit(101);
  }
  while(1)
  {
    fgets(buffer,MAX,filedb);
    if(feof(filedb)) break;
    Chop(buffer);
    FDBptr=malloc(sizeof(struct FileDBList));
    FDBptr->file=strdup(buffer);
    FDBptr->next=NULL;
    if(!FDBhead) FDBhead=FDBptr;
    else {
      for(FDBptr2=FDBhead;FDBptr2->next;FDBptr2=FDBptr2->next);
      FDBptr2->next=FDBptr;
    }
  }
  fclose(filedb);
}
int FindBItem(FILE *idx, char *key, struct DiskBTreeHead *head)
{
  int i;
  while(1)
  {
    fread(head, sizeof(struct DiskBTreeHead), 1, idx);
    if(!strncasecmp(key, head->key, KEYSIZE)) return(S_OK);
    for(i=0;i<B;i++)
    {
      if((*head->treerefs[i].top) &&
         (strncasecmp(key,head->treerefs[i].top,KEYSIZE)<=0))
      {
        fseek(idx, head->treerefs[i].offset, SEEK_SET);
        break;
      }
    }
    if(i==B) return(S_ERROR);
  }
}
void DumpRefs(FILE *idx, long numrefs, long restart)
{
  union DiskTripleRef ref;
  struct FileDBList *FDBptr;
  FILE *db;
  char buffer[MAX];
  long itemcnt;
  db=NULL;
  FDBptr=NULL;
  itemcnt=0L;
  while(numrefs--)
  {
    fread(&ref, sizeof(union DiskTripleRef), 1, idx);
    if(ref.file.mark==BTREEENDMARK) {
      if(db) fclose(db);
      FDBptr=FDBhead;
      while((FDBptr)&&(ref.file.fileno--)) FDBptr=FDBptr->next;
      if(!FDBptr) {fprintf(stderr,"Error: out of FDB\n");exit(105);}
      db=fopen(FDBptr->file,"r");
      continue;
    }
    if(!restart || (restart<itemcnt)) {
      fseek(db, ref.offset, SEEK_SET);
      fgets(buffer,MAX,db);
      /* It ends with CR/LF; cut off when interrupted */
      if(!printf("%ld|%s:%s",itemcnt,FDBptr->file,buffer)) break;
    }
    itemcnt++;
  }
  fclose(db);
}
void OpenIdx(void)
{
  switch(SearchFiles)
  {
    case 0:
      if(!(idx=fopen(SEEDBTREE,"r"))) {
        perror("550 Cannot open SEEDBTREE");
        exit(102);
      }
      break;
    case 1:
      if(!(idx=fopen(SEEDBTREEFILES,"r"))) {
        perror("550 Cannot open SEEDBTREEFILES");
        exit(102);
      }
      break;
    default:
      break;
  }
}
void DumpBTree(long restart)
{
  struct DiskBTreeHead head;
  if(!FDBhead) LoadFDB();
  if(!idx) {fprintf(stderr,"ERROR ! Idx not open !\n");return;}
  fread(&head, sizeof(struct DiskBTreeHead), 1, idx);
  DumpRefs(idx, head.numrefs, restart);
  fclose(idx);
  idx=NULL;
}
int DumpFilesSearch(char *path, char *file, int *count)
{
  char *size;
  char *ptr, *ptr1;
  int rc;
  if(!path) {
    /* a call to flush down the buffers */
    if(*FilesFile) {
      printf("200-*:%s%s%s",FilesPath,(strcmp(FilesPath,"/"))?"/":"",
        FilesFile);
      if(FilesFile[strlen(FilesFile)-1]!='/') printf(" %s", FilesSize);
      printf("\n");
    }
    return(1);
  }
  /* Cut off the size/date from the filename */
  if((size=strchr(file,' '))!=NULL) *size++='\0';
  /* Cut off the date from the size */
  if(size &&((ptr=strchr(size,' '))!=NULL)) *ptr='\0';
  if(!strcasecmp(file,FilesFile) && ((file[strlen(file)-1]=='/') ||
    !size || !strcasecmp(size,FilesSize)))
  {
    /* if counted, increase the counter */
    if(*count!=-1) (*count)++;
    /* Compare the paths */
    ptr1=path+strlen(path)-1;
    ptr=FilesPath+strlen(FilesPath)-1;
    while(ptr>FilesPath)
    if(tolower(*ptr)!=tolower(*ptr1)) break; else {ptr--;ptr1--;}
    if(ptr!=FilesPath) strcpy(FilesPath,ptr+1);
    rc=1;
  }
  else {
    /* a new file */
    if(*FilesFile) {
      rc=printf("200-*:%s%s%s",FilesPath,(strcmp(FilesPath,"/"))?"/":"",
        FilesFile);
    if(FilesFile[strlen(FilesFile)-1]!='/') printf(" %s", FilesSize);
    printf("\n");
    } else rc=1;
    strcpy(FilesPath,path);
    strcpy(FilesFile,file);
    if(size) strcpy(FilesSize,size); else *FilesSize='\0';
  }
  return(rc);
}
void egrepDB(char *key,char *seekfor,int count, long restart)
{
  char genbuf[MAX];
  char path[MAX];
  char lastoff[MAX];
  char *ptr,*ptr1,*ptr3;
  int intoegrep[2], outofegrep[2];
  FILE *egrep,*pathdb;
  long offset;
  pid_t child1, child2;
  *FilesPath=*FilesFile=*FilesSize='\0';
  pipe(intoegrep);
  pipe(outofegrep);
  fflush(stdout);
  if((child1=fork())==-1) {puts("545 Btree fork failed"); return;}
  if(!child1) {
    dup2(intoegrep[1],1);
    close(intoegrep[0]);
    close(intoegrep[1]);
    close(outofegrep[0]);
    close(outofegrep[1]);
    DumpBTree(restart);
    exit(0);
  }
  if((child2=fork())==-1) {puts("545 Egrep fork failed"); return;}
  if(!child2) {
    dup2(intoegrep[0],0);
    close(intoegrep[0]);
    close(intoegrep[1]);
    dup2(outofegrep[1],1);
    close(outofegrep[0]);
    close(outofegrep[1]);
    exline[2]=seekfor;
    execve(GREPPATH, exline, NULL);
    perror("Grep exec failed");
    exit(1);
  }
  close(outofegrep[1]);
  close(intoegrep[0]);
  close(intoegrep[1]);
  if(!(egrep=fdopen(outofegrep[0],"r")))
  {
    puts("555 Cannot open pipe thru fdopen");
    return;
  }
  if(!(pathdb=fopen(SEEDPATHDB,"r")))
  perror("550 Cannot open SEEDPATHDB");
  else {
    while((count==-1) || count)
    {
      count--;
      fgets(genbuf,MAX,egrep);
      if(feof(egrep)) break;
      ptr=NULL;
/* Cut off the restart code & the host name */
      ptr3=strchr(genbuf,':');
      if(!ptr3) return;
      *ptr3++='\0';
/* Cut off the restart code */
      ptr1=strchr(genbuf,'|');
      if(!ptr1) continue;
      *ptr1++='\0';
/* Cut off the pathDB offset */
      ptr=strrchr(ptr3,' ');
      if(ptr) {
        *ptr++='\0';
        sscanf(ptr,"%ld",&offset);
      }
      else offset=-1;
      strcpy(lastoff,genbuf);
/* offset  missing ? */
      if(offset==-1) printf("200-%s:%s\n",ptr1,ptr3);
      else {
        fseek(pathdb,offset,SEEK_SET);
        fgets(path,MAX,pathdb);
        Chop(path);
        if(!SearchFiles) { /* Sort by hosts */
          /* Did the client close the connection prematurely? */
          if(!printf("200-%s:%s/%s\n",ptr1,path[1]?path:"",ptr3)) break;
        }
        else if(!DumpFilesSearch(path,ptr3,&count)) break;
      }
    }
    if(SearchFiles) DumpFilesSearch(NULL,NULL,NULL);
    fclose(pathdb);
    fclose(egrep);
    if(!count)
    printf("258 End of S/W search (\"%s%s\" to continue)\n",key,lastoff);
    else printf("257 End of S/W search\n");
  }
/* Paranoia precaution */
  kill(child1, SIGTERM);
  kill(child2, SIGTERM);
  wait(NULL);
  wait(NULL);
  close(intoegrep[0]);
  close(intoegrep[1]);
  close(outofegrep[0]);
  close(outofegrep[1]);
}
void Substring(char *CLine)
{
  char *p;
  char pattern[MAX];
  char *c;
  char z[2];
  char key[KEYSIZE+1];
  struct DiskBTreeHead head;
  long minrefs;
  long bestoffset;
  long Res;
  int len;
  int i=0;
#ifndef DEBUG
  if(chdir(SEEDDB)) {printf("550 Can't chdir to SEEDDB %s\n",SEEDDB);exit(10
6);}
#endif
  if(!(c=strchr(CLine,' '))) {
    puts("501 No lookup key");
    return;
  }
  while(*c && (*c==' ')) c++;
  if(!*c) {
    puts("501 No lookup key");
    return;
  }
  Chop(c);
  if(strlen(c)<MINLENGTHOFSEARCH)
  {
    puts("504 The query is too short");
    return;
  }
  if(!idx) OpenIdx();
  minrefs=-1;
  bestoffset=0L;
  if(!*RestartK) {
    len=strlen(c)-KEYSIZE;
    for(i=0;i<=len;i++)
    {
      rewind(idx);
      if(FindBItem(idx, &c[i], &head)!=S_OK) {
        bestoffset=-1;
        break;
      }
      if((minrefs==-1) || (minrefs>head.numrefs)) {
        strncpy(key,&c[i],KEYSIZE);
        minrefs=head.numrefs;
        bestoffset=ftell(idx)-sizeof(struct DiskBTreeHead);
      }
    }
    if(bestoffset==-1) {
      Log("Unsuccessful substring search for %s",c,NULL);
      puts("125 Dummy searching");
      puts("257 End of dummy search");
      return;
    }
    key[KEYSIZE]='\0';
    Res=0L;
  }
  else /* Restarting the search */
  {
    strncpy(key,RestartK,KEYSIZE);
    key[KEYSIZE]='\0';
    sscanf(&RestartK[KEYSIZE],"%ld",&Res);
    rewind(idx);
    if(FindBItem(idx, key, &head)!=S_OK) {
      Log("Unsuccessful restarted substring search for %s",c,NULL);
      puts("125 Dummy restarted searching");
      puts("257 End of dummy search");
      return;
    }
    bestoffset=ftell(idx)-sizeof(struct DiskBTreeHead);
  }
  fseek(idx, bestoffset, SEEK_SET);
  printf("125 Searching (key=`%s', restart at %ld)\n",key,Res);
  fflush(stdout);
  Log("Substring search for %s",c,NULL);
  p=c;
  strcpy(pattern,"^[^:]*:.*");
  z[1]='\0';
  while(*p)
  {
    *z=*p++;
    if(strchr(SPECIALREGCHAR,*z)) strcat(pattern,"\\");
    strcat(pattern,z);
  }
  strcat(pattern,"[^/]*/? ");
  egrepDB(key,pattern,Counter,Res);
  *RestartK='\0';
}
void Wildcards(char *CLine)
{
  char *p;
  char pattern[MAX];
  char *c;
  char z[2];
  char key[KEYSIZE+1];
  struct DiskBTreeHead head;
  long minrefs;
  long bestoffset;
  int len;
  int i=0;
  long Res;
#ifndef DEBUG
  if(chdir(SEEDDB)) {printf("550 Can't chdir to SEEDDB %s\n",SEEDDB);exit(10
6);}
#endif
  if(!(c=strchr(CLine,' '))) {
    puts("501 No lookup key");
    return;
  }
  while(*c && (*c==' ')) c++;
  if(!*c) {
    puts("501 No lookup key");
    return;
  }
  Chop(c);
  if(!idx) OpenIdx();
  minrefs=-1;
  bestoffset=0L;
  len=strlen(c)-KEYSIZE;
  if(!*RestartK) {
    for(i=0;i<=len;i++)
    {
      if(!strchr(SPECIALWILDCHAR,c[i]) &&
         !strchr(SPECIALWILDCHAR,c[i+1]) &&
         !strchr(SPECIALWILDCHAR,c[i+2]))
      {
        rewind(idx);
        if(FindBItem(idx, &c[i], &head)!=S_OK) {
          bestoffset=-1;
          break;
        }
        if((minrefs==-1) || (minrefs>head.numrefs)) {
          strncpy(key,&c[i],KEYSIZE);
          minrefs=head.numrefs;
          bestoffset=ftell(idx)-sizeof(struct DiskBTreeHead);
        }
      }
    }
    if(bestoffset==-1) {
      Log("Unsuccessful wildcards search for %s",c,NULL);
      puts("125 Dummy searching");
      puts("257 End of dummy search");
      return;
    }
    if(minrefs==-1) {
      puts("501 Wildcards must contain at least 3 subsequent ASCII character
s");
      return;
    }
    Res=0L;
  }
  else /* Restarting the search */
  {
    strncpy(key,RestartK,KEYSIZE);
    key[KEYSIZE]='\0';
    sscanf(&RestartK[KEYSIZE],"%ld",&Res);
    rewind(idx);
    if(FindBItem(idx, key, &head)!=S_OK) {
      Log("Unsuccessful restarted substring search for %s",c,NULL);
      puts("125 Dummy restarted searching");
      puts("257 End of dummy search");
      return;
    }
    bestoffset=ftell(idx)-sizeof(struct DiskBTreeHead);
  }
  fseek(idx, bestoffset, SEEK_SET);
  key[KEYSIZE]='\0';
  printf("125 Searching (key=`%s', restart at %ld)\n",key,Res);
  fflush(stdout);
  Log("Wildcards search for %s",c,NULL);
  p=c;
  strcpy(pattern,"^[^:]*:");
  z[1]='\0';
  while(*p)
  {
    if(*p=='?') strcat(pattern,"[^/]");
    else if(*p=='*') strcat(pattern,"[^/]*");
    else {
      *z=*p;
      if(strchr(SPECIALREGCHAR,*z)) strcat(pattern,"\\");
      strcat(pattern,z);
    }
    p++;
  }
  strcat(pattern,"/? ");
  egrepDB(key,pattern,Counter,Res);
  *RestartK='\0';
}
void AskEgrep(char *CLine)
{
  char *p,*ptr;
  char *c;
  int okegrep;
  char buf[MAX];
#ifndef DEBUG
  if(chdir(SEEDDB)) {printf("550 Can't chdir to SEEDDB %s\n",SEEDDB);exit(10
6);}
#endif
  if(!(c=strchr(CLine,' '))) {
    puts("501 No lookup key");
    return;
  }
  while(*c && (*c==' ')) c++;
  if(!*c) {
    puts("501 No lookup key");
    return;
  }
  Chop(c);
  if(strlen(c)<MINLENGTHOFSEARCH) {
    puts("504 The query is too short");
    return;
  }
  p=SPECIALREGCHAR;
  okegrep=0;
  while(*p)
  {
    ptr=c-1;
    while((ptr=strchr(ptr+1,*p))!=NULL) okegrep++;
    p++;
  }
  if(okegrep<2) {
    puts("504 Not a regular expression. This doesn't look like an Egrep quer
y.");
    return;
  }
  if(*c=='^') { /* the regexp starts with carrot -> gotta insert 200- */
    sprintf(buf,"^200-%s",c+1);
    strcpy(c,buf);
  }
  puts("125 Searchin  fflush(stdout);
  Log("Egrep search for %s",c,NULL);
  Egrep(c, Counter);
}
struct CMDS Cmds[]=
{
  "help", Help,       "help              Display this message",
  "reset", Reset,     "reset             Restore default settings",
  "files", Files,     "files             Sort the result by files",
  "hosts", Hosts,     "hosts             Sort the result by hosts",
  "hits", Hits,       "hits <number>     Set the hit count",
  "restart", Restart,     "restart <rstkey>  Enter the restart key",
  "substring", Substring, "substring <key>   Perform a substring search",
  "wildcards", Wildcards, "wildcards <key>   Perform a wildcards search",
  "egrep", AskEgrep,      "egrep <key>       Perform an egrep search",
  "status", Status,   "status            Display the database status",
  "topdir", TopDir,   "topdir <host>     Display the top dir of the hierarch
y",
  "quit", Quit,       "quit              Disconnect from the server",
  NULL, NULL, NULL
};

--
当一个女孩儿觉得她不太容易了解那个男人的时候,她会爱他。

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 天外飞仙]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:211.342毫秒