/* MikMod example player (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for complete list. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /*============================================================================== $Id: marchive.c,v 1.4 2003/09/25 16:38:42 raph Exp $ Archive support These routines are used to detect different archive/compression formats and decompress/de-archive the mods from them if necessary. ==============================================================================*/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_UNISTD_H #include #endif #ifndef HAVE_FNMATCH_H #include "fnmatch.h" #else #include #endif #include #include #include #include #include #include #include #if !defined(__OS2__)&&!defined(__EMX__) #ifdef HAVE_FCNTL_H #include #endif #include #include #endif #include #include "player.h" #include "mutilities.h" /*========== Archives */ /* The following table describes how MikMod should deal with archives. The first two fields are for identification. The code will consider that a given file is a recognized archive if a signature is found at a fixed location in the file. The first field is the offset into the archive of the signature, and the second field points to the signature to check (an 8-bit string). The third field contains the name of the program to invoke to list and extract the archive. The fourth and fifth fields are the first two arguments which will be given to the program when it is invoked to extract a file from the archive. The command which will be invoked is : archiver_command extract_arg1 extract_arg2 archive_file file_to_extract If only one argument is needed, set extract_arg2 to NULL. The sixth and seventh fields are the first two arguments which will be given to the program when it is invoked to list the contents of the archive. The command which will be invoked is : archiver_command list_arg1 list_arg2 archive_file If only one argument is needed, set list_arg2 to NULL. For the special case of mono-file archives (gzip and bzip2 compressed files, for example), set *both* arguments to NULL. In this case, the code will determine the contents of the file without having to invoke the list function of the archiver. This is necessary since bzip2 has no list function, and the only way to get the archive contents is to test it, which can be a really slow process. The last field is the column, in the archive listing output, where the filenames begin (starting from zero for the leftmost column). A good archiver will put them last on line, so they can embed spaces and be as long as necessary. The constant MAXCOLUMN later should be set to the highest column number found in the table. When adding new entries to the table, be sure to compile a debug version, it will check at runtime if your MAXCOLUMN value is correct. The OS/2 variant of the table is shorter, since there is no need to make the two extract and list arguments separate, so only one string is needed in both cases, and for mono-file archives, the listing arguments string is set to NULL. */ typedef struct { int location; /* <0 -> file extensions are checked */ char *marker; char *command; #if !defined(__OS2__)&&!defined(__EMX__) char *extrargs,*extrargs2; char *listargs,*listargs2; #else char *extrargs; char *listargs; #endif unsigned int nameoffset; } ARCHIVE; /* use similar signature idea to "file" to see what format we have... */ static char pksignature[]={'P','K',0x03,0x04,0}; static char zoosignature[]={0xdc,0xa7,0xc4,0xfd,0}; static char rarsignature[]={'R','a','r','!',0}; static char gzsignature[]={0x1f,0x8b,0}; static char bzip2signature[]={'B','Z','h',0}; static char tarsignature[]={'u','s','t','a','r',0}; #if !defined(__OS2__)&&!defined(__EMX__) static char lhsignature[]={'-','l','h',0}; static char lzsignature[]={'-','l','z',0}; #endif /* interesting file extensions */ #if !defined(__OS2__)&&!defined(__EMX__) static char targzextension[]=".TAR.GZ .TAZ .TGZ"; static char tarbzip2extension[]=".TAR.BZ2 .TBZ .TBZ2"; #endif static ARCHIVE MA_archiver[]={ /* location, marker, command, extract and view args, filenames column */ #if !defined(__OS2__)&&!defined(__EMX__) /* PKzip archive */ { 0, pksignature, "unzip", "-pqq", NULL, "-vqq", NULL, 58}, /* zoo */ { 20, zoosignature, "zoo", "xpq", NULL, "lfq", NULL, 0}, /* rar */ { 0, rarsignature, "unrar", "p", "-inul", "v", "-c-", 1}, /* lharc */ { 2, lhsignature, "lha", "pq", NULL, "vvq", NULL, 0}, { 2, lzsignature, "lha", "pq", NULL, "vvq", NULL, 0}, /* tar */ { 257,tarsignature, "tar", "-xOf", NULL, "-tf", NULL, 0}, /* tar.gz */ { -1, targzextension, "tar", "-xOzf", NULL, "-tzf", NULL, 0}, /* tar.bz2 */ { -1, tarbzip2extension, "tar", "--use-compress-program=bzip2", "-xOf", "--use-compress-program=bzip2", "-tf", 0}, /* gzip */ { 0, gzsignature, "gzip", "-dqc", NULL, NULL, NULL, 0}, /* bzip2 */ { 0, bzip2signature, "bzip2", "-dc", NULL, NULL, NULL, 0}, /* needed to end the array... */ { 0, NULL, NULL, NULL, NULL, NULL, NULL, 0} #else /* PKzip archive */ { 0, pksignature, "unzip", "-pqq", "-lqq", 41}, /* zoo */ { 20, zoosignature, "zoo", "xpq", "lq", 47}, { 0, rarsignature, "unrar", "p -inul", "v -c-", 1}, /* tar */ { 257,tarsignature, "tar", "-xOf", "-tRf", 16}, /* gzip */ { 0, gzsignature, "gzip", "-dqc", NULL, 27}, /* bzip2 */ { 0, bzip2signature, "bzip2", "-dc", NULL, 0}, /* needed to end the array... */ { 0, NULL, NULL, NULL, NULL, 0} #endif }; /* rightmost column position */ #if defined(__OS2__)||defined(__EMX__) #define MAXCOLUMN 47 #else #define MAXCOLUMN 58 #endif /* largest signature length */ #define MAXSIGNATURE 16 /* module filenames patterns */ static CHAR* modulepatterns[]={ "*.669", "*.[Aa][Mm][Ff]", "*.[Aa][Pp][Uu][Nn]", "*.[Dd][Ss][Mm]", "*.[Ff][Aa][Rr]", "*.[Gg][Dd][Mm]", "*.[Ii][Mm][Ff]", "*.[Ii][Tt]", "*.[Mm][Ee][Dd]", "*.[Mm][Oo][Dd]", "*.[Mm][Tt][Mm]", "*.[Nn][Ss][Tt]", /* noisetracker */ "*.[Ss]3[Mm]", "*.[Ss][Tt][Mm]", "*.[Ss][Tt][Xx]", "*.[Uu][Ll][Tt]", "*.[Uu][Nn][Ii]", "*.[Xx][Mm]", NULL }; /* On the Amiga (Aminet) the module type seems often to be used as prefix */ static CHAR* prefixmodulepatterns[]={ "669.*", "[Aa][Mm][Ff].*", "[Aa][Pp][Uu][Nn].*", "[Dd][Ss][Mm].*", "[Ff][Aa][Rr].*", "[Gg][Dd][Mm].*", "[Ii][Mm][Ff].*", "[Ii][Tt].*", "[Mm][Ee][Dd].*", "[Mm][Oo][Dd].*", "[Mm][Tt][Mm].*", "[Nn][Ss][Tt].*", "[Ss]3[Mm].*", "[Ss][Tt][Mm].*", "[Ss][Tt][Xx].*", "[Uu][Ll][Tt].*", "[Uu][Nn][Ii].*", "[Xx][Mm].*", NULL }; #if !defined(__OS2__)&&!defined(__EMX__) /* Drop all root privileges we might have */ BOOL DropPrivileges(void) { if (!geteuid()) { if (getuid()) { /* we are setuid root -> drop setuid to become the real user */ if (setuid(getuid())) return 1; } else { /* we are run as root -> drop all and become user 'nobody' */ struct passwd *nobody; int uid; if (!(nobody=getpwnam("nobody"))) return 1; /* no such user ? */ uid=nobody->pw_uid; if (!uid) /* user 'nobody' has root privileges ? weird... */ return 1; if (setuid(uid)) return 1; } } return 0; } #endif /* Determines if a filename matches a module filename pattern */ static BOOL MA_isModuleFilename(CHAR* filename) { int t; for (t=0;modulepatterns[t];t++) if (!fnmatch(modulepatterns[t],filename,FNM_NOESCAPE)) return 1; return 0; } /* The same, but also checks for prefix names */ static BOOL MA_isModuleFilename2(CHAR* filename) { int t; /* a filename may not start with '-' since this could be abused to feed another option to the archiver */ if (filename[0]=='-') return 0; for (t=0;modulepatterns[t];t++) if (!fnmatch(modulepatterns[t],filename,FNM_NOESCAPE)) return 1; for (t=0;prefixmodulepatterns[t];t++) if (!fnmatch(prefixmodulepatterns[t],filename,FNM_NOESCAPE)) return 1; return 0; } /* Determines if a filename extension matches an archive filename extension pattern */ static BOOL MA_MatchExtension(CHAR *archive,CHAR *ends) { CHAR *pos=ends; int nr,arch_nr; do { while ((*pos)&&(*pos!=' ')) pos++; nr=pos-ends; arch_nr=strlen(archive); while ((nr>0)&&(arch_nr>0)&& (toupper((int)archive[arch_nr-1])==*(ends+nr-1))) nr--,arch_nr--; if (nr<=0) return 1; pos++; ends=pos; } while (*(pos-1)); return 0; } /* Tests if 'filename' has the signature 'header-string' at offset 'header_location' */ static int MA_identify(CHAR* filename,int header_location,CHAR* header_string) { FILE *fp; CHAR id[MAXSIGNATURE+1]; if (header_location<0) { /* check extension of file rather than signature */ return MA_MatchExtension(filename,header_string); } else { /* check in-file signature */ #ifdef MIKMOD_DEBUG if (strlen(header_string)>MAXSIGNATURE) { fputs("Error: wrong constant MAXSIGNATURE in MikMod source\n",stderr); return 0; } #endif if (!(fp=fopen(filename,"rb"))) return 0; fseek(fp,header_location,SEEK_SET); if (!fread(id,strlen(header_string),1,fp)) { fclose(fp); return 0; } if (!memcmp(id,(CHAR*)header_string,strlen(header_string))) { fclose(fp); return 1; } fclose(fp); return 0; } } /* Extracts the file 'file' from the archive 'arc' */ CHAR* MA_dearchive(CHAR* arc,CHAR* file) { int t; CHAR *tmp_file; /* not an archive file... */ if ((!arc)||(!arc[0])) return strdup(file); tmp_file=get_tmp_name(); for (t=0;MA_archiver[t].command;t++) if (MA_identify(arc,MA_archiver[t].location,MA_archiver[t].marker)) { /* display "extracting" message, as this may take some time... */ display_extractbanner(); #if defined(__OS2__)||defined(__EMX__) /* extracting, the non-Unix way */ { CHAR command_buff[PATH_MAX<<1]; if (MA_archiver[t].listargs) /* multi-file archive : need to give filename to extract */ #ifdef HAVE_SNPRINTF snprintf(command_buff,PATH_MAX<<1,"%s %s \"%s\" \"%s\" >\"%s\"", MA_archiver[t].command,MA_archiver[t].extrargs, arc,file,tmp_file); #else sprintf(command_buff,"%s %s \"%s\" \"%s\" >\"%s\"", MA_archiver[t].command,MA_archiver[t].extrargs, arc,file,tmp_file); #endif else /* single-file archive */ #ifdef HAVE_SNPRINTF snprintf(command_buff,PATH_MAX<<1,"%s %s \"%s\" >\"%s\"", MA_archiver[t].command,MA_archiver[t].extrargs, arc,tmp_file); #else sprintf(command_buff,"%s %s \"%s\" >\"%s\"", MA_archiver[t].command,MA_archiver[t].extrargs, arc,tmp_file); #endif system(command_buff); } #else /* extracting, the Unix way */ { pid_t pid; int status,fd; switch (pid=fork()) { case -1: /* fork failed */ return NULL; break; case 0: /* fork succeeded, child process code */ /* if we have root privileges, drop them */ if (DropPrivileges()) exit(0); fd=open(tmp_file,O_WRONLY|O_CREAT|O_TRUNC,S_IREAD|S_IWRITE); if (fd!=-1) { char *argv[6]; int index=2; close(0);close(1);close(2); dup2(fd,1); signal(SIGINT,SIG_DFL);signal(SIGQUIT,SIG_DFL); argv[0]=MA_archiver[t].command; argv[1]=MA_archiver[t].extrargs; if (MA_archiver[t].extrargs2) argv[index++]=MA_archiver[t].extrargs2; argv[index++]=arc; if (MA_archiver[t].listargs) argv[index++]=file; argv[index++]=NULL; execvp(MA_archiver[t].command,argv); close(fd); unlink(tmp_file); } exit(0); break; default: /* fork succeeded, main process code */ waitpid(pid,&status,0); if (!WIFEXITED(status)) { unlink(tmp_file); return NULL; } break; } } #endif break; } return tmp_file; } /* Examines file 'filename' to add modules to the playlist 'pl' */ void MA_FindFiles(PLAYLIST* pl,CHAR* filename) { int t; int archive=0; CHAR string[PATH_MAX+(MAXCOLUMN+1)+1]; struct stat statbuf; #ifdef MIKMOD_DEBUG for (t=0;MA_archiver[t].command;t++) if (MA_archiver[t].nameoffset>MAXCOLUMN) { fputs("Error: wrong constant MAXCOLUMN in MikMod source\n",stderr); return; } #endif if (stat(filename,&statbuf)) return; /* File does not exist or not enough access rights */ if ((S_ISDIR(statbuf.st_mode))||(S_ISCHR(statbuf.st_mode)) #ifndef __EMX__ ||(S_ISBLK(statbuf.st_mode)) #endif ) return; /* Directories and devices can't be modules... */ /* if filename looks like a playlist, load as a playlist */ if (PL_isPlaylistFilename(filename)) if (PL_Load(pl,filename)) return; /* if filename looks like a module and not like an archive, don't try to unzip the file...*/ if (MA_isModuleFilename(filename)) { PL_Add(pl,filename,NULL,0,0); return; } for (t=0;MA_archiver[t].command;t++) if (MA_identify(filename,MA_archiver[t].location,MA_archiver[t].marker)) { archive=t+1; break; } if (archive--) { if (MA_archiver[t].listargs) { /* multi-file archive, need to invoke list function */ #if defined(__OS2__)||defined(__EMX__) /* Archive display, the non-Unix way */ FILE *file; #ifdef HAVE_SNPRINTF sprintf(string,PATH_MAX+(MAXCOLUMN+1)+1,"%s %s \"%s\"",MA_archiver[archive].command, MA_archiver[archive].listargs,filename); #else sprintf(string,"%s %s \"%s\"",MA_archiver[archive].command, MA_archiver[archive].listargs,filename); #endif #ifdef __WATCOMC__ file=_popen(string,"r"); #else file=popen(string,"r"); #endif fgets(string,PATH_MAX+MAXCOLUMN+1,file); while (!feof(file)) { string[strlen(string)-1]=0; if (!MA_archiver[archive].nameoffset) { for (t=0;string[t]!=' ';t++); string[t]=0; } if (MA_isModuleFilename2(string+MA_archiver[archive].nameoffset)) PL_Add(pl,(string+MA_archiver[archive].nameoffset),filename,0,0); fgets(string,PATH_MAX,file); } #ifdef __WATCOMC__ _pclose(file); #else pclose(file); #endif #else /* Archive display, the Unix way */ int fd[2]; if (!pipe(fd)) { pid_t pid; FILE *file; int status,s; switch (pid=fork()) { case -1: /* fork failed */ break; case 0: /* fork succeeded, child process code */ { char *argv[5]; int index=2; /* if we have root privileges, drop them */ if (DropPrivileges()) exit(0); close(0);close(1);close(2); dup2(fd[1],1);dup2(fd[1],2); signal(SIGINT,SIG_DFL);signal(SIGQUIT,SIG_DFL); argv[0]=MA_archiver[archive].command; argv[1]=MA_archiver[archive].listargs; if (MA_archiver[archive].listargs2) argv[index++]=MA_archiver[archive].listargs2; argv[index++]=filename; argv[index++]=NULL; execvp(MA_archiver[t].command,argv); close(fd[1]); exit(0); } break; default: /* fork succeeded, main process code */ close(fd[1]); if (!(file=fdopen(fd[0],"r"))) { close(fd[1]); waitpid(pid,&status,0); break; } /* read from the pipe */ while (fgets(string,PATH_MAX+MAXCOLUMN+1,file)) { s=strlen(string)-1; if (string[s]=='\n') string[s]='\0'; /* remove trailing whitespace from the filename */ for (s=0,t=0;string[t++];) if (string[t]!=' ') s=t; string[s]='\0'; if (MA_isModuleFilename2(string+MA_archiver[archive].nameoffset)) PL_Add(pl,(string+MA_archiver[archive].nameoffset),filename,0,0); } fclose(file); waitpid(pid,&status,0); break; } } #endif } else { /* single-file archive, guess the name */ CHAR *dot,*slash,*spare; dot=strrchr(filename,'.'); slash=strrchr(filename,PATH_SEP); if (!slash) slash=filename; else slash++; if (!dot) for (dot=slash;*dot;dot++); spare=malloc((1+dot-slash)*sizeof(CHAR)); if (spare) { strncpy(spare,slash,dot-slash);spare[dot-slash]=0; if (MA_isModuleFilename2(spare)) PL_Add(pl,spare,filename,0,0); free(spare); } } } else PL_Add(pl,filename,NULL,0,0); } /* ex:set ts=4: */