OSDN Git Service

Merge pull request #3532 from sikabane-works/release/3.0.0.87-alpha
[hengbandforosx/hengbandosx.git] / src / util / angband-files.cpp
1 #include "util/angband-files.h"
2 #include "locale/japanese.h"
3 #include "system/angband-exceptions.h"
4 #include "util/string-processor.h"
5 #include <sstream>
6 #include <string>
7
8 #ifdef SET_UID
9
10 #ifndef HAVE_USLEEP
11
12 /*
13  * For those systems that don't have "usleep()" but need it.
14  *
15  * Fake "usleep()" function grabbed from the inl netrek server -cba
16  */
17 int usleep(ulong usecs)
18 {
19     struct timeval timer;
20
21     int nfds = 0;
22
23     fd_set *no_fds = nullptr;
24     if (usecs > 4000000L) {
25         core(_("不当な usleep() 呼び出し", "Illegal usleep() call"));
26     }
27
28     timer.tv_sec = (usecs / 1000000L);
29     timer.tv_usec = (usecs % 1000000L);
30     if (select(nfds, no_fds, no_fds, no_fds, &timer) < 0) {
31         if (errno != EINTR) {
32             return -1;
33         }
34     }
35
36     return 0;
37 }
38 #endif
39
40 /*
41  * Hack -- External functions
42  */
43 #ifdef SET_UID
44 struct passwd *getpwuid(uid_t uid);
45 struct passwd *getpwnam(concptr name);
46 #endif
47
48 /*
49  * Find a default user name from the system.
50  */
51 void user_name(char *buf, int id)
52 {
53     struct passwd *pw;
54     if ((pw = getpwuid(id))) {
55         (void)strcpy(buf, pw->pw_name);
56         buf[16] = '\0';
57
58 #ifdef JP
59         if (!iskanji(buf[0]))
60 #endif
61             if (islower(buf[0])) {
62                 buf[0] = toupper(buf[0]);
63             }
64
65         return;
66     }
67
68     strcpy(buf, "PLAYER");
69 }
70
71 #endif /* SET_UID */
72
73 std::filesystem::path path_parse(const std::filesystem::path &path)
74 #ifdef SET_UID
75 {
76     /*
77      * Extract a "parsed" path from an initial filename
78      * Normally, we simply copy the filename into the buffer
79      * But leading tilde symbols must be handled in a special way
80      * Replace "~user/" by the home directory of the user named "user"
81      * Replace "~/" by the home directory of the current user
82      */
83     const auto &file = path.string();
84     if (file.empty() || (file[0] != '~')) {
85         return file;
86     }
87
88     auto u = file.data() + 1;
89     auto s = angband_strstr(u, PATH_SEP);
90     constexpr auto user_size = 128;
91     char user[user_size]{};
92     if ((s != nullptr) && (s >= u + user_size)) {
93         THROW_EXCEPTION(std::runtime_error, "User name is too long!");
94     }
95
96     if (s != nullptr) {
97         int i;
98         for (i = 0; u < s; ++i) {
99             user[i] = *u++;
100         }
101
102         u = user;
103     }
104
105     if (u[0] == '\0') {
106         u = getlogin();
107     }
108
109     struct passwd *pw;
110     if (u != nullptr) {
111         pw = getpwnam(u);
112     } else {
113         pw = getpwuid(getuid());
114     }
115
116     if (pw == nullptr) {
117         THROW_EXCEPTION(std::runtime_error, "Failed to get User ID!");
118     }
119
120     if (s == nullptr) {
121         return pw->pw_dir;
122     }
123
124     std::stringstream ss;
125     ss << pw->pw_dir << s;
126     return ss.str();
127 }
128 #else
129 {
130     return path;
131 }
132 #endif /* SET_UID */
133
134 #ifndef HAVE_MKSTEMP
135
136 /*
137  * Hack -- acquire a "temporary" file name if possible
138  *
139  * This filename is always in "system-specific" form.
140  */
141 static errr path_temp(char *buf, int max)
142 {
143     concptr s = tmpnam(nullptr);
144     if (!s) {
145         return -1;
146     }
147
148 #if !defined(WIN32) || (defined(_MSC_VER) && (_MSC_VER >= 1900))
149     (void)strnfmt(buf, max, "%s", s);
150 #else
151     (void)strnfmt(buf, max, ".%s", s);
152 #endif
153
154     return 0;
155 }
156 #endif
157
158 /*!
159  * @brief OSの差異を吸収しつつ、絶対パスを生成する.
160  * @param path file 引数があるディレクトリ
161  * @param file ファイル名またはディレクトリ名
162  */
163 std::filesystem::path path_build(const std::filesystem::path &path, std::string_view file)
164 {
165     if ((file[0] == '~') || (prefix(file, PATH_SEP)) || path.empty()) {
166         return file;
167     }
168
169     const auto path_ret = std::filesystem::path(path).append(file);
170     constexpr auto max_path_length = 1024;
171     const auto path_str = path_ret.string();
172     if (path_str.length() > max_path_length) {
173         THROW_EXCEPTION(std::runtime_error, format("Path is too long! %s", path_str.data()));
174     }
175
176     return path_ret;
177 }
178
179 static std::string make_file_mode(const FileOpenMode mode, const bool is_binary)
180 {
181     std::stringstream ss;
182     switch (mode) {
183     case FileOpenMode::READ:
184         ss << 'r';
185         break;
186     case FileOpenMode::WRITE:
187         ss << 'w';
188         break;
189     case FileOpenMode::APPEND:
190         ss << 'a';
191         break;
192     default:
193         THROW_EXCEPTION(std::logic_error, "Invalid file mode is specified!");
194     }
195
196     if (is_binary) {
197         ss << 'b';
198     }
199
200     return ss.str();
201 }
202
203 /*!
204  * @brief OSごとの差異を吸収してファイルを開く
205  * @param path ファイルの相対パスまたは絶対パス
206  * @param mode ファイルを開くモード
207  * @param is_binary バイナリモードか否か (無指定の場合false:テキストモード)
208  * @return ファイルポインタ
209  */
210 FILE *angband_fopen(const std::filesystem::path &path, const FileOpenMode mode, const bool is_binary)
211 {
212     const auto &parsed_path = path_parse(path);
213     const auto &open_mode = make_file_mode(mode, is_binary);
214     return fopen(parsed_path.string().data(), open_mode.data());
215 }
216
217 /*
218  * Hack -- replacement for "fclose()"
219  */
220 errr angband_fclose(FILE *fff)
221 {
222     if (!fff) {
223         return -1;
224     }
225     if (fclose(fff) == EOF) {
226         return 1;
227     }
228     return 0;
229 }
230
231 #ifdef HAVE_MKSTEMP
232 FILE *angband_fopen_temp(char *buf, int max)
233 {
234     strncpy(buf, "/tmp/anXXXXXX", max);
235     int fd = mkstemp(buf);
236     if (fd < 0) {
237         return nullptr;
238     }
239
240     return fdopen(fd, "w");
241 }
242 #else /* HAVE_MKSTEMP */
243 FILE *angband_fopen_temp(char *buf, int max)
244 {
245     if (path_temp(buf, max)) {
246         return nullptr;
247     }
248     return angband_fopen(buf, FileOpenMode::WRITE);
249 }
250 #endif /* HAVE_MKSTEMP */
251
252 /*
253  * Hack -- replacement for "fgets()"
254  *
255  * Read a string, without a newline, to a file
256  *
257  * Process tabs, replace internal non-printables with '?'
258  */
259 errr angband_fgets(FILE *fff, char *buf, ulong n)
260 {
261     ulong i = 0;
262     char *s;
263
264     if (n <= 1) {
265         return 1;
266     }
267     // Reserve for null termination
268     --n;
269
270     std::vector<char> file_read_tmp(FILE_READ_BUFF_SIZE);
271     if (fgets(file_read_tmp.data(), file_read_tmp.size(), fff)) {
272 #ifdef JP
273         guess_convert_to_system_encoding(file_read_tmp.data(), FILE_READ_BUFF_SIZE);
274 #endif
275         for (s = file_read_tmp.data(); *s; s++) {
276             if (*s == '\n') {
277                 buf[i] = '\0';
278                 return 0;
279             } else if (*s == '\t') {
280                 if (i + 8 >= n) {
281                     break;
282                 }
283
284                 buf[i++] = ' ';
285                 while (0 != (i % 8)) {
286                     buf[i++] = ' ';
287                 }
288             }
289 #ifdef JP
290             else if (iskanji(*s)) {
291                 if (i + 1 >= n) {
292                     break;
293                 }
294                 if (!s[1]) {
295                     break;
296                 }
297                 buf[i++] = *s++;
298                 buf[i++] = *s;
299             } else if (iskana(*s)) {
300                 /* 半角かなに対応 */
301                 buf[i++] = *s;
302                 if (i >= n) {
303                     break;
304                 }
305             }
306 #endif
307             else if (isprint((unsigned char)*s)) {
308                 buf[i++] = *s;
309                 if (i >= n) {
310                     break;
311                 }
312             } else {
313                 buf[i++] = '?';
314                 if (i >= n) {
315                     break;
316                 }
317             }
318         }
319
320         buf[i] = '\0';
321         return 0;
322     }
323
324     buf[0] = '\0';
325     return 1;
326 }
327
328 /*
329  * Hack -- replacement for "fputs()"
330  * Dump a string, plus a newline, to a file
331  * Process internal weirdness?
332  */
333 errr angband_fputs(FILE *fff, concptr buf, ulong n)
334 {
335     n = n ? n : 0;
336     (void)fprintf(fff, "%s\n", buf);
337     return 0;
338 }
339
340 /*
341  * Several systems have no "O_BINARY" flag
342  */
343 #ifndef O_BINARY
344 #define O_BINARY 0
345 #endif /* O_BINARY */
346
347 /*!
348  * @brief OSごとの差異を吸収してファイルを削除する
349  * @param file ファイルの相対パスまたは絶対パス
350  */
351 void fd_kill(const std::filesystem::path &path)
352 {
353     const auto &parsed_path = path_parse(path);
354
355     std::error_code ec;
356     std::filesystem::remove(parsed_path, ec);
357 }
358
359 /*!
360  * @brief OSごとの差異を吸収してファイルを移動する
361  * @param path_from 移動元のファイルの相対パスまたは絶対パス
362  * @param path_to 移動先のファイルの相対パスまたは絶対パス
363  */
364 void fd_move(const std::filesystem::path &path_from, const std::filesystem::path &path_to)
365 {
366     const auto &abs_path_from = path_parse(path_from);
367     const auto &abs_path_to = path_parse(path_to);
368
369     std::error_code ec;
370     std::filesystem::rename(abs_path_from, abs_path_to, ec);
371 }
372
373 /*!
374  * @brief OSごとの差異を吸収してファイルを作成する
375  * @param path 作成先ファイルの相対パスまたは絶対パス
376  * @param can_write_group グループに書き込みを許可するか否か
377  */
378 int fd_make(const std::filesystem::path &path, bool can_write_group)
379 {
380     const auto permission = can_write_group ? 0644 : 0664;
381     const auto &parsed_path = path_parse(path);
382     return open(parsed_path.string().data(), O_CREAT | O_EXCL | O_WRONLY | O_BINARY, permission);
383 }
384
385 /*
386  * @brief OSごとの差異を吸収してファイルを開く
387  * @param path ファイルの相対パスまたは絶対パス
388  * @param mode ファイルのオープンモード (読み書き、Append/Trunc等)
389  */
390 int fd_open(const std::filesystem::path &path, int mode)
391 {
392     const auto &path_abs = path_parse(path);
393     return open(path_abs.string().data(), mode | O_BINARY, 0);
394 }
395
396 /*
397  * Hack -- attempt to lock a file descriptor
398  *
399  * Legal lock types -- F_UNLCK, F_RDLCK, F_WRLCK
400  */
401 errr fd_lock(int fd, int what)
402 {
403     what = what ? what : 0;
404     if (fd < 0) {
405         return -1;
406     }
407
408 #if defined(SET_UID) && defined(LOCK_UN) && defined(LOCK_EX)
409     if (what == F_UNLCK) {
410         (void)flock(fd, LOCK_UN);
411     } else {
412         if (flock(fd, LOCK_EX) != 0) {
413             return 1;
414         }
415     }
416 #endif
417
418     return 0;
419 }
420
421 /*
422  * Hack -- attempt to seek on a file descriptor
423  */
424 errr fd_seek(int fd, ulong n)
425 {
426     if (fd < 0) {
427         return -1;
428     }
429
430     ulong p = lseek(fd, n, SEEK_SET);
431     if (p != n) {
432         return 1;
433     }
434
435     return 0;
436 }
437
438 /*
439  * Hack -- attempt to read data from a file descriptor
440  */
441 errr fd_read(int fd, char *buf, ulong n)
442 {
443     if (fd < 0) {
444         return -1;
445     }
446 #ifndef SET_UID
447     while (n >= 16384) {
448         if (read(fd, buf, 16384) != 16384) {
449             return 1;
450         }
451
452         buf += 16384;
453         n -= 16384;
454     }
455 #endif
456
457     if (read(fd, buf, n) != (int)n) {
458         return 1;
459     }
460
461     return 0;
462 }
463
464 /*
465  * Hack -- Attempt to write data to a file descriptor
466  */
467 errr fd_write(int fd, concptr buf, ulong n)
468 {
469     if (fd < 0) {
470         return -1;
471     }
472
473 #ifndef SET_UID
474     while (n >= 16384) {
475         if (write(fd, buf, 16384) != 16384) {
476             return 1;
477         }
478
479         buf += 16384;
480         n -= 16384;
481     }
482 #endif
483
484     if (write(fd, buf, n) != (int)n) {
485         return 1;
486     }
487
488     return 0;
489 }
490
491 /*
492  * Hack -- attempt to close a file descriptor
493  */
494 errr fd_close(int fd)
495 {
496     if (fd < 0) {
497         return -1;
498     }
499
500     (void)close(fd);
501     return 0;
502 }