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

Last change on this file since 739 was 738, checked in by knut, 9 years ago

Redefined the API function addToMapWithSize to fix problem with Python/PHP support. Added some variable initializations and NULL pointer checks.

  • 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#define _LARGEFILE64_SOURCE 1
26#ifdef USE_MS
27#include "service_internal_ms.h"
28#else
29#include "cpl_vsi.h"
30#endif
31#include "service_internal.h"
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#ifdef WIN32
99      return NULL;
100#else
101    if(itn<ZOO_LOCK_MAX_RETRY){
102      itn++;
103      goto toRetry1;
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 = NULL;
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#ifndef WIN32
214        removeShmLock(conf,1);
215#else
216        unlockShm(lockid);
217#endif
218        free(stat);
219      }
220      return fcontent;
221    }
222    fclose(f0);
223    free(fbkpid);
224    if(stat!=NULL){
225      removeShmLock(conf,1);
226      free(stat);
227    }
228    return NULL;
229  }else{
230    free(fbkpid);
231    char* stat=getStatusId(conf,lid);
232    setMapInMaps(conf,"lenv","lid",stat);
233    removeShmLock(conf,1);
234    return NULL;
235  }
236}
237
238/**
239 * Stop handling status repport.
240 *
241 * @param conf the map containing the setting of the main.cfg file
242 */
243void unhandleStatus(maps *conf){
244  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
245  map* usid = getMapFromMaps (conf, "lenv", "usid");
246  char* fbkpid =
247    (char *) malloc ((strlen (r_inputs->value) + strlen (usid->value) + 9) 
248                     * sizeof (char));
249  sprintf (fbkpid, "%s/%s.status", r_inputs->value, usid->value);
250  unlink(fbkpid);
251  free(fbkpid);
252}
253
254/**
255 * Update the current status of the running service.
256 *
257 * @see acquireLock, lockShm
258 * @param conf the map containing the setting of the main.cfg file
259 * @return 0 on success, -2 if shmget failed, -1 if shmat failed
260 */
261int _updateStatus(maps *conf){
262  map* r_inputs = getMapFromMaps (conf, "main", "tmpPath");
263  map* sid = getMapFromMaps (conf, "lenv", "usid");
264  char* fbkpid =
265    (char *)
266    malloc ((strlen (r_inputs->value) + strlen (sid->value) + 9) * sizeof (char));
267  sprintf (fbkpid, "%s/%s.status", r_inputs->value, sid->value);
268  map* status=getMapFromMaps(conf,"lenv","status");
269  map* msg=getMapFromMaps(conf,"lenv","message");
270  if(status!=NULL && msg!=NULL &&
271     status->value!=NULL && msg->value!=NULL && 
272     strlen(status->value)>0 && strlen(msg->value)>1){   
273    semid lockid = NULL;
274    char* stat=getStatusId(conf,sid->value);
275    if(stat!=NULL){
276      lockid=acquireLock(conf);
277      if(lockid<0){
278        dumpMap(status);
279        return ZOO_LOCK_ACQUIRE_FAILED;
280      }
281    }
282    FILE* fstatus=fopen(fbkpid,"w");
283    if(fstatus!=NULL){
284      fprintf(fstatus,"%s|%s",status->value,msg->value);
285      fflush(fstatus);
286      fclose(fstatus);
287    }
288    if(stat!=NULL){
289      unlockShm(lockid);
290      free(stat);
291    }
292  }
293  return 0;
294}
295
296#endif
297
298#ifdef WIN32
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  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    VSIStatBufL file_status;
690    VSIStatL(dataSource, &file_status);
691    if(fichier==NULL){
692      char tmp[1024];
693      sprintf(tmp,"Failed to open file %s for reading purpose. File seems empty %lld.",
694              dataSource,file_status.st_size);
695      setMapInMaps(conf,"lenv","message",tmp);
696      return NULL;
697    }
698    char *res1=(char *)malloc(file_status.st_size*sizeof(char));
699    VSIFReadL(res1,1,file_status.st_size*sizeof(char),fichier);
700    res1[file_status.st_size-1]=0;
701    VSIFCloseL(fichier);
702    VSIUnlink(dataSource);
703    return res1;
704}
705
706/**
707 * Set an output value
708 *
709 * @param outputs the maps to define the output value
710 * @param parameterName the output name to set the value
711 * @param data the value to set
712 * @param numberOfBytes size of the value to add (for binary values), -1 for
713 *  basic char* data
714 * @return 0
715 */
716int  setOutputValue( maps* outputs, const char* parameterName, char* data, size_t numberOfBytes ){
717  if(numberOfBytes==-1){
718    setMapInMaps(outputs,parameterName,"value",data);
719  }else{
720    char size[1024];
721    map* tmp=getMapFromMaps(outputs,parameterName,"value");
722    if(tmp==NULL){
723      setMapInMaps(outputs,parameterName,"value","");
724      tmp=getMapFromMaps(outputs,parameterName,"value");
725    }
726    free(tmp->value);
727    tmp->value=(char*) malloc((numberOfBytes+1)*sizeof(char));
728    memcpy(tmp->value,data,numberOfBytes);
729    sprintf(size,"%lu",numberOfBytes);
730    setMapInMaps(outputs,parameterName,"size",size);
731  }
732  return 0;
733}
734
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