]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - fs.c
FS_LoadFile: don't segfault when trying to open a non-regular file
[xonotic/darkplaces.git] / fs.c
diff --git a/fs.c b/fs.c
index f6360fde49ba7ca054b9c03d59f8f876e93f3cb0..4f7baf9d2a6f41e6c00e4097c4efbca26e4dac1f 100644 (file)
--- a/fs.c
+++ b/fs.c
@@ -222,6 +222,8 @@ typedef struct dpackheader_s
 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
 // file compressed using the deflate algorithm
 #define PACKFILE_FLAG_DEFLATED (1 << 1)
+// file is a symbolic link
+#define PACKFILE_FLAG_SYMLINK (1 << 2)
 
 typedef struct packfile_s
 {
@@ -235,6 +237,7 @@ typedef struct packfile_s
 typedef struct pack_s
 {
        char filename [MAX_OSPATH];
+       char shortname [MAX_QPATH];
        int handle;
        int ignorecase;  // PK3 ignores case
        int numfiles;
@@ -469,7 +472,11 @@ int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
        // Load the central directory in memory
        central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
        lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
-       read (pack->handle, central_dir, eocd->cdir_size);
+       if(read (pack->handle, central_dir, eocd->cdir_size) != (ssize_t) eocd->cdir_size)
+       {
+               Mem_Free (central_dir);
+               return -1;
+       }
 
        // Extract the files properties
        // The parsing is done "by hand" because some fields have variable sizes and
@@ -538,6 +545,18 @@ int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
                                offset = BuffLittleLong (&ptr[42]);
                                packsize = BuffLittleLong (&ptr[20]);
                                realsize = BuffLittleLong (&ptr[24]);
+
+                               switch(ptr[5]) // C_VERSION_MADE_BY_1
+                               {
+                                       case 3: // UNIX_
+                                       case 2: // VMS_
+                                       case 16: // BEOS_
+                                               if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
+                                                       // can't use S_ISLNK here, as this has to compile on non-UNIX too
+                                                       flags |= PACKFILE_FLAG_SYMLINK;
+                                               break;
+                               }
+
                                FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
                        }
                }
@@ -726,7 +745,7 @@ static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
 ============
 FS_CreatePath
 
-Only used for FS_Open.
+Only used for FS_OpenRealFile.
 ============
 */
 void FS_CreatePath (char *path)
@@ -793,7 +812,12 @@ pack_t *FS_LoadPackPAK (const char *packfile)
 #endif
        if (packhandle < 0)
                return NULL;
