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

Last change on this file since 652 was 652, checked in by djay, 7 years ago

Better concurrency gesture for asynchronous requests, add db backend support for status informations.

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