1 /* diritor.cc: Iterator through entries in a directory.
3 * Copyright (C) 2007,2008,2010,2011,2012,2013,2014 Olly Betts
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 #include "safeunistd.h"
25 #include <sys/types.h>
32 CommitAndExit::CommitAndExit(const char * msg_
, const std::string
& path
,
39 msg
+= strerror(errno_
);
43 CommitAndExit::CommitAndExit(const char * msg_
, int errno_
)
47 msg
+= strerror(errno_
);
51 CommitAndExit::CommitAndExit(const char * msg_
, const char * error
)
59 #if defined O_NOATIME && O_NOATIME != 0
60 uid_t
DirectoryIterator::euid
= geteuid();
63 magic_t
DirectoryIterator::magic_cookie
= NULL
;
66 DirectoryIterator::call_stat()
71 retval
= fstat(fd
, &statbuf
);
73 } else if (!follow_symlinks
) {
74 retval
= lstat(path
.c_str(), &statbuf
);
77 retval
= stat(path
.c_str(), &statbuf
);
80 if (errno
== ENOENT
|| errno
== ENOTDIR
)
83 throw string(strerror(errno
));
84 // Commit changes to files processed so far.
85 throw CommitAndExit("Can't stat", path
, errno
);
90 DirectoryIterator::build_path()
92 if (path
.length() == path_len
) {
99 DirectoryIterator::start(const std::string
& path_
)
101 if (dir
) closedir(dir
);
103 path_len
= path
.length();
104 dir
= opendir(path
.c_str());
106 if (errno
== ENOENT
|| errno
== ENOTDIR
)
107 throw FileNotFound();
109 throw string(strerror(errno
));
110 // Commit changes to files processed so far.
111 throw CommitAndExit("Can't open directory", path
, errno
);
116 DirectoryIterator::next_failed() const
118 // The Linux getdents() syscall (which readdir uses internally) is
119 // documented as being able to return ENOENT and ENOTDIR. Also,
120 // EACCES has been observed here on CIFS mounts.
121 if (errno
== ENOENT
|| errno
== ENOTDIR
)
122 throw FileNotFound();
124 throw string(strerror(errno
));
125 throw CommitAndExit("Can't read next entry from directory", path
, errno
);
129 DirectoryIterator::get_magic_mimetype()
131 if (rare(magic_cookie
== NULL
)) {
132 #ifdef MAGIC_MIME_TYPE
133 magic_cookie
= magic_open(MAGIC_SYMLINK
|MAGIC_MIME_TYPE
|MAGIC_ERROR
);
135 // MAGIC_MIME_TYPE was added in 4.22, released 2007-12-27. If we don't
136 // have it then use MAGIC_MIME instead and trim any encoding off below.
137 magic_cookie
= magic_open(MAGIC_SYMLINK
|MAGIC_MIME
|MAGIC_ERROR
);
139 if (magic_cookie
== NULL
) {
140 // Commit changes to files processed so far.
141 throw CommitAndExit("Failed to initialise the file magic library",
144 if (magic_load(magic_cookie
, NULL
) == -1) {
145 // Commit changes to files processed so far.
146 const char * err
= magic_error(magic_cookie
);
147 throw CommitAndExit("Failed to load the file magic database", err
);
151 const char * res
= NULL
;
152 // Prior to 5.15, magic_descriptor() closed the fd passed, so avoid it.
153 #if defined MAGIC_VERSION && MAGIC_VERSION - 0 >= 515
155 if (lseek(fd
, 0, SEEK_SET
) == 0)
156 res
= magic_descriptor(magic_cookie
, fd
);
161 res
= magic_file(magic_cookie
, path
.c_str());
164 const char * err
= magic_error(magic_cookie
);
166 int eno
= magic_errno(magic_cookie
);
167 if (eno
== ENOENT
|| eno
== ENOTDIR
)
168 throw FileNotFound();
169 string
m("Failed to use magic on file: ");
176 // Sometimes libmagic returns this string instead of a mime-type for some
177 // Microsoft documents, so pick a suitable MIME content-type based on the
178 // extension. Newer versions seem to return "application/CDFV2-corrupt"
179 // instead for this case (on Debian, file 5.11 gives the former and file
181 #define COMPOSITE_DOC "Composite Document File V2 Document"
182 if (strncmp(res
, COMPOSITE_DOC
, sizeof(COMPOSITE_DOC
) - 1) == 0 ||
183 strcmp(res
, "application/CDFV2-corrupt") == 0) {
184 // Default to something self-explanatory.
185 res
= "application/x-compound-document-file";
186 const char * leaf
= leafname();
187 const char * ext
= strrchr(leaf
, '.');
188 if (ext
&& strlen(++ext
) == 3) {
190 for (int i
= 0; i
!= 3; ++i
) {
191 if (ext
[i
] <= 'Z' && ext
[i
] >= 'A')
192 e
[i
] = ext
[i
] + ('a' - 'A');
199 res
= "application/msword";
202 if (e
[1] == 's' && e
[2] == 'g')
203 res
= "application/vnd.ms-outlook";
206 if (e
[1] == 'p' || e
[1] == 'o')
207 res
= "application/vnd.ms-powerpoint";
208 else if (e
[1] == 'u' && e
[2] == 'b')
209 res
= "application/x-mspublisher";
213 res
= "application/vnd.ms-excel";
216 if (e
[1] == 'p' && e
[2] != 'd')
217 res
= "application/vnd.ms-works";
222 #ifndef MAGIC_MIME_TYPE
223 // Discard any encoding returned.
224 char * spc
= strchr(res
, ' ');