source: trunk/zoo-project/zoo-kernel/service_internal.c @ 660

Last change on this file since 660 was 654, checked in by djay, 10 years ago

Initial support for WPS 2.0.0 including the Dismiss extension.

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/x-csrc
File size: 18.5 KB
Line 
1/*
2 * Author : Gérald FENOY
3 *
4 * Copyright (c) 2009-2015 GeoLabs SARL
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24
25#include "fcgi_stdio.h"
26#include "service_internal.h"
27#ifdef USE_MS
28#include "service_internal_ms.h"
29#else
30#include "cpl_vsi.h"
31#endif
32
33#ifndef TRUE
34#define TRUE 1
35#endif
36#ifndef FALSE
37#define FALSE -1
38#endif
39
40#define ERROR_MSG_MAX_LENGTH 1024
41#ifndef RELY_ON_DB
42#include <dirent.h>
43
44/**
45 * Read the sid file attached of a service if any
46 *
47 * @param conf the maps containing the setting of the main.cfg file
48 * @param pid the service identifier (usid key from the [lenv] section)
49 * @return the reported status char* (temporary/final result)
50 */
51char* getStatusId(maps* conf,char* pid){
52  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
53  char* fbkpid =
54    (char *)
55    malloc ((strlen (r_inputs->value) + strlen (pid) + 7) * sizeof (char));
56  sprintf (fbkpid, "%s/%s.sid", r_inputs->value, pid);
57  FILE* f0 = fopen (fbkpid, "r");
58  if(f0!=NULL){
59    long flen;
60    char *fcontent;
61    fseek (f0, 0, SEEK_END);
62    flen = ftell (f0);
63    fseek (f0, 0, SEEK_SET);
64    fcontent = (char *) malloc ((flen + 1) * sizeof (char));
65    fread(fcontent,flen,1,f0);
66    fcontent[flen]=0;
67    fclose(f0);
68    return fcontent;
69  }else
70    return NULL;
71}
72
73/**
74 * Acquire the global lock
75 *
76 * @param conf the maps containing the setting of the main.cfg file
77 * @return a semid
78 */
79semid acquireLock(maps* conf){
80  semid lockid;
81  int itn=0;
82 toRetry1:
83  lockid=getShmLockId(conf,1);
84  if(
85#ifdef WIN32
86     lockid==NULL
87#else
88     lockid<0
89#endif
90     ){
91#ifdef WIN32
92    return NULL
93#else
94    return -1;
95#endif
96  }
97  if(lockShm(lockid)<0){
98    if(itn<ZOO_LOCK_MAX_RETRY){
99      itn++;
100      goto toRetry1;
101    }else
102#ifdef WIN32
103    return NULL
104#else
105    return -1;
106#endif
107  }else
108    return lockid;
109}
110
111/**
112 * Read the cache file of a running service
113 *
114 * @param conf the maps containing the setting of the main.cfg file
115 * @param pid the service identifier (usid key from the [lenv] section)
116 * @return the reported status char* (temporary/final result)
117 */
118char* _getStatusFile(maps* conf,char* pid){
119  map* tmpTmap = getMapFromMaps (conf, "main", "tmpPath");
120
121  struct dirent *dp;
122  DIR *dirp = opendir(tmpTmap->value);
123  char fileName[1024];
124  int hasFile=-1;
125  if(dirp!=NULL){
126    char tmp[128];
127    sprintf(tmp,"_%s.xml",pid);
128    while ((dp = readdir(dirp)) != NULL){
129#ifdef DEBUG
130      fprintf(stderr,"File : %s searched : %s\n",dp->d_name,tmp);
131#endif
132      if(strstr(dp->d_name,"final_")==0 && strstr(dp->d_name,tmp)!=0){
133        sprintf(fileName,"%s/%s",tmpTmap->value,dp->d_name);
134        hasFile=1;
135        break;
136      }
137    }
138  }
139  if(hasFile>0){
140    semid lockid;
141    char* stat=getStatusId(conf,pid);
142    if(stat!=NULL){
143      setMapInMaps(conf,"lenv","lid",stat);
144      lockid=acquireLock(conf);
145      if(lockid<0)
146        return NULL;
147    }
148
149    FILE* f0 = fopen (fileName, "r");
150    if(f0!=NULL){
151      fseek (f0, 0, SEEK_END);
152      long flen = ftell (f0);
153      fseek (f0, 0, SEEK_SET);
154      char *tmps1 = (char *) malloc ((flen + 1) * sizeof (char));
155      fread(tmps1,flen,1,f0);
156      tmps1[flen]=0;
157      fclose(f0);
158      if(stat!=NULL){
159        unlockShm(lockid);
160        free(stat);
161      }
162
163      return tmps1;
164    }
165    else{
166      if(stat!=NULL){
167        unlockShm(lockid);
168        free(stat);
169      }
170      return NULL;
171    }
172  }
173  else
174    return NULL;
175}
176
177/**
178 * Get the ongoing status of a running service
179 *
180 * @param conf the maps containing the setting of the main.cfg file
181 * @param pid the service identifier (usid key from the [lenv] section)
182 * @return the reported status char* (MESSAGE|POURCENTAGE)
183 */
184char* _getStatus(maps* conf,char* lid){
185  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
186  char* fbkpid =
187    (char *)
188    malloc ((strlen (r_inputs->value) + strlen (lid) + 9) * sizeof (char));
189  sprintf (fbkpid, "%s/%s.status", r_inputs->value, lid);
190  FILE* f0 = fopen (fbkpid, "r");
191  if(f0!=NULL){
192    semid lockid;
193    char* stat;
194    long flen;
195    stat=getStatusId(conf,lid);
196    if(stat!=NULL){
197      setMapInMaps(conf,"lenv","lid",stat);
198      lockid=acquireLock(conf);
199      if(lockid<0)
200        return NULL;
201    }
202    fseek (f0, 0, SEEK_END);
203    flen = ftell (f0);
204    if(flen>0){
205      char *fcontent;
206      fseek (f0, 0, SEEK_SET);
207      fcontent = (char *) malloc ((flen + 1) * sizeof (char));
208      fread(fcontent,flen,1,f0);
209      fcontent[flen]=0;
210      fclose(f0);
211      free(fbkpid);
212      if(stat!=NULL){
213        removeShmLock(conf,1);
214        free(stat);
215      }
216      return fcontent;
217    }
218    fclose(f0);
219    free(fbkpid);
220    if(stat!=NULL){
221      removeShmLock(conf,1);
222      free(stat);
223    }
224    return NULL;
225  }else{
226    free(fbkpid);
227    char* stat=getStatusId(conf,lid);
228    setMapInMaps(conf,"lenv","lid",stat);
229    removeShmLock(conf,1);
230    return NULL;
231  }
232}
233
234/**
235 * Stop handling status repport.
236 *
237 * @param conf the map containing the setting of the main.cfg file
238 */
239void unhandleStatus(maps *conf){
240  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
241  map* usid = getMapFromMaps (conf, "lenv", "usid");
242  char* fbkpid =
243    (char *) malloc ((strlen (r_inputs->value) + strlen (usid->value) + 9) 
244                     * sizeof (char));
245  sprintf (fbkpid, "%s/%s.status", r_inputs->value, usid->value);
246  unlink(fbkpid);
247  free(fbkpid);
248}
249
250/**
251 * Update the current status of the running service.
252 *
253 * @see acquireLock, lockShm
254 * @param conf the map containing the setting of the main.cfg file
255 * @return 0 on success, -2 if shmget failed, -1 if shmat failed
256 */
257int _updateStatus(maps *conf){
258  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
259  map* sid = getMapFromMaps (conf, "lenv", "usid");
260  char* fbkpid =
261    (char *)
262    malloc ((strlen (r_inputs->value) + strlen (sid->value) + 9) * sizeof (char));
263  sprintf (fbkpid, "%s/%s.status", r_inputs->value, sid->value);
264  map* status=getMapFromMaps(conf,"lenv","status");
265  map* msg=getMapFromMaps(conf,"lenv","message");
266  if(status!=NULL && msg!=NULL &&
267     status->value!=NULL && msg->value!=NULL && 
268     strlen(status->value)>0 && strlen(msg->value)>1){
269    semid lockid;
270    char* stat=getStatusId(conf,sid->value);
271    if(stat!=NULL){
272      lockid=acquireLock(conf);
273      if(lockid<0)
274        return ZOO_LOCK_ACQUIRE_FAILED;
275    }
276    FILE* fstatus=fopen(fbkpid,"w");
277    if(fstatus!=NULL){
278      fprintf(fstatus,"%s|%s",status->value,msg->value);
279      fflush(fstatus);
280      fclose(fstatus);
281    }
282    if(stat!=NULL){
283      unlockShm(lockid);
284      free(stat);
285    }
286  }
287  return 0;
288}
289
290#endif
291
292#ifdef WIN32
293
294#include <windows.h>
295#include <fcgi_stdio.h>
296#include <stdio.h>
297#include <conio.h>
298#include <tchar.h>
299
300#define SHMEMSIZE 4096
301
302size_t getKeyValue(maps* conf, char* key, size_t length){
303  if(conf==NULL) {
304    strncpy(key, "700666", length);
305    return strlen(key);
306  }
307 
308  map *tmpMap=getMapFromMaps(conf,"lenv","lid");
309  if(tmpMap==NULL)
310        tmpMap=getMapFromMaps(conf,"lenv","osid");
311
312  if(tmpMap!=NULL){
313        snprintf(key, length, "zoo_sem_%s", tmpMap->value);     
314  }
315  else {
316        strncpy(key, "-1", length); 
317  }
318  return strlen(key);
319}
320
321
322semid getShmLockId(maps* conf, int nsems){
323    semid sem_id;
324    char key[MAX_PATH];
325    getKeyValue(conf, key, MAX_PATH);
326   
327    sem_id = CreateSemaphore( NULL, nsems, nsems+1, key);
328    if(sem_id==NULL){
329#ifdef DEBUG
330      fprintf(stderr,"Semaphore failed to create: %s\n", getLastErrorMessage());
331#endif
332      return NULL;
333    }
334#ifdef DEBUG
335    fprintf(stderr,"%s Accessed !\n",key);
336#endif
337
338    return sem_id;
339}
340
341int removeShmLock(maps* conf, int nsems){
342  semid sem_id=getShmLockId(conf,1);
343  if (CloseHandle(sem_id) == 0) {
344#ifdef DEBUG
345    fprintf(stderr,"Unable to remove semaphore: %s\n", getLastErrorMessage());
346#endif
347    return -1;
348  }
349#ifdef DEBUG
350  fprintf(stderr,"%d Removed !\n",sem_id);
351#endif
352  return 0;
353}
354
355int lockShm(semid id){
356  DWORD dwWaitResult=WaitForSingleObject(id,INFINITE);
357  switch (dwWaitResult){
358    case WAIT_OBJECT_0:
359      return 0;
360      break;
361    case WAIT_TIMEOUT:
362      return -1;
363      break;
364    default:
365      return -2;
366      break;
367  }
368  return 0;
369}
370
371int unlockShm(semid id){
372  if(!ReleaseSemaphore(id,1,NULL)){
373    return -1;
374  }
375  return 0;
376}
377
378static LPVOID lpvMemG = NULL;      // pointer to shared memory
379static HANDLE hMapObjectG = NULL;  // handle to file mapping
380
381
382char* getStatus(int pid){
383  char *lpszBuf=(char*) malloc(SHMEMSIZE*sizeof(char));
384  int i=0;
385  LPWSTR lpszTmp=NULL;
386  LPVOID lpvMem = NULL;
387  HANDLE hMapObject = NULL;
388  BOOL fIgnore,fInit;
389  char tmp[1024];
390  sprintf(tmp,"%d",pid);
391  if(hMapObject==NULL)
392    hMapObject = CreateFileMapping( 
393                                   INVALID_HANDLE_VALUE,   // use paging file
394                                   NULL,                   // default security attributes
395                                   PAGE_READWRITE,         // read/write access
396                                   0,                      // size: high 32-bits
397                                   4096,                   // size: low 32-bits
398                                   TEXT(tmp));   // name of map object
399  if (hMapObject == NULL){
400#ifdef DEBUG
401    fprintf(stderr,"ERROR on line %d\n",__LINE__);
402#endif
403    return "-1";
404  }
405  if((GetLastError() != ERROR_ALREADY_EXISTS)){
406#ifdef DEBUG
407    fprintf(stderr,"ERROR on line %d\n",__LINE__);
408    fprintf(stderr,"READING STRING S %s\n", getLastErrorMessage());
409#endif
410    fIgnore = UnmapViewOfFile(lpvMem); 
411    fIgnore = CloseHandle(hMapObject);
412    return "-1";
413  }
414  fInit=TRUE;
415  if(lpvMem==NULL)
416    lpvMem = MapViewOfFile( 
417                           hMapObject,     // object to map view of
418                           FILE_MAP_READ,  // read/write access
419                           0,              // high offset:  map from
420                           0,              // low offset:   beginning
421                           0);             // default: map entire file
422  if (lpvMem == NULL){
423#ifdef DEBUG
424    fprintf(stderr,"READING STRING S %d\n",__LINE__);
425    fprintf(stderr,"READING STRING S %s\n", getLastErrorMessage());
426#endif
427    return "-1"; 
428  }
429  lpszTmp = (LPWSTR) lpvMem;
430  while (*lpszTmp){
431    lpszBuf[i] = (char)*lpszTmp;
432    *lpszTmp++; 
433    lpszBuf[i+1] = '\0'; 
434    i++;
435  }
436  return (char*)lpszBuf;
437}
438
439#else
440/**
441 * Number of time to try to access a semaphores set
442 * @see getShmLockId
443 */
444#define MAX_RETRIES 10
445
446#ifndef __APPLE__
447/**
448 * arg for semctl system calls.
449 */
450union semun {
451  int val; //!< value for SETVAL
452  struct semid_ds *buf; //!< buffer for IPC_STAT & IPC_SET
453  ushort *array; //!< array for GETALL & SETALL
454};
455#endif
456
457/**
458 * Set in the pre-allocated key the zoo_sem_[OSID] string
459 * where [OSID] is the lid (if any) or osid value from the [lenv] section.
460 *
461 * @param conf the map containing the setting of the main.cfg file
462 */
463int getKeyValue(maps* conf){
464  if(conf==NULL)
465     return 700666;
466  map *tmpMap=getMapFromMaps(conf,"lenv","lid");
467  if(tmpMap==NULL)
468    tmpMap=getMapFromMaps(conf,"lenv","osid");
469  int key=-1;
470  if(tmpMap!=NULL)
471    key=atoi(tmpMap->value);
472  return key;
473}
474
475/**
476 * Try to create or access a semaphore set.
477 *
478 * @see getKeyValue
479 * @param conf the map containing the setting of the main.cfg file
480 * @param nsems number of semaphores
481 * @return a semaphores set indentifier on success, -1 in other case
482 */
483int getShmLockId(maps* conf, int nsems){
484    int i;
485    union semun arg;
486    struct semid_ds buf;
487    struct sembuf sb;
488    semid sem_id;
489    int key=getKeyValue(conf);
490   
491    sem_id = semget(key, nsems, IPC_CREAT | IPC_EXCL | 0666);
492
493    if (sem_id >= 0) { /* we got it first */
494        sb.sem_op = 1; 
495        sb.sem_flg = 0;
496        arg.val=1;
497        for(sb.sem_num = 0; sb.sem_num < nsems; sb.sem_num++) { 
498            /* do a semop() to "free" the semaphores. */
499            /* this sets the sem_otime field, as needed below. */
500            if (semop(sem_id, &sb, 1) == -1) {
501                int e = errno;
502                semctl(sem_id, 0, IPC_RMID); /* clean up */
503                errno = e;
504                return -1; /* error, check errno */
505            }
506        }
507    } else if (errno == EEXIST) { /* someone else got it first */
508        int ready = 0;
509
510        sem_id = semget(key, nsems, 0); /* get the id */
511        if (sem_id < 0) return sem_id; /* error, check errno */
512
513        /* wait for other process to initialize the semaphore: */
514        arg.buf = &buf;
515        for(i = 0; i < MAX_RETRIES && !ready; i++) {
516            semctl(sem_id, nsems-1, IPC_STAT, arg);
517            if (arg.buf->sem_otime != 0) {
518#ifdef DEBUG
519              fprintf(stderr,"Semaphore acquired ...\n");
520#endif
521              ready = 1;
522            } else {
523#ifdef DEBUG
524              fprintf(stderr,"Retry to access the semaphore later ...\n");
525#endif
526              zSleep(1);
527            }
528        }
529        errno = ZOO_LOCK_ACQUIRE_FAILED;
530        if (!ready) {
531#ifdef DEBUG
532          fprintf(stderr,"Unable to access the semaphore ...\n");
533#endif
534          errno = ETIME;
535          return -1;
536        }
537    } else {
538        return sem_id; /* error, check errno */
539    }
540#ifdef DEBUG
541    fprintf(stderr,"%d Created !\n",sem_id);
542#endif
543    return sem_id;
544}
545
546/**
547 * Try to remove a semaphore set.
548 *
549 * @param conf the map containing the setting of the main.cfg file
550 * @param nsems number of semaphores
551 * @return 0 if the semaphore can be removed, -1 in other case.
552 */
553int removeShmLock(maps* conf, int nsems){
554  union semun arg;
555  int sem_id=getShmLockId(conf,nsems);
556  if (semctl(sem_id, 0, IPC_RMID, arg) == -1) {
557#ifdef DEBUG
558    perror("semctl remove");
559#endif
560    return -1;
561  }
562#ifdef DEBUG
563  fprintf(stderr,"Semaphore removed!\n");
564#endif
565  return 0;
566}
567
568/**
569 * Lock a semaphore set.
570 *
571 * @param id the semaphores set indetifier
572 * @return 0 if the semaphore can be locked, -1 in other case.
573 */
574int lockShm(int id){
575  struct sembuf sb;
576  sb.sem_num = 0;
577  sb.sem_op = -1;  /* set to allocate resource */
578  sb.sem_flg = SEM_UNDO;
579  if (semop(id, &sb, 1) == -1){
580#ifdef DEBUG
581    perror("semop lock");
582#endif
583    return -1;
584  }
585  return 0;
586}
587
588/**
589 * unLock a semaphore set.
590 *
591 * @param id the semaphores set indetifier
592 * @return 0 if the semaphore can be locked, -1 in other case.
593 */
594int unlockShm(int id){
595  struct sembuf sb;
596  sb.sem_num = 0;
597  sb.sem_op = 1;  /* free resource */
598  sb.sem_flg = SEM_UNDO;
599  if (semop(id, &sb, 1) == -1) {
600#ifdef DEBUG
601    perror("semop unlock");
602#endif
603    return -1;
604  }
605  return 0;
606}
607
608/**
609 * Get the current status of the running service.
610 *
611 * @see getKeyValue, getShmLockId, lockShm
612 * @param pid the semaphores
613 * @return 0 on success, -2 if shmget failed, -1 if shmat failed
614 */
615char* getStatus(int pid){
616  int shmid;
617  key_t key;
618  void *shm;
619  key=pid;
620  if ((shmid = shmget(key, SHMSZ, 0666)) < 0) {
621#ifdef DEBUG
622    fprintf(stderr,"shmget failed in getStatus\n");
623#endif
624  }else{
625    if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
626#ifdef DEBUG
627      fprintf(stderr,"shmat failed in getStatus\n");
628#endif
629    }else{
630      char *ret=strdup((char*)shm);
631      shmdt((void *)shm);
632      return ret;
633    }
634  }
635  return (char*)"-1";
636}
637
638#endif
639
640/**
641 * Update the status of an ongoing service
642 *
643 * @param conf the maps containing the settings of the main.cfg file
644 * @param percentCompleted percentage of completude of execution of the service
645 * @param message information about the current step executed
646 * @return the value of _updateStatus
647 * @see _updateStatus
648 */
649int updateStatus( maps* conf, const int percentCompleted, const char* message ){
650  char tmp[4];
651  snprintf(tmp,4,"%d",percentCompleted);
652  setMapInMaps( conf, "lenv", "status", tmp );
653  setMapInMaps( conf, "lenv", "message", message);
654  return _updateStatus( conf );
655}
656
657/**
658 * Access an input value
659 *
660 * @param inputs the maps to search for the input value
661 * @param parameterName the input name to fetch the value
662 * @param numberOfBytes the resulting size of the value to add (for binary
663 *  values), -1 for basic char* data
664 * @return a pointer to the input value if found, NULL in other case.
665 */
666char* getInputValue( maps* inputs, const char* parameterName, size_t* numberOfBytes){
667  map* res=getMapFromMaps(inputs,parameterName,"value");
668  if(res!=NULL){
669    map* size=getMapFromMaps(inputs,parameterName,"size");
670    if(size!=NULL){
671      *numberOfBytes=(size_t)atoi(size->value);
672      return res->value;
673    }else{
674      *numberOfBytes=strlen(res->value);
675      return res->value;
676    }
677  }
678  return NULL;
679}
680
681/**
682 * Read a file using the GDAL VSI API
683 *
684 * @param conf the maps containing the settings of the main.cfg file
685 * @param dataSource the datasource name to read
686 * @warning make sure to free ressources returned by this function
687 */
688char *readVSIFile(maps* conf,const char* dataSource){
689    VSILFILE * fichier=VSIFOpenL(dataSource,"rb");
690    VSIStatBuf file_status;
691    VSIStat(dataSource, &file_status);
692    if(fichier==NULL){
693      char tmp[1024];
694      sprintf(tmp,"Failed to open file %s for reading purpose. File seems empty %lld.",
695              dataSource,file_status.st_size);
696      setMapInMaps(conf,"lenv","message",tmp);
697      return NULL;
698    }
699    char *res1=(char *)malloc(file_status.st_size*sizeof(char));
700    VSIFReadL(res1,1,file_status.st_size*sizeof(char),fichier);
701    res1[file_status.st_size-1]=0;
702    VSIFCloseL(fichier);
703    VSIUnlink(dataSource);
704    return res1;
705}
706
707/**
708 * Set an output value
709 *
710 * @param outputs the maps to define the output value
711 * @param parameterName the output name to set the value
712 * @param data the value to set
713 * @param numberOfBytes size of the value to add (for binary values), -1 for
714 *  basic char* data
715 * @return 0
716 */
717int  setOutputValue( maps* outputs, const char* parameterName, char* data, size_t numberOfBytes ){
718  if(numberOfBytes==-1){
719    setMapInMaps(outputs,parameterName,"value",data);
720  }else{
721    char size[1024];
722    map* tmp=getMapFromMaps(outputs,parameterName,"value");
723    if(tmp==NULL){
724      setMapInMaps(outputs,parameterName,"value","");
725      tmp=getMapFromMaps(outputs,parameterName,"value");
726    }
727    free(tmp->value);
728    tmp->value=(char*) malloc((numberOfBytes+1)*sizeof(char));
729    memcpy(tmp->value,data,numberOfBytes);
730    sprintf(size,"%lu",numberOfBytes);
731    setMapInMaps(outputs,parameterName,"size",size);
732  }
733  return 0;
734}
735
Note: See TracBrowser for help on using the repository browser.

Search

ZOO Sponsors

http://www.zoo-project.org/trac/chrome/site/img/geolabs-logo.pnghttp://www.zoo-project.org/trac/chrome/site/img/neogeo-logo.png http://www.zoo-project.org/trac/chrome/site/img/apptech-logo.png http://www.zoo-project.org/trac/chrome/site/img/3liz-logo.png http://www.zoo-project.org/trac/chrome/site/img/gateway-logo.png

Become a sponsor !

Knowledge partners

http://www.zoo-project.org/trac/chrome/site/img/ocu-logo.png http://www.zoo-project.org/trac/chrome/site/img/gucas-logo.png http://www.zoo-project.org/trac/chrome/site/img/polimi-logo.png http://www.zoo-project.org/trac/chrome/site/img/fem-logo.png http://www.zoo-project.org/trac/chrome/site/img/supsi-logo.png http://www.zoo-project.org/trac/chrome/site/img/cumtb-logo.png

Become a knowledge partner

Related links

http://zoo-project.org/img/ogclogo.png http://zoo-project.org/img/osgeologo.png