/* Copyright (c) 1993, 2015, Oracle and/or its affiliates. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <netdb.h>
#ifdef SERVER_DGA
#include <X11/Xlib.h>
#else
#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#endif /* SERVER_DGA */

#include "dga_incls.h"
#include "pix_grab.h"



#define NDIGITS     8
#define MINSHMEMSIZE    (8*1024)
#define MAXSHMEMSIZE    (0x00040000)
#define MAXSHPXMEMSIZE    (0x01000000)
#define SHPX_MAX_CLIENTS    64
#define SHPX_MAX_PIXMAPS    341



/* Some structure definition for internal bookkeeping
 * NOTE: there is only one locking window created per device for
 * the use of all pixmaps on that device 
*/
typedef struct dga_pixlist {
    Dga_token       	p_token;     	  /* Token associated with pix */
    int             	num_clientp;
    struct dga_pixmap   *dga_clientplist; /*List of client ptrs in pix */
    struct dga_pixlist  *next_plist;  	  /* Next link in the dga list */
} *Dga_pixlist;

static struct dga_pixlist *dga_plist = NULL;
#ifdef SERVER_DGA
extern  SHPX_CLIENT_ENTRY    shpx_client_directory[];
extern  int                 Dga_shpx_client_count;
#else
static  SHPX_CLIENT_ENTRY   shpx_client_directory[SHPX_MAX_CLIENTS];
static  int                 Dga_shpx_client_count = 0;
#endif /* SERVER_DGA */
static u_long pagesize;

extern int _dga_winlockat(u_long cookie, int **lockp, int **unlockp);
extern int _dga_winlockdt(int *lockp, int *unlockp);
extern void *_dga_is_X_pixmap(Pixmap pix, Display **dpyp);

/******************************************
 *
 * dga_pix_grab:
 *
 *  create shared memory file for pixmap information
 *  map to lock page
 *
 *  arguments:
 *
 *  Dga_token   token;  INPUT
 *      magic cookie supplied by the server
 *
 *  returns a user virtual address for a dga_window structure.
 *  returns NULL if anything goes awry.
 *
 *****************************************/

