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

Last change on this file since 681 was 681, checked in by djay, 4 years ago

Fix commit 651, use VSIStatL.

  • 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 "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#ifdef WIN32
98      return NULL;
99#else
100    if(itn<ZOO_LOCK_MAX_RETRY){
101      itn++;
102      goto toRetry1;
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#ifndef WIN32
213        removeShmLock(conf,1);
214#else
215        unlockShm(lockid);
216#endif
217        free(stat);
218      }
219      return fcontent;
220    }
221    fclose(f0);
222    free(fbkpid);
223    if(stat!=NULL){
224      removeShmLock(conf,1);
225      free(stat);
226    }
227    return NULL;
228  }else{
229    free(fbkpid);
230    char* stat=getStatusId(conf,lid);
231    setMapInMaps(conf,"lenv","lid",stat);
232    removeShmLock(conf,1);
233    return NULL;
234  }
235}
236
237/**
238 * Stop handling status repport.
239 *
240 * @param conf the map containing the setting of the main.cfg file
241 */
242void unhandleStatus(maps *conf){
243  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
244  map* usid = getMapFromMaps (conf, "lenv", "usid");
245  char* fbkpid =
246    (char *) malloc ((strlen (r_inputs->value) + strlen (usid->value) + 9) 
247                     * sizeof (char));
248  sprintf (fbkpid, "%s/%s.status", r_inputs->value, usid->value);
249  unlink(fbkpid);
250  free(fbkpid);
251}
252
253/**
254 * Update the current status of the running service.
255 *
256 * @see acquireLock, lockShm
257 * @param conf the map containing the setting of the main.cfg file
258 * @return 0 on success, -2 if shmget failed, -1 if shmat failed
259 */
260int _updateStatus(maps *conf){
261  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
262  map* sid = getMapFromMaps (conf, "lenv", "usid");
263  char* fbkpid =
264    (char *)
265    malloc ((strlen (r_inputs->value) + strlen (sid->value) + 9) * sizeof (char));
266  sprintf (fbkpid, "%s/%s.status", r_inputs->value, sid->value);
267  map* status=getMapFromMaps(conf,"lenv","status");
268  map* msg=getMapFromMaps(conf,"lenv","message");
269  if(status!=NULL && msg!=NULL &&
270     status->value!=NULL && msg->value!=NULL && 
271     strlen(status->value)>0 && strlen(msg->value)>1){
272    semid lockid;
273    char* stat=getStatusId(conf,sid->value);
274    if(stat!=NULL){
275      lockid=acquireLock(conf);
276      if(lockid<0){
277        dumpMap(status);
278        return ZOO_LOCK_ACQUIRE_FAILED;
279      }
280    }
281    FILE* fstatus=fopen(fbkpid,"w");
282    if(fstatus!=NULL){
283      fprintf(fstatus,"%s|%s",status->value,msg->value);
284      fflush(fstatus);
285      fclose(fstatus);
286    }
287    if(stat!=NULL){
288      unlockShm(lockid);
289      free(stat);
290    }
291  }
292  return 0;
293}
294
295#endif
296
297#ifdef WIN32
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  return sem_id;
337}
338
339int removeShmLock(maps* conf, int nsems){
340  semid sem_id=getShmLockId(conf,1);
341  if (CloseHandle(sem_id) == 0) {
342#ifdef DEBUG
343    fprintf(stderr,"Unable to remove semaphore: %s\n", getLastErrorMessage());
344#endif
345    return -1;
346  }
347#ifdef DEBUG
348  fprintf(stderr,"%d Removed !\n",sem_id);
349#endif
350  return 0;
351}
352
353int lockShm(semid id){
354  DWORD dwWaitResult=WaitForSingleObject(id,INFINITE);
355  switch (dwWaitResult){
356    case WAIT_OBJECT_0:
357      return 0;
358      break;
359    case WAIT_TIMEOUT:
360      return -1;
361      break;
362    default:
363      return -2;
364      break;
365  }
366  return 0;
367}
368
369int unlockShm(semid id){
370  if(!ReleaseSemaphore(id,1,NULL)){
371    return -1;
372  }
373  return 0;
374}
375
376static LPVOID lpvMemG = NULL;      // pointer to shared memory
377static HANDLE hMapObjectG = NULL;  // handle to file mapping
378
379
380char* getStatus(int pid){
381  char *lpszBuf=(char*) malloc(SHMEMSIZE*sizeof(char));
382  int i=0;
383  LPWSTR lpszTmp=NULL;
384  LPVOID lpvMem = NULL;
385  HANDLE hMapObject = NULL;
386  BOOL fIgnore,fInit;
387  char tmp[1024];
388  sprintf(tmp,"%d",pid);
389  if(hMapObject==NULL)
390    hMapObject = CreateFileMapping( 
391                                   INVALID_HANDLE_VALUE,   // use paging file
392                                   NULL,                   // default security attributes
393                                   PAGE_READWRITE,         // read/write access
394                                   0,                      // size: high 32-bits
395                                   4096,                   // size: low 32-bits
396                                   TEXT(tmp));   // name of map object
397  if (hMapObject == NULL){
398#ifdef DEBUG
399    fprintf(stderr,"ERROR on line %d\n",__LINE__);
400#endif
401    return "-1";
402  }
403  if((GetLastError() != ERROR_ALREADY_EXISTS)){
404#ifdef DEBUG
405    fprintf(stderr,"ERROR on line %d\n",__LINE__);
406    fprintf(stderr,"READING STRING S %s\n", getLastErrorMessage());
407#endif
408    fIgnore = UnmapViewOfFile(lpvMem); 
409    fIgnore = CloseHandle(hMapObject);
410    return "-1";
411  }
412  fInit=TRUE;
413  if(lpvMem==NULL)
414    lpvMem = MapViewOfFile( 
415                           hMapObject,     // object to map view of
416                           FILE_MAP_READ,  // read/write access
417                           0,              // high offset:  map from
418                           0,              // low offset:   beginning
419                           0);             // default: map entire file
420  if (lpvMem == NULL){
421#ifdef DEBUG
422    fprintf(stderr,"READING STRING S %d\n",__LINE__);
423    fprintf(stderr,"READING STRING S %s\n", getLastErrorMessage());
424#endif
425    return "-1"; 
426  }
427  lpszTmp = (LPWSTR) lpvMem;
428  while (*lpszTmp){
429    lpszBuf[i] = (char)*lpszTmp;
430    *lpszTmp++; 
431    lpszBuf[i+1] = '\0'; 
432    i++;
433  }
434  return (char*)lpszBuf;
435}
436
437#else
438/**
439 * Number of time to try to access a semaphores set
440 * @see getShmLockId
441 */
442#define MAX_RETRIES 10
443
444#ifndef __APPLE__
445/**
446 * arg for semctl system calls.
447 */
448union semun {
449  int val; //!< value for SETVAL
450  struct semid_ds *buf; //!< buffer for IPC_STAT & IPC_SET
451  ushort *array; //!< array for GETALL & SETALL
452};
453#endif
454
455/**
456 * Set in the pre-allocated key the zoo_sem_[OSID] string
457 * where [OSID] is the lid (if any) or osid value from the [lenv] section.
458 *
459 * @param conf the map containing the setting of the main.cfg file
460 */
461int getKeyValue(maps* conf){
462  if(conf==NULL)
463     return 700666;
464  map *tmpMap=getMapFromMaps(conf,"lenv","lid");
465  if(tmpMap==NULL)
466    tmpMap=getMapFromMaps(conf,"lenv","osid");
467  int key=-1;
468  if(tmpMap!=NULL)
469    key=atoi(tmpMap->value);
470  return key;
471}
472
473/**
474 * Try to create or access a semaphore set.
475 *
476 * @see getKeyValue
477 * @param conf the map containing the setting of the main.cfg file
478 * @param nsems number of semaphores
479 * @return a semaphores set indentifier on success, -1 in other case
480 */
481int getShmLockId(maps* conf, int nsems){
482    int i;
483    union semun arg;
484    struct semid_ds buf;
485    struct sembuf sb;
486    semid sem_id;
487    int key=getKeyValue(conf);
488   
489    sem_id = semget(key, nsems, IPC_CREAT | IPC_EXCL | 0666);
490
491    if (sem_id >= 0) { /* we got it first */
492        sb.sem_op = 1; 
493        sb.sem_flg = 0;
494        arg.val=1;
495        for(sb.sem_num = 0; sb.sem_num < nsems; sb.sem_num++) { 
496            /* do a semop() to "free" the semaphores. */
497            /* this sets the sem_otime field, as needed below. */
498            if (semop(sem_id, &sb, 1) == -1) {
499                int e = errno;
500                semctl(sem_id, 0, IPC_RMID); /* clean up */
501                errno = e;
502                return -1; /* error, check errno */
503            }
504        }
505    } else if (errno == EEXIST) { /* someone else got it first */
506        int ready = 0;
507
508        sem_id = semget(key, nsems, 0); /* get the id */
509        if (sem_id < 0) return sem_id; /* error, check errno */
510
511        /* wait for other process to initialize the semaphore: */
512        arg.buf = &buf;
513        for(i = 0; i < MAX_RETRIES && !ready; i++) {
514            semctl(sem_id, nsems-1, IPC_STAT, arg);
515            if (arg.buf->sem_otime != 0) {
516#ifdef DEBUG
517              fprintf(stderr,"Semaphore acquired ...\n");
518#endif
519              ready = 1;
520            } else {
521#ifdef DEBUG
522              fprintf(stderr,"Retry to access the semaphore later ...\n");
523#endif
524              zSleep(1);
525            }
526        }
527        errno = ZOO_LOCK_ACQUIRE_FAILED;
528        if (!ready) {
529#ifdef DEBUG
530          fprintf(stderr,"Unable to access the semaphore ...\n");
531#endif
532          errno = ETIME;
533          return -1;
534        }
535    } else {
536        return sem_id; /* error, check errno */
537    }
538#ifdef DEBUG
539    fprintf(stderr,"%d Created !\n",sem_id);
540#endif
541    return sem_id;
542}
543
544/**
545 * Try to remove a semaphore set.
546 *
547 * @param conf the map containing the setting of the main.cfg file
548 * @param nsems number of semaphores
549 * @return 0 if the semaphore can be removed, -1 in other case.
550 */
551int removeShmLock(maps* conf, int nsems){
552  union semun arg;
553  int sem_id=getShmLockId(conf,nsems);
554  if (semctl(sem_id, 0, IPC_RMID, arg) == -1) {
555#ifdef DEBUG
556    perror("semctl remove");
557#endif
558    return -1;
559  }
560#ifdef DEBUG
561  fprintf(stderr,"Semaphore removed!\n");
562#endif
563  return 0;
564}
565
566/**
567 * Lock a semaphore set.
568 *
569 * @param id the semaphores set indetifier
570 * @return 0 if the semaphore can be locked, -1 in other case.
571 */
572int lockShm(int id){
573  struct sembuf sb;
574  sb.sem_num = 0;
575  sb.sem_op = -1;  /* set to allocate resource */
576  sb.sem_flg = SEM_UNDO;
577  if (semop(id, &sb, 1) == -1){
578#ifdef DEBUG
579    perror("semop lock");
580#endif
581    return -1;
582  }
583  return 0;
584}
585
586/**
587 * unLock a semaphore set.
588 *
589 * @param id the semaphores set indetifier
590 * @return 0 if the semaphore can be locked, -1 in other case.
591 */
592int unlockShm(int id){
593  struct sembuf sb;
594  sb.sem_num = 0;
595  sb.sem_op = 1;  /* free resource */
596  sb.sem_flg = SEM_UNDO;
597  if (semop(id, &sb, 1) == -1) {
598#ifdef DEBUG
599    perror("semop unlock");
600#endif
601    return -1;
602  }
603  return 0;
604}
605
606/**
607 * Get the current status of the running service.
608 *
609 * @see getKeyValue, getShmLockId, lockShm
610 * @param pid the semaphores
611 * @return 0 on success, -2 if shmget failed, -1 if shmat failed
612 */
613char* getStatus(int pid){
614  int shmid;
615  key_t key;
616  void *shm;
617  key=pid;
618  if ((shmid = shmget(key, SHMSZ, 0666)) < 0) {
619#ifdef DEBUG
620    fprintf(stderr,"shmget failed in getStatus\n");
621#endif
622  }else{
623    if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
624#ifdef DEBUG
625      fprintf(stderr,"shmat failed in getStatus\n");
626#endif
627    }else{
628      char *ret=strdup((char*)shm);
629      shmdt((void *)shm);
630      return ret;
631    }
632  }
633  return (char*)"-1";
634}
635
636#endif
637
638/**
639 * Update the status of an ongoing service
640 *
641 * @param conf the maps containing the settings of the main.cfg file
642 * @param percentCompleted percentage of completude of execution of the service
643 * @param message information about the current step executed
644 * @return the value of _updateStatus
645 * @see _updateStatus
646 */
647int updateStatus( maps* conf, const int percentCompleted, const char* message ){
648  char tmp[4];
649  snprintf(tmp,4,"%d",percentCompleted);
650  setMapInMaps( conf, "lenv", "status", tmp );
651  setMapInMaps( conf, "lenv", "message", message);
652  return _updateStatus( conf );
653}
654
655/**
656 * Access an input value
657 *
658 * @param inputs the maps to search for the input value
659 * @param parameterName the input name to fetch the value
660 * @param numberOfBytes the resulting size of the value to add (for binary
661 *  values), -1 for basic char* data
662 * @return a pointer to the input value if found, NULL in other case.
663 */
664char* getInputValue( maps* inputs, const char* parameterName, size_t* numberOfBytes){
665  map* res=getMapFromMaps(inputs,parameterName,"value");
666  if(res!=NULL){
667    map* size=getMapFromMaps(inputs,parameterName,"size");
668    if(size!=NULL){
669      *numberOfBytes=(size_t)atoi(size->value);
670      return res->value;
671    }else{
672      *numberOfBytes=strlen(res->value);
673      return res->value;
674    }
675  }
676  return NULL;
677}
678
679/**
680 * Read a file using the GDAL VSI API
681 *
682 * @param conf the maps containing the settings of the main.cfg file
683 * @param dataSource the datasource name to read
684 * @warning make sure to free ressources returned by this function
685 */
686char *readVSIFile(maps* conf,const char* dataSource){
687    VSILFILE * fichier=VSIFOpenL(dataSource,"rb");
688    VSIStatBuf file_status;
689    VSIStatL(dataSource, &file_status);
690    if(fichier==NULL){
691      char tmp[1024];
692      sprintf(tmp,"Failed to open file %s for reading purpose. File seems empty %lld.",
693              dataSource,file_status.st_size);
694      setMapInMaps(conf,"lenv","message",tmp);
695      return NULL;
696    }
697    char *res1=(char *)malloc(file_status.st_size*sizeof(char));
698    VSIFReadL(res1,1,file_status.st_size*sizeof(char),fichier);
699    res1[file_status.st_size-1]=0;
700    VSIFCloseL(fichier);
701    VSIUnlink(dataSource);
702    return res1;
703}
704
705/**
706 * Set an output value
707 *
708 * @param outputs the maps to define the output value
709 * @param parameterName the output name to set the value
710 * @param data the value to set
711 * @param numberOfBytes size of the value to add (for binary values), -1 for
712 *  basic char* data
713 * @return 0
714 */
715int  setOutputValue( maps* outputs, const char* parameterName, char* data, size_t numberOfBytes ){
716  if(numberOfBytes==-1){
717    setMapInMaps(outputs,parameterName,"value",data);
718  }else{
719    char size[1024];
720    map* tmp=getMapFromMaps(outputs,parameterName,"value");
721    if(tmp==NULL){
722      setMapInMaps(outputs,parameterName,"value","");
723      tmp=getMapFromMaps(outputs,parameterName,"value");
724    }
725    free(tmp->value);
726    tmp->value=(char*) malloc((numberOfBytes+1)*sizeof(char));
727    memcpy(tmp->value,data,numberOfBytes);
728    sprintf(size,"%lu",numberOfBytes);
729    setMapInMaps(outputs,parameterName,"size",size);
730  }
731  return 0;
732}
733
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