source: branches/PublicaMundi_David_integration_01-devel/zoo-project/zoo-kernel/service_internal.c @ 741

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

Context Navigation

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