檢測目錄遞歸循環 (Linux C 實現)

效果:算法

root@localhost :/home/James/mypro/Linux-Pro/dir_operation# ./detect_dir_loop /home/James/mypro/        
Error occured when getting realpath of [link2]: No such file or directory
Error occured when getting realpath of [link3]: No such file or directory
Error occured when getting realpath of [link5]: No such file or directory
Error occured when getting realpath of [link4]: No such file or directory
Error occured when getting realpath of [link6]: No such file or directory
Error occured when getting realpath of [link1]: No such file or directory
Error occured when getting realpath of [bar]: Too many levels of symbolic links
/home/James/mypro/Linux-Pro/dir_operation/mnt1/dir1/link1 recursively reference itself!
/home/James/mypro/Linux-Pro/dir_operation/mnt1/dir2/link2/link1 recursively reference itself! 函數

 

正確性檢測:oop

root@localhost :/home/James/proto3-standard/external/alsa-lib/include# grep -Ri "xxxxxxxxxx" /home/James/mypro/                
grep: /home/James/mypro/Linux-Pro/Toys/symlink/link2: 沒有那個文件或目錄
grep: /home/James/mypro/Linux-Pro/Toys/symlink/link3: 沒有那個文件或目錄
grep: /home/James/mypro/Linux-Pro/Toys/symlink/link5: 沒有那個文件或目錄
grep: /home/James/mypro/Linux-Pro/Toys/symlink/link4: 沒有那個文件或目錄
grep: /home/James/mypro/Linux-Pro/Toys/symlink/link6: 沒有那個文件或目錄
grep: /home/James/mypro/Linux-Pro/Toys/symlink/link1: 沒有那個文件或目錄
grep: /home/James/mypro/Linux-Pro/Toys/symlink/bar: 符號鏈接的層數過多
grep: 警告: /home/James/mypro/Linux-Pro/dir_operation/mnt1/dir1/link1: 遞歸目錄循環ui

grep: 警告: /home/James/mypro/Linux-Pro/dir_operation/mnt1/dir2/link2/link1: 遞歸目錄循環
spa

由上可見它是正確的。.net

速度:對8G全部的目錄檢查,須要17秒。以下rest

root@localhost :/home/James/mypro/Linux-Pro/dir_operation# time ./detect_dir_loop /home/James/proto3-standard/
Error occured when getting realpath of [fsl_cache.h]: No such file or directory
/home/James/proto3-standard/external/alsa-lib/include/alsa recursively reference itself!
Error occured when getting realpath of [target]: No such file or directory
Error occured when getting realpath of [testsuite]: No such file or directory
Error occured when getting realpath of [en-US]: No such file or directory
Error occured when getting realpath of [testsuite]: No such file or directory
Error occured when getting realpath of [en-US]: No such file or directory code

real    0m17.783s
user    0m0.444s
sys     0m2.340s遞歸

 

源碼:內存

 

/**
 * detect_dir_loop.c
 *
 * detect recursive references in a specified directory
 **/
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>

#define MAX_NESTED_DIR_DEPTH 256

#define DEBUG 0
#if DEBUG
#define PDEBUG(fmt, arg...) printf(fmt, ##arg)
#else
#define PDEBUG(fmt, arg...)
#endif

struct stack 
{
	char **stack;
	int top;
};

struct stack stack_dir;	/* store absolute path of a directory */
struct stack stack_relative_path;

static void parse(const char *path);
static void parse_dir(const char *dir);
static void parse_link(const char *link);

static void init_stack(struct stack *st);
static void clean_stack(struct stack *st);
static void push_stack(const char *dir, struct stack *st); /* alloc memory, push dir into stack, called only after check_collision's called */
static void pop_stack(struct stack *st);	   /* pop the top element out, free corresponding memory */
static char* top_stack(struct stack *st);
static void print_stack_from_bottom_to_up(struct stack *st, const char *sep);

static int check_collision(char *dir, struct stack *st); /* check whether dir conflicts, if so, it recursively reference itself*/

static void parse(const char *path)
{
	assert(path != NULL);
//	PDEBUG("parse: %s\n", path);

	if (strcmp(".", path) == 0 ||
	    strcmp("..", path) == 0)
		return;

	struct stat statbuf;
	int ret = lstat(path, &statbuf);
	if (ret < 0)
	{
		/* read status of a path failed */
		fprintf(stderr, "lstat [%s] failed: %s \n",
			path, strerror(errno));
		return;
	}
	/* success */
	if (S_ISDIR(statbuf.st_mode))
	{
		parse_dir(path);
	}
	else if (S_ISLNK(statbuf.st_mode))
	{
		parse_link(path);
	}
	else
	{
		/* do something with other files */
		;
	}

}

static void parse_dir(const char *dir)
{
	/* parse_dir is called from parse, so dir must be a valid directory */
	PDEBUG("parse_dir: %s\n", dir);
		
	char *real_path = realpath(dir, NULL);
	if (real_path == NULL)
	{
		fprintf(stderr, "Error occured when getting realpath of [%s]: %s \n",
			dir, strerror(errno));
		return;
	}

	int ret = check_collision(real_path, &stack_dir);
	if (ret == 0)		/* no collision */
	{
		/* push stack */
		push_stack(real_path, &stack_dir);
		free(real_path);
		real_path = NULL;
		push_stack(dir, &stack_relative_path);
		/* traverse the directory */
		DIR *dp;
		struct dirent *entry;
		if ((dp = opendir(dir)) == NULL)
		{
			fprintf(stderr, "cannot open diretory [%s]: %s\n", 
				dir, strerror(errno));
			return;
		}

		/* enter dir */
		chdir(dir);
		while ((entry=readdir(dp)) != NULL)
		{
			parse(entry->d_name);
		}
		/* restore current dir */
		chdir("..");
		closedir(dp);

		/* pop stack */
		pop_stack(&stack_relative_path);
		pop_stack(&stack_dir);
	}
	else			/* detect recursive reference */
	{
		print_stack_from_bottom_to_up(&stack_relative_path, "/");
		printf("%s recursively reference itself! \n", dir);
	}
}

static void parse_link(const char *link)
{
	/* parse_link is called from pase, so link must be a valid link */
	PDEBUG("parse_link: %s\n", link);
	char *cur_dir;

	char *real_path = realpath(link, NULL);
	if (real_path == NULL)
	{
		fprintf(stderr, "Error occured when getting realpath of [%s]: %s \n",
			link, strerror(errno));
		return;
	}

	struct stat statbuf;
	lstat(real_path, &statbuf);
	cur_dir = getcwd(NULL, 256);

	int ret = S_ISDIR(statbuf.st_mode);
	if (ret == 0)
		return;		/* link does not points to a directory */

	ret = check_collision(real_path, &stack_dir);
	if (ret == 0)		/* directory */
	{
		/* push stack */
		push_stack(real_path, &stack_dir);
		push_stack(link, &stack_relative_path);
		/* traverse the directory */
		DIR *dp;
		struct dirent *entry;
		if ((dp = opendir(real_path)) == NULL)
		{
			fprintf(stderr, "cannot open diretory [%s]: %s\n", 
				link, strerror(errno));
			return;
		}
		free(real_path);
		real_path = NULL;

		chdir(link);
		while ((entry=readdir(dp)) != NULL)
		{
			parse(entry->d_name);
		}
		chdir(cur_dir);
		free(cur_dir);
		closedir(dp);

		/* pop stack */
		pop_stack(&stack_relative_path);
		pop_stack(&stack_dir);
	}
	else			/* detect recursive reference */
	{
		print_stack_from_bottom_to_up(&stack_relative_path, "/");
		printf("%s recursively reference itself! \n", link);
	}
}

static void init_resource()
{
	init_stack(&stack_dir);
	init_stack(&stack_relative_path);
}

static void clean_resource()
{
	clean_stack(&stack_relative_path);
	clean_stack(&stack_dir);
}

static void init_stack(struct stack *st)
{
	int i;
	st->stack = (char **)malloc( sizeof(char *) * MAX_NESTED_DIR_DEPTH );
	if (st->stack == NULL)
	{
		fprintf(stderr, "allocate stack failed: %s \n", strerror(errno));
		exit(EXIT_FAILURE);
	}
	for (i=0; i<MAX_NESTED_DIR_DEPTH; i++)
		st->stack[i] = NULL;
	st->top = 0;		/* there're totally top elements in stack */
}

static void clean_stack(struct stack *st)
{
	int i;
	for (i=0; i<MAX_NESTED_DIR_DEPTH; i++)
	{
		free(st->stack[i]);
		st->stack[i] = NULL;
	}
	free(st->stack);
	st->top = 0;
}

static void print_stack_from_bottom_to_up(struct stack *st, const char *sep)
{
	assert(sep != NULL);
	int i=0;
	for (i=0; i<st->top; i++)
	{
		if (i==0)
		{
			printf("%s", st->stack[i]);
		}
		else
		{
			printf("%s%s", st->stack[i], sep);
		}
	}
}

static char* top_stack(struct stack *st)
{
	assert(st->top >= 0);
	if (st->top == 0)
		return NULL;
	else
		return (st->stack[st->top-1]);
}

static void pop_stack(struct stack *st)
{
	assert(st->top >= 0);
	if (st->top == 0)
		fprintf(stderr, "Error in pop stack: stack empty!\n");
	else
	{
		st->top--;
		free(st->stack[st->top]);
		st->stack[st->top] = NULL;
	}
}

static void push_stack(const char *path, struct stack *st)
{
	assert (path != NULL);
	assert(st->top >= 0);
	assert(st->stack[st->top] == NULL); /* everytime we pop, we make it NULL */
	if (st->top >= MAX_NESTED_DIR_DEPTH)
	{
		fprintf(stderr, "Error in push_stack: stack overflow!\n");
		exit(EXIT_FAILURE);
	}
	int len = strlen(path);
	st->stack[st->top] = (char *)malloc(sizeof(char) * (len+1));
	if (st->stack[st->top] == NULL)
	{
		fprintf(stderr, "Memory allocation in push_stack failed\n");
		exit(EXIT_FAILURE);
	}
	memset(st->stack[st->top], 0, len+1);
	strcpy(st->stack[st->top], path);
	st->top++;

}

static int is_or_is_parent(char *dir2, char *dir1)
{
	/* dir2 and dir1 are all absolute path */
	assert(dir2[0] == '/');
	assert(dir1[0] == '/');

//	PDEBUG("is_or_is_parent: [%s] [%s] \n", dir2, dir1);
	int i=0;
	while (dir2[i]!='\0' && dir1[i]!='\0')
	{
		if (dir2[i] != dir1[i])
			return 0; 
		i++;
	}
	if (dir2[i] == '\0')
		return 1;
	return 0;		/* dir2[i] != 0 but dir1[i] == 0 */

}

static int check_collision(char *dir, struct stack *st)
{

	assert(dir != NULL);
	assert(dir[0] == '/');	/* dir must be an absolute path */

//	PDEBUG("check_collision: [%s] \n", dir);
	int i=0;
	for (i=0; i<st->top; i++)
	{
		if (is_or_is_parent(dir, st->stack[i]))
			return 1;
	}
	return 0;
	
}

int main(int argc, char *argv[])
{
	assert(argc == 2);
	const char *targetpath = argv[1];
	init_resource();
	parse(targetpath);
	clean_resource();
	exit(EXIT_SUCCESS);
}

 

後記:

1. C語言對沒有內存自動回收機制,在內存處理上要當心。說實話,本身寫個stack,而後每次獲得real path時還要注意釋放內存,這個真的有點讓人不爽。

2. 主要算法是「遞歸降低分析」。遞歸遞歸就解決了。(數學和算法就是讓人看起來比他原來要聰明)

3. 該算法中,限制了目錄的嵌套層數是256.說實在的,你目錄嵌套了256層是位了啥吧,確定有問題!因此,若是目錄嵌套了256層以上,會有stack overflow提示。雖然能夠作stack自動擴展,可是,我認爲,更好的處理方法,就是提示stack overflow,讓執行者知道,有個目錄嵌套過多!

4. 參見http://www.oschina.net/question/158589_56229 (Linux對symbolic link的限制)。注意這裏既是有超過40層的link,只要總數沒有超過256(stack overflow),也能夠正確解析,緣由是咱們作的是path resolution,每次遇到link,會作解析出絕對路徑,而後進棧的操做。因而系統用lstat等函數時,獲得的是一個最多隻帶一個link的路徑名,天然不到達到40這個數目。

相關文章
相關標籤/搜索