source: trunk/src/kmk/dir-nt-bird.c@ 2948

Last change on this file since 2948 was 2948, checked in by bird, 9 years ago

kWorker/kDep: save a few header stat calls while optimizing dependencies.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 21.2 KB
Line 
1/* $Id: dir-nt-bird.c 2948 2016-09-20 15:36:07Z bird $ */
2/** @file
3 * Reimplementation of dir.c for NT using kFsCache.
4 *
5 * This should perform better on NT, especially on machines "infected"
6 * by antivirus programs.
7 */
8
9/*
10 * Copyright (c) 2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
11 *
12 * This file is part of kBuild.
13 *
14 * kBuild is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * kBuild is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with kBuild. If not, see <http://www.gnu.org/licenses/>
26 *
27 */
28
29
30/*********************************************************************************************************************************
31* Header Files *
32*********************************************************************************************************************************/
33#include "nt/kFsCache.h"
34#include "make.h"
35#if defined(KMK) && !defined(__OS2__)
36# include "glob/glob.h"
37#else
38# include <glob.h>
39#endif
40
41
42#include "nt_fullpath.h" /* for the time being - will be implemented here later on. */
43
44
45/*********************************************************************************************************************************
46* Defined Constants And Macros *
47*********************************************************************************************************************************/
48/** User data key indicating that it's an impossible file to make.
49 * See file_impossible() and file_impossible_p(). */
50#define KMK_DIR_NT_IMPOSSIBLE_KEY (~(KUPTR)7)
51
52
53/*********************************************************************************************************************************
54* Structures and Typedefs *
55*********************************************************************************************************************************/
56/**
57 * glob directory stream.
58 */
59typedef struct KMKNTOPENDIR
60{
61 /** Reference to the directory. */
62 PKFSDIR pDir;
63 /** Index of the next directory entry (child) to return. */
64 KU32 idxNext;
65 /** The structure in which to return the directory entry. */
66 struct dirent DirEnt;
67} KMKNTOPENDIR;
68
69
70/*********************************************************************************************************************************
71* Global Variables *
72*********************************************************************************************************************************/
73/** The cache.*/
74PKFSCACHE g_pFsCache = NULL;
75/** Number of times dir_cache_invalid_missing was called. */
76static KU32 g_cInvalidates = 0;
77/** Set by dir_cache_volatile_dir to indicate that the user has marked the
78 * volatile parts of the file system with custom revisioning and we only need to
79 * flush these. This is very handy when using a separate output directory
80 * from the sources. */
81static KBOOL g_fFsCacheIsUsingCustomRevision = K_FALSE;
82
83
84void hash_init_directories(void)
85{
86 g_pFsCache = kFsCacheCreate(0);
87 if (g_pFsCache)
88 return;
89 fputs("kFsCacheCreate failed!", stderr);
90 exit(9);
91}
92
93
94/**
95 * Checks if @a pszName exists in directory @a pszDir.
96 *
97 * @returns 1 if it does, 0 if it doesn't.
98 *
99 * @param pszDir The directory.
100 * @param pszName The name.
101 *
102 * If empty string, just check if the directory exists.
103 *
104 * If NULL, just read the whole cache the directory into
105 * the cache (we always do that).
106 */
107int dir_file_exists_p(const char *pszDir, const char *pszName)
108{
109 int fRc = 0;
110 KFSLOOKUPERROR enmError;
111 PKFSOBJ pDirObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
112 if (pDirObj)
113 {
114 if (pDirObj->bObjType == KFSOBJ_TYPE_DIR)
115 {
116 if (pszName != 0)
117 {
118 /* Empty filename is just checking out the directory. */
119 if (*pszName == '\0')
120 fRc = 1;
121 else
122 {
123 PKFSOBJ pNameObj = kFsCacheLookupRelativeToDirA(g_pFsCache, (PKFSDIR)pDirObj, pszName, strlen(pszName),
124 0/*fFlags*/, &enmError, NULL);
125 if (pNameObj)
126 {
127 fRc = pNameObj->bObjType == KFSOBJ_TYPE_MISSING;
128 kFsCacheObjRelease(g_pFsCache, pNameObj);
129 }
130 }
131 }
132 }
133 kFsCacheObjRelease(g_pFsCache, pDirObj);
134 }
135 return fRc;
136}
137
138
139/**
140 * Checks if a file exists.
141 *
142 * @returns 1 if it does exist, 0 if it doesn't.
143 * @param pszPath The path to check out.
144 */
145int file_exists_p(const char *pszPath)
146{
147 int fRc;
148 KFSLOOKUPERROR enmError;
149 PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
150 if (pPathObj)
151 {
152 fRc = pPathObj->bObjType != KFSOBJ_TYPE_MISSING;
153 kFsCacheObjRelease(g_pFsCache, pPathObj);
154 }
155 else
156 fRc = 0;
157 return fRc;
158}
159
160
161/**
162 * Just a way for vpath.c to get a correctly cased path, I think.
163 *
164 * @returns Directory path in string cache.
165 * @param pszDir The directory.
166 */
167const char *dir_name(const char *pszDir)
168{
169 char szTmp[MAX_PATH];
170 nt_fullpath(pszDir, szTmp, sizeof(szTmp));
171 return strcache_add(szTmp);
172}
173
174
175/**
176 * Makes future file_impossible_p calls return 1 for pszPath.
177 */
178void file_impossible(const char *pszPath)
179{
180 KFSLOOKUPERROR enmError;
181 PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
182 if (pPathObj)
183 {
184 kFsCacheObjAddUserData(g_pFsCache, pPathObj, KMK_DIR_NT_IMPOSSIBLE_KEY, sizeof(KFSUSERDATA));
185 kFsCacheObjRelease(g_pFsCache, pPathObj);
186 }
187}
188
189/**
190 * Makes future file_impossible_p calls return 1 for pszPath.
191 */
192int file_impossible_p(const char *pszPath)
193{
194 int fRc;
195 KFSLOOKUPERROR enmError;
196 PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
197 if (pPathObj)
198 {
199 fRc = kFsCacheObjGetUserData(g_pFsCache, pPathObj, KMK_DIR_NT_IMPOSSIBLE_KEY) != NULL;
200 kFsCacheObjRelease(g_pFsCache, pPathObj);
201 }
202 else
203 fRc = 0;
204 return fRc;
205}
206
207
208/**
209 * opendir for glob.
210 *
211 * @returns Pointer to DIR like handle, NULL if directory not found.
212 * @param pszDir The directory to enumerate.
213 */
214static __ptr_t dir_glob_opendir(const char *pszDir)
215{
216 KFSLOOKUPERROR enmError;
217 PKFSOBJ pDirObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
218 if (pDirObj)
219 {
220 if (pDirObj->bObjType == KFSOBJ_TYPE_DIR)
221 {
222 if (kFsCacheDirEnsurePopuplated(g_pFsCache, (PKFSDIR)pDirObj, NULL))
223 {
224 KMKNTOPENDIR *pDir = xmalloc(sizeof(*pDir));
225 pDir->pDir = (PKFSDIR)pDirObj;
226 pDir->idxNext = 0;
227 return pDir;
228 }
229 }
230 kFsCacheObjRelease(g_pFsCache, pDirObj);
231 }
232 return NULL;
233}
234
235
236/**
237 * readdir for glob.
238 *
239 * @returns Pointer to DIR like handle, NULL if directory not found.
240 * @param pDir Directory enum handle by dir_glob_opendir.
241 */
242static struct dirent *dir_glob_readdir(__ptr_t pvDir)
243{
244 KMKNTOPENDIR *pDir = (KMKNTOPENDIR *)pvDir;
245 KU32 const cChildren = pDir->pDir->cChildren;
246 while (pDir->idxNext < cChildren)
247 {
248 PKFSOBJ pEntry = pDir->pDir->papChildren[pDir->idxNext++];
249
250 /* Don't return missing objects. */
251 if (pEntry->bObjType != KFSOBJ_TYPE_MISSING)
252 {
253 /* Copy the name that fits. If neither fits, skip the name. */
254 if (pEntry->cchName < sizeof(pDir->DirEnt.d_name))
255 {
256 pDir->DirEnt.d_namlen = pEntry->cchName;
257 memcpy(pDir->DirEnt.d_name, pEntry->pszName, pEntry->cchName + 1);
258 }
259 else if (pEntry->cchShortName < sizeof(pDir->DirEnt.d_name))
260 {
261 pDir->DirEnt.d_namlen = pEntry->cchShortName;
262 memcpy(pDir->DirEnt.d_name, pEntry->pszShortName, pEntry->cchShortName + 1);
263 }
264 else
265 continue;
266
267 pDir->DirEnt.d_reclen = offsetof(struct dirent, d_name) + pDir->DirEnt.d_namlen;
268 if (pEntry->bObjType == KFSOBJ_TYPE_DIR)
269 pDir->DirEnt.d_type = DT_DIR;
270 else if (pEntry->bObjType == KFSOBJ_TYPE_FILE)
271 pDir->DirEnt.d_type = DT_REG;
272 else
273 pDir->DirEnt.d_type = DT_UNKNOWN;
274
275 return &pDir->DirEnt;
276 }
277 }
278
279 /*
280 * Fake the '.' and '..' directories because they're not part of papChildren above.
281 */
282 if (pDir->idxNext < cChildren + 2)
283 {
284 pDir->idxNext++;
285 pDir->DirEnt.d_type = DT_DIR;
286 pDir->DirEnt.d_namlen = pDir->idxNext - cChildren;
287 pDir->DirEnt.d_reclen = offsetof(struct dirent, d_name) + pDir->DirEnt.d_namlen;
288 pDir->DirEnt.d_name[0] = '.';
289 pDir->DirEnt.d_name[1] = '.';
290 pDir->DirEnt.d_name[pDir->DirEnt.d_namlen] = '\0';
291 return &pDir->DirEnt;
292 }
293
294 return NULL;
295}
296
297
298/**
299 * closedir for glob.
300 *
301 * @param pDir Directory enum handle by dir_glob_opendir.
302 */
303static void dir_glob_closedir(__ptr_t pvDir)
304{
305 KMKNTOPENDIR *pDir = (KMKNTOPENDIR *)pvDir;
306 kFsCacheObjRelease(g_pFsCache, &pDir->pDir->Obj);
307 pDir->pDir = NULL;
308 free(pDir);
309}
310
311
312/**
313 * stat for glob.
314 *
315 * @returns 0 on success, -1 + errno on failure.
316 * @param pszPath The path to stat.
317 * @param pStat Where to return the info.
318 */
319static int dir_glob_stat(const char *pszPath, struct stat *pStat)
320{
321 KFSLOOKUPERROR enmError;
322 PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
323/** @todo follow symlinks vs. on symlink! */
324 if (pPathObj)
325 {
326 if (pPathObj->bObjType != KFSOBJ_TYPE_MISSING)
327 {
328 kHlpAssert(pPathObj->fHaveStats); /* currently always true. */
329 *pStat = pPathObj->Stats;
330 kFsCacheObjRelease(g_pFsCache, pPathObj);
331 return 0;
332 }
333 kFsCacheObjRelease(g_pFsCache, pPathObj);
334 }
335 errno = ENOENT;
336 return -1;
337}
338
339
340/**
341 * lstat for glob.
342 *
343 * @returns 0 on success, -1 + errno on failure.
344 * @param pszPath The path to stat.
345 * @param pStat Where to return the info.
346 */
347static int dir_glob_lstat(const char *pszPath, struct stat *pStat)
348{
349 KFSLOOKUPERROR enmError;
350 PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
351 if (pPathObj)
352 {
353 if (pPathObj->bObjType != KFSOBJ_TYPE_MISSING)
354 {
355 kHlpAssert(pPathObj->fHaveStats); /* currently always true. */
356 *pStat = pPathObj->Stats;
357 kFsCacheObjRelease(g_pFsCache, pPathObj);
358 return 0;
359 }
360 kFsCacheObjRelease(g_pFsCache, pPathObj);
361 errno = ENOENT;
362 }
363 else
364 errno = enmError == KFSLOOKUPERROR_NOT_DIR
365 || enmError == KFSLOOKUPERROR_PATH_COMP_NOT_DIR
366 ? ENOTDIR : ENOENT;
367
368 return -1;
369}
370
371
372/**
373 * Checks if @a pszDir exists and is a directory.
374 *
375 * @returns 1 if is directory, 0 if isn't or doesn't exists.
376 * @param pszDir The alleged directory.
377 */
378static int dir_globl_dir_exists_p(const char *pszDir)
379{
380 int fRc;
381 KFSLOOKUPERROR enmError;
382 PKFSOBJ pDirObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
383 if (pDirObj)
384 {
385 fRc = pDirObj->bObjType == KFSOBJ_TYPE_DIR;
386 kFsCacheObjRelease(g_pFsCache, pDirObj);
387 }
388 else
389 fRc = 0;
390 return fRc;
391
392}
393
394
395/**
396 * Sets up pGlob with the necessary callbacks.
397 *
398 * @param pGlob Structure to populate.
399 */
400void dir_setup_glob(glob_t *pGlob)
401{
402 pGlob->gl_opendir = dir_glob_opendir;
403 pGlob->gl_readdir = dir_glob_readdir;
404 pGlob->gl_closedir = dir_glob_closedir;
405 pGlob->gl_stat = dir_glob_stat;
406#ifdef __EMX__ /* The FreeBSD implementation actually uses gl_lstat!! */
407 pGlob->gl_lstat = dir_glob_lstat;
408#else
409 pGlob->gl_exists = file_exists_p;
410 pGlob->gl_isdir = dir_globl_dir_exists_p;
411#endif
412}
413
414
415/**
416 * Print statitstics.
417 */
418void print_dir_stats(void)
419{
420 FILE *pOut = stdout;
421 KU32 cMisses;
422
423 fputs("\n"
424 "# NT dir cache stats:\n", pOut);
425 fprintf(pOut, "# %u objects, taking up %u (%#x) bytes, avg %u bytes\n",
426 g_pFsCache->cObjects, g_pFsCache->cbObjects, g_pFsCache->cbObjects, g_pFsCache->cbObjects / g_pFsCache->cObjects);
427 fprintf(pOut, "# %u A path hashes, taking up %u (%#x) bytes, avg %u bytes, %u collision\n",
428 g_pFsCache->cAnsiPaths, g_pFsCache->cbAnsiPaths, g_pFsCache->cbAnsiPaths,
429 g_pFsCache->cbAnsiPaths / K_MAX(g_pFsCache->cAnsiPaths, 1), g_pFsCache->cAnsiPathCollisions);
430#ifdef KFSCACHE_CFG_UTF16
431 fprintf(pOut, "# %u W path hashes, taking up %u (%#x) bytes, avg %u bytes, %u collisions\n",
432 g_pFsCache->cUtf16Paths, g_pFsCache->cbUtf16Paths, g_pFsCache->cbUtf16Paths,
433 g_pFsCache->cbUtf16Paths / K_MAX(g_pFsCache->cUtf16Paths, 1), g_pFsCache->cUtf16PathCollisions);
434#endif
435 fprintf(pOut, "# %u child hash tables, total of %u entries, %u children inserted, %u collisions\n",
436 g_pFsCache->cChildHashTabs, g_pFsCache->cChildHashEntriesTotal,
437 g_pFsCache->cChildHashed, g_pFsCache->cChildHashCollisions);
438
439 cMisses = g_pFsCache->cLookups - g_pFsCache->cPathHashHits - g_pFsCache->cWalkHits;
440 fprintf(pOut, "# %u lookups: %u (%" KU64_PRI " %%) path hash hits, %u (%" KU64_PRI "%%) walks hits, %u (%" KU64_PRI "%%) misses\n",
441 g_pFsCache->cLookups,
442 g_pFsCache->cPathHashHits, g_pFsCache->cPathHashHits * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1),
443 g_pFsCache->cWalkHits, g_pFsCache->cWalkHits * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1),
444 cMisses, cMisses * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1));
445 fprintf(pOut, "# %u child searches, %u (%" KU64_PRI "%%) hash hits\n",
446 g_pFsCache->cChildSearches,
447 g_pFsCache->cChildHashHits, g_pFsCache->cChildHashHits * (KU64)100 / K_MAX(g_pFsCache->cChildSearches, 1));
448}
449
450
451void print_dir_data_base(void)
452{
453 /** @todo. */
454
455}
456
457
458/* duplicated in kWorker.c */
459void nt_fullpath_cached(const char *pszPath, char *pszFull, size_t cbFull)
460{
461 KFSLOOKUPERROR enmError;
462 PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
463 if (pPathObj)
464 {
465 KSIZE off = pPathObj->cchParent;
466 if (off > 0)
467 {
468 KSIZE offEnd = off + pPathObj->cchName;
469 if (offEnd < cbFull)
470 {
471 PKFSDIR pAncestor;
472
473 pszFull[off + pPathObj->cchName] = '\0';
474 memcpy(&pszFull[off], pPathObj->pszName, pPathObj->cchName);
475
476 for (pAncestor = pPathObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent)
477 {
478 kHlpAssert(off > 1);
479 kHlpAssert(pAncestor != NULL);
480 kHlpAssert(pAncestor->Obj.cchName > 0);
481 pszFull[--off] = '/';
482 off -= pAncestor->Obj.cchName;
483 kHlpAssert(pAncestor->Obj.cchParent == off);
484 memcpy(&pszFull[off], pAncestor->Obj.pszName, pAncestor->Obj.cchName);
485 }
486 kFsCacheObjRelease(g_pFsCache, pPathObj);
487 return;
488 }
489 }
490 else
491 {
492 if ((size_t)pPathObj->cchName + 1 < cbFull)
493 {
494 memcpy(pszFull, pPathObj->pszName, pPathObj->cchName);
495 pszFull[pPathObj->cchName] = '/';
496 pszFull[pPathObj->cchName + 1] = '\0';
497
498 kFsCacheObjRelease(g_pFsCache, pPathObj);
499 return;
500 }
501 }
502
503 /* do fallback. */
504 kHlpAssertFailed();
505 kFsCacheObjRelease(g_pFsCache, pPathObj);
506 }
507
508 nt_fullpath(pszPath, pszFull, cbFull);
509}
510
511
512/**
513 * Special stat call used by remake.c
514 *
515 * @returns 0 on success, -1 + errno on failure.
516 * @param pszPath The path to stat.
517 * @param pStat Where to return the mtime field only.
518 */
519int stat_only_mtime(const char *pszPath, struct stat *pStat)
520{
521 /* Currently a little expensive, so just hit the file system once the
522 jobs starts comming in. */
523 if (g_cInvalidates == 0)
524 {
525 KFSLOOKUPERROR enmError;
526 PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
527 if (pPathObj)
528 {
529 if (pPathObj->bObjType != KFSOBJ_TYPE_MISSING)
530 {
531 kHlpAssert(pPathObj->fHaveStats); /* currently always true. */
532 pStat->st_mtime = pPathObj->Stats.st_mtime;
533 kFsCacheObjRelease(g_pFsCache, pPathObj);
534 return 0;
535 }
536
537 kFsCacheObjRelease(g_pFsCache, pPathObj);
538 errno = ENOENT;
539 }
540 else
541 errno = enmError == KFSLOOKUPERROR_NOT_DIR
542 || enmError == KFSLOOKUPERROR_PATH_COMP_NOT_DIR
543 ? ENOTDIR : ENOENT;
544 return -1;
545 }
546 return birdStatModTimeOnly(pszPath, &pStat->st_mtim, 1 /*fFollowLink*/);
547}
548
549/**
550 * Do cache invalidation after a job completes.
551 */
552void dir_cache_invalid_after_job(void)
553{
554 g_cInvalidates++;
555 if (g_fFsCacheIsUsingCustomRevision)
556 kFsCacheInvalidateCustomBoth(g_pFsCache);
557 else
558 kFsCacheInvalidateAll(g_pFsCache);
559}
560
561/**
562 * Invalidate the whole directory cache
563 *
564 * Used by $(dircache-ctl invalidate)
565 */
566void dir_cache_invalid_all(void)
567{
568 g_cInvalidates++;
569 kFsCacheInvalidateAll(g_pFsCache);
570}
571
572/**
573 * Invalidate missing bits of the directory cache.
574 *
575 * Used by $(dircache-ctl invalidate-missing)
576 */
577void dir_cache_invalid_missing(void)
578{
579 g_cInvalidates++;
580 kFsCacheInvalidateAll(g_pFsCache);
581}
582
583/**
584 * Invalidate the volatile bits of the directory cache.
585 *
586 * Used by $(dircache-ctl invalidate-missing)
587 */
588void dir_cache_invalid_volatile(void)
589{
590 g_cInvalidates++;
591 if (g_fFsCacheIsUsingCustomRevision)
592 kFsCacheInvalidateCustomBoth(g_pFsCache);
593 else
594 kFsCacheInvalidateAll(g_pFsCache);
595}
596
597/**
598 * Used by $(dircache-ctl ) to mark a directory subtree or file as volatile.
599 *
600 * The first call changes the rest of the cache to be considered non-volatile.
601 *
602 * @returns 0 on success, -1 on failure.
603 * @param pszDir The directory (or file for what that is worth).
604 */
605int dir_cache_volatile_dir(const char *pszDir)
606{
607 KFSLOOKUPERROR enmError;
608 PKFSOBJ pObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
609 if (pObj)
610 {
611 KBOOL fRc = kFsCacheSetupCustomRevisionForTree(g_pFsCache, pObj);
612 kFsCacheObjRelease(g_pFsCache, pObj);
613 if (fRc)
614 {
615 g_fFsCacheIsUsingCustomRevision = K_TRUE;
616 return 0;
617 }
618 error(reading_file, "failed to mark '%s' as volatile", pszDir);
619 }
620 else
621 error(reading_file, "failed to mark '%s' as volatile (not found)", pszDir);
622 return -1;
623}
624
625/**
626 * Invalidates a deleted directory so the cache can close handles to it.
627 *
628 * Used by kmk_builtin_rm and kmk_builtin_rmdir.
629 *
630 * @returns 0 on success, -1 on failure.
631 * @param pszDir The directory to invalidate as deleted.
632 */
633int dir_cache_deleted_directory(const char *pszDir)
634{
635 if (kFsCacheInvalidateDeletedDirectoryA(g_pFsCache, pszDir))
636 return 0;
637 return -1;
638}
639
640
641int kmk_builtin_dircache(int argc, char **argv, char **envp)
642{
643 if (argc >= 2)
644 {
645 const char *pszCmd = argv[1];
646 if (strcmp(pszCmd, "invalidate") == 0)
647 {
648 if (argc == 2)
649 {
650 dir_cache_invalid_all();
651 return 0;
652 }
653 fprintf(stderr, "kmk_builtin_dircache: the 'invalidate' command takes no arguments!\n");
654 }
655 else if (strcmp(pszCmd, "invalidate-missing") == 0)
656 {
657 if (argc == 2)
658 {
659 dir_cache_invalid_missing ();
660 return 0;
661 }
662 fprintf(stderr, "kmk_builtin_dircache: the 'invalidate-missing' command takes no arguments!\n");
663 }
664 else if (strcmp(pszCmd, "volatile") == 0)
665 {
666 int i;
667 for (i = 2; i < argc; i++)
668 dir_cache_volatile_dir(argv[i]);
669 return 0;
670 }
671 else if (strcmp(pszCmd, "deleted") == 0)
672 {
673 int i;
674 for (i = 2; i < argc; i++)
675 dir_cache_deleted_directory(argv[i]);
676 return 0;
677 }
678 else
679 fprintf(stderr, "kmk_builtin_dircache: Invalid command '%s'!\n", pszCmd);
680 }
681 else
682 fprintf(stderr, "kmk_builtin_dircache: No command given!\n");
683
684 K_NOREF(envp);
685 return 2;
686}
687
Note: See TracBrowser for help on using the repository browser.