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

Last change on this file since 649 was 649, checked in by djay, 9 years ago

Migrate readVSIFile from server_internal to zoo_service shared library.

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