Dga_pixmap
dga_pix_grab(token, pix)
    Dga_token   token;
    Pixmap  pix;
{
    SHARED_PIXMAP_INFO *infop;
    _Dga_pixmap clientp ;
    Display  *dpy;
    int c_fd, i, entry_found;
    u_int	port;
    size_t size;
    SHPX_DIRECTORY  *shpx_dir;
    char    c_fn[256];
    char   *dpystr;
    static char    path[256];
    char    host[MAXHOSTNAMELEN];
    Dga_lockp	lockp, unlockp;

    /* Remember to account for multiple clients grabbing the same pixmap
     * later
     */ 

    /* First determine if this is a X pixmap - if so, get 
     * the dpy and pixid
    */
    if (!_dga_is_X_pixmap(pix, &dpy)) {
#ifdef DEBUG
        (void) fprintf(stderr, "dga_pix_grab: Unsupported pixmap type\n");
#endif
        return (PIX_FAILED);
    }

    if (!dga_plist) {
	/* This is the first time through this code so get the
	 * retained path */
	if (!XDgaGetRetainedPath(dpy, pix, path)) {
#ifdef DEBUG
	    (void) fprintf(stderr, 
		"dga_pix_grab: XDgaGetRetainedPath failed\n");
#endif
	    return (PIX_FAILED);
	}	
    }
#ifndef SERVER_DGA
    /*  Now get the port number for this display */
    dpystr = DisplayString(dpy);
    if (dpystr[0] == ':')
        (void) sscanf(dpystr, ":%u", &port);
    else
        (void) sscanf(dpystr, "%[^:]:%u", host, &port);

    if (Dga_shpx_client_count == 0) {
    	/* Now start by initializing all the per client structs - 
    	 * all 64 of them  - if thie is the first time you 
    	 * are grabbing  a pixmap
    	*/
        for (i = 0; i < SHPX_MAX_CLIENTS; i++) {
        	shpx_client_directory[i].cid = 0;
        	shpx_client_directory[i].shpx_directory = NULL;
        	shpx_client_directory[i].fd = 0;
        	shpx_client_directory[i].size = 0;
        	shpx_client_directory[i].npix = 0;
        }
    } else 
#endif /* SERVER_DGA */
    {
    	/* If it is not the first time, see if this client has grabbed
	 * a pixmap before and therefore done all the set up.
	 * Search through the client structures for matching token
	 */
        i = 0;
        while ((i < SHPX_MAX_CLIENTS) &&
        	(shpx_client_directory[i].cid != token)) {
            i++;
	}
        if ((i == SHPX_MAX_CLIENTS) &&
        	(Dga_shpx_client_count == SHPX_MAX_CLIENTS)) {
            return(0);
        }
    }
    
    if ((Dga_shpx_client_count > 0) && (i < SHPX_MAX_CLIENTS)) {
	/* We found a match and the pixmap already has been grabbed before */
        shpx_dir = shpx_client_directory[i].shpx_directory;
	shpx_client_directory[i].npix++;
    } 
#ifndef SERVER_DGA
    else {
	/* This client has Never grabbed before set up the direct 
	 * structure etc.
         * Open the shared file using server command line 
	 * -sharedretainedpath variable for file path if it is 
	 * set, else use /tmp.  This is because these files can 
	 * be very big and there usually isn't much space in /tmp.
         */
        c_fn[0] = 0;


        if ((path) && (strlen(path) > 0))
	    strcpy(c_fn, path);
         else 
	    strcpy(c_fn, "/tmp");

        strcat(c_fn, PIX_FILE);
        size = strlen(c_fn);
        sprintf(c_fn+size,"%01x.%08x", port, token);
 
#ifdef SYSV
        pagesize = sysconf(_SC_PAGESIZE);
#else
        pagesize = getpagesize();
#endif
        i = 0;
        while ((i < SHPX_MAX_CLIENTS) && (shpx_client_directory[i].cid != 0))
        	i++;
        if (i >= SHPX_MAX_CLIENTS)
        	return(0);
        if ((c_fd = open(c_fn,O_RDWR ,0666)) < 0) 
        	return(0);
        /* map the shpx directory for this client and map at 4 megabytes */
        shpx_dir =   (SHPX_DIRECTORY *)mmap(0,
                MAXSHPXMEMSIZE,
                PROT_READ|PROT_WRITE,
                MAP_SHARED,
                c_fd,
                (off_t)0);
 
        if (shpx_dir == (SHPX_DIRECTORY *)-1) {
	    close(c_fd);
	    return(0);
        }
        Dga_shpx_client_count++;
        shpx_client_directory[i].cid = token; /* BMAC - correct?? */
        shpx_client_directory[i].shpx_directory = shpx_dir;
        shpx_client_directory[i].fd = c_fd; /* no longer need to save it */
	shpx_client_directory[i].size = shpx_dir[0].shpx_entry_0.s_size;
	shpx_client_directory[i].npix = 1;
    }
#endif /* SERVER_DGA */

    /* The first 2 entries on the file have special meaning. */
    i = 2;
    entry_found = 0;
    while ((i < SHPX_MAX_PIXMAPS) && (!entry_found)) {
	if (shpx_dir[i].shpx_entry.xid == pix) 
	    entry_found = 1;
        else 
	    i++;
    }
 
    if (!entry_found) {
#ifndef SERVER_DGA
        close(c_fd);
#endif /* SERVER_DGA */
        return(0);
    }
    infop = (SHARED_PIXMAP_INFO *)
            (((u_char *)shpx_dir) + shpx_dir[i].shpx_entry.offset);
 
    if ((infop->magic != PXMPMAGIC) || (infop->version > PXMPVERS)) {
#ifndef SERVER_DGA
        close(c_fd);
#endif /* SERVER_DGA */
        return(0);
    }
    if (infop->obsolete) {
#ifndef SERVER_DGA
        close(c_fd);
#endif /* SERVER_DGA */
        return(0);
    }
    /* BMAC - Find out about the rache code - what should I do there? */

    /* Now fill out the Dga_pixmap structure */
    if( (clientp = (_Dga_pixmap) malloc(sizeof(struct dga_pixmap))) == NULL )
        return NULL ;

    clientp->drawable_type = DGA_DRAW_PIXMAP;
    clientp->p_lockcnt = 0;
    clientp->obsolete = 0;
    clientp->p_modif = NULL;
    clientp->p_infop = (void *)infop ;
    clientp->c_chngcnt[0] = 0; 
#ifdef MT
    clientp->shadow_chngcnt[0] = 0;
#endif
    /* This is the new location added for locking performance
     * For windows it pts to the second member in the c_wm_chngcnt
     * array but here I think that it just pts to the previous field
     * since for pixmaps the array ctr is -1 always
    */
    clientp->p_chngcnt_2nd = clientp->c_chngcnt +1;

    clientp->s_chngcnt_p = &(infop->s_modified);	
    clientp->c_dirchngcnt = shpx_dir[1].shpx_entry_1.s_dir_seq;
    clientp->s_dirchngcnt_p = &(shpx_dir[1].shpx_entry_1.s_dir_seq);
    clientp->c_devinfocnt = 0;
    clientp->s_devinfocnt_p = &(infop->s_devinfoseq);
    clientp->c_cachecnt = 0;
    clientp->s_cachecnt_p = &(infop->s_cacheseq);	
    clientp->c_cached = 0;
    clientp->s_cached_p = &(infop->cached);	
    clientp->p_dir_index = i;
    clientp->p_shpx_dir = (void *)shpx_dir; /*CHECK ME */
#ifdef MT
    if (dgaThreaded) {
	clientp->p_unlock_func = dgai_unlock;
    } else {
	clientp->p_unlock_func = NULL;
    }
#else
    clientp->p_lock_func = NULL;
    clientp->p_unlock_func = NULL;
#endif
    clientp->p_update_func = dgai_pix_update;
    clientp->p_shpx_client = NULL;
    clientp->p_token = token;
    clientp->c_size = infop->s_size;
    clientp->c_pixels = (u_char *)(infop + 1);
    clientp->depth = infop->depth;
    clientp->linebytes = infop->linebytes;
    clientp->p_next = NULL;
    clientp->p_infofd = NULL;
    clientp->pix_flags = PIX_NOTICE_CLIPCHG;	
    clientp->p_dpy = 0;
    clientp->p_id = NULL;
    clientp->p_client = NULL;
    clientp->changeMask = NULL;
    clientp->siteChgReason = NULL;
    clientp->siteNotifyFunc = NULL;
    clientp->siteNotifyClientData = NULL;

#ifdef SERVER_DGA
    clientp->p_lockp = infop->p_lockp;
    clientp->p_unlockp = infop->p_unlockp;
#else
    lockp = NULL;	/* init to NULL for check below */
    unlockp = NULL;

    /* Check to see if there are already a lockp and unlockp 
     * for this device--if not create 'em
    */
    if (dga_plist) {
	lockp = dga_plist->dga_clientplist->p_lockp;
	unlockp = dga_plist->dga_clientplist->p_unlockp;
    }

    if (!lockp) {
	/* only get new lock pages if necessary */
        if( _dga_winlockat(infop->p_cookie, &lockp, &unlockp) != 0 ) {
            munmap((caddr_t)infop, MAXSHPXMEMSIZE);
/* REMIND Daryl: What else do we need to clean up? */
            free(clientp) ;
            return(NULL);
        }
    }

    clientp->p_lockp = lockp;
    clientp->p_unlockp = unlockp;
#endif /* SERVER_DGA */

    /* add to linked list of grabbed pixmaps - for internal bookkeeping
    */
    if (!dga_plist) {
       if ((dga_plist =
	    (Dga_pixlist) malloc(sizeof(struct dga_pixlist))) == NULL )
            return NULL ;
       	dga_plist->p_token = token;
       	dga_plist->num_clientp = 1;
       	dga_plist->dga_clientplist = clientp;
       	dga_plist->next_plist = NULL;
    } else {
       struct dga_pixlist *new_plist;

       if ((new_plist =               
                   (Dga_pixlist) malloc(sizeof(struct dga_pixlist))) == NULL )
        return NULL;
       new_plist->p_token = token;
       new_plist->num_clientp = 1;
       new_plist->dga_clientplist = clientp;
       new_plist->next_plist = dga_plist;
       dga_plist = new_plist;
    }
#ifdef MT
    if (dgaThreaded) {
	clientp->mutexp = &dgaGlobalPixmapMutex;
    } else {
	clientp->mutexp = NULL;
    }
#endif
    return ((Dga_pixmap) clientp);
}

