Reorder initialization so free parameters are available for expectations
[openmx:openmx.git] / src / omxImportFrontendState.c
1 /*
2  *  Copyright 2007-2013 The OpenMx Project
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *       http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *   Unless required by applicable law or agreed to in writing, software
11  *   distributed under the License is distributed on an "AS IS" BASIS,
12  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  */
16
17 #include "R.h"
18 #include <Rinternals.h>
19 #include <Rdefines.h>
20
21 #include <sys/stat.h>
22 #include <errno.h>
23
24 #include "omxDefines.h"
25 #include "omxState.h"
26 #include "omxGlobalState.h"
27 #include "omxNPSOLSpecific.h"
28
29 /* Outside R Functions */
30 static int isDir(const char *path);
31
32 int matchCaseInsensitive(const char *source, const char *target) {
33         return strcasecmp(source, target) == 0;
34 }
35
36 int omxProcessMxDataEntities(SEXP data) {
37         int errOut = FALSE;
38         SEXP nextLoc;
39         if(OMX_DEBUG) { Rprintf("Processing %d data source(s).\n", length(data));}
40         globalState->numData = length(data);
41         globalState->dataList = (omxData**) R_alloc(length(data), sizeof(omxData*));
42
43         for(int index = 0; index < length(data); index++) {
44                 PROTECT(nextLoc = VECTOR_ELT(data, index));                     // Retrieve the data object
45                 globalState->dataList[index] = omxNewDataFromMxData(NULL, nextLoc, globalState);
46                 if(OMX_DEBUG) {
47                         Rprintf("Data initialized at 0x%0xd = (%d x %d).\n",
48                                 globalState->dataList[index], globalState->dataList[index]->rows, globalState->dataList[index]->cols);
49                 }
50                 UNPROTECT(1); // nextMat
51                 if(globalState->statusCode < 0) {
52                         errOut = TRUE;
53                         globalState->numData = index+1;
54                         break;
55                 }
56         }
57         return(errOut);
58 }
59
60 int omxProcessMxMatrixEntities(SEXP matList) {
61         if(OMX_DEBUG) { Rprintf("Processing %d matrix(ces).\n", length(matList));}
62         int errOut = FALSE;
63         SEXP nextLoc, nextMat;
64         globalState->numMats = length(matList);
65         globalState->matrixList = (omxMatrix**) R_alloc(length(matList), sizeof(omxMatrix*));
66         SEXP matListNames = getAttrib(matList, R_NamesSymbol);
67
68         for(int index = 0; index < length(matList); index++) {
69                 PROTECT(nextLoc = VECTOR_ELT(matList, index));          // This is the matrix + populations
70                 PROTECT(nextMat = VECTOR_ELT(nextLoc, 0));              // The first element of the list is the matrix of values
71                 globalState->matrixList[index] = omxNewMatrixFromRPrimitive(
72                         nextMat, globalState, 1, -index - 1);
73                 globalState->matrixList[index]->name = CHAR(STRING_ELT(matListNames, index));
74                 if(OMX_DEBUG) {
75                         Rprintf("Matrix initialized at 0x%0xd = (%d x %d).\n",
76                                 globalState->matrixList[index], globalState->matrixList[index]->rows, globalState->matrixList[index]->cols);
77                 }
78                 UNPROTECT(2); // nextLoc and nextMat
79                 if(globalState->statusCode < 0) {
80                         if(OMX_DEBUG) { Rprintf("Initialization Error processing %dth matrix.\n", index+1);}
81                         errOut = TRUE;
82                         globalState->numMats = index+1;
83                         break;
84                 }
85         }
86         return(errOut);
87 }
88
89 void omxProcessMxAlgebraEntities(SEXP algList) {
90         SEXP nextAlgTuple;
91         SEXP algListNames = getAttrib(algList, R_NamesSymbol);
92
93         if(OMX_DEBUG) { Rprintf("Processing %d algebras.\n", globalState->numAlgs); }
94         globalState->algebraList = (omxMatrix**) R_alloc(globalState->numAlgs, sizeof(omxMatrix*));
95
96         for(int index = 0; index < globalState->numAlgs; index++) {
97                 globalState->algebraList[index] = omxInitMatrix(NULL, 0, 0, TRUE, globalState);
98         }
99
100         for(int index = 0; index < globalState->numAlgs; index++) {
101                 PROTECT(nextAlgTuple = VECTOR_ELT(algList, index));             // The next algebra or fit function to process
102                 if(OMX_DEBUG) { Rprintf("Initializing algebra %d at location 0x%0x.\n", index, globalState->algebraList + index); }
103                 if(IS_S4_OBJECT(nextAlgTuple)) {                // This is a fit function object.
104                         omxFillMatrixFromMxFitFunction(globalState->algebraList[index], nextAlgTuple, 1, index);
105                 } else {                                                                // This is an algebra spec.
106                         SEXP initialValue, formula, dependencies;
107                         PROTECT(initialValue = VECTOR_ELT(nextAlgTuple, 0));
108                         omxFillMatrixFromRPrimitive(globalState->algebraList[index],
109                                 initialValue, globalState, 1, index);
110                         UNPROTECT(1);   // initialValue
111                         PROTECT(formula = VECTOR_ELT(nextAlgTuple, 1));
112                         omxFillMatrixFromMxAlgebra(globalState->algebraList[index],
113                                 formula, CHAR(STRING_ELT(algListNames, index)));
114                         UNPROTECT(1);   // formula
115                         PROTECT(dependencies = VECTOR_ELT(nextAlgTuple, 2));
116                         UNPROTECT(1);   // dependencies
117                 }
118                 UNPROTECT(1);   // nextAlgTuple
119                 if (globalState->statusMsg[0]) return;
120         }
121 }
122
123 int omxProcessMxExpectationEntities(SEXP expList) {
124         if(OMX_DEBUG) { Rprintf("Initializing %d Model Expectation(s).\n", length(expList));}
125         int errOut = FALSE;
126         SEXP nextExp;
127         globalState->numExpects = length(expList);
128         globalState->expectationList = (omxExpectation**) R_alloc(length(expList), sizeof(omxExpectation*));
129
130         for(int index = 0; index < length(expList); index++) {
131                 PROTECT(nextExp = VECTOR_ELT(expList, index));
132                 globalState->expectationList[index] = omxNewIncompleteExpectation(nextExp, index, globalState);
133                 if(OMX_DEBUG) {
134                         Rprintf("%s incomplete expectation set up at 0x%0xd.\n",
135                                 (globalState->expectationList[index]->expType
136                                         == NULL ? "Untyped" : globalState->expectationList[index]->expType),
137                                          globalState->expectationList[index]);
138                 }
139                 UNPROTECT(1); // nextExp
140                 if(globalState->statusCode < 0) {
141                         if(OMX_DEBUG) { Rprintf("Initialization Error processing %dth Expectation.\n", index+1);}
142                         errOut = TRUE;
143                         globalState->numMats = index+1;
144                         break;
145                 }
146         }
147         return(errOut);
148 }
149
150
151 int omxCompleteMxExpectationEntities() {
152         if(OMX_DEBUG) { Rprintf("Completing %d Model Expectation(s).\n", globalState->numExpects);}
153         int errOut = FALSE;
154         
155         for(int index = 0; index < globalState->numExpects; index++) {
156                 omxCompleteExpectation(globalState->expectationList[index]);
157                 if(OMX_DEBUG) {
158                         Rprintf("%s expectation completed at 0x%0xd.\n",
159                                 (globalState->expectationList[index]->expType
160                                         == NULL ? "Untyped" : globalState->expectationList[index]->expType),
161                                          globalState->expectationList[index]);
162                 }
163                 if(globalState->statusCode < 0) {
164                         if(OMX_DEBUG) { Rprintf("Initialization Error processing %dth Expectation.\n", index+1);}
165                         errOut = TRUE;
166                         globalState->numMats = index+1;
167                         break;
168                 }
169         }
170         return(errOut);
171 }
172
173
174 void omxInitialMatrixAlgebraCompute() {
175         int numMats = globalState->numMats;
176         int numAlgs = globalState->numAlgs;
177
178         if(OMX_DEBUG) {Rprintf("Completed Algebras and Matrices.  Beginning Initial Compute.\n");}
179         omxStateNextEvaluation(globalState);
180
181         for(int index = 0; index < numMats; index++) {
182                 omxRecompute(globalState->matrixList[index]);
183         }
184
185         for(int index = 0; index < numAlgs; index++) {
186                 omxRecompute(globalState->algebraList[index]);
187         }
188 }
189
190 /*
191 checkpointList is a list().  Each element refers to one checkpointing request.
192 Each interval request is a list of length 5.
193 The first element is an integer that specifies type: 0 = file, 1 = socket, 2=R_connection
194 For a file, the next two are the directory(string)  and file name (string).
195 For a socket, they are server (string) and port number (int).
196 For a connection, the next one is the R_connection SEXP object.
197 After that is an integer <type> specifier.  0 means minutes, 1 means iterations.
198 The last element is an integer count, indicating the number of <type>s per checkpoint.
199 */
200 void omxProcessCheckpointOptions(SEXP checkpointList) {
201         if(OMX_VERBOSE) { Rprintf("Processing Checkpoint Requests.\n");}
202         globalState->numCheckpoints = length(checkpointList);
203         if(OMX_DEBUG) {Rprintf("Found %d checkpoints.\n", globalState->numCheckpoints); }
204         globalState->checkpointList = (omxCheckpoint*) R_alloc(globalState->numCheckpoints, sizeof(omxCheckpoint));
205         SEXP nextLoc;
206
207         for(int index = 0; index < globalState->numCheckpoints; index++) {
208                 omxCheckpoint *oC = &(globalState->checkpointList[index]);
209
210                 /* Initialize Checkpoint object */
211                 oC->socket = -1;
212                 oC->file = NULL;
213                 oC->connection = NULL;
214                 oC->time = 0;
215                 oC->numIterations = 0;
216                 oC->lastCheckpoint = 0;
217
218                 const char *pathName, *fileName;
219                 const char __attribute__((unused)) *serverName;
220
221                 PROTECT(nextLoc = VECTOR_ELT(checkpointList, index));
222                 int next = 0;
223                 oC->type = INTEGER(VECTOR_ELT(nextLoc, next++))[0];
224                 switch(oC->type) {
225                 case OMX_FILE_CHECKPOINT:
226                         pathName = CHAR(STRING_ELT(VECTOR_ELT(nextLoc, next++), 0));                    // FIXME: Might need PROTECT()ion
227                         fileName = CHAR(STRING_ELT(VECTOR_ELT(nextLoc, next++), 0));
228                         char sep ='/';
229
230                         if(!isDir(pathName)) {
231                                 error("Unable to open directory %s for checkpoint storage.\n", pathName);
232                         }
233
234                         char* fullname = Calloc(strlen(pathName) + strlen(fileName) + 5, char);
235                         sprintf(fullname, "%s%c%s", pathName, sep, fileName);
236                         if(OMX_VERBOSE) { Rprintf("Opening File: %s\n", fullname); }
237                         oC->file = fopen(fullname, "w");
238                         if(!oC->file) {
239                                 error("Unable to open file %s for checkpoint storage: %s.\n", fullname, strerror(errno));
240                         }
241                         Free(fullname);
242                         oC->saveHessian = FALSE;        // TODO: Decide if this should be true.
243                         break;
244
245                 case OMX_SOCKET_CHECKPOINT:
246                         serverName = CHAR(VECTOR_ELT(nextLoc, next++));
247                         int __attribute__((unused)) portno = INTEGER(AS_INTEGER(VECTOR_ELT(nextLoc, next++)))[0];
248                         Rprintf("Warning NYI: Socket checkpoints Not Yet Implemented.\n");
249                         oC->saveHessian = FALSE;
250                         break;
251
252                 case OMX_CONNECTION_CHECKPOINT: // NYI :::DEBUG:::
253                         oC->connection = VECTOR_ELT(nextLoc, next++);
254                         Rprintf("Warning NYI: Socket checkpoints Not Yet Implemented.\n");
255                         oC->saveHessian = FALSE;
256                         break;
257                 }
258
259                 int isCount = INTEGER(VECTOR_ELT(nextLoc, next++))[0];
260                 if(isCount) {
261                         oC->numIterations = INTEGER(AS_INTEGER(VECTOR_ELT(nextLoc, next++)))[0];
262                 } else {
263                         oC->time = REAL(AS_NUMERIC(VECTOR_ELT(nextLoc, next++)))[0] * 60;       // Constrained to seconds.
264                         if(oC->time < 1) oC->time = 1;                                                                          // Constrained to at least one.
265                 }
266
267                 UNPROTECT(1); /* nextLoc */
268
269         }
270 }
271
272 /*
273 varList is a list().  Each element of this list corresponds to one free parameter.
274 Each free parameter is a list.  The first element of this list is the lower bound.
275 The second element of the list is the upper bound.  The third element of the list
276 is a vector of mxIndices specifying the dependencies of the free parameter. 
277 The remaining elements of the list are 3-tuples.  These 3-tuples are (mxIndex, row, col).
278 */
279 void omxProcessFreeVarList(SEXP varList) {
280         int n = globalState->numFreeParams = length(varList);
281         SEXP nextVar, nextLoc;
282         if(OMX_VERBOSE) { Rprintf("Processing Free Parameters.\n"); }
283         globalState->freeVarList = (omxFreeVar*) R_alloc (n, sizeof (omxFreeVar));                      // Data for replacement of free vars
284         for(int freeVarIndex = 0; freeVarIndex < n; freeVarIndex++) {
285                 int numDeps;
286                 PROTECT(nextVar = VECTOR_ELT(varList, freeVarIndex));
287                 int numLocs = length(nextVar) - 3;
288                 globalState->freeVarList[freeVarIndex].numLocations = numLocs;
289                 globalState->freeVarList[freeVarIndex].matrices = (int*) R_alloc(numLocs, sizeof(int));
290                 globalState->freeVarList[freeVarIndex].row               = (int*) R_alloc(numLocs, sizeof(int));
291                 globalState->freeVarList[freeVarIndex].col               = (int*) R_alloc(numLocs, sizeof(int));
292                 globalState->freeVarList[freeVarIndex].name = CHAR(STRING_ELT(GET_NAMES(varList), freeVarIndex));
293
294                 /* Lower Bound */
295                 PROTECT(nextLoc = VECTOR_ELT(nextVar, 0));                                                      // Position 0 is lower bound.
296                 globalState->freeVarList[freeVarIndex].lbound = REAL(nextLoc)[0];
297                 if(ISNA(globalState->freeVarList[freeVarIndex].lbound)) globalState->freeVarList[freeVarIndex].lbound = NEG_INF;
298                 if(globalState->freeVarList[freeVarIndex].lbound == 0.0) globalState->freeVarList[freeVarIndex].lbound = 0.0;
299                 UNPROTECT(1); // nextLoc
300                 /* Upper Bound */
301                 PROTECT(nextLoc = VECTOR_ELT(nextVar, 1));                                                      // Position 1 is upper bound.
302                 globalState->freeVarList[freeVarIndex].ubound = REAL(nextLoc)[0];
303                 if(ISNA(globalState->freeVarList[freeVarIndex].ubound)) globalState->freeVarList[freeVarIndex].ubound = INF;
304                 if(globalState->freeVarList[freeVarIndex].ubound == 0.0) globalState->freeVarList[freeVarIndex].ubound = -0.0;
305                 UNPROTECT(1); // nextLoc
306
307                 PROTECT(nextLoc = VECTOR_ELT(nextVar, 2));                                                      // Position 2 is a vector of dependencies.
308                 numDeps = LENGTH(nextLoc);
309                 globalState->freeVarList[freeVarIndex].numDeps = numDeps;
310                 globalState->freeVarList[freeVarIndex].deps = (int*) R_alloc(numDeps, sizeof(int));
311                 for(int i = 0; i < numDeps; i++) {
312                         globalState->freeVarList[freeVarIndex].deps[i] = INTEGER(nextLoc)[i];
313                 }
314                 UNPROTECT(1); // nextLoc
315
316
317                 if(OMX_DEBUG) { 
318                         Rprintf("Free parameter %d bounded (%f, %f): %d locations\n", freeVarIndex, 
319                                 globalState->freeVarList[freeVarIndex].lbound, 
320                                 globalState->freeVarList[freeVarIndex].ubound, numLocs);
321                 }
322                 for(int locIndex = 0; locIndex < globalState->freeVarList[freeVarIndex].numLocations; locIndex++) {
323                         PROTECT(nextLoc = VECTOR_ELT(nextVar, locIndex+3));
324                         int* theVarList = INTEGER(nextLoc);                     // These come through as integers.
325
326                         int theMat = theVarList[0];                     // Matrix is zero-based indexed.
327                         int theRow = theVarList[1];                     // Row is zero-based.
328                         int theCol = theVarList[2];                     // Column is zero-based.
329
330                         globalState->freeVarList[freeVarIndex].matrices[locIndex] = theMat;
331                         globalState->freeVarList[freeVarIndex].row[locIndex] = theRow;
332                         globalState->freeVarList[freeVarIndex].col[locIndex] = theCol;
333                         UNPROTECT(1); // nextLoc
334                 }
335                 UNPROTECT(1); // nextVar
336         }
337
338         int numMats = globalState->numMats;
339
340         for(int freeVarIndex = 0; freeVarIndex < n; freeVarIndex++) {
341                 omxFreeVar* freeVar = globalState->freeVarList + freeVarIndex;
342                 int *deps   = freeVar->deps;
343                 int numDeps = freeVar->numDeps;
344                 for (int index = 0; index < numDeps; index++) {
345                         globalState->markMatrices[deps[index] + numMats] = 1;
346                 }
347         }
348
349 }
350
351 /*
352         intervalList is a list().  Each element refers to one confidence interval request.
353         Each interval request is a length 5 vector of REAL.
354         The first three elements are the matrixPointer, Row, and Column of the element
355         for which bounds are to be calculated, and are cast to ints here for speed.
356         The last two are the upper and lower boundaries for the confidence space (respectively).
357 */
358 void omxProcessConfidenceIntervals(SEXP intervalList)  {
359         SEXP nextVar;
360         if(OMX_VERBOSE) { Rprintf("Processing Confidence Interval Requests.\n");}
361         globalState->numIntervals = length(intervalList);
362         if(OMX_DEBUG) {Rprintf("Found %d requests.\n", globalState->numIntervals); }
363         globalState->intervalList = (omxConfidenceInterval*) R_alloc(globalState->numIntervals, sizeof(omxConfidenceInterval));
364         for(int index = 0; index < globalState->numIntervals; index++) {
365                 omxConfidenceInterval *oCI = &(globalState->intervalList[index]);
366                 PROTECT(nextVar = VECTOR_ELT(intervalList, index));
367                 double* intervalInfo = REAL(nextVar);
368                 oCI->matrix = omxMatrixLookupFromState1( nextVar, globalState); // Expects an R object
369                 oCI->row = (int) intervalInfo[1];               // Cast to int in C to save memory/Protection ops
370                 oCI->col = (int) intervalInfo[2];               // Cast to int in C to save memory/Protection ops
371                 oCI->lbound = intervalInfo[3];
372                 oCI->ubound = intervalInfo[4];
373                 UNPROTECT(1);
374                 oCI->max = R_NaReal;                                    // NAs, in case something goes wrong
375                 oCI->min = R_NaReal;
376         }
377         if(OMX_VERBOSE) { Rprintf("Processed.\n"); }
378         if(OMX_DEBUG) { Rprintf("%d intervals requested.\n", globalState->numIntervals); }
379 }
380
381 void omxProcessConstraints(SEXP constraints)  {
382         int ncnln = 0; 
383         if(OMX_VERBOSE) { Rprintf("Processing Constraints.\n");}
384         omxMatrix *arg1, *arg2;
385         SEXP nextVar, nextLoc;
386         globalState->numConstraints = length(constraints);
387         if(OMX_DEBUG) {Rprintf("Found %d constraints.\n", globalState->numConstraints); }
388         globalState->conList = (omxConstraint*) R_alloc(globalState->numConstraints, sizeof(omxConstraint));
389         ncnln = 0;
390         for(int constraintIndex = 0; constraintIndex < globalState->numConstraints; constraintIndex++) {
391                 PROTECT(nextVar = VECTOR_ELT(constraints, constraintIndex));
392                 PROTECT(nextLoc = VECTOR_ELT(nextVar, 0));
393                 arg1 = omxMatrixLookupFromState1(nextLoc, globalState);
394                 PROTECT(nextLoc = VECTOR_ELT(nextVar, 1));
395                 arg2 = omxMatrixLookupFromState1(nextLoc, globalState);
396                 PROTECT(nextLoc = AS_INTEGER(VECTOR_ELT(nextVar, 2)));
397                 globalState->conList[constraintIndex].opCode = INTEGER(nextLoc)[0];
398                 UNPROTECT(4);
399                 omxMatrix *args[2] = {arg1, arg2};
400                 globalState->conList[constraintIndex].result = omxNewAlgebraFromOperatorAndArgs(10, args, 2, globalState); // 10 = binary subtract
401                 omxRecompute(globalState->conList[constraintIndex].result);
402                 int nrows = globalState->conList[constraintIndex].result->rows;
403                 int ncols = globalState->conList[constraintIndex].result->cols;
404                 globalState->conList[constraintIndex].size = nrows * ncols;
405                 ncnln += globalState->conList[constraintIndex].size;
406         }
407         if(OMX_VERBOSE) { Rprintf("Processed.\n"); }
408         if(OMX_DEBUG) { Rprintf("%d effective constraints.\n", ncnln); }
409         globalState->ncnln = ncnln;
410         globalState->nclin = 0;
411 }
412
413 void omxSetupBoundsAndConstraints(double * bl, double * bu, int n, int nclin) {
414         /* Set min and max limits */
415         for(int index = 0; index < n; index++) {
416                 bl[index] = globalState->freeVarList[index].lbound;                             // -Infinity'd be -10^20.
417                 bu[index] = globalState->freeVarList[index].ubound;                             // Infinity would be at 10^20.
418         }
419
420         for(int index = n; index < n+nclin; index++) {                                          // At present, nclin == 0.
421                 bl[index] = NEG_INF;                                                    // Linear constraints have no bounds.
422                 bu[index] = INF;                                                                // Because there are no linear constraints.
423         }                                                                                                   // But if there were, they would go here.
424
425         int index = n + nclin;
426         for(int constraintIndex = 0; constraintIndex < globalState->numConstraints; constraintIndex++) {                // Nonlinear constraints:
427                 if(OMX_DEBUG) { Rprintf("Constraint %d: ", constraintIndex);}
428                 switch(globalState->conList[constraintIndex].opCode) {
429                 case 0:                                                                 // Less than: Must be strictly less than 0.
430                         if(OMX_DEBUG) { Rprintf("Bounded at (-INF, 0).\n");}
431                         for(int offset = 0; offset < globalState->conList[constraintIndex].size; offset++) {
432                                 bl[index] = NEG_INF;
433                                 bu[index] = -0.0;
434                                 index++;
435                         }
436                         break;
437                 case 1:                                                                 // Equal: Must be roughly equal to 0.
438                         if(OMX_DEBUG) { Rprintf("Bounded at (-0, 0).\n");}
439                         for(int offset = 0; offset < globalState->conList[constraintIndex].size; offset++) {
440                                 bl[index] = -0.0;
441                                 bu[index] = 0.0;
442                                 index++;
443                         }
444                         break;
445                 case 2:                                                                 // Greater than: Must be strictly greater than 0.
446                         if(OMX_DEBUG) { Rprintf("Bounded at (0, INF).\n");}
447                         for(int offset = 0; offset < globalState->conList[constraintIndex].size; offset++) {
448                                 if(OMX_DEBUG) { Rprintf("\tBounds set for constraint %d.%d.\n", constraintIndex, offset);}
449                                 bl[index] = 0.0;
450                                 bu[index] = INF;
451                                 index++;
452                         }
453                         break;
454                 default:
455                         if(OMX_DEBUG) { Rprintf("Bounded at (-INF, INF).\n");}
456                         for(int offset = 0; offset < globalState->conList[constraintIndex].size; offset++) {
457                                 bl[index] = NEG_INF;
458                                 bu[index] = INF;
459                                 index++;
460                         }
461                         break;
462                 }
463         }
464 }
465
466 /*
467 *  Acknowledgement:
468 *  This function is duplicated from the function of the same name in the R source code.
469 *  The function appears in src/main/sysutils.c
470 *  Thanks to Michael Spiegel for finding it.
471 *  This code is licensed under the terms of the GNU General Public License.
472 */
473 static int isDir(const char *path)
474 {
475     struct stat sb;
476     int isdir = 0;
477     if(!path) return 0;
478     if(stat(path, &sb) == 0) {
479         isdir = (sb.st_mode & S_IFDIR) > 0; /* is a directory */
480         /* We want to know if the directory is writable by this user,
481            which mode does not tell us */
482         isdir &= (access(path, W_OK) == 0);
483     }
484     return isdir;
485 }
486