OSDN Git Service

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