void
dga_pix_ungrab(clientpi)
Dga_pixmap clientpi;
{
    _Dga_pixmap clientp = (struct dga_pixmap *)clientpi;
    Dga_pixlist	prev_pixlist = NULL;
    Dga_pixlist pixlist = dga_plist;
    u_int	i;
    
    /* Find pixmap in dga_plist */
    while (pixlist) {
	if (pixlist->dga_clientplist == clientp) {
#ifndef SERVER_DGA
            i = 0;
            while ((i < SHPX_MAX_CLIENTS) &&
        	    (shpx_client_directory[i].cid != pixlist->p_token)) {
                i++;
	    }
            if (i >= SHPX_MAX_CLIENTS) {
                return;
            }
	    shpx_client_directory[i].npix--;
	    if (!shpx_client_directory[i].npix) {
		munmap((caddr_t)shpx_client_directory[i].shpx_directory, 
		       MAXSHPXMEMSIZE);
		close(shpx_client_directory[i].fd);
		shpx_client_directory[i].cid = 0;
		shpx_client_directory[i].shpx_directory = NULL;
		shpx_client_directory[i].fd = 0;
		shpx_client_directory[i].size = 0;
	    }	
#endif /* SERVER_DGA */

	    if (prev_pixlist)
	        prev_pixlist->next_plist = pixlist->next_plist;
            else
		dga_plist = pixlist->next_plist;
#ifndef SERVER_DGA
            if (!dga_plist)  {
                _dga_winlockdt(clientp->p_lockp, clientp->p_unlockp);
            }
#endif /* SERVER_DGA */
	    free(pixlist);
	    free(clientp);
		if(--Dga_shpx_client_count < 0) 
			Dga_shpx_client_count = 0;
	    return;
        } else {
	    prev_pixlist = pixlist;
	    pixlist = pixlist->next_plist;
        }
    }
}

