macutils.inc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2004 by Olle Raab
  4. Some utilities specific for Mac OS.
  5. Modified portions from Peter N. Lewis (PNL Libraries). Thanks !
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. {NOTE: This file requires the following global variables to be declared:
  13. workingDirectorySpec: FSSpec;}
  14. function FourCharCodeToLongword(fourcharcode: Shortstring): Longword;
  15. begin
  16. FourCharCodeToLongword:=
  17. (ord(fourcharcode[1]) shl 24) or
  18. (ord(fourcharcode[2]) shl 16) or
  19. (ord(fourcharcode[3]) shl 8) or
  20. (ord(fourcharcode[4]))
  21. end;
  22. function BitIsSet(arg: Longint; bitnr: Integer): Boolean;
  23. begin
  24. BitIsSet:= (arg and (1 shl bitnr)) <> 0;
  25. end;
  26. { Converts MacOS specific error codes to the correct FPC error code.
  27. All non zero MacOS errors corresponds to a nonzero FPC error.}
  28. Function MacOSErr2RTEerr(err: OSErr): Integer;
  29. var
  30. res: Integer;
  31. begin
  32. if err = noErr then { Else it will go through all the cases }
  33. res:= 0
  34. else case err of
  35. dirFulErr, { Directory full }
  36. dskFulErr { disk full }
  37. :res:=101;
  38. nsvErr { no such volume }
  39. :res:=3;
  40. ioErr, { I/O error (bummers) }
  41. bdNamErr { there may be no bad names in the final system! }
  42. :res:=1; //TODO Exchange to something better
  43. fnOpnErr { File not open }
  44. :res:=103;
  45. eofErr, { End of file }
  46. posErr { tried to position to before start of file (r/w) }
  47. :res:=100;
  48. mFulErr { memory full (open) or file won't fit (load) }
  49. :res:=1; //TODO Exchange to something better
  50. tmfoErr { too many files open}
  51. :res:=4;
  52. fnfErr { File not found }
  53. :res:=2;
  54. wPrErr { diskette is write protected. }
  55. :res:=150;
  56. fLckdErr { file is locked }
  57. :res:=5;
  58. vLckdErr { volume is locked }
  59. :res:=150;
  60. fBsyErr { File is busy (delete) }
  61. :res:=5;
  62. dupFNErr { duplicate filename (rename) }
  63. :res:=5;
  64. opWrErr { file already open with with write permission }
  65. :res:=5;
  66. rfNumErr, { refnum error }
  67. gfpErr { get file position error }
  68. :res:=1; //TODO Exchange to something better
  69. volOffLinErr { volume not on line error (was Ejected) }
  70. :res:=152;
  71. permErr { permissions error (on file open) }
  72. :res:=5;
  73. volOnLinErr{ drive volume already on-line at MountVol }
  74. :res:=1; //TODO Exchange to something other
  75. nsDrvErr { no such drive (tried to mount a bad drive num) }
  76. :res:=1; //TODO Perhaps exchange to something better
  77. noMacDskErr, { not a mac diskette (sig bytes are wrong) }
  78. extFSErr { volume in question belongs to an external fs }
  79. :res:=157; //TODO Perhaps exchange to something better
  80. fsRnErr, { file system internal error:during rename the old
  81. entry was deleted but could not be restored. }
  82. badMDBErr { bad master directory block }
  83. :res:=1; //TODO Exchange to something better
  84. wrPermErr { write permissions error }
  85. :res:=5;
  86. dirNFErr { Directory not found }
  87. :res:=3;
  88. tmwdoErr { No free WDCB available }
  89. :res:=1; //TODO Exchange to something better
  90. badMovErr { Move into offspring error }
  91. :res:=5;
  92. wrgVolTypErr { Wrong volume type error [operation not
  93. supported for MFS] }
  94. :res:=1; //TODO Exchange to something better
  95. volGoneErr { Server volume has been disconnected. }
  96. :res:=152;
  97. diffVolErr { files on different volumes }
  98. :res:=17;
  99. catChangedErr { the catalog has been modified }
  100. { OR comment: when searching with PBCatSearch }
  101. :res:=1; //TODO Exchange to something other
  102. afpAccessDenied, { Insufficient access privileges for operation }
  103. afpDenyConflict { Specified open/deny modes conflict with current open modes }
  104. :res:=5;
  105. afpNoMoreLocks { Maximum lock limit reached }
  106. :res:=5;
  107. afpRangeNotLocked, { Tried to unlock range that was not locked by user }
  108. afpRangeOverlap { Some or all of range already locked by same user }
  109. :res:=1; //TODO Exchange to something better
  110. afpObjectTypeErr { File/Directory specified where Directory/File expected }
  111. :res:=3;
  112. afpCatalogChanged { OR comment: when searching with PBCatSearch }
  113. :res:=1; //TODO Exchange to something other
  114. afpSameObjectErr
  115. :res:=5; //TODO Exchange to something better
  116. memFullErr { Not enough room in heap zone }
  117. :res:=203;
  118. else
  119. res := 1; //TODO Exchange to something better
  120. end;
  121. MacOSErr2RTEerr:= res;
  122. end;
  123. {Translates a unix or dos path to a mac path. Even a mac path can be input, }
  124. {then it is returned as is. A trailing directory separator in input}
  125. {will result in a trailing mac directory separator. For absolute paths, the }
  126. {parameter mpw affects how the root volume is denoted. If mpw is true, }
  127. {the path is intended for use in MPW, and the environment variable Boot is}
  128. {prepended. Otherwise the actual boot volume name is appended.}
  129. {All kinds of paths are attempted to be translated, except the unusal }
  130. {dos construct: a relative path on a certain drive like : C:xxx\yyy}
  131. function TranslatePathToMac (const path: string; mpw: Boolean): string;
  132. function GetVolumeIdentifier: string;
  133. var
  134. s: Str255;
  135. dummy: Integer;
  136. err: OSErr;
  137. begin
  138. if mpw then
  139. GetVolumeIdentifier := '{Boot}'
  140. else
  141. GetVolumeIdentifier := macosBootVolumeName;
  142. end;
  143. var
  144. slashPos, oldpos, newpos, oldlen, maxpos: Longint;
  145. begin
  146. oldpos := 1;
  147. slashPos := Pos('/', path);
  148. if (slashPos <> 0) then {its a unix path}
  149. begin
  150. if slashPos = 1 then
  151. begin {its a full path}
  152. oldpos := 2;
  153. TranslatePathToMac := GetVolumeIdentifier;
  154. end
  155. else {its a partial path}
  156. TranslatePathToMac := ':';
  157. end
  158. else
  159. begin
  160. slashPos := Pos('\', path);
  161. if (slashPos <> 0) then {its a dos path}
  162. begin
  163. if slashPos = 1 then
  164. begin {its a full path, without drive letter}
  165. oldpos := 2;
  166. TranslatePathToMac := GetVolumeIdentifier;
  167. end
  168. else if (Length(path) >= 2) and (path[2] = ':') then {its a full path, with drive letter}
  169. begin
  170. oldpos := 4;
  171. TranslatePathToMac := GetVolumeIdentifier;
  172. end
  173. else {its a partial path}
  174. TranslatePathToMac := ':';
  175. end;
  176. end;
  177. if (slashPos <> 0) then {its a unix or dos path}
  178. begin
  179. {Translate "/../" to "::" , "/./" to ":" and "/" to ":" }
  180. newpos := Length(TranslatePathToMac);
  181. oldlen := Length(path);
  182. SetLength(TranslatePathToMac, newpos + oldlen); {It will be no longer than what is already}
  183. {prepended plus length of path.}
  184. maxpos := Length(TranslatePathToMac); {Get real maxpos, can be short if String is ShortString}
  185. {There is never a slash in the beginning, because either it was an absolute path, and then the}
  186. {drive and slash was removed, or it was a relative path without a preceding slash.}
  187. while oldpos <= oldlen do
  188. begin
  189. {Check if special dirs, ./ or ../ }
  190. if path[oldPos] = '.' then
  191. if (oldpos + 1 <= oldlen) and (path[oldPos + 1] = '.') then
  192. begin
  193. if (oldpos + 2 > oldlen) or (path[oldPos + 2] in AllowDirectorySeparators) then
  194. begin
  195. {It is "../" or ".." translates to ":" }
  196. if newPos = maxPos then
  197. begin {Shouldn't actually happen, but..}
  198. Exit('');
  199. end;
  200. newPos := newPos + 1;
  201. TranslatePathToMac[newPos] := ':';
  202. oldPos := oldPos + 3;
  203. continue; {Start over again}
  204. end;
  205. end
  206. else if (oldpos + 1 > oldlen) or (path[oldPos + 1] in AllowDirectorySeparators) then
  207. begin
  208. {It is "./" or "." ignor it }
  209. oldPos := oldPos + 2;
  210. continue; {Start over again}
  211. end;
  212. {Collect file or dir name}
  213. while (oldpos <= oldlen) and not (path[oldPos] in AllowDirectorySeparators) do
  214. begin
  215. if newPos = maxPos then
  216. begin {Shouldn't actually happen, but..}
  217. Exit('');
  218. end;
  219. newPos := newPos + 1;
  220. TranslatePathToMac[newPos] := path[oldPos];
  221. oldPos := oldPos + 1;
  222. end;
  223. {When we come here there is either a slash or we are at the end.}
  224. if (oldpos <= oldlen) then
  225. begin
  226. if newPos = maxPos then
  227. begin {Shouldn't actually happen, but..}
  228. Exit('');
  229. end;
  230. newPos := newPos + 1;
  231. TranslatePathToMac[newPos] := ':';
  232. oldPos := oldPos + 1;
  233. end;
  234. end;
  235. SetLength(TranslatePathToMac, newpos);
  236. end
  237. else if (path = '.') then
  238. TranslatePathToMac := ':'
  239. else if (path = '..') then
  240. TranslatePathToMac := '::'
  241. else
  242. TranslatePathToMac := path; {its a mac path}
  243. end;
  244. {Concats the relative or full path path1 and the relative path path2.}
  245. function ConcatMacPath (path1, path2: string): string;
  246. begin
  247. if Pos(':', path1) = 0 then {its partial}
  248. Insert(':', path1, 1); {because otherwise it would be interpreted}
  249. {as a full path, when path2 is appended.}
  250. if path1[Length(path1)] = ':' then
  251. begin
  252. if path2[1] = ':' then
  253. begin
  254. Delete(path1, Length(path1), 1);
  255. ConcatMacPath := Concat(path1, path2)
  256. end
  257. else
  258. ConcatMacPath := Concat(path1, path2)
  259. end
  260. else
  261. begin
  262. if path2[1] = ':' then
  263. ConcatMacPath := Concat(path1, path2)
  264. else
  265. ConcatMacPath := Concat(path1, ':', path2)
  266. end;
  267. end;
  268. function IsMacFullPath (const path: string): Boolean;
  269. begin
  270. if Pos(':', path) = 0 then {its partial}
  271. IsMacFullPath := false
  272. else if path[1] = ':' then
  273. IsMacFullPath := false
  274. else
  275. IsMacFullPath := true
  276. end;
  277. function IsDirectory (var spec: FSSpec): Boolean;
  278. var
  279. err: OSErr;
  280. paramBlock: CInfoPBRec;
  281. begin
  282. with paramBlock do
  283. begin
  284. ioVRefNum := spec.vRefNum;
  285. ioDirID := spec.parID;
  286. ioNamePtr := @spec.name;
  287. ioFDirIndex := 0;
  288. err := PBGetCatInfoSync(@paramBlock);
  289. if err = noErr then
  290. IsDirectory := (paramBlock.ioFlAttrib and $10) <> 0
  291. else
  292. IsDirectory := false;
  293. end;
  294. end;
  295. {Gives the path for a given file or directory. If parent is true,
  296. a path to the directory, where the file or directory is located,
  297. is returned. Functioning even with System 6.}
  298. function FSpGetFullPath (spec: FSSpec; var fullPath: RawByteString;
  299. parent: Boolean): OSErr;
  300. var
  301. res: OSErr;
  302. pb: CInfoPBRec;
  303. begin
  304. res := noErr;
  305. if spec.parID = fsRtParID then { The object is a volume }
  306. begin
  307. if not parent then
  308. begin
  309. { Add a colon to make it a full pathname }
  310. fullPath:= spec.name + ':';
  311. end
  312. else
  313. begin
  314. fullPath:= '';
  315. res:= afpObjectTypeErr; {to have something close to this error.}
  316. end;
  317. end
  318. else
  319. begin
  320. { The object isn't a volume }
  321. { Add the object name }
  322. if not parent then
  323. fullPath:= spec.name
  324. else
  325. fullPath:= '';
  326. { Get the ancestor directory names }
  327. pb.ioNamePtr := @spec.name;
  328. pb.ioVRefNum := spec.vRefNum;
  329. pb.ioDrParID := spec.parID;
  330. repeat { loop until we have an error or find the root directory }
  331. begin
  332. pb.ioFDirIndex := -1;
  333. pb.ioDrDirID := pb.ioDrParID;
  334. res := PBGetCatInfoSync(@pb);
  335. if res = noErr then
  336. begin
  337. { Append colon to directory name }
  338. spec.name := spec.name + ':';
  339. { Add directory name to fullPathHandle }
  340. fullPath:= spec.name + fullPath;
  341. end
  342. end
  343. until not ((res = noErr) and (pb.ioDrDirID <> fsRtDirID));
  344. end;
  345. FSpGetFullPath := res;
  346. end;
  347. function PathArgToFSSpec(s: string; var spec: FSSpec): Integer;
  348. var
  349. err: OSErr;
  350. begin
  351. if pathTranslation then
  352. s := TranslatePathToMac(s, false);
  353. err:= FSMakeFSSpec(workingDirectorySpec.vRefNum,
  354. workingDirectorySpec.parID, s, spec);
  355. if s <> '' then
  356. PathArgToFSSpec := MacOSErr2RTEerr(err)
  357. else
  358. PathArgToFSSpec := 3; {Empty paths are invalid paths}
  359. end;
  360. function PathArgToFullPath(s: string; var fullpath: RawByteString): Integer;
  361. var
  362. err: OSErr;
  363. res: Integer;
  364. spec: FSSpec;
  365. begin
  366. res:= PathArgToFSSpec(s, spec);
  367. if (res = 0) or (res = 2) then
  368. begin
  369. err:= FSpGetFullPath(spec, fullpath, false);
  370. PathArgToFullPath:= MacOSErr2RTEerr(err);
  371. end
  372. else
  373. PathArgToFullPath:=res;
  374. end;
  375. function GetVolumeName(vRefNum: Integer; var volName: String): OSErr;
  376. var
  377. pb: HParamBlockRec;
  378. begin
  379. pb.ioNamePtr := @volName;
  380. pb.ioVRefNum := vRefNum;
  381. pb.ioVolIndex := 0;
  382. PBHGetVInfoSync(@pb);
  383. volName:= volName + ':';
  384. GetVolumeName:= pb.ioResult;
  385. end;
  386. function GetWorkingDirectoryVRefNum: Integer;
  387. begin
  388. GetWorkingDirectoryVRefNum:= workingDirectorySpec.vRefNum;
  389. end;
  390. function GetVolInfo (var name: Str63; var vrn: integer; index: integer; var CrDate: longint): OSErr;
  391. var
  392. pb: ParamBlockRec;
  393. oe: OSErr;
  394. begin
  395. if (name <> '') and (name[length(name)] <> ':') then begin
  396. name := concat(name, ':');
  397. end;
  398. pb.ioNamePtr := @name;
  399. pb.ioVRefNum := vrn;
  400. pb.ioVolIndex := index;
  401. oe := PBGetVInfoSync(@pb);
  402. if oe = noErr then begin
  403. vrn := pb.ioVRefNum;
  404. CrDate := pb.ioVCrDate;
  405. end;
  406. GetVolInfo := oe;
  407. end;
  408. {Checks that fs really is an application with the specified creator}
  409. function ConfirmApplicationExists (creator: OSType; var fs: FSSpec): OSErr;
  410. var
  411. err: OSErr;
  412. info: FInfo;
  413. begin
  414. err := HGetFInfo(fs.vRefNum, fs.parID, fs.name, info);
  415. if err = noErr then begin
  416. if (info.fdType <> FourCharCodeToLongword('APPL')) or (info.fdCreator <> creator) then begin
  417. err := fnfErr;
  418. end;
  419. end;
  420. ConfirmApplicationExists := err;
  421. end;
  422. {Find an application with the given creator, in any of the mounted volumes.}
  423. function FindApplication (creator: OSType; var fs: FSSpec): OSErr;
  424. var
  425. i: integer;
  426. pbdt: DTPBRec;
  427. crdate: longint;
  428. oe: OSErr;
  429. found: Boolean;
  430. begin
  431. found := false;
  432. if (macosSystemVersion >= $0700) then begin
  433. i := 1;
  434. repeat
  435. fs.vRefNum := 0;
  436. {Get info for volume i}
  437. oe := GetVolInfo(fs.name, fs.vRefNum, i, crdate);
  438. i := i + 1;
  439. if oe = noErr then begin
  440. with pbdt do begin
  441. fs.name := '';
  442. ioNamePtr := @fs.name;
  443. ioVRefNum := fs.vRefNum;
  444. {Get the desktop database for this volume}
  445. oe := PBDTGetPath(@pbdt);
  446. if oe = noErr then begin
  447. ioFileCreator := creator;
  448. {Look first for the "default" (newest) application file}
  449. ioIndex := 0;
  450. oe := PBDTGetAPPLSync(@pbdt);
  451. if oe = noErr then begin
  452. fs.parID := pbdt.ioAPPLParID;
  453. found := ConfirmApplicationExists(creator,fs)=noErr;
  454. end;
  455. {If not ok, look for older ones.}
  456. if not found then begin
  457. ioIndex := 1;
  458. repeat
  459. oe := PBDTGetAPPLSync(@pbdt);
  460. if oe = noErr then begin
  461. fs.parID := pbdt.ioAPPLParID;
  462. found := ConfirmApplicationExists(creator,fs)=noErr;
  463. end;
  464. ioIndex := ioIndex + 1;
  465. until found or (oe <> noErr);
  466. end;
  467. end;
  468. end;
  469. oe := noErr;
  470. end;
  471. until found or (oe <> noErr);
  472. end;
  473. if found then begin
  474. oe := noErr;
  475. end else begin
  476. oe := fnfErr;
  477. fs.vRefNum := 0;
  478. fs.parID := 2;
  479. fs.name := '';
  480. end;
  481. FindApplication := oe;
  482. end;
  483. function LaunchFSSpec (tofront: Boolean; const applicationFileSpec: FSSpec): OSErr;
  484. var
  485. launchThis: LaunchParamBlockRec;
  486. begin
  487. launchThis.launchAppSpec := @applicationFileSpec;
  488. launchThis.launchAppParameters := nil;
  489. launchThis.launchBlockID := extendedBlock;
  490. launchThis.launchEPBLength := extendedBlockLen;
  491. launchThis.launchFileFlags := 0;
  492. launchThis.launchControlFlags := launchContinue or launchNoFileFlags;
  493. if not tofront then begin
  494. launchThis.launchControlFlags := launchThis.launchControlFlags or launchDontSwitch;
  495. end;
  496. LaunchFSSpec:= LaunchApplication(@launchThis);
  497. end;