0 /*
1 * Copyright (c) 2023 RMF <rawmonk@firemail.cc>
2 *
3 * Permission to use, copy, modify, and distribute this software for any
4 * purpose with or without fee is hereby granted, provided that the above
5 * copyright notice and this permission notice appear in all copies.
6 *
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 */
15 #ifdef __linux__
16 #define _GNU_SOURCE
17 #else
18 #define _BSD_SOURCE
19 #endif
20 #include <dirent.h>
21 #include <unistd.h>
22 #include <ctype.h>
23 #include <fcntl.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdint.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <fts.h>
30 #include <stdio.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include "termbox.h"
34 #include "view.h"
35 #include "file.h"
36 #include "strlcpy.h"
37 #include "client.h"
38 #include "util.h"
39
40 int file_init(struct view *view, const char* path) {
41
42 PZERO(view);
43
44 if (path)
45 sstrcpy(view->path, path);
46 else if (view->path != getcwd(V(view->path)))
47 return -1;
48
49 view->fd = open(view->path, O_DIRECTORY);
50 if (view->fd < 0)
51 return -1;
52
53 return 0;
54 }
55
56 int file_up(struct view *view) {
57 return file_cd(view, "..");
58 }
59
60 int file_cd(struct view *view, const char *path) {
61
62 char buf[PATH_MAX];
63 int fd, len, back;
64
65 if (!strcmp(path, ".")) return 0;
66
67 len = sstrcpy(buf, view->path);
68 back = 0;
69 if (!strcmp(path, "..")) {
70 if (!strcmp(view->path, "/")) {
71 return 0;
72 }
73 len--;
74 while (len >= 0) {
75 if (buf[len] == '/') {
76 buf[AZ(len)] = '\0';
77 back = 1;
78 break;
79 }
80 len--;
81 }
82 if (!back) return 0;
83 } else {
84 if (buf[AZ(len) - 1] != '/') {
85 buf[len] = '/';
86 len++;
87 }
88 strlcpy(&buf[len], path, sizeof(buf) - len);
89 }
90
91 fd = open(buf, O_DIRECTORY);
92 if (fd < 0) return -1;
93 close(view->fd);
94 view->fd = fd;
95 fchdir(fd);
96 sstrcpy(view->path, buf);
97 view->selected = 0;
98 return 0;
99 }
100
101 void file_free(struct view *view) {
102 free(view->entries);
103 view->length = 0;
104 view->entries = NULL;
105 }
106
107 int sort(const void* a, const void* b)
108 {
109 struct entry *first = (struct entry*)a;
110 struct entry *second = (struct entry*)b;
111 int i = 0, j = 0;
112
113 if (first->type != second->type) {
114 if (first->type == DT_DIR) return -1;
115 if (second->type == DT_DIR) return 1;
116 }
117 while (1) {
118 uint32_t c1, c2;
119 do {
120 i += tb_utf8_char_to_unicode(&c1, &first->name[i]);
121 if (!first->name[i]) return 0;
122 } while (c1 == ' ' || c1 == '\t');
123 do {
124 j += tb_utf8_char_to_unicode(&c2, &second->name[j]);
125 if (!second->name[j]) return 0;
126 } while (c2 == ' ' || c2 == '\t');
127 c1 = tolower(c1);
128 c2 = tolower(c2);
129 if (c1 != c2)
130 return c1 < c2 ? -1 : 1;
131 }
132 return 0;
133 }
134
135 int file_reload(struct view *view) {
136 close(view->fd);
137 view->fd = open(view->path, O_DIRECTORY);
138 if (view->fd < 0) return -1;
139 return file_ls(view);
140 }
141
142 int file_ls(struct view *view) {
143 struct dirent *entry;
144 DIR *dp;
145 int length, i, fd;
146
147 fd = dup(view->fd);
148 dp = fdopendir(fd);
149 if (dp == NULL)
150 return -1;
151
152 length = 0;
153 while ((entry = readdir(dp))) {
154 if (!view->showhidden && entry->d_name[0] == '.')
155 continue;
156 if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, ".."))
157 length++;
158 }
159 if (length == 0) {
160 closedir(dp);
161 file_free(view);
162 return 0;
163 }
164
165 rewinddir(dp);
166 file_free(view);
167 view->entries = malloc(sizeof(struct entry) * length);
168 i = 0;
169 while ((entry = readdir(dp))) {
170 if (!strcmp(entry->d_name, ".") ||
171 !strcmp(entry->d_name, ".."))
172 continue;
173 if (!view->showhidden && entry->d_name[0] == '.')
174 continue;
175 view->entries[i].selected = 0;
176 strlcpy(view->entries[i].name, entry->d_name,
177 sizeof(view->entries[i].name));
178
179 #ifndef sun
180 if (entry->d_type == DT_LNK) {
181 #endif
182 struct stat buf;
183 view->entries[i].type =
184 fstatat(view->fd, entry->d_name, &buf, 0);
185 view->entries[i].type = S_ISDIR(buf.st_mode) ?
186 DT_DIR : DT_REG;
187 #ifndef sun
188 } else
189 view->entries[i].type = entry->d_type;
190 #endif
191 i++;
192 }
193
194 qsort(view->entries, length, sizeof(struct entry), sort);
195
196 closedir(dp);
197 close(fd);
198 view->length = length;
199 view->scroll = 0;
200 lseek(view->fd, 0, SEEK_SET);
201 return 0;
202 }
203
204 int file_select(struct view *view, const char *path) {
205 size_t i = 0;
206 while (i < view->length) {
207 if (!strncmp(view->entries[i].name, path,
208 sizeof(view->entries[i].name))) {
209 view->selected = i;
210 return 0;
211 }
212 i++;
213 }
214 return -1;
215 }
216
217 int file_move_entry(struct view *view, struct entry *entry) {
218
219 int fd = open(client.copy_path, O_DIRECTORY);
220 if (fd < 0) return -1;
221 return file_move(client.copy_path, fd, entry->name,
222 view->fd, view->path, entry->name);
223 }
224
225 #ifdef __linux__
226 #define lseek lseek64
227 #define off_t off64_t
228 #endif
229
230 #if !defined(__linux__) && !defined(__FreeBSD__)
231 #define NO_COPY_FILE_RANGE
232 #endif
233
234 int file_copy_entry(struct view *view, struct entry *entry) {
235
236 struct stat st;
237 int fd, dstfd, srcfd;
238
239 fd = openat(view->fd, entry->name, 0);
240 if (fd > -1) {
241 close(fd);
242 errno = EEXIST;
243 return -1;
244 }
245
246 fd = open(client.copy_path, O_DIRECTORY);
247 if (fd < 0) return -1;
248 srcfd = openat(fd, entry->name, O_RDONLY);
249 close(fd);
250 if (srcfd < 0) return -1;
251
252 if (fstat(srcfd, &st)) {
253 close(srcfd);
254 return -1;
255 }
256
257 if (S_ISDIR(st.st_mode)) {
258 char buf[PATH_MAX];
259 snprintf(V(buf), "cp -r \"%s/%s\" \"%s\"", client.copy_path,
260 client.copy->name, view->path);
261 return system(buf);
262 }
263
264 dstfd = openat(view->fd, entry->name, O_WRONLY|O_CREAT, st.st_mode);
265 if (dstfd < 0) return -1;
266
267 return file_copy(srcfd, dstfd, 0);
268 }
269
270 int file_copy(int src, int dst, int usebuf) {
271
272 size_t length, ret;
273
274 #ifdef NO_COPY_FILE_RANGE
275 if (usebuf == -1) return -1;
276 #endif
277
278 length = lseek(src, 0, SEEK_END);
279 if (length == (size_t)-1 ||
280 lseek(src, 0, SEEK_SET) == (off_t)-1) {
281 close(dst);
282 close(src);
283 return -1;
284 }
285
286 ret = 0;
287 while (1) {
288 ssize_t i;
289 char buf[4096];
290 #ifndef NO_COPY_FILE_RANGE
291 if (usebuf) {
292 #endif
293 i = read(src, buf, sizeof(buf));
294 if (i <= 0) break;
295 write(dst, buf, i);
296 #ifndef NO_COPY_FILE_RANGE
297 } else {
298 i = copy_file_range(src, 0, dst, 0, length - ret, 0);
299 if (i <= 0) break;
300 }
301 #endif
302 ret += i;
303 }
304
305 #ifndef NO_COPY_FILE_RANGE
306 /* retry without copy_file_range if the operation failed */
307 if (ret != length && !usebuf) return file_copy(src, dst, 1);
308 #endif
309
310 close(dst);
311 close(src);
312 if (ret != length) {
313 return -1;
314 }
315
316 return 0;
317 }
318
319 int file_move(const char *oldpath, int srcdir, const char *oldname,
320 int dstdir, const char *newpath, const char *newname) {
321
322 int error, fd;
323
324 fd = openat(dstdir, newname, 0);
325 if (fd > -1) {
326 errno = EEXIST;
327 close(fd);
328 return -1;
329 }
330
331 error = renameat(srcdir, oldname, dstdir, newname);
332 /* EXDEV : when trying to move a file to another file system */
333 if (error && errno == EXDEV) {
334 int src, dst;
335 struct stat st;
336
337 if (fstatat(srcdir, oldname, &st, 0)) return -1;
338 /* use a shell command instead of recursively copying files */
339 if (S_ISDIR(st.st_mode)) {
340 char buf[PATH_MAX * 3];
341 snprintf(V(buf), "mv \"%s/%s\" \"%s/%s\"",
342 oldpath, oldname,
343 newpath, newname);
344 return system(buf);
345 }
346
347 src = openat(srcdir, oldname, O_RDONLY);
348 if (src < 0) return -1;
349 dst = openat(dstdir, newname, O_WRONLY|O_CREAT, st.st_mode);
350 if (dst < 0) {
351 close(src);
352 return -1;
353 }
354 if (!file_copy(src, dst, 1)) {
355 char buf[2048];
356 snprintf(V(buf), "%s/%s", oldpath, oldname);
357 if (!remove(buf)) error = 0;
358 }
359 close(src);
360 close(dst);
361 }
362 return error;
363 }
364