#ifdef COMMENT
/* TODO: Daryl what is this routine supposed to be used for??? */
int 
dga_pix_sync(clientpi)
Dga_pixmap clientpi;
{
    _Dga_pixmap clientp = (struct dga_pixmap *)clientpi;
	/* this checks to see if the shared pixmap info
	 * area has changed and if so reinits the data
	*/
	/* This routine seems to requoire that the handle have
	1. if the pixmap is cached or not
	2. what type the pixmap is = like retained one??
	3. something called sh_rache_scr0, sh_rache_scr1;
	4. something called sh_Scr_Virt[];      virtual screen table */
}
#endif

int 
dga_pix_cachechg(clientpi)
Dga_pixmap clientpi;
{
    _Dga_pixmap clientp = (struct dga_pixmap *)clientpi;
    
    if (clientp->c_cachecnt != *(clientp->s_cachecnt_p)) {
	/* Something changed */
	clientp->c_cachecnt = *(clientp->s_cachecnt_p);
	return 1;
    } else
	/* Nothing has changed */
	return 0;
}

int 
dga_pix_cached(clientpi)
Dga_pixmap clientpi;
{
    _Dga_pixmap clientp = (struct dga_pixmap *)clientpi;

    if (clientp->c_cached != *(clientp->s_cached_p)) {
	/* Something changed */
	clientp->c_cached = *(clientp->s_cached_p);
	return 1;
    } else
	/* Nothing has changed */
	return 0;
}

char *
dga_pix_devname(clientpi)
Dga_pixmap clientpi;
{
    _Dga_pixmap clientp = (struct dga_pixmap *)clientpi;
    /* CHECK THIS ! */
    return ((char *) PIX_INFOP(clientp)->scr_name);
}

void * 
dga_pix_pixels(clientpi)
Dga_pixmap clientpi;
{
    _Dga_pixmap clientp = (struct dga_pixmap *)clientpi;
    
    return ((void*)clientp->c_pixels);
}

int 
dga_pix_linebytes(clientpi)
Dga_pixmap clientpi;
{
    _Dga_pixmap clientp = (struct dga_pixmap *)clientpi;
    
    return (clientp->linebytes);
}


u_char 
dga_pix_depth(clientpi)
Dga_pixmap clientpi;
{
   _Dga_pixmap clientp = (struct dga_pixmap *)clientpi; 
    return (clientp->depth);
}

void * 
dga_pix_devinfo(clientpi)
Dga_pixmap clientpi;
{
    _Dga_pixmap clientp = (struct dga_pixmap *)clientpi;
    return (((char *)clientp->p_infop) + PIX_INFOP(clientp)->device_offset);
}