-       read (packhandle, (void *)&header, sizeof(header));
+       if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
+       {
+               Con_Printf ("%s is not a packfile\n", packfile);
+               close(packhandle);
+               return NULL;
+       }
        if (memcmp(header.id, "PACK", 4))
        {
                Con_Printf ("%s is not a packfile\n", packfile);
@@ -865,7 +889,7 @@ If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
 plain directories.
 ================
 */
-static qboolean FS_AddPack_Fullpath(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
+static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
 {
        searchpath_t *search;
        pack_t *pak = NULL;
@@ -893,6 +917,8 @@ static qboolean FS_AddPack_Fullpath(const char *pakfile, qboolean *already_loade
 
        if (pak)
        {
+               strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
+               //Con_DPrintf("  Registered pack with short name %s\n", shortname);
                if(keep_plain_dirs)
                {
                        // find the first item whose next one is a pack or NULL
@@ -978,7 +1004,7 @@ qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep
 
        dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
 
-       return FS_AddPack_Fullpath(fullpath, already_loaded, keep_plain_dirs);
+       return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
 }
 
 
@@ -1007,7 +1033,7 @@ void FS_AddGameDirectory (const char *dir)
        {
                if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
                {
-                       FS_AddPack_Fullpath(list.strings[i], NULL, false);
+                       FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
                }
        }
 
@@ -1016,7 +1042,7 @@ void FS_AddGameDirectory (const char *dir)
        {
                if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3"))
                {
-                       FS_AddPack_Fullpath(list.strings[i], NULL, false);
+                       FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
                }
        }
 
@@ -1857,7 +1883,7 @@ FS_OpenReadFile
 Look for a file in the search paths and open it in read-only mode
 ===========
 */
-qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
+qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
 {
        searchpath_t *search;
        int pack_ind;
@@ -1877,6 +1903,80 @@ qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonbloc
        }
 
        // So, we found it in a package...
+
+       // Is it a PK3 symlink?
+       // TODO also handle directory symlinks by parsing the whole structure...
+       // but heck, file symlinks are good enough for now
+       if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
+       {
+               if(symlinkLevels <= 0)
+               {
+                       Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
+                       return NULL;
+               }
+               else
+               {
+                       char linkbuf[MAX_QPATH];
+                       fs_offset_t count;
+                       qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
+                       const char *mergeslash;
+                       char *mergestart;
+
+                       if(!linkfile)
+                               return NULL;
+                       count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
+                       FS_Close(linkfile);
+                       if(count < 0)
+                               return NULL;
+                       linkbuf[count] = 0;
+                       
+                       // Now combine the paths...
+                       mergeslash = strrchr(filename, '/');
+                       mergestart = linkbuf;
+                       if(!mergeslash)
+                               mergeslash = filename;
+                       while(!strncmp(mergestart, "../", 3))
+                       {
+                               mergestart += 3;
+                               while(mergeslash > filename)
+                               {
+                                       --mergeslash;
+                                       if(*mergeslash == '/')
+                                               break;
+                               }
+                       }
+                       // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
+                       if(mergeslash == filename)
+                       {
+                               // Either mergeslash == filename, then we just replace the name (done below)
+                       }
+                       else
+                       {
+                               // Or, we append the name after mergeslash;
+                               // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
+                               int spaceNeeded = mergeslash - filename + 1;
+                               int spaceRemoved = mergestart - linkbuf;
+                               if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
+                               {
+                                       Con_DPrintf("symlink: too long path rejected\n");
+                                       return NULL;
+                               }
+                               memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
+                               memcpy(linkbuf, filename, spaceNeeded);
+                               linkbuf[count - spaceRemoved + spaceNeeded] = 0;
+                               mergestart = linkbuf;
+                       }
+                       if (!quiet && developer_loading.integer)
+                               Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
+                       if(FS_CheckNastyPath (mergestart, false))
+                       {
+                               Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
+                               return NULL;
+                       }
+                       return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
+               }
+       }
+
        return FS_OpenPackedFile (search->pack, pack_ind);
 }
 
@@ -1891,35 +1991,48 @@ MAIN PUBLIC FUNCTIONS
 
 /*
 ====================
-FS_Open
+FS_OpenRealFile
 
-Open a file. The syntax is the same as fopen
+Open a file in the userpath. The syntax is the same as fopen
+Used for savegame scanning in menu, and all file writing.
 ====================
 */
-qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
+qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
 {
+       char real_path [MAX_OSPATH];
+
        if (FS_CheckNastyPath(filepath, false))
        {
-               Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
+               Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
                return NULL;
        }
 
-       // If the file is opened in "write", "append", or "read/write" mode
+       dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
+
+       // If the file is opened in "write", "append", or "read/write" mode,
+       // create directories up to the file.
        if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
-       {
-               char real_path [MAX_OSPATH];
+               FS_CreatePath (real_path);
+       return FS_SysOpen (real_path, mode, false);
+}
 
-               // Open the file on disk directly
-               dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
 
-               // Create directories up to the file
-               FS_CreatePath (real_path);
+/*
+====================
+FS_OpenVirtualFile
 
-               return FS_SysOpen (real_path, mode, nonblocking);
+Open a file. The syntax is the same as fopen
+====================
+*/
+qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
+{
+       if (FS_CheckNastyPath(filepath, false))
+       {
+               Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
+               return NULL;
        }
-       // Else, we look at the various search paths and open the file in read-only mode
-       else
-               return FS_OpenReadFile (filepath, quiet, nonblocking);
+
+       return FS_OpenReadFile (filepath, quiet, false, 16);
 }
 
 
@@ -2400,10 +2513,17 @@ unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, f
        unsigned char *buf = NULL;
        fs_offset_t filesize = 0;
 
-       file = FS_Open (path, "rb", quiet, false);
+       file = FS_OpenVirtualFile(path, quiet);
        if (file)
        {
                filesize = file->real_length;
+               if(filesize < 0)
+               {
+                       Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
+                       FS_Close(file);
+                       return NULL;
+               }
+
                buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
                buf[filesize] = '\0';
                FS_Read (file, buf, filesize);
@@ -2429,7 +2549,7 @@ qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
 {
        qfile_t *file;
 
-       file = FS_Open (filename, "wb", false, false);
+       file = FS_OpenRealFile(filename, "wb", false);
        if (!file)
        {
                Con_Printf("FS_WriteFile: failed on %s\n", filename);
@@ -2903,7 +3023,7 @@ const char *FS_WhichPack(const char *filename)
        int index;
        searchpath_t *sp = FS_FindFile(filename, &index, true);
        if(sp && sp->pack)
-               return sp->pack->filename;
+               return sp->pack->shortname;
        else
                return 0;
 }