1 /*
2  * Copyright (C) 2002 - David W. Durham
3  *
4  * This file is part of ReZound, an audio editing application.
5  *
6  * ReZound is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published
8  * by the Free Software Foundation; either version 2 of the License,
9  * or (at your option) any later version.
10  *
11  * ReZound is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
19  */
20 
21 #ifndef CCXX_path_H_
22 #define CCXX_path_H_
23 
24 #include "../../config/common.h"
25 
26 /*
27 	This was first written and tested under linux, however a few provisions
28 	have been made for a win32 port.
29 	Also, several functions used are probably only available under linux, i.e.
30 		realpath, dirname, basename
31 	But there should be some equivalent under other unices, and implementation
32 	changes should be made when this file is compiled under these other platforms.
33  */
34 
35 #ifndef WIN32
36 	#include <sys/types.h>
37 	#include <sys/stat.h>
38 
39 	#include <errno.h>
40 	#include <unistd.h>
41 
42 	#include <fcntl.h>
43 
44 	#include <limits.h>
45 	#include <stdlib.h>
46 
47 	#include <string.h>
48 
49 	#include <libgen.h>  // for basename and dirname
50 #else
51 	#error unimplemented for win32 platform (yet)
52 #endif
53 
54 #include <stdio.h> /* for popen/pclose */
55 
56 #include <stdexcept>
57 #include <string>
58 
59 
60 // this needs to be placed somewhere once in the application
61 #ifdef WIN32
62 #define DECLARE_STATIC_CPATH const char CPath::dirDelim='\\';
63 #else
64 #define DECLARE_STATIC_CPATH const char CPath::dirDelim='/';
65 #endif
66 
67 
68 
69 class CPath
70 {
71 public:
72 	static const char dirDelim;
73 
74 	CPath(const string &_path="") :
75 		path(NULL),
76 		_exists(false),
77 		_permDenied(false)
78 	{
79 		setPath(_path);
80 	}
81 
82 	virtual ~CPath()
83 	{
84 		free(path);
85 	}
86 
87 	/*
88 		changes the path from what it was constructed with
89 	*/
90 	void setPath(const string &_path)
91 	{
92 		if(_path.size()>=PATH_MAX)
93 			throw runtime_error(string(__func__)+" -- path is >= than PATH_MAX -- '"+path+"'");
94 
95 		if(path!=NULL)
96 			free(path);
97 		path=strdup(_path.c_str());
98 
99 		if(stat(path,&statBuf)!=0)
100 		{
101 			_exists=false;
102 			_permDenied= (errno==EACCES);
103 			if(errno!=ENOENT && errno!=EACCES)
104 				throw runtime_error(string(__func__)+" -- error stat-ing path name -- '"+path+"' -- "+strerror(errno));
105 		}
106 		else
107 		{
108 			_exists=true;
109 			_permDenied=false;
110 		}
111 	}
112 
113 	/*
114 		returns the path constructed with or the last one passed to setPath
115 	*/
116 	const char *getPath() const
117 	{
118 		return(path);
119 	}
120 
121 	/*
122 		returns true iff the pathname exists and we could access it
123 		hence, permission denied makes this returned false even if it did exist :-/
124 	*/
125 	bool exists() const
126 	{
127 		return(_exists);
128 	}
129 
130 	/*
131 		returns true if the maybe some of the pathname exists but we couldn't access it
132 	*/
133 	bool permDenied() const
134 	{
135 		return(_permDenied);
136 	}
137 
138 	/*
139 		possbily creates the path and/or updates its time(s) to current
140 	*/
141 	bool touch(bool canCreate=true,bool throwIfError=true) const
142 	{
143 		if(canCreate)
144 		{
145 			int fd = open (path, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY,S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
146 			if (fd == -1)
147 			{
148 				if(throwIfError)
149 					throw runtime_error(string(__func__)+" -- error touching path name '"+path+"' -- "+strerror(errno));
150 				else
151 					return(false);
152 			}
153 			close(fd);
154 		}
155 		else
156 		{
157 			int fd = open (path, O_WRONLY | O_NONBLOCK | O_NOCTTY,S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
158 			if (fd == -1 && errno!=ENOENT)
159 			{
160 				if(throwIfError)
161 					throw runtime_error(string(__func__)+" -- error touching path name '"+path+"'-- "+strerror(errno));
162 				else
163 					return(false);
164 			}
165 
166 			if(fd!=-1)
167 				close(fd);
168 		}
169 
170 		return(true);
171 	}
172 
173 	/*
174 		returns the absolute path resolved from a possibly relative path
175 	*/
176 	const string realPath() const
177 	{
178 		if(!_exists)
179 			return("");
180 
181 		char resolvedPath[PATH_MAX];
182 		char *result=realpath(path,resolvedPath);
183 		if(result!=NULL)
184 			return(result);
185 		else
186 			return("");
187 	}
188 
189 	/*
190 		returns the extension of the path name (i.e. returns "ext" for "/dir1/dir2/file.ext")
191 	*/
192 	const string extension() const
193 	{
194 		// find the right-most '.' which is also after the right-most dirDelim
195 
196 		char *lastDot=strrchr(path,'.');
197 		if(lastDot==NULL) // go ahead and bail if there wasn't even a '.' in the path
198 			return("");
199 
200 		char *lastDirDelim=strrchr(path,CPath::dirDelim);
201 
202 		if(lastDot>lastDirDelim)
203 			return(lastDot+1);
204 		else
205 			return("");
206 
207 	}
208 
209 	/*
210 		returns just the directory name(s) part of the path (i.e. returns "/dir1/dir2" for "/dir1/dir2/file.ext")
211 	*/
212 	const string dirName() const
213 	{
214 		// make a copy because the function modifies the contents
215 		char tmp[PATH_MAX];
216 		strcpy(tmp,path);
217 
218 		return(dirname(tmp));
219 	}
220 
221 	/*
222 		returns just the filename part of the path (i.e. returns "file.ext" for "/dir1/dir2/file.ext")
223 	*/
224 	const string baseName() const
225 	{
226 		// make a copy because the function modifies the contents
227 		char tmp[PATH_MAX];
228 		strcpy(tmp,path);
229 
230 		return(basename(tmp));
231 	}
232 
233 	/*
234 		returns the number of bytes allocated for the file
235 		either returns 0 or throws an exception if it doesn't exist or wasn't accessible
236 	*/
237 	long getSize(bool throwIfError=true) const
238 	{
239 		if(!_exists)
240 		{
241 			if(throwIfError)
242 				throw(runtime_error(string(__func__)+" -- path did not exist or was inaccessible -- '"+path+"'"));
243 			else
244 				return(0);
245 		}
246 
247 		return(statBuf.st_size);
248 	}
249 
250 	// many other things could be returned from the stat
251 	//time_t get ... Time() const;
252 
253 
254 	// returns true even if it's a link to a regular file
255 	bool isRegularFile() const
256 	{
257 		return S_ISREG(statBuf.st_mode);
258 	}
259 
260 	// returns true if the path is a directory
261 	bool isDirectory() const
262 	{
263 		return S_ISDIR(statBuf.st_mode);
264 	}
265 
266 	bool isLink() const
267 	{
268 		return S_ISLNK(statBuf.st_mode);
269 	}
270 
271 	bool isDevice() const
272 	{
273 		return S_ISCHR(statBuf.st_mode) || S_ISBLK(statBuf.st_mode);
274 	}
275 
276 	/* returns a string containing the first time the executable 'exeName' is found on $PATH */
277 	static const string which(const string exeName)
278 	{
279 		/*??? would have to change implementation if WIN32 was ACTUALLY supported */
280 		string ret;
281 		FILE *p=popen(("which '"+exeName+"' 2>/dev/null").c_str(),"r");
282 
283 		char buffer[4096+1];
284 		redo:
285 		buffer[0]=0;
286 		if(p!=NULL && fgets(buffer,4096,p))
287 		{
288 			// remove \n at end
289 			if(strlen(buffer)>0)
290 				buffer[strlen(buffer)-1]=0;
291 
292 #if defined(rez_OS_LINUX)
293 			// on linux which simply returns no stdout when not found
294 			ret=buffer;
295 #elif defined(rez_OS_BSD)
296 			// on bsd which returns "XXX: Command not found." when not found
297 			if(strstr(buffer,": Command not found.")==NULL)
298 				ret=buffer;
299 #elif defined(rez_OS_SOLARIS)
300 			// on solaris if the path is really long it puts "Warning: ..." on the first line
301 			if(strncmp(buffer,"Warning: ",9)==0)
302 				goto redo;
303 
304 			// on solaris which returns "no XXX in ..." when not found
305 			if(strncmp(buffer,"no ",3)!=0)
306 				ret=buffer;
307 #else
308 			#error CPath::which needs to be implemented on this platform
309 #endif
310 		}
311 		pclose(p);
312 
313 		return ret;
314 	}
315 
316 private:
317 	char *path;
318 	bool _exists;
319 	bool _permDenied;
320 	struct stat statBuf;
321 
322 };
323 
324 #